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