tui_lipan/widgets/
segment_cap.rs1use crate::app::ContrastPolicy;
4use crate::core::element::Element;
5use crate::style::{Color, Length, Style};
6use crate::widgets::{HStack, Text};
7
8#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
10pub enum CapStyle {
11 #[default]
13 Padded,
14 Half,
16 Round,
18 Arrow,
20}
21
22impl CapStyle {
23 pub const fn glyphs(self) -> Option<(&'static str, &'static str)> {
25 match self {
26 Self::Padded => None,
27 Self::Half => Some(("\u{2590}", "\u{258c}")),
30 Self::Round => Some(("\u{e0b6}", "\u{e0b4}")),
31 Self::Arrow => Some(("\u{e0b2}", "\u{e0b0}")),
32 }
33 }
34
35 pub const fn requires_nerd_font(self) -> bool {
37 matches!(self, Self::Round | Self::Arrow)
38 }
39
40 pub const fn font_safe(self) -> Self {
44 if self.requires_nerd_font() {
45 Self::Padded
46 } else {
47 self
48 }
49 }
50
51 pub const fn all() -> &'static [Self] {
53 &[Self::Padded, Self::Half, Self::Round, Self::Arrow]
54 }
55}
56
57#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
59pub enum CapSides {
60 #[default]
62 Both,
63 Left,
65 Right,
67 None,
69}
70
71impl CapSides {
72 pub(crate) const fn has_left(self) -> bool {
73 matches!(self, Self::Both | Self::Left)
74 }
75
76 pub(crate) const fn has_right(self) -> bool {
77 matches!(self, Self::Both | Self::Right)
78 }
79}
80
81pub(crate) fn segment_cap(
83 child: Element,
84 cap_style: CapStyle,
85 cap_sides: CapSides,
86 badge_bg: Color,
87 cap_behind: Color,
88 same_color_left: bool,
89) -> Element {
90 let glyphs = cap_style.glyphs();
91 if glyphs.is_none() && !(same_color_left && cap_sides.has_left()) {
92 return child;
93 }
94
95 let cap = |glyph: &'static str, same_color: bool| {
96 let contrast = if same_color {
97 ContrastPolicy::BlackOrWhite
98 } else {
99 ContrastPolicy::Off
100 };
101 Text::new(glyph)
102 .style(
103 Style::new()
104 .fg(badge_bg)
105 .bg(cap_behind)
106 .contrast_policy(contrast),
107 )
108 .width(Length::Px(1))
109 .height(Length::Px(1))
110 };
111
112 let mut row = HStack::new().width(Length::Auto).height(Length::Auto);
113 if cap_sides.has_left() {
114 if same_color_left {
115 row = row.child(cap(same_color_separator(cap_style), true));
116 } else if let Some((left, _)) = glyphs {
117 row = row.child(cap(left, false));
118 }
119 }
120 row = row.child(child);
121 if cap_sides.has_right()
122 && let Some((_, right)) = glyphs
123 {
124 row = row.child(cap(right, false));
125 }
126 row.into()
127}
128
129const fn same_color_separator(cap_style: CapStyle) -> &'static str {
131 match cap_style {
132 CapStyle::Arrow => "\u{e0b3}",
133 CapStyle::Padded | CapStyle::Half | CapStyle::Round => "▏",
134 }
135}
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140
141 #[test]
142 fn cap_style_glyphs_match_workbar_cap_sets() {
143 assert_eq!(CapStyle::Padded.glyphs(), None);
144 assert_eq!(CapStyle::Half.glyphs(), Some(("\u{2590}", "\u{258c}")));
145 assert_eq!(CapStyle::Round.glyphs(), Some(("\u{e0b6}", "\u{e0b4}")));
146 assert_eq!(CapStyle::Arrow.glyphs(), Some(("\u{e0b2}", "\u{e0b0}")));
147 }
148
149 #[test]
150 fn nerd_font_detection_and_safe_degradation_are_exact() {
151 assert!(!CapStyle::Padded.requires_nerd_font());
152 assert!(!CapStyle::Half.requires_nerd_font());
153 assert!(CapStyle::Round.requires_nerd_font());
154 assert!(CapStyle::Arrow.requires_nerd_font());
155 assert_eq!(CapStyle::Padded.font_safe(), CapStyle::Padded);
156 assert_eq!(CapStyle::Half.font_safe(), CapStyle::Half);
157 assert_eq!(CapStyle::Round.font_safe(), CapStyle::Padded);
158 assert_eq!(CapStyle::Arrow.font_safe(), CapStyle::Padded);
159 }
160
161 #[test]
162 fn all_returns_the_canonical_cap_style_cycle() {
163 assert_eq!(
164 CapStyle::all(),
165 &[
166 CapStyle::Padded,
167 CapStyle::Half,
168 CapStyle::Round,
169 CapStyle::Arrow
170 ]
171 );
172 }
173
174 #[test]
175 fn cap_sides_have_only_the_requested_edges() {
176 assert!(CapSides::Both.has_left());
177 assert!(CapSides::Both.has_right());
178 assert!(CapSides::Left.has_left());
179 assert!(!CapSides::Left.has_right());
180 assert!(!CapSides::Right.has_left());
181 assert!(CapSides::Right.has_right());
182 assert!(!CapSides::None.has_left());
183 assert!(!CapSides::None.has_right());
184 }
185
186 #[test]
187 fn same_color_separators_match_hyprmux_workbar_behavior() {
188 assert_eq!(same_color_separator(CapStyle::Arrow), "\u{e0b3}");
189 assert_eq!(same_color_separator(CapStyle::Round), "▏");
190 assert_eq!(same_color_separator(CapStyle::Half), "▏");
191 assert_eq!(same_color_separator(CapStyle::Padded), "▏");
192 }
193}