1use retroglyph_core::color::{Color, Style};
3
4use super::{BorderType, Widget};
5use crate::Surface;
6use crate::theme::Theme;
7
8#[derive(Clone, Copy, Debug, Default)]
26pub struct BoxBorder {
27 style: Style,
28 border_type: BorderType,
29}
30
31impl BoxBorder {
32 #[must_use]
35 pub fn new() -> Self {
36 Self::default().theme(Theme::DARK)
37 }
38
39 #[must_use]
41 pub const fn style(mut self, style: Style) -> Self {
42 self.style = style;
43 self
44 }
45
46 #[must_use]
49 pub const fn border_type(mut self, border_type: BorderType) -> Self {
50 self.border_type = border_type;
51 self
52 }
53
54 #[must_use]
66 pub fn theme(self, theme: Theme) -> Self {
67 self.theme_on(theme, theme.panel_bg)
68 }
69
70 #[must_use]
74 pub fn theme_on(mut self, theme: Theme, bg: Color) -> Self {
75 self.style = Style::new().fg(theme.border).bg(bg);
76 self
77 }
78}
79
80impl Widget for BoxBorder {
81 fn render(&self, surface: &mut Surface<'_>) {
82 let (w, h) = (surface.width(), surface.height());
83 if w < 2 || h < 2 {
84 return;
85 }
86
87 let x1 = w - 1;
88 let y1 = h - 1;
89 let glyphs = self.border_type.glyphs();
90 let mut surface = surface.with_style(self.style);
91
92 surface.put((0, 0), glyphs.top_left);
94 surface.put((x1, 0), glyphs.top_right);
95 surface.put((0, y1), glyphs.bottom_left);
96 surface.put((x1, y1), glyphs.bottom_right);
97
98 for x in 1..x1 {
100 surface.put((x, 0), glyphs.horizontal);
101 surface.put((x, y1), glyphs.horizontal);
102 }
103
104 for y in 1..y1 {
106 surface.put((0, y), glyphs.vertical);
107 surface.put((x1, y), glyphs.vertical);
108 }
109 }
110}
111
112#[cfg(test)]
113mod tests {
114 use retroglyph_core::color::Color;
115 use retroglyph_core::grid::{Grid, Pos, Rect};
116 use retroglyph_core::symbols::border::PLAIN;
117
118 use super::*;
119
120 #[test]
121 fn draws_corners_and_edges() {
122 let area = Rect::new(0, 0, 5, 3);
123 let mut grid = Grid::new(5, 3);
124 BoxBorder::new()
125 .style(Style::new().fg(Color::WHITE))
126 .render(&mut Surface::new(&mut grid, area, 0));
127
128 assert_eq!(grid[Pos::new(0, 0)].glyph(), PLAIN.top_left);
129 assert_eq!(grid[Pos::new(4, 0)].glyph(), PLAIN.top_right);
130 assert_eq!(grid[Pos::new(0, 2)].glyph(), PLAIN.bottom_left);
131 assert_eq!(grid[Pos::new(4, 2)].glyph(), PLAIN.bottom_right);
132 assert_eq!(grid[Pos::new(2, 0)].glyph(), PLAIN.horizontal);
133 assert_eq!(grid[Pos::new(0, 1)].glyph(), PLAIN.vertical);
134 assert_eq!(grid[Pos::new(2, 1)].glyph(), ' ');
136 }
137
138 #[test]
139 fn border_type_defaults_to_plain() {
140 let area = Rect::new(0, 0, 5, 3);
141 let mut grid = Grid::new(5, 3);
142 BoxBorder::new().render(&mut Surface::new(&mut grid, area, 0));
143
144 assert_eq!(grid[Pos::new(0, 0)].glyph(), PLAIN.top_left);
145 }
146
147 #[test]
148 fn border_type_selects_the_glyph_set() {
149 let area = Rect::new(0, 0, 5, 3);
150 let mut grid = Grid::new(5, 3);
151 BoxBorder::new()
152 .border_type(BorderType::Rounded)
153 .render(&mut Surface::new(&mut grid, area, 0));
154
155 assert_eq!(grid[Pos::new(0, 0)].glyph(), '╭');
156 assert_eq!(grid[Pos::new(4, 0)].glyph(), '╮');
157 assert_eq!(grid[Pos::new(0, 2)].glyph(), '╰');
158 assert_eq!(grid[Pos::new(4, 2)].glyph(), '╯');
159 assert_eq!(grid[Pos::new(2, 0)].glyph(), '─');
160 assert_eq!(grid[Pos::new(0, 1)].glyph(), '│');
161 }
162
163 #[test]
164 fn border_type_double() {
165 let area = Rect::new(0, 0, 5, 3);
166 let mut grid = Grid::new(5, 3);
167 BoxBorder::new()
168 .border_type(BorderType::Double)
169 .render(&mut Surface::new(&mut grid, area, 0));
170
171 assert_eq!(grid[Pos::new(0, 0)].glyph(), '╔');
172 assert_eq!(grid[Pos::new(4, 0)].glyph(), '╗');
173 assert_eq!(grid[Pos::new(0, 2)].glyph(), '╚');
174 assert_eq!(grid[Pos::new(4, 2)].glyph(), '╝');
175 assert_eq!(grid[Pos::new(2, 0)].glyph(), '═');
176 assert_eq!(grid[Pos::new(0, 1)].glyph(), '║');
177 }
178
179 #[test]
180 fn border_type_thick() {
181 let area = Rect::new(0, 0, 5, 3);
182 let mut grid = Grid::new(5, 3);
183 BoxBorder::new()
184 .border_type(BorderType::Thick)
185 .render(&mut Surface::new(&mut grid, area, 0));
186
187 assert_eq!(grid[Pos::new(0, 0)].glyph(), '┏');
188 assert_eq!(grid[Pos::new(4, 0)].glyph(), '┓');
189 assert_eq!(grid[Pos::new(0, 2)].glyph(), '┗');
190 assert_eq!(grid[Pos::new(4, 2)].glyph(), '┛');
191 assert_eq!(grid[Pos::new(2, 0)].glyph(), '━');
192 assert_eq!(grid[Pos::new(0, 1)].glyph(), '┃');
193 }
194
195 #[test]
196 fn too_small_is_a_no_op() {
197 let area = Rect::new(0, 0, 1, 1);
198 let mut grid = Grid::new(1, 1);
199 BoxBorder::new().render(&mut Surface::new(&mut grid, area, 0));
200 assert_eq!(grid[Pos::new(0, 0)].glyph(), ' ');
201 }
202
203 #[test]
204 fn theme_maps_border_role_onto_style() {
205 let area = Rect::new(0, 0, 5, 3);
206 let mut grid = Grid::new(5, 3);
207 BoxBorder::new()
208 .theme(Theme::DARK)
209 .render(&mut Surface::new(&mut grid, area, 0));
210
211 assert_eq!(
212 grid[Pos::new(0, 0)].style().foreground(),
213 Theme::DARK.border
214 );
215 assert_eq!(
216 grid[Pos::new(0, 0)].style().background(),
217 Theme::DARK.panel_bg
218 );
219 }
220
221 #[test]
222 fn theme_on_uses_the_given_backdrop_instead_of_panel_bg() {
223 let area = Rect::new(0, 0, 5, 3);
224 let mut grid = Grid::new(5, 3);
225 BoxBorder::new()
226 .theme_on(Theme::DARK, Color::Default)
227 .render(&mut Surface::new(&mut grid, area, 0));
228
229 assert_eq!(
230 grid[Pos::new(0, 0)].style().foreground(),
231 Theme::DARK.border
232 );
233 assert_eq!(grid[Pos::new(0, 0)].style().background(), Color::Default);
234 }
235}