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)]
14pub struct BoxBorder {
15 style: Style,
16}
17
18impl BoxBorder {
19 #[must_use]
21 pub fn new() -> Self {
22 Self::default()
23 }
24
25 #[must_use]
27 pub const fn style(mut self, style: Style) -> Self {
28 self.style = style;
29 self
30 }
31
32 #[must_use]
47 pub fn theme(self, theme: Theme) -> Self {
48 self.theme_on(theme, theme.panel_bg)
49 }
50
51 #[must_use]
55 pub fn theme_on(mut self, theme: Theme, bg: Color) -> Self {
56 self.style = Style::new().fg(theme.border).bg(bg);
57 self
58 }
59}
60
61impl<B: Backend> Widget<B> for BoxBorder {
62 fn render(self, area: Rect, term: &mut Terminal<B>) {
63 if area.width() < 2 || area.height() < 2 {
64 return;
65 }
66
67 let x0 = area.left();
68 let y0 = area.top();
69 let x1 = area.right().saturating_sub(1);
70 let y1 = area.bottom().saturating_sub(1);
71
72 term.reset_style()
73 .fg(self.style.foreground())
74 .bg(self.style.background());
75
76 term.put(x0, y0, TL);
78 term.put(x1, y0, TR);
79 term.put(x0, y1, BL);
80 term.put(x1, y1, BR);
81
82 for x in (x0 + 1)..x1 {
84 term.put(x, y0, H);
85 term.put(x, y1, H);
86 }
87
88 for y in (y0 + 1)..y1 {
90 term.put(x0, y, V);
91 term.put(x1, y, V);
92 }
93
94 term.reset_style();
95 }
96}
97
98#[cfg(test)]
99mod tests {
100 use retroglyph_core::{Color, Headless};
101
102 use super::*;
103
104 #[test]
105 fn draws_corners_and_edges() {
106 let area = Rect::new(0, 0, 5, 3);
107 let mut term = Terminal::new(Headless::new(5, 3));
108 BoxBorder::new()
109 .style(Style::new().fg(Color::WHITE))
110 .render(area, &mut term);
111
112 assert_eq!(term.grid().get(0, 0).glyph(), TL);
113 assert_eq!(term.grid().get(4, 0).glyph(), TR);
114 assert_eq!(term.grid().get(0, 2).glyph(), BL);
115 assert_eq!(term.grid().get(4, 2).glyph(), BR);
116 assert_eq!(term.grid().get(2, 0).glyph(), H);
117 assert_eq!(term.grid().get(0, 1).glyph(), V);
118 assert_eq!(term.grid().get(2, 1).glyph(), ' ');
120 }
121
122 #[test]
123 fn too_small_is_a_no_op() {
124 let area = Rect::new(0, 0, 1, 1);
125 let mut term = Terminal::new(Headless::new(1, 1));
126 BoxBorder::new().render(area, &mut term);
127 assert_eq!(term.grid().get(0, 0).glyph(), ' ');
128 }
129
130 #[test]
131 fn theme_maps_border_role_onto_style() {
132 let area = Rect::new(0, 0, 5, 3);
133 let mut term = Terminal::new(Headless::new(5, 3));
134 BoxBorder::new().theme(Theme::DARK).render(area, &mut term);
135
136 assert_eq!(
137 term.grid().get(0, 0).style().foreground(),
138 Theme::DARK.border
139 );
140 assert_eq!(
141 term.grid().get(0, 0).style().background(),
142 Theme::DARK.panel_bg
143 );
144 }
145
146 #[test]
147 fn theme_on_uses_the_given_backdrop_instead_of_panel_bg() {
148 let area = Rect::new(0, 0, 5, 3);
149 let mut term = Terminal::new(Headless::new(5, 3));
150 BoxBorder::new()
151 .theme_on(Theme::DARK, Color::Default)
152 .render(area, &mut term);
153
154 assert_eq!(
155 term.grid().get(0, 0).style().foreground(),
156 Theme::DARK.border
157 );
158 assert_eq!(term.grid().get(0, 0).style().background(), Color::Default);
159 }
160}