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