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