photon_ui/components/
divider.rs1use crate::{
4 Component,
5 RenderError,
6 Rendered,
7 layout::Direction,
8 theme::{
9 Palette,
10 Style,
11 Theme,
12 },
13};
14
15pub struct Divider {
21 direction: Direction,
22 style: Style,
23 label: Option<String>,
24}
25
26impl Divider {
27 pub fn horizontal() -> Self {
29 Self {
30 direction: Direction::Horizontal,
31 style: Style::new(),
32 label: None,
33 }
34 }
35
36 pub fn vertical() -> Self {
38 Self {
39 direction: Direction::Vertical,
40 style: Style::new(),
41 label: None,
42 }
43 }
44
45 pub fn styled(mut self, style: Style) -> Self {
47 self.style = style;
48 self
49 }
50
51 pub fn labeled(mut self, label: impl Into<String>) -> Self {
53 self.label = Some(label.into());
54 self
55 }
56}
57
58impl Component for Divider {
59 fn render(&self, width: u16) -> Result<Rendered, RenderError> {
60 let theme = Theme::current();
61 let style = if self.style == Style::new() {
62 Style::new().fg(theme.border_default())
63 } else {
64 self.style.clone()
65 };
66
67 let line = match self.direction {
68 | Direction::Horizontal => {
69 if let Some(ref label) = self.label {
70 let label = format!(" {} ", label);
71 let label_vw = crate::utils::visible_width(&label);
72 if label_vw + 4 > width as usize {
73 let line = "─".repeat(width as usize);
74 crate::theme::stylize(&line, &style)
75 } else {
76 let side = (width as usize - label_vw) / 2;
77 let left = "─".repeat(side);
78 let right = "─".repeat(width as usize - side - label_vw);
79 let text = crate::theme::stylize(&label, &style);
80 let left = crate::theme::stylize(&left, &style);
81 let right = crate::theme::stylize(&right, &style);
82 format!("{}{}{}", left, text, right)
83 }
84 } else {
85 let line = "─".repeat(width as usize);
86 crate::theme::stylize(&line, &style)
87 }
88 },
89 | Direction::Vertical => {
90 let line = "│".repeat(width as usize);
91 crate::theme::stylize(&line, &style)
92 },
93 };
94
95 Ok(Rendered {
96 lines: vec![line],
97 cursor: None,
98 images: Vec::new(),
99 })
100 }
101
102 fn render_rect(&self, rect: crate::layout::Rect) -> Result<Rendered, RenderError> {
103 if self.direction == Direction::Vertical && rect.height > 1 {
104 let theme = Theme::current();
105 let style = if self.style == Style::new() {
106 Style::new().fg(theme.border_default())
107 } else {
108 self.style.clone()
109 };
110 let ch = crate::theme::stylize("│", &style);
111 let mut lines = Vec::new();
112 for _ in 0..rect.height {
113 lines.push(ch.clone());
114 }
115 Ok(Rendered {
116 lines,
117 cursor: None,
118 images: Vec::new(),
119 })
120 } else {
121 self.render(rect.width)
122 }
123 }
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129 use crate::theme::Theme;
130
131 #[test]
132 fn divider_horizontal_renders_line() {
133 Theme::with(Theme::Light, || {
134 let div = Divider::horizontal();
135 let rendered = div.render(10).unwrap();
136 assert_eq!(rendered.lines.len(), 1);
137 assert!(rendered.lines[0].contains("─"));
138 });
139 }
140
141 #[test]
142 fn divider_vertical_renders_pipe() {
143 Theme::with(Theme::Light, || {
144 let div = Divider::vertical();
145 let rendered = div.render(1).unwrap();
146 assert!(rendered.lines[0].contains("│"));
147 });
148 }
149
150 #[test]
151 fn divider_labeled_renders_label() {
152 Theme::with(Theme::Light, || {
153 let div = Divider::horizontal().labeled("Section");
154 let rendered = div.render(30).unwrap();
155 assert!(rendered.lines[0].contains("Section"));
156 });
157 }
158
159 #[test]
160 fn divider_rect_vertical_multi_line() {
161 Theme::with(Theme::Light, || {
162 let div = Divider::vertical();
163 let rendered = div
164 .render_rect(crate::layout::Rect::new(0, 0, 1, 3))
165 .unwrap();
166 assert_eq!(rendered.lines.len(), 3);
167 for line in &rendered.lines {
168 assert!(line.contains("│"));
169 }
170 });
171 }
172}