1use crate::buffer::ScreenBuffer;
7use crate::cell::Cell;
8use crate::geometry::Rect;
9use crate::style::Style;
10
11use super::BorderStyle;
12
13pub type BorderChars = (
15 &'static str,
16 &'static str,
17 &'static str,
18 &'static str,
19 &'static str,
20 &'static str,
21);
22
23impl BorderStyle {
24 pub fn chars(self) -> Option<BorderChars> {
28 match self {
29 BorderStyle::None => None,
30 BorderStyle::Single => Some((
31 "\u{250c}", "\u{2510}", "\u{2514}", "\u{2518}", "\u{2500}", "\u{2502}",
32 )),
33 BorderStyle::Double => Some((
34 "\u{2554}", "\u{2557}", "\u{255a}", "\u{255d}", "\u{2550}", "\u{2551}",
35 )),
36 BorderStyle::Rounded => Some((
37 "\u{256d}", "\u{256e}", "\u{2570}", "\u{256f}", "\u{2500}", "\u{2502}",
38 )),
39 BorderStyle::Heavy => Some((
40 "\u{250f}", "\u{2513}", "\u{2517}", "\u{251b}", "\u{2501}", "\u{2503}",
41 )),
42 }
43 }
44}
45
46pub fn render_border(
50 area: Rect,
51 border_style: BorderStyle,
52 cell_style: Style,
53 buf: &mut ScreenBuffer,
54) {
55 let Some((tl, tr, bl, br, h, v)) = border_style.chars() else {
56 return;
57 };
58
59 let x1 = area.position.x;
60 let y1 = area.position.y;
61 let w = area.size.width;
62 let h_val = area.size.height;
63
64 if w == 0 || h_val == 0 {
65 return;
66 }
67
68 let x2 = x1.saturating_add(w.saturating_sub(1));
69 let y2 = y1.saturating_add(h_val.saturating_sub(1));
70
71 buf.set(x1, y1, Cell::new(tl, cell_style.clone()));
73 buf.set(x2, y1, Cell::new(tr, cell_style.clone()));
74 buf.set(x1, y2, Cell::new(bl, cell_style.clone()));
75 buf.set(x2, y2, Cell::new(br, cell_style.clone()));
76
77 for x in (x1 + 1)..x2 {
79 buf.set(x, y1, Cell::new(h, cell_style.clone()));
80 buf.set(x, y2, Cell::new(h, cell_style.clone()));
81 }
82
83 for y in (y1 + 1)..y2 {
85 buf.set(x1, y, Cell::new(v, cell_style.clone()));
86 buf.set(x2, y, Cell::new(v, cell_style.clone()));
87 }
88}
89
90pub fn inner_area(area: Rect, border_style: BorderStyle) -> Rect {
97 match border_style {
98 BorderStyle::None => area,
99 _ => {
100 if area.size.width < 2 || area.size.height < 2 {
101 return Rect::new(area.position.x, area.position.y, 0, 0);
102 }
103 Rect::new(
104 area.position.x + 1,
105 area.position.y + 1,
106 area.size.width.saturating_sub(2),
107 area.size.height.saturating_sub(2),
108 )
109 }
110 }
111}
112
113#[cfg(test)]
114mod tests {
115 use super::*;
116 use crate::geometry::Size;
117
118 #[test]
119 fn border_style_chars_single() {
120 let chars = BorderStyle::Single.chars();
121 assert!(chars.is_some());
122 match chars {
123 Some((tl, tr, bl, br, h, v)) => {
124 assert_eq!(tl, "\u{250c}");
125 assert_eq!(tr, "\u{2510}");
126 assert_eq!(bl, "\u{2514}");
127 assert_eq!(br, "\u{2518}");
128 assert_eq!(h, "\u{2500}");
129 assert_eq!(v, "\u{2502}");
130 }
131 None => unreachable!("should have border chars"),
132 }
133 }
134
135 #[test]
136 fn border_style_chars_none() {
137 assert!(BorderStyle::None.chars().is_none());
138 }
139
140 #[test]
141 fn inner_area_no_border() {
142 let area = Rect::new(5, 5, 20, 10);
143 let inner = inner_area(area, BorderStyle::None);
144 assert_eq!(inner, area);
145 }
146
147 #[test]
148 fn inner_area_with_border() {
149 let area = Rect::new(5, 5, 20, 10);
150 let inner = inner_area(area, BorderStyle::Single);
151 assert_eq!(inner.position.x, 6);
152 assert_eq!(inner.position.y, 6);
153 assert_eq!(inner.size.width, 18);
154 assert_eq!(inner.size.height, 8);
155 }
156
157 #[test]
158 fn inner_area_too_small() {
159 let area = Rect::new(0, 0, 1, 1);
160 let inner = inner_area(area, BorderStyle::Single);
161 assert_eq!(inner.size.width, 0);
162 assert_eq!(inner.size.height, 0);
163 }
164
165 #[test]
166 #[allow(clippy::unwrap_used)]
167 fn render_border_single() {
168 let mut buf = ScreenBuffer::new(Size::new(10, 5));
169 let area = Rect::new(0, 0, 10, 5);
170 let style = Style::default();
171
172 render_border(area, BorderStyle::Single, style, &mut buf);
173
174 assert_eq!(buf.get(0, 0).unwrap().grapheme, "\u{250c}");
176 assert_eq!(buf.get(9, 0).unwrap().grapheme, "\u{2510}");
177 assert_eq!(buf.get(0, 4).unwrap().grapheme, "\u{2514}");
178 assert_eq!(buf.get(9, 4).unwrap().grapheme, "\u{2518}");
179
180 assert_eq!(buf.get(1, 0).unwrap().grapheme, "\u{2500}");
182 assert_eq!(buf.get(0, 1).unwrap().grapheme, "\u{2502}");
183 }
184
185 #[test]
186 #[allow(clippy::unwrap_used)]
187 fn render_border_none_does_nothing() {
188 let mut buf = ScreenBuffer::new(Size::new(10, 5));
189 let area = Rect::new(0, 0, 10, 5);
190 let style = Style::default();
191
192 render_border(area, BorderStyle::None, style, &mut buf);
193
194 assert_eq!(buf.get(0, 0).unwrap().grapheme, " ");
196 }
197}