1use crate::layout::Rect;
4use crate::render::Cell;
5use crate::style::Color;
6use crate::utils::border::BorderChars;
7use crate::widget::traits::{RenderContext, View, WidgetProps};
8use crate::{impl_props_builders, impl_styled_view};
9
10#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
12pub enum BorderType {
13 None,
15 #[default]
17 Single,
18 Double,
20 Rounded,
22 Thick,
24 Ascii,
26}
27
28const NONE_CHARS: BorderChars = BorderChars {
30 top_left: ' ',
31 top_right: ' ',
32 bottom_left: ' ',
33 bottom_right: ' ',
34 horizontal: ' ',
35 vertical: ' ',
36};
37
38impl BorderType {
39 pub fn chars(&self) -> BorderChars {
41 match self {
42 BorderType::None => NONE_CHARS,
43 BorderType::Single => BorderChars::SINGLE,
44 BorderType::Double => BorderChars::DOUBLE,
45 BorderType::Rounded => BorderChars::ROUNDED,
46 BorderType::Thick => BorderChars::BOLD,
47 BorderType::Ascii => BorderChars::ASCII,
48 }
49 }
50}
51
52pub struct Border {
54 child: Option<Box<dyn View>>,
55 border_type: BorderType,
56 title: Option<String>,
57 fg: Option<Color>,
58 bg: Option<Color>,
59 props: WidgetProps,
60}
61
62impl Border {
63 pub fn new() -> Self {
65 Self {
66 child: None,
67 border_type: BorderType::Single,
68 title: None,
69 fg: None,
70 bg: None,
71 props: WidgetProps::new(),
72 }
73 }
74
75 pub fn child(mut self, child: impl View + 'static) -> Self {
77 self.child = Some(Box::new(child));
78 self
79 }
80
81 pub fn border_type(mut self, border_type: BorderType) -> Self {
83 self.border_type = border_type;
84 self
85 }
86
87 pub fn title(mut self, title: impl Into<String>) -> Self {
89 self.title = Some(title.into());
90 self
91 }
92
93 pub fn fg(mut self, color: Color) -> Self {
95 self.fg = Some(color);
96 self
97 }
98
99 pub fn bg(mut self, color: Color) -> Self {
101 self.bg = Some(color);
102 self
103 }
104
105 pub fn single() -> Self {
111 Self::new().border_type(BorderType::Single)
112 }
113
114 pub fn double() -> Self {
116 Self::new().border_type(BorderType::Double)
117 }
118
119 pub fn rounded() -> Self {
121 Self::new().border_type(BorderType::Rounded)
122 }
123
124 pub fn thick() -> Self {
126 Self::new().border_type(BorderType::Thick)
127 }
128
129 pub fn ascii() -> Self {
131 Self::new().border_type(BorderType::Ascii)
132 }
133
134 pub fn panel() -> Self {
136 Self::new().border_type(BorderType::Double).fg(Color::CYAN)
137 }
138
139 pub fn card() -> Self {
141 Self::new()
142 .border_type(BorderType::Rounded)
143 .fg(Color::WHITE)
144 }
145
146 pub fn error_box() -> Self {
148 Self::new().border_type(BorderType::Single).fg(Color::RED)
149 }
150
151 pub fn success_box() -> Self {
153 Self::new().border_type(BorderType::Single).fg(Color::GREEN)
154 }
155}
156
157impl Default for Border {
158 fn default() -> Self {
159 Self::new()
160 }
161}
162
163impl View for Border {
164 fn render(&self, ctx: &mut RenderContext) {
165 let area = ctx.area;
166 if area.width < 2 || area.height < 2 {
167 return;
168 }
169
170 let chars = self.border_type.chars();
171
172 let mut cell = Cell::new(chars.top_left);
174 cell.fg = self.fg;
175 cell.bg = self.bg;
176 ctx.buffer.set(area.x, area.y, cell);
177
178 let title_start = if let Some(ref title) = self.title {
180 let max_title_len = (area.width as usize).saturating_sub(4);
181 let display_title: String = title.chars().take(max_title_len).collect();
182 let title_len = display_title.len();
183
184 for x in 1..2 {
186 let mut c = Cell::new(chars.horizontal);
187 c.fg = self.fg;
188 c.bg = self.bg;
189 ctx.buffer.set(area.x + x, area.y, c);
190 }
191
192 for (i, ch) in display_title.chars().enumerate() {
194 let mut c = Cell::new(ch);
195 c.fg = self.fg;
196 c.bg = self.bg;
197 ctx.buffer.set(area.x + 2 + i as u16, area.y, c);
198 }
199
200 2 + title_len as u16
201 } else {
202 1
203 };
204
205 for x in title_start..(area.width - 1) {
207 let mut c = Cell::new(chars.horizontal);
208 c.fg = self.fg;
209 c.bg = self.bg;
210 ctx.buffer.set(area.x + x, area.y, c);
211 }
212
213 let mut cell = Cell::new(chars.top_right);
215 cell.fg = self.fg;
216 cell.bg = self.bg;
217 ctx.buffer.set(area.x + area.width - 1, area.y, cell);
218
219 for y in 1..(area.height - 1) {
221 let mut left = Cell::new(chars.vertical);
222 left.fg = self.fg;
223 left.bg = self.bg;
224 ctx.buffer.set(area.x, area.y + y, left);
225
226 let mut right = Cell::new(chars.vertical);
227 right.fg = self.fg;
228 right.bg = self.bg;
229 ctx.buffer.set(area.x + area.width - 1, area.y + y, right);
230 }
231
232 let mut cell = Cell::new(chars.bottom_left);
234 cell.fg = self.fg;
235 cell.bg = self.bg;
236 ctx.buffer.set(area.x, area.y + area.height - 1, cell);
237
238 for x in 1..(area.width - 1) {
239 let mut c = Cell::new(chars.horizontal);
240 c.fg = self.fg;
241 c.bg = self.bg;
242 ctx.buffer.set(area.x + x, area.y + area.height - 1, c);
243 }
244
245 let mut cell = Cell::new(chars.bottom_right);
246 cell.fg = self.fg;
247 cell.bg = self.bg;
248 ctx.buffer
249 .set(area.x + area.width - 1, area.y + area.height - 1, cell);
250
251 if let Some(ref child) = self.child {
253 let inner = Rect::new(
254 area.x + 1,
255 area.y + 1,
256 area.width.saturating_sub(2),
257 area.height.saturating_sub(2),
258 );
259 let mut child_ctx = RenderContext::new(ctx.buffer, inner);
260 child.render(&mut child_ctx);
261 }
262 }
263
264 crate::impl_view_meta!("Border");
265}
266
267pub fn border() -> Border {
269 Border::new()
270}
271
272impl_styled_view!(Border);
273impl_props_builders!(Border);
274
275pub fn draw_border(
287 buffer: &mut crate::render::Buffer,
288 area: Rect,
289 border_type: BorderType,
290 fg: Option<Color>,
291 bg: Option<Color>,
292) {
293 if area.width < 2 || area.height < 2 || border_type == BorderType::None {
294 return;
295 }
296
297 let chars = border_type.chars();
298
299 let mut cell = Cell::new(chars.top_left);
301 cell.fg = fg;
302 cell.bg = bg;
303 buffer.set(area.x, area.y, cell);
304
305 for x in 1..(area.width - 1) {
307 let mut c = Cell::new(chars.horizontal);
308 c.fg = fg;
309 c.bg = bg;
310 buffer.set(area.x + x, area.y, c);
311 }
312
313 let mut cell = Cell::new(chars.top_right);
315 cell.fg = fg;
316 cell.bg = bg;
317 buffer.set(area.x + area.width - 1, area.y, cell);
318
319 for y in 1..(area.height - 1) {
321 let mut left = Cell::new(chars.vertical);
322 left.fg = fg;
323 left.bg = bg;
324 buffer.set(area.x, area.y + y, left);
325
326 let mut right = Cell::new(chars.vertical);
327 right.fg = fg;
328 right.bg = bg;
329 buffer.set(area.x + area.width - 1, area.y + y, right);
330 }
331
332 let mut cell = Cell::new(chars.bottom_left);
334 cell.fg = fg;
335 cell.bg = bg;
336 buffer.set(area.x, area.y + area.height - 1, cell);
337
338 for x in 1..(area.width - 1) {
340 let mut c = Cell::new(chars.horizontal);
341 c.fg = fg;
342 c.bg = bg;
343 buffer.set(area.x + x, area.y + area.height - 1, c);
344 }
345
346 let mut cell = Cell::new(chars.bottom_right);
348 cell.fg = fg;
349 cell.bg = bg;
350 buffer.set(area.x + area.width - 1, area.y + area.height - 1, cell);
351}
352
353#[cfg(test)]
354mod tests {
355 use super::*;
356 use crate::render::Buffer;
357 use crate::widget::Text;
358
359 #[test]
360 fn test_border_new() {
361 let b = Border::new();
362 assert_eq!(b.border_type, BorderType::Single);
363 assert!(b.title.is_none());
364 }
365
366 #[test]
367 fn test_border_types() {
368 assert_eq!(Border::single().border_type, BorderType::Single);
369 assert_eq!(Border::double().border_type, BorderType::Double);
370 assert_eq!(Border::rounded().border_type, BorderType::Rounded);
371 }
372
373 #[test]
374 fn test_border_render_single() {
375 let mut buffer = Buffer::new(10, 5);
376 let area = Rect::new(0, 0, 10, 5);
377 let mut ctx = RenderContext::new(&mut buffer, area);
378
379 let b = Border::single();
380 b.render(&mut ctx);
381
382 assert_eq!(buffer.get(0, 0).unwrap().symbol, '┌');
383 assert_eq!(buffer.get(9, 0).unwrap().symbol, '┐');
384 assert_eq!(buffer.get(0, 4).unwrap().symbol, '└');
385 assert_eq!(buffer.get(9, 4).unwrap().symbol, '┘');
386 assert_eq!(buffer.get(0, 2).unwrap().symbol, '│');
387 assert_eq!(buffer.get(5, 0).unwrap().symbol, '─');
388 }
389
390 #[test]
391 fn test_border_render_double() {
392 let mut buffer = Buffer::new(10, 5);
393 let area = Rect::new(0, 0, 10, 5);
394 let mut ctx = RenderContext::new(&mut buffer, area);
395
396 let b = Border::double();
397 b.render(&mut ctx);
398
399 assert_eq!(buffer.get(0, 0).unwrap().symbol, '╔');
400 assert_eq!(buffer.get(9, 0).unwrap().symbol, '╗');
401 }
402
403 #[test]
404 fn test_border_with_title() {
405 let mut buffer = Buffer::new(20, 5);
406 let area = Rect::new(0, 0, 20, 5);
407 let mut ctx = RenderContext::new(&mut buffer, area);
408
409 let b = Border::single().title("Test");
410 b.render(&mut ctx);
411
412 assert_eq!(buffer.get(2, 0).unwrap().symbol, 'T');
413 assert_eq!(buffer.get(3, 0).unwrap().symbol, 'e');
414 assert_eq!(buffer.get(4, 0).unwrap().symbol, 's');
415 assert_eq!(buffer.get(5, 0).unwrap().symbol, 't');
416 }
417
418 #[test]
419 fn test_border_with_child() {
420 let mut buffer = Buffer::new(10, 5);
421 let area = Rect::new(0, 0, 10, 5);
422 let mut ctx = RenderContext::new(&mut buffer, area);
423
424 let b = Border::single().child(Text::new("Hi"));
425 b.render(&mut ctx);
426
427 assert_eq!(buffer.get(1, 1).unwrap().symbol, 'H');
429 assert_eq!(buffer.get(2, 1).unwrap().symbol, 'i');
430 }
431
432 #[test]
433 fn test_border_rounded() {
434 let mut buffer = Buffer::new(10, 5);
435 let area = Rect::new(0, 0, 10, 5);
436 let mut ctx = RenderContext::new(&mut buffer, area);
437
438 let b = Border::rounded();
439 b.render(&mut ctx);
440
441 assert_eq!(buffer.get(0, 0).unwrap().symbol, '╭');
442 assert_eq!(buffer.get(9, 0).unwrap().symbol, '╮');
443 assert_eq!(buffer.get(0, 4).unwrap().symbol, '╰');
444 assert_eq!(buffer.get(9, 4).unwrap().symbol, '╯');
445 }
446
447 #[test]
448 fn test_border_type_chars() {
449 let none = BorderType::None.chars();
451 assert_eq!(none.top_left, ' ');
452 assert_eq!(none.horizontal, ' ');
453
454 let single = BorderType::Single.chars();
455 assert_eq!(single.top_left, '┌');
456 assert_eq!(single.horizontal, '─');
457
458 let double = BorderType::Double.chars();
459 assert_eq!(double.top_left, '╔');
460 assert_eq!(double.horizontal, '═');
461
462 let rounded = BorderType::Rounded.chars();
463 assert_eq!(rounded.top_left, '╭');
464 assert_eq!(rounded.bottom_right, '╯');
465
466 let thick = BorderType::Thick.chars();
467 assert_eq!(thick.top_left, '┏');
468 assert_eq!(thick.horizontal, '━');
469
470 let ascii = BorderType::Ascii.chars();
471 assert_eq!(ascii.top_left, '+');
472 assert_eq!(ascii.horizontal, '-');
473 }
474
475 #[test]
476 fn test_draw_border_utility() {
477 let mut buffer = Buffer::new(10, 5);
479 let area = Rect::new(0, 0, 10, 5);
480
481 draw_border(&mut buffer, area, BorderType::Single, None, None);
482
483 assert_eq!(buffer.get(0, 0).unwrap().symbol, '┌');
484 assert_eq!(buffer.get(9, 0).unwrap().symbol, '┐');
485 assert_eq!(buffer.get(0, 4).unwrap().symbol, '└');
486 assert_eq!(buffer.get(9, 4).unwrap().symbol, '┘');
487 }
488
489 #[test]
490 fn test_draw_border_none_type() {
491 let mut buffer = Buffer::new(10, 5);
493 let area = Rect::new(0, 0, 10, 5);
494
495 draw_border(&mut buffer, area, BorderType::None, None, None);
496
497 assert_eq!(buffer.get(0, 0).unwrap().symbol, ' ');
499 }
500}