blaeck/components/
divider.rs1use crate::element::{Component, Element};
17use crate::style::{Color, Modifier, Style};
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
21pub enum DividerStyle {
22 #[default]
24 Single,
25 Double,
27 Dashed,
29 Dotted,
31 Bold,
33 Ascii,
35}
36
37impl DividerStyle {
38 pub fn char(&self) -> char {
40 match self {
41 DividerStyle::Single => '─',
42 DividerStyle::Double => '═',
43 DividerStyle::Dashed => '┄',
44 DividerStyle::Dotted => '·',
45 DividerStyle::Bold => '━',
46 DividerStyle::Ascii => '-',
47 }
48 }
49}
50
51#[derive(Debug, Clone)]
53pub struct DividerProps {
54 pub width: Option<usize>,
56 pub style: DividerStyle,
58 pub color: Option<Color>,
60 pub dim: bool,
62 pub label: Option<String>,
64 pub label_color: Option<Color>,
66}
67
68impl Default for DividerProps {
69 fn default() -> Self {
70 Self {
71 width: None,
72 style: DividerStyle::Single,
73 color: None,
74 dim: false,
75 label: None,
76 label_color: None,
77 }
78 }
79}
80
81impl DividerProps {
82 pub fn new() -> Self {
84 Self::default()
85 }
86
87 #[must_use]
89 pub fn width(mut self, width: usize) -> Self {
90 self.width = Some(width);
91 self
92 }
93
94 #[must_use]
96 pub fn line_style(mut self, style: DividerStyle) -> Self {
97 self.style = style;
98 self
99 }
100
101 #[must_use]
103 pub fn color(mut self, color: Color) -> Self {
104 self.color = Some(color);
105 self
106 }
107
108 #[must_use]
110 pub fn dim(mut self) -> Self {
111 self.dim = true;
112 self
113 }
114
115 #[must_use]
117 pub fn label(mut self, label: impl Into<String>) -> Self {
118 self.label = Some(label.into());
119 self
120 }
121
122 #[must_use]
124 pub fn label_color(mut self, color: Color) -> Self {
125 self.label_color = Some(color);
126 self
127 }
128
129 pub fn render_string(&self) -> String {
131 let width = self.width.unwrap_or(20);
132 let ch = self.style.char();
133
134 if let Some(ref label) = self.label {
135 let label_len = label.chars().count();
137 let remaining = width.saturating_sub(label_len + 2); let left = remaining / 2;
139 let right = remaining - left;
140
141 format!(
142 "{} {} {}",
143 ch.to_string().repeat(left),
144 label,
145 ch.to_string().repeat(right)
146 )
147 } else {
148 ch.to_string().repeat(width)
149 }
150 }
151}
152
153pub struct Divider;
171
172impl Component for Divider {
173 type Props = DividerProps;
174
175 fn render(props: &Self::Props) -> Element {
176 let content = props.render_string();
177
178 let mut style = Style::new();
179 if let Some(color) = props.color {
180 style = style.fg(color);
181 }
182 if props.dim {
183 style = style.add_modifier(Modifier::DIM);
184 }
185
186 Element::styled_text(&content, style)
187 }
188}
189
190pub fn divider(width: usize) -> String {
192 DividerProps::new().width(width).render_string()
193}
194
195pub fn divider_with_label(width: usize, label: &str) -> String {
197 DividerProps::new()
198 .width(width)
199 .label(label)
200 .render_string()
201}
202
203#[cfg(test)]
204mod tests {
205 use super::*;
206
207 #[test]
208 fn test_divider_default() {
209 let props = DividerProps::default();
210 let s = props.render_string();
211 assert_eq!(s.chars().count(), 20);
212 assert!(s.chars().all(|c| c == '─'));
213 }
214
215 #[test]
216 fn test_divider_width() {
217 let props = DividerProps::new().width(10);
218 let s = props.render_string();
219 assert_eq!(s, "──────────");
220 }
221
222 #[test]
223 fn test_divider_double() {
224 let props = DividerProps::new()
225 .width(5)
226 .line_style(DividerStyle::Double);
227 let s = props.render_string();
228 assert_eq!(s, "═════");
229 }
230
231 #[test]
232 fn test_divider_with_label() {
233 let props = DividerProps::new().width(20).label("Test");
234 let s = props.render_string();
235 assert!(s.contains("Test"));
236 assert!(s.contains("─"));
237 }
238
239 #[test]
240 fn test_divider_helper() {
241 let s = divider(10);
242 assert_eq!(s.chars().count(), 10);
243 }
244
245 #[test]
246 fn test_divider_with_label_helper() {
247 let s = divider_with_label(20, "Hello");
248 assert!(s.contains("Hello"));
249 }
250}