retroglyph_widgets/widget/
box_border.rs1use retroglyph_core::{Backend, Color, Rect, Style, Terminal};
3
4use super::Widget;
5use crate::Theme;
6use crate::draw::{BL, BR, H, TL, TR, V};
7
8#[derive(Clone, Copy, Debug, Default)]
24pub struct BoxBorder {
25 style: Style,
26}
27
28impl BoxBorder {
29 #[must_use]
31 pub fn new() -> Self {
32 Self::default()
33 }
34
35 #[must_use]
37 pub const fn style(mut self, style: Style) -> Self {
38 self.style = style;
39 self
40 }
41
42 #[must_use]
57 pub fn theme(self, theme: Theme) -> Self {
58 self.theme_on(theme, theme.panel_bg)
59 }
60
61 #[must_use]
65 pub fn theme_on(mut self, theme: Theme, bg: Color) -> Self {
66 self.style = Style::new().fg(theme.border).bg(bg);
67 self
68 }
69}
70
71impl<B: Backend> Widget<B> for BoxBorder {
72 fn render(self, area: Rect, term: &mut Terminal<B>) {
73 if area.width() < 2 || area.height() < 2 {
74 return;
75 }
76
77 let x0 = area.left();
78 let y0 = area.top();
79 let x1 = area.right().saturating_sub(1);
80 let y1 = area.bottom().saturating_sub(1);
81
82 term.reset_style()
83 .fg(self.style.foreground())
84 .bg(self.style.background());
85
86 term.put(x0, y0, TL);
88 term.put(x1, y0, TR);
89 term.put(x0, y1, BL);
90 term.put(x1, y1, BR);
91
92 for x in (x0 + 1)..x1 {
94 term.put(x, y0, H);
95 term.put(x, y1, H);
96 }
97
98 for y in (y0 + 1)..y1 {
100 term.put(x0, y, V);
101 term.put(x1, y, V);
102 }
103
104 term.reset_style();
105 }
106}
107
108#[cfg(test)]
109mod tests {
110 use retroglyph_core::{Color, Headless};
111
112 use super::*;
113
114 #[test]
115 fn draws_corners_and_edges() {
116 let area = Rect::new(0, 0, 5, 3);
117 let mut term = Terminal::new(Headless::new(5, 3));
118 BoxBorder::new()
119 .style(Style::new().fg(Color::WHITE))
120 .render(area, &mut term);
121
122 assert_eq!(term.grid().get(0, 0).glyph(), TL);
123 assert_eq!(term.grid().get(4, 0).glyph(), TR);
124 assert_eq!(term.grid().get(0, 2).glyph(), BL);
125 assert_eq!(term.grid().get(4, 2).glyph(), BR);
126 assert_eq!(term.grid().get(2, 0).glyph(), H);
127 assert_eq!(term.grid().get(0, 1).glyph(), V);
128 assert_eq!(term.grid().get(2, 1).glyph(), ' ');
130 }
131
132 #[test]
133 fn too_small_is_a_no_op() {
134 let area = Rect::new(0, 0, 1, 1);
135 let mut term = Terminal::new(Headless::new(1, 1));
136 BoxBorder::new().render(area, &mut term);
137 assert_eq!(term.grid().get(0, 0).glyph(), ' ');
138 }
139
140 #[test]
141 fn theme_maps_border_role_onto_style() {
142 let area = Rect::new(0, 0, 5, 3);
143 let mut term = Terminal::new(Headless::new(5, 3));
144 BoxBorder::new().theme(Theme::DARK).render(area, &mut term);
145
146 assert_eq!(
147 term.grid().get(0, 0).style().foreground(),
148 Theme::DARK.border
149 );
150 assert_eq!(
151 term.grid().get(0, 0).style().background(),
152 Theme::DARK.panel_bg
153 );
154 }
155
156 #[test]
157 fn theme_on_uses_the_given_backdrop_instead_of_panel_bg() {
158 let area = Rect::new(0, 0, 5, 3);
159 let mut term = Terminal::new(Headless::new(5, 3));
160 BoxBorder::new()
161 .theme_on(Theme::DARK, Color::Default)
162 .render(area, &mut term);
163
164 assert_eq!(
165 term.grid().get(0, 0).style().foreground(),
166 Theme::DARK.border
167 );
168 assert_eq!(term.grid().get(0, 0).style().background(), Color::Default);
169 }
170}