use crate::app::ContrastPolicy;
use crate::core::element::Element;
use crate::style::{Color, Length, Style};
use crate::widgets::{HStack, Text};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum CapStyle {
#[default]
Padded,
Half,
Round,
Arrow,
}
impl CapStyle {
pub const fn glyphs(self) -> Option<(&'static str, &'static str)> {
match self {
Self::Padded => None,
Self::Half => Some(("\u{2590}", "\u{258c}")),
Self::Round => Some(("\u{e0b6}", "\u{e0b4}")),
Self::Arrow => Some(("\u{e0b2}", "\u{e0b0}")),
}
}
pub const fn requires_nerd_font(self) -> bool {
matches!(self, Self::Round | Self::Arrow)
}
pub const fn font_safe(self) -> Self {
if self.requires_nerd_font() {
Self::Padded
} else {
self
}
}
pub const fn all() -> &'static [Self] {
&[Self::Padded, Self::Half, Self::Round, Self::Arrow]
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum CapSides {
#[default]
Both,
Left,
Right,
None,
}
impl CapSides {
pub(crate) const fn has_left(self) -> bool {
matches!(self, Self::Both | Self::Left)
}
pub(crate) const fn has_right(self) -> bool {
matches!(self, Self::Both | Self::Right)
}
}
pub(crate) fn segment_cap(
child: Element,
cap_style: CapStyle,
cap_sides: CapSides,
badge_bg: Color,
cap_behind: Color,
same_color_left: bool,
) -> Element {
let glyphs = cap_style.glyphs();
if glyphs.is_none() && !(same_color_left && cap_sides.has_left()) {
return child;
}
let cap = |glyph: &'static str, same_color: bool| {
let contrast = if same_color {
ContrastPolicy::BlackOrWhite
} else {
ContrastPolicy::Off
};
Text::new(glyph)
.style(
Style::new()
.fg(badge_bg)
.bg(cap_behind)
.contrast_policy(contrast),
)
.width(Length::Px(1))
.height(Length::Px(1))
};
let mut row = HStack::new().width(Length::Auto).height(Length::Auto);
if cap_sides.has_left() {
if same_color_left {
row = row.child(cap(same_color_separator(cap_style), true));
} else if let Some((left, _)) = glyphs {
row = row.child(cap(left, false));
}
}
row = row.child(child);
if cap_sides.has_right()
&& let Some((_, right)) = glyphs
{
row = row.child(cap(right, false));
}
row.into()
}
const fn same_color_separator(cap_style: CapStyle) -> &'static str {
match cap_style {
CapStyle::Arrow => "\u{e0b3}",
CapStyle::Padded | CapStyle::Half | CapStyle::Round => "▏",
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cap_style_glyphs_match_workbar_cap_sets() {
assert_eq!(CapStyle::Padded.glyphs(), None);
assert_eq!(CapStyle::Half.glyphs(), Some(("\u{2590}", "\u{258c}")));
assert_eq!(CapStyle::Round.glyphs(), Some(("\u{e0b6}", "\u{e0b4}")));
assert_eq!(CapStyle::Arrow.glyphs(), Some(("\u{e0b2}", "\u{e0b0}")));
}
#[test]
fn nerd_font_detection_and_safe_degradation_are_exact() {
assert!(!CapStyle::Padded.requires_nerd_font());
assert!(!CapStyle::Half.requires_nerd_font());
assert!(CapStyle::Round.requires_nerd_font());
assert!(CapStyle::Arrow.requires_nerd_font());
assert_eq!(CapStyle::Padded.font_safe(), CapStyle::Padded);
assert_eq!(CapStyle::Half.font_safe(), CapStyle::Half);
assert_eq!(CapStyle::Round.font_safe(), CapStyle::Padded);
assert_eq!(CapStyle::Arrow.font_safe(), CapStyle::Padded);
}
#[test]
fn all_returns_the_canonical_cap_style_cycle() {
assert_eq!(
CapStyle::all(),
&[
CapStyle::Padded,
CapStyle::Half,
CapStyle::Round,
CapStyle::Arrow
]
);
}
#[test]
fn cap_sides_have_only_the_requested_edges() {
assert!(CapSides::Both.has_left());
assert!(CapSides::Both.has_right());
assert!(CapSides::Left.has_left());
assert!(!CapSides::Left.has_right());
assert!(!CapSides::Right.has_left());
assert!(CapSides::Right.has_right());
assert!(!CapSides::None.has_left());
assert!(!CapSides::None.has_right());
}
#[test]
fn same_color_separators_match_hyprmux_workbar_behavior() {
assert_eq!(same_color_separator(CapStyle::Arrow), "\u{e0b3}");
assert_eq!(same_color_separator(CapStyle::Round), "▏");
assert_eq!(same_color_separator(CapStyle::Half), "▏");
assert_eq!(same_color_separator(CapStyle::Padded), "▏");
}
}