1#![forbid(unsafe_code)]
2
3use crate::buffer::Buffer;
13use crate::cell::{Cell, CellContent};
14use crate::grapheme_width;
15use ftui_core::geometry::Rect;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub struct BorderChars {
23 pub top_left: char,
25 pub top_right: char,
27 pub bottom_left: char,
29 pub bottom_right: char,
31 pub horizontal: char,
33 pub vertical: char,
35}
36
37impl BorderChars {
38 pub const SQUARE: Self = Self {
40 top_left: '┌',
41 top_right: '┐',
42 bottom_left: '└',
43 bottom_right: '┘',
44 horizontal: '─',
45 vertical: '│',
46 };
47
48 pub const ROUNDED: Self = Self {
50 top_left: '╭',
51 top_right: '╮',
52 bottom_left: '╰',
53 bottom_right: '╯',
54 horizontal: '─',
55 vertical: '│',
56 };
57
58 pub const DOUBLE: Self = Self {
60 top_left: '╔',
61 top_right: '╗',
62 bottom_left: '╚',
63 bottom_right: '╝',
64 horizontal: '═',
65 vertical: '║',
66 };
67
68 pub const HEAVY: Self = Self {
70 top_left: '┏',
71 top_right: '┓',
72 bottom_left: '┗',
73 bottom_right: '┛',
74 horizontal: '━',
75 vertical: '┃',
76 };
77
78 pub const ASCII: Self = Self {
80 top_left: '+',
81 top_right: '+',
82 bottom_left: '+',
83 bottom_right: '+',
84 horizontal: '-',
85 vertical: '|',
86 };
87}
88
89pub trait Draw {
91 fn draw_horizontal_line(&mut self, x: u16, y: u16, width: u16, cell: Cell);
93
94 fn draw_vertical_line(&mut self, x: u16, y: u16, height: u16, cell: Cell);
96
97 fn draw_rect_filled(&mut self, rect: Rect, cell: Cell);
99
100 fn draw_rect_outline(&mut self, rect: Rect, cell: Cell);
102
103 fn print_text(&mut self, x: u16, y: u16, text: &str, base_cell: Cell) -> u16;
108
109 fn print_text_clipped(
114 &mut self,
115 x: u16,
116 y: u16,
117 text: &str,
118 base_cell: Cell,
119 max_x: u16,
120 ) -> u16;
121
122 fn draw_border(&mut self, rect: Rect, chars: BorderChars, base_cell: Cell);
127
128 fn draw_box(&mut self, rect: Rect, chars: BorderChars, border_cell: Cell, fill_cell: Cell);
134
135 fn paint_area(
140 &mut self,
141 rect: Rect,
142 fg: Option<crate::cell::PackedRgba>,
143 bg: Option<crate::cell::PackedRgba>,
144 );
145}
146
147impl Draw for Buffer {
148 fn draw_horizontal_line(&mut self, x: u16, y: u16, width: u16, cell: Cell) {
149 for i in 0..width {
150 self.set_fast(x.saturating_add(i), y, cell);
151 }
152 }
153
154 fn draw_vertical_line(&mut self, x: u16, y: u16, height: u16, cell: Cell) {
155 for i in 0..height {
156 self.set_fast(x, y.saturating_add(i), cell);
157 }
158 }
159
160 fn draw_rect_filled(&mut self, rect: Rect, cell: Cell) {
161 self.fill(rect, cell);
162 }
163
164 fn draw_rect_outline(&mut self, rect: Rect, cell: Cell) {
165 if rect.is_empty() {
166 return;
167 }
168
169 self.draw_horizontal_line(rect.x, rect.y, rect.width, cell);
171
172 if rect.height > 1 {
174 self.draw_horizontal_line(rect.x, rect.bottom().saturating_sub(1), rect.width, cell);
175 }
176
177 if rect.height > 2 {
179 self.draw_vertical_line(rect.x, rect.y.saturating_add(1), rect.height - 2, cell);
180 }
181
182 if rect.width > 1 && rect.height > 2 {
184 self.draw_vertical_line(
185 rect.right().saturating_sub(1),
186 rect.y.saturating_add(1),
187 rect.height - 2,
188 cell,
189 );
190 }
191 }
192
193 fn print_text(&mut self, x: u16, y: u16, text: &str, base_cell: Cell) -> u16 {
194 self.print_text_clipped(x, y, text, base_cell, self.width())
195 }
196
197 fn print_text_clipped(
198 &mut self,
199 x: u16,
200 y: u16,
201 text: &str,
202 base_cell: Cell,
203 max_x: u16,
204 ) -> u16 {
205 use unicode_segmentation::UnicodeSegmentation;
206
207 let mut cx = x;
208 for grapheme in text.graphemes(true) {
209 if cx >= max_x {
210 break;
211 }
212
213 let Some(first) = grapheme.chars().next() else {
214 continue;
215 };
216
217 let rendered_content = CellContent::from_char(first);
222 let rendered_width = rendered_content.width();
223 let mut width = grapheme_width(grapheme);
224 if width == 0 {
225 width = rendered_width;
226 }
227 width = width.max(rendered_width);
228 if width == 0 {
229 continue;
230 }
231
232 if cx as u32 + width as u32 > max_x as u32 {
234 break;
235 }
236
237 let cell = Cell {
238 content: rendered_content,
239 fg: base_cell.fg,
240 bg: base_cell.bg,
241 attrs: base_cell.attrs,
242 };
243 self.set_fast(cx, y, cell);
244
245 if rendered_width < width {
248 let filler = Cell {
249 content: CellContent::from_char(' '),
250 fg: base_cell.fg,
251 bg: base_cell.bg,
252 attrs: base_cell.attrs,
253 };
254 for offset in rendered_width..width {
255 self.set_fast(cx.saturating_add(offset as u16), y, filler);
256 }
257 }
258
259 cx = cx.saturating_add(width as u16);
260 }
261 cx
262 }
263
264 fn draw_border(&mut self, rect: Rect, chars: BorderChars, base_cell: Cell) {
265 if rect.is_empty() {
266 return;
267 }
268
269 let make_cell = |c: char| -> Cell {
270 Cell {
271 content: CellContent::from_char(c),
272 fg: base_cell.fg,
273 bg: base_cell.bg,
274 attrs: base_cell.attrs,
275 }
276 };
277
278 let h_cell = make_cell(chars.horizontal);
279 let v_cell = make_cell(chars.vertical);
280
281 for x in rect.left()..rect.right() {
283 self.set_fast(x, rect.top(), h_cell);
284 }
285
286 if rect.height > 1 {
288 for x in rect.left()..rect.right() {
289 self.set_fast(x, rect.bottom().saturating_sub(1), h_cell);
290 }
291 }
292
293 if rect.height > 2 {
295 for y in (rect.top().saturating_add(1))..(rect.bottom().saturating_sub(1)) {
296 self.set_fast(rect.left(), y, v_cell);
297 }
298 }
299
300 if rect.width > 1 && rect.height > 2 {
302 for y in (rect.top().saturating_add(1))..(rect.bottom().saturating_sub(1)) {
303 self.set_fast(rect.right().saturating_sub(1), y, v_cell);
304 }
305 }
306
307 self.set_fast(rect.left(), rect.top(), make_cell(chars.top_left));
309
310 if rect.width > 1 {
311 self.set_fast(
312 rect.right().saturating_sub(1),
313 rect.top(),
314 make_cell(chars.top_right),
315 );
316 }
317
318 if rect.height > 1 {
319 self.set_fast(
320 rect.left(),
321 rect.bottom().saturating_sub(1),
322 make_cell(chars.bottom_left),
323 );
324 }
325
326 if rect.width > 1 && rect.height > 1 {
327 self.set_fast(
328 rect.right().saturating_sub(1),
329 rect.bottom().saturating_sub(1),
330 make_cell(chars.bottom_right),
331 );
332 }
333 }
334
335 fn draw_box(&mut self, rect: Rect, chars: BorderChars, border_cell: Cell, fill_cell: Cell) {
336 if rect.is_empty() {
337 return;
338 }
339
340 if rect.width > 2 && rect.height > 2 {
342 let inner = Rect::new(
343 rect.x.saturating_add(1),
344 rect.y.saturating_add(1),
345 rect.width - 2,
346 rect.height - 2,
347 );
348 self.fill(inner, fill_cell);
349 }
350
351 self.draw_border(rect, chars, border_cell);
353 }
354
355 fn paint_area(
356 &mut self,
357 rect: Rect,
358 fg: Option<crate::cell::PackedRgba>,
359 bg: Option<crate::cell::PackedRgba>,
360 ) {
361 for y in rect.y..rect.bottom() {
362 for x in rect.x..rect.right() {
363 if let Some(cell) = self.get_mut(x, y) {
364 if let Some(fg_color) = fg {
365 cell.fg = fg_color;
366 }
367 if let Some(bg_color) = bg {
368 cell.bg = bg_color;
369 }
370 }
371 }
372 }
373 }
374}
375
376#[cfg(test)]
377mod tests {
378 use super::*;
379 use crate::cell::PackedRgba;
380
381 fn char_at(buf: &Buffer, x: u16, y: u16) -> Option<char> {
384 buf.get(x, y).and_then(|c| {
385 if c.is_empty() {
386 None
387 } else {
388 c.content.as_char()
389 }
390 })
391 }
392
393 #[test]
396 fn horizontal_line_basic() {
397 let mut buf = Buffer::new(10, 1);
398 let cell = Cell::from_char('─');
399 buf.draw_horizontal_line(2, 0, 5, cell);
400 assert_eq!(char_at(&buf, 1, 0), None);
401 assert_eq!(char_at(&buf, 2, 0), Some('─'));
402 assert_eq!(char_at(&buf, 6, 0), Some('─'));
403 assert_eq!(char_at(&buf, 7, 0), None);
404 }
405
406 #[test]
407 fn horizontal_line_zero_width() {
408 let mut buf = Buffer::new(10, 1);
409 buf.draw_horizontal_line(0, 0, 0, Cell::from_char('x'));
410 assert!(buf.get(0, 0).unwrap().is_empty());
412 }
413
414 #[test]
415 fn horizontal_line_clipped_by_scissor() {
416 let mut buf = Buffer::new(10, 1);
417 buf.push_scissor(Rect::new(0, 0, 3, 1));
418 buf.draw_horizontal_line(0, 0, 10, Cell::from_char('x'));
419 assert_eq!(char_at(&buf, 0, 0), Some('x'));
420 assert_eq!(char_at(&buf, 2, 0), Some('x'));
421 assert!(buf.get(3, 0).unwrap().is_empty());
423 }
424
425 #[test]
428 fn vertical_line_basic() {
429 let mut buf = Buffer::new(1, 10);
430 let cell = Cell::from_char('│');
431 buf.draw_vertical_line(0, 1, 4, cell);
432 assert!(buf.get(0, 0).unwrap().is_empty());
433 assert_eq!(char_at(&buf, 0, 1), Some('│'));
434 assert_eq!(char_at(&buf, 0, 4), Some('│'));
435 assert!(buf.get(0, 5).unwrap().is_empty());
436 }
437
438 #[test]
439 fn vertical_line_zero_height() {
440 let mut buf = Buffer::new(1, 10);
441 buf.draw_vertical_line(0, 0, 0, Cell::from_char('x'));
442 assert!(buf.get(0, 0).unwrap().is_empty());
443 }
444
445 #[test]
448 fn rect_filled() {
449 let mut buf = Buffer::new(5, 5);
450 let cell = Cell::from_char('█');
451 buf.draw_rect_filled(Rect::new(1, 1, 3, 3), cell);
452 assert_eq!(char_at(&buf, 1, 1), Some('█'));
454 assert_eq!(char_at(&buf, 3, 3), Some('█'));
455 assert!(buf.get(0, 0).unwrap().is_empty());
457 assert!(buf.get(4, 4).unwrap().is_empty());
458 }
459
460 #[test]
461 fn rect_filled_empty() {
462 let mut buf = Buffer::new(5, 5);
463 buf.draw_rect_filled(Rect::new(0, 0, 0, 0), Cell::from_char('x'));
464 assert!(buf.get(0, 0).unwrap().is_empty());
465 }
466
467 #[test]
470 fn rect_outline_basic() {
471 let mut buf = Buffer::new(5, 5);
472 let cell = Cell::from_char('#');
473 buf.draw_rect_outline(Rect::new(0, 0, 5, 5), cell);
474
475 assert_eq!(char_at(&buf, 0, 0), Some('#'));
477 assert_eq!(char_at(&buf, 4, 0), Some('#'));
478 assert_eq!(char_at(&buf, 0, 4), Some('#'));
479 assert_eq!(char_at(&buf, 4, 4), Some('#'));
480
481 assert_eq!(char_at(&buf, 2, 0), Some('#'));
483 assert_eq!(char_at(&buf, 0, 2), Some('#'));
484
485 assert!(buf.get(2, 2).unwrap().is_empty());
487 }
488
489 #[test]
490 fn rect_outline_1x1() {
491 let mut buf = Buffer::new(5, 5);
492 buf.draw_rect_outline(Rect::new(1, 1, 1, 1), Cell::from_char('o'));
493 assert_eq!(char_at(&buf, 1, 1), Some('o'));
494 }
495
496 #[test]
497 fn rect_outline_2x2() {
498 let mut buf = Buffer::new(5, 5);
499 buf.draw_rect_outline(Rect::new(0, 0, 2, 2), Cell::from_char('#'));
500 assert_eq!(char_at(&buf, 0, 0), Some('#'));
501 assert_eq!(char_at(&buf, 1, 0), Some('#'));
502 assert_eq!(char_at(&buf, 0, 1), Some('#'));
503 assert_eq!(char_at(&buf, 1, 1), Some('#'));
504 }
505
506 #[test]
509 fn print_text_basic() {
510 let mut buf = Buffer::new(20, 1);
511 let cell = Cell::from_char(' '); let end_x = buf.print_text(2, 0, "Hello", cell);
513 assert_eq!(char_at(&buf, 2, 0), Some('H'));
514 assert_eq!(char_at(&buf, 3, 0), Some('e'));
515 assert_eq!(char_at(&buf, 6, 0), Some('o'));
516 assert_eq!(end_x, 7);
517 }
518
519 #[test]
520 fn print_text_preserves_style() {
521 let mut buf = Buffer::new(10, 1);
522 let cell = Cell::from_char(' ')
523 .with_fg(PackedRgba::rgb(255, 0, 0))
524 .with_bg(PackedRgba::rgb(0, 0, 255));
525 buf.print_text(0, 0, "AB", cell);
526 let a = buf.get(0, 0).unwrap();
527 assert_eq!(a.fg, PackedRgba::rgb(255, 0, 0));
528 assert_eq!(a.bg, PackedRgba::rgb(0, 0, 255));
529 }
530
531 #[test]
532 fn print_text_clips_at_buffer_edge() {
533 let mut buf = Buffer::new(5, 1);
534 let end_x = buf.print_text(0, 0, "Hello World", Cell::from_char(' '));
535 assert_eq!(char_at(&buf, 4, 0), Some('o'));
536 assert_eq!(end_x, 5);
537 }
538
539 #[test]
540 fn print_text_clipped_stops_at_max_x() {
541 let mut buf = Buffer::new(20, 1);
542 let end_x = buf.print_text_clipped(0, 0, "Hello World", Cell::from_char(' '), 5);
543 assert_eq!(char_at(&buf, 4, 0), Some('o'));
544 assert_eq!(end_x, 5);
545 assert!(buf.get(5, 0).unwrap().is_empty());
547 }
548
549 #[test]
550 fn print_text_multi_codepoint_grapheme_fills_width() {
551 let mut buf = Buffer::new(4, 1);
555
556 buf.set_raw(1, 0, Cell::from_char('|'));
558
559 let base = Cell::from_char(' ')
560 .with_fg(PackedRgba::rgb(255, 0, 0))
561 .with_bg(PackedRgba::rgb(0, 0, 255));
562
563 let end_x = buf.print_text_clipped(0, 0, "👍🏽", base, 4);
564 assert_eq!(end_x, 2);
565 assert_eq!(char_at(&buf, 0, 0), Some('👍'));
566
567 let c1 = buf.get(1, 0).unwrap();
569 assert!(c1.is_continuation());
570 }
571
572 #[test]
573 fn print_text_wide_chars() {
574 let mut buf = Buffer::new(10, 1);
575 let end_x = buf.print_text(0, 0, "AB", Cell::from_char(' '));
576 assert_eq!(end_x, 2);
578 assert_eq!(char_at(&buf, 0, 0), Some('A'));
579 assert_eq!(char_at(&buf, 1, 0), Some('B'));
580 }
581
582 #[test]
583 fn print_text_wide_char_clipped() {
584 let mut buf = Buffer::new(10, 1);
585 let end_x = buf.print_text_clipped(4, 0, "中", Cell::from_char(' '), 5);
587 assert_eq!(end_x, 4);
589 }
590
591 #[test]
592 fn print_text_empty_string() {
593 let mut buf = Buffer::new(10, 1);
594 let end_x = buf.print_text(0, 0, "", Cell::from_char(' '));
595 assert_eq!(end_x, 0);
596 }
597
598 #[test]
601 fn draw_border_square() {
602 let mut buf = Buffer::new(5, 3);
603 buf.draw_border(
604 Rect::new(0, 0, 5, 3),
605 BorderChars::SQUARE,
606 Cell::from_char(' '),
607 );
608
609 assert_eq!(char_at(&buf, 0, 0), Some('┌'));
611 assert_eq!(char_at(&buf, 4, 0), Some('┐'));
612 assert_eq!(char_at(&buf, 0, 2), Some('└'));
613 assert_eq!(char_at(&buf, 4, 2), Some('┘'));
614
615 assert_eq!(char_at(&buf, 1, 0), Some('─'));
617 assert_eq!(char_at(&buf, 2, 0), Some('─'));
618 assert_eq!(char_at(&buf, 3, 0), Some('─'));
619
620 assert_eq!(char_at(&buf, 0, 1), Some('│'));
622 assert_eq!(char_at(&buf, 4, 1), Some('│'));
623
624 assert!(buf.get(2, 1).unwrap().is_empty());
626 }
627
628 #[test]
629 fn draw_border_rounded() {
630 let mut buf = Buffer::new(4, 3);
631 buf.draw_border(
632 Rect::new(0, 0, 4, 3),
633 BorderChars::ROUNDED,
634 Cell::from_char(' '),
635 );
636 assert_eq!(char_at(&buf, 0, 0), Some('╭'));
637 assert_eq!(char_at(&buf, 3, 0), Some('╮'));
638 assert_eq!(char_at(&buf, 0, 2), Some('╰'));
639 assert_eq!(char_at(&buf, 3, 2), Some('╯'));
640 }
641
642 #[test]
643 fn draw_border_1x1() {
644 let mut buf = Buffer::new(5, 5);
645 buf.draw_border(
646 Rect::new(1, 1, 1, 1),
647 BorderChars::SQUARE,
648 Cell::from_char(' '),
649 );
650 assert_eq!(char_at(&buf, 1, 1), Some('┌'));
652 }
653
654 #[test]
655 fn draw_border_2x2() {
656 let mut buf = Buffer::new(5, 5);
657 buf.draw_border(
658 Rect::new(0, 0, 2, 2),
659 BorderChars::SQUARE,
660 Cell::from_char(' '),
661 );
662 assert_eq!(char_at(&buf, 0, 0), Some('┌'));
663 assert_eq!(char_at(&buf, 1, 0), Some('┐'));
664 assert_eq!(char_at(&buf, 0, 1), Some('└'));
665 assert_eq!(char_at(&buf, 1, 1), Some('┘'));
666 }
667
668 #[test]
669 fn draw_border_empty_rect() {
670 let mut buf = Buffer::new(5, 5);
671 buf.draw_border(
672 Rect::new(0, 0, 0, 0),
673 BorderChars::SQUARE,
674 Cell::from_char(' '),
675 );
676 assert!(buf.get(0, 0).unwrap().is_empty());
678 }
679
680 #[test]
681 fn draw_border_preserves_style() {
682 let mut buf = Buffer::new(5, 3);
683 let cell = Cell::from_char(' ')
684 .with_fg(PackedRgba::rgb(0, 255, 0))
685 .with_bg(PackedRgba::rgb(0, 0, 128));
686 buf.draw_border(Rect::new(0, 0, 5, 3), BorderChars::SQUARE, cell);
687
688 let corner = buf.get(0, 0).unwrap();
689 assert_eq!(corner.fg, PackedRgba::rgb(0, 255, 0));
690 assert_eq!(corner.bg, PackedRgba::rgb(0, 0, 128));
691
692 let edge = buf.get(2, 0).unwrap();
693 assert_eq!(edge.fg, PackedRgba::rgb(0, 255, 0));
694 }
695
696 #[test]
697 fn draw_border_clipped_by_scissor() {
698 let mut buf = Buffer::new(10, 5);
699 buf.push_scissor(Rect::new(0, 0, 3, 3));
700 buf.draw_border(
701 Rect::new(0, 0, 6, 4),
702 BorderChars::SQUARE,
703 Cell::from_char(' '),
704 );
705
706 assert_eq!(char_at(&buf, 0, 0), Some('┌'));
708 assert_eq!(char_at(&buf, 2, 0), Some('─'));
709
710 assert!(buf.get(5, 0).unwrap().is_empty());
712 assert!(buf.get(0, 3).unwrap().is_empty());
713 }
714
715 #[test]
718 fn draw_box_basic() {
719 let mut buf = Buffer::new(5, 4);
720 let border = Cell::from_char(' ').with_fg(PackedRgba::rgb(255, 255, 255));
721 let fill = Cell::from_char('.').with_bg(PackedRgba::rgb(50, 50, 50));
722 buf.draw_box(Rect::new(0, 0, 5, 4), BorderChars::SQUARE, border, fill);
723
724 assert_eq!(char_at(&buf, 0, 0), Some('┌'));
726 assert_eq!(char_at(&buf, 4, 3), Some('┘'));
727
728 assert_eq!(char_at(&buf, 1, 1), Some('.'));
730 assert_eq!(char_at(&buf, 3, 2), Some('.'));
731 assert_eq!(buf.get(2, 1).unwrap().bg, PackedRgba::rgb(50, 50, 50));
732 }
733
734 #[test]
735 fn draw_box_too_small_for_interior() {
736 let mut buf = Buffer::new(5, 5);
737 let border = Cell::from_char(' ');
738 let fill = Cell::from_char('X');
739 buf.draw_box(Rect::new(0, 0, 2, 2), BorderChars::SQUARE, border, fill);
740
741 assert_eq!(char_at(&buf, 0, 0), Some('┌'));
743 assert_eq!(char_at(&buf, 1, 0), Some('┐'));
744 }
745
746 #[test]
747 fn draw_box_empty() {
748 let mut buf = Buffer::new(5, 5);
749 buf.draw_box(
750 Rect::new(0, 0, 0, 0),
751 BorderChars::SQUARE,
752 Cell::from_char(' '),
753 Cell::from_char('.'),
754 );
755 assert!(buf.get(0, 0).unwrap().is_empty());
756 }
757
758 #[test]
761 fn paint_area_sets_colors() {
762 let mut buf = Buffer::new(5, 3);
763 buf.set(1, 1, Cell::from_char('X'));
765 buf.set(2, 1, Cell::from_char('Y'));
766
767 buf.paint_area(
768 Rect::new(0, 0, 5, 3),
769 None,
770 Some(PackedRgba::rgb(30, 30, 30)),
771 );
772
773 assert_eq!(char_at(&buf, 1, 1), Some('X'));
775 assert_eq!(buf.get(1, 1).unwrap().bg, PackedRgba::rgb(30, 30, 30));
777 assert_eq!(buf.get(0, 0).unwrap().bg, PackedRgba::rgb(30, 30, 30));
778 }
779
780 #[test]
781 fn paint_area_sets_fg() {
782 let mut buf = Buffer::new(3, 1);
783 buf.set(0, 0, Cell::from_char('A'));
784
785 buf.paint_area(
786 Rect::new(0, 0, 3, 1),
787 Some(PackedRgba::rgb(200, 100, 50)),
788 None,
789 );
790
791 assert_eq!(buf.get(0, 0).unwrap().fg, PackedRgba::rgb(200, 100, 50));
792 }
793
794 #[test]
795 fn paint_area_empty_rect() {
796 let mut buf = Buffer::new(5, 5);
797 buf.set(0, 0, Cell::from_char('A'));
798 let original_fg = buf.get(0, 0).unwrap().fg;
799
800 buf.paint_area(
801 Rect::new(0, 0, 0, 0),
802 Some(PackedRgba::rgb(255, 0, 0)),
803 None,
804 );
805
806 assert_eq!(buf.get(0, 0).unwrap().fg, original_fg);
808 }
809
810 #[test]
813 fn all_border_presets() {
814 let mut buf = Buffer::new(6, 4);
815 let cell = Cell::from_char(' ');
816 let rect = Rect::new(0, 0, 6, 4);
817
818 for chars in [
819 BorderChars::SQUARE,
820 BorderChars::ROUNDED,
821 BorderChars::DOUBLE,
822 BorderChars::HEAVY,
823 BorderChars::ASCII,
824 ] {
825 buf.clear();
826 buf.draw_border(rect, chars, cell);
827 assert!(buf.get(0, 0).unwrap().content.as_char().is_some());
829 assert!(buf.get(5, 3).unwrap().content.as_char().is_some());
830 }
831 }
832
833 #[test]
836 fn draw_border_then_print_title() {
837 let mut buf = Buffer::new(12, 3);
838 let cell = Cell::from_char(' ');
839
840 buf.draw_border(Rect::new(0, 0, 12, 3), BorderChars::SQUARE, cell);
842
843 buf.print_text(1, 0, "Title", cell);
845
846 assert_eq!(char_at(&buf, 0, 0), Some('┌'));
847 assert_eq!(char_at(&buf, 1, 0), Some('T'));
848 assert_eq!(char_at(&buf, 5, 0), Some('e'));
849 assert_eq!(char_at(&buf, 6, 0), Some('─'));
850 assert_eq!(char_at(&buf, 11, 0), Some('┐'));
851 }
852
853 #[test]
854 fn draw_nested_borders() {
855 let mut buf = Buffer::new(10, 6);
856 let cell = Cell::from_char(' ');
857
858 buf.draw_border(Rect::new(0, 0, 10, 6), BorderChars::DOUBLE, cell);
859 buf.draw_border(Rect::new(1, 1, 8, 4), BorderChars::SQUARE, cell);
860
861 assert_eq!(char_at(&buf, 0, 0), Some('╔'));
863 assert_eq!(char_at(&buf, 9, 5), Some('╝'));
864
865 assert_eq!(char_at(&buf, 1, 1), Some('┌'));
867 assert_eq!(char_at(&buf, 8, 4), Some('┘'));
868 }
869
870 #[test]
873 fn border_chars_debug_clone_copy_eq() {
874 let a = BorderChars::SQUARE;
875 let dbg = format!("{:?}", a);
876 assert!(dbg.contains("BorderChars"), "Debug: {dbg}");
877 let copied: BorderChars = a; assert_eq!(a, copied);
879 assert_ne!(a, BorderChars::ROUNDED);
880 assert_ne!(BorderChars::DOUBLE, BorderChars::HEAVY);
881 }
882
883 #[test]
884 fn border_chars_double_characters() {
885 let d = BorderChars::DOUBLE;
886 assert_eq!(d.top_left, '╔');
887 assert_eq!(d.top_right, '╗');
888 assert_eq!(d.bottom_left, '╚');
889 assert_eq!(d.bottom_right, '╝');
890 assert_eq!(d.horizontal, '═');
891 assert_eq!(d.vertical, '║');
892 }
893
894 #[test]
895 fn border_chars_heavy_characters() {
896 let h = BorderChars::HEAVY;
897 assert_eq!(h.top_left, '┏');
898 assert_eq!(h.top_right, '┓');
899 assert_eq!(h.bottom_left, '┗');
900 assert_eq!(h.bottom_right, '┛');
901 assert_eq!(h.horizontal, '━');
902 assert_eq!(h.vertical, '┃');
903 }
904
905 #[test]
906 fn border_chars_ascii_characters() {
907 let a = BorderChars::ASCII;
908 assert_eq!(a.top_left, '+');
909 assert_eq!(a.top_right, '+');
910 assert_eq!(a.bottom_left, '+');
911 assert_eq!(a.bottom_right, '+');
912 assert_eq!(a.horizontal, '-');
913 assert_eq!(a.vertical, '|');
914 }
915
916 #[test]
919 fn rect_outline_empty_rect() {
920 let mut buf = Buffer::new(5, 5);
921 buf.draw_rect_outline(Rect::new(0, 0, 0, 0), Cell::from_char('#'));
922 assert!(buf.get(0, 0).unwrap().is_empty());
924 }
925
926 #[test]
927 fn rect_outline_1xn_tall() {
928 let mut buf = Buffer::new(5, 5);
929 buf.draw_rect_outline(Rect::new(1, 0, 1, 4), Cell::from_char('#'));
930 assert_eq!(char_at(&buf, 1, 0), Some('#'));
932 assert_eq!(char_at(&buf, 1, 3), Some('#'));
933 assert_eq!(char_at(&buf, 1, 1), Some('#'));
935 assert_eq!(char_at(&buf, 1, 2), Some('#'));
936 }
937
938 #[test]
939 fn rect_outline_nx1_wide() {
940 let mut buf = Buffer::new(5, 5);
941 buf.draw_rect_outline(Rect::new(0, 1, 4, 1), Cell::from_char('#'));
942 for x in 0..4 {
944 assert_eq!(char_at(&buf, x, 1), Some('#'));
945 }
946 assert!(buf.get(0, 2).unwrap().is_empty());
948 }
949
950 #[test]
951 fn rect_outline_3x3() {
952 let mut buf = Buffer::new(5, 5);
953 buf.draw_rect_outline(Rect::new(0, 0, 3, 3), Cell::from_char('#'));
954 for &(x, y) in &[
956 (0, 0),
957 (1, 0),
958 (2, 0),
959 (0, 1),
960 (2, 1),
961 (0, 2),
962 (1, 2),
963 (2, 2),
964 ] {
965 assert_eq!(char_at(&buf, x, y), Some('#'), "({x},{y})");
966 }
967 assert!(buf.get(1, 1).unwrap().is_empty());
969 }
970
971 #[test]
974 fn draw_border_1x3_narrow() {
975 let mut buf = Buffer::new(5, 5);
977 buf.draw_border(
978 Rect::new(1, 0, 1, 3),
979 BorderChars::SQUARE,
980 Cell::from_char(' '),
981 );
982 assert_eq!(char_at(&buf, 1, 0), Some('┌'));
983 assert_eq!(char_at(&buf, 1, 1), Some('│'));
984 assert_eq!(char_at(&buf, 1, 2), Some('└'));
985 }
986
987 #[test]
988 fn draw_border_3x1_flat() {
989 let mut buf = Buffer::new(5, 5);
991 buf.draw_border(
992 Rect::new(0, 0, 3, 1),
993 BorderChars::SQUARE,
994 Cell::from_char(' '),
995 );
996 assert_eq!(char_at(&buf, 0, 0), Some('┌'));
997 assert_eq!(char_at(&buf, 1, 0), Some('─'));
998 assert_eq!(char_at(&buf, 2, 0), Some('┐'));
999 }
1000
1001 #[test]
1002 fn draw_border_2x1() {
1003 let mut buf = Buffer::new(5, 5);
1005 buf.draw_border(
1006 Rect::new(0, 0, 2, 1),
1007 BorderChars::SQUARE,
1008 Cell::from_char(' '),
1009 );
1010 assert_eq!(char_at(&buf, 0, 0), Some('┌'));
1011 assert_eq!(char_at(&buf, 1, 0), Some('┐'));
1012 }
1013
1014 #[test]
1015 fn draw_border_1x2() {
1016 let mut buf = Buffer::new(5, 5);
1018 buf.draw_border(
1019 Rect::new(0, 0, 1, 2),
1020 BorderChars::SQUARE,
1021 Cell::from_char(' '),
1022 );
1023 assert_eq!(char_at(&buf, 0, 0), Some('┌'));
1024 assert_eq!(char_at(&buf, 0, 1), Some('└'));
1025 }
1026
1027 #[test]
1030 fn draw_box_3x3_minimal_interior() {
1031 let mut buf = Buffer::new(5, 5);
1032 let border = Cell::from_char(' ');
1033 let fill = Cell::from_char('.');
1034 buf.draw_box(Rect::new(0, 0, 3, 3), BorderChars::SQUARE, border, fill);
1035 assert_eq!(char_at(&buf, 0, 0), Some('┌'));
1037 assert_eq!(char_at(&buf, 2, 2), Some('┘'));
1038 assert_eq!(char_at(&buf, 1, 1), Some('.'));
1040 }
1041
1042 #[test]
1043 fn draw_box_1x1() {
1044 let mut buf = Buffer::new(5, 5);
1045 let border = Cell::from_char(' ');
1046 let fill = Cell::from_char('X');
1047 buf.draw_box(Rect::new(1, 1, 1, 1), BorderChars::SQUARE, border, fill);
1048 assert_eq!(char_at(&buf, 1, 1), Some('┌'));
1050 }
1051
1052 #[test]
1053 fn draw_box_border_overwrites_fill() {
1054 let mut buf = Buffer::new(5, 5);
1056 let border = Cell::from_char(' ').with_fg(PackedRgba::rgb(255, 0, 0));
1057 let fill = Cell::from_char('.').with_fg(PackedRgba::rgb(0, 255, 0));
1058 buf.draw_box(Rect::new(0, 0, 4, 4), BorderChars::SQUARE, border, fill);
1059 assert_eq!(char_at(&buf, 0, 0), Some('┌'));
1061 assert_eq!(buf.get(0, 0).unwrap().fg, PackedRgba::rgb(255, 0, 0));
1062 assert_eq!(char_at(&buf, 1, 1), Some('.'));
1064 assert_eq!(buf.get(1, 1).unwrap().fg, PackedRgba::rgb(0, 255, 0));
1065 }
1066
1067 #[test]
1070 fn paint_area_sets_both_fg_and_bg() {
1071 let mut buf = Buffer::new(3, 3);
1072 buf.set(1, 1, Cell::from_char('X'));
1073 buf.paint_area(
1074 Rect::new(0, 0, 3, 3),
1075 Some(PackedRgba::rgb(100, 200, 50)),
1076 Some(PackedRgba::rgb(10, 20, 30)),
1077 );
1078 let cell = buf.get(1, 1).unwrap();
1079 assert_eq!(cell.content.as_char(), Some('X'));
1080 assert_eq!(cell.fg, PackedRgba::rgb(100, 200, 50));
1081 assert_eq!(cell.bg, PackedRgba::rgb(10, 20, 30));
1082 }
1083
1084 #[test]
1085 fn paint_area_beyond_buffer() {
1086 let mut buf = Buffer::new(3, 3);
1087 buf.paint_area(
1089 Rect::new(0, 0, 100, 100),
1090 Some(PackedRgba::rgb(255, 0, 0)),
1091 None,
1092 );
1093 assert_eq!(buf.get(0, 0).unwrap().fg, PackedRgba::rgb(255, 0, 0));
1095 assert_eq!(buf.get(2, 2).unwrap().fg, PackedRgba::rgb(255, 0, 0));
1096 }
1097
1098 #[test]
1099 fn paint_area_no_colors() {
1100 let mut buf = Buffer::new(3, 1);
1101 let cell = Cell::from_char('A').with_fg(PackedRgba::rgb(10, 20, 30));
1102 buf.set(0, 0, cell);
1103 buf.paint_area(Rect::new(0, 0, 3, 1), None, None);
1105 assert_eq!(buf.get(0, 0).unwrap().fg, PackedRgba::rgb(10, 20, 30));
1106 }
1107
1108 #[test]
1111 fn print_text_max_x_zero() {
1112 let mut buf = Buffer::new(10, 1);
1113 let end_x = buf.print_text_clipped(0, 0, "Hello", Cell::from_char(' '), 0);
1114 assert_eq!(end_x, 0);
1115 assert!(buf.get(0, 0).unwrap().is_empty());
1116 }
1117
1118 #[test]
1119 fn print_text_start_past_max_x() {
1120 let mut buf = Buffer::new(10, 1);
1121 let end_x = buf.print_text_clipped(5, 0, "Hello", Cell::from_char(' '), 3);
1122 assert_eq!(end_x, 5); }
1124
1125 #[test]
1126 fn print_text_single_char() {
1127 let mut buf = Buffer::new(10, 1);
1128 let end_x = buf.print_text(0, 0, "X", Cell::from_char(' '));
1129 assert_eq!(end_x, 1);
1130 assert_eq!(char_at(&buf, 0, 0), Some('X'));
1131 }
1132
1133 #[test]
1136 fn horizontal_line_at_buffer_bottom() {
1137 let mut buf = Buffer::new(5, 3);
1138 buf.draw_horizontal_line(0, 2, 5, Cell::from_char('='));
1139 for x in 0..5 {
1140 assert_eq!(char_at(&buf, x, 2), Some('='));
1141 }
1142 }
1143
1144 #[test]
1145 fn vertical_line_at_buffer_right_edge() {
1146 let mut buf = Buffer::new(5, 5);
1147 buf.draw_vertical_line(4, 0, 5, Cell::from_char('|'));
1148 for y in 0..5 {
1149 assert_eq!(char_at(&buf, 4, y), Some('|'));
1150 }
1151 }
1152
1153 #[test]
1154 fn horizontal_line_exceeds_buffer() {
1155 let mut buf = Buffer::new(3, 1);
1157 buf.draw_horizontal_line(0, 0, 100, Cell::from_char('-'));
1158 for x in 0..3 {
1159 assert_eq!(char_at(&buf, x, 0), Some('-'));
1160 }
1161 }
1162
1163 #[test]
1164 fn vertical_line_exceeds_buffer() {
1165 let mut buf = Buffer::new(1, 3);
1166 buf.draw_vertical_line(0, 0, 100, Cell::from_char('|'));
1167 for y in 0..3 {
1168 assert_eq!(char_at(&buf, 0, y), Some('|'));
1169 }
1170 }
1171
1172 #[test]
1175 fn rect_filled_clipped_by_scissor() {
1176 let mut buf = Buffer::new(10, 10);
1177 buf.push_scissor(Rect::new(2, 2, 3, 3));
1178 buf.draw_rect_filled(Rect::new(0, 0, 10, 10), Cell::from_char('#'));
1179 assert_eq!(char_at(&buf, 2, 2), Some('#'));
1181 assert_eq!(char_at(&buf, 4, 4), Some('#'));
1182 assert!(buf.get(0, 0).unwrap().is_empty());
1184 assert!(buf.get(5, 5).unwrap().is_empty());
1185 buf.pop_scissor();
1186 }
1187
1188 #[test]
1189 fn vertical_line_clipped_by_scissor() {
1190 let mut buf = Buffer::new(5, 10);
1191 buf.push_scissor(Rect::new(0, 2, 5, 3));
1192 buf.draw_vertical_line(2, 0, 10, Cell::from_char('|'));
1193 assert_eq!(char_at(&buf, 2, 2), Some('|'));
1195 assert_eq!(char_at(&buf, 2, 4), Some('|'));
1196 assert!(buf.get(2, 0).unwrap().is_empty());
1198 assert!(buf.get(2, 5).unwrap().is_empty());
1199 buf.pop_scissor();
1200 }
1201
1202 #[test]
1205 fn drawing_on_1x1_buffer() {
1206 let mut buf = Buffer::new(1, 1);
1207 buf.draw_horizontal_line(0, 0, 1, Cell::from_char('H'));
1208 assert_eq!(char_at(&buf, 0, 0), Some('H'));
1209
1210 buf.clear();
1211 buf.draw_vertical_line(0, 0, 1, Cell::from_char('V'));
1212 assert_eq!(char_at(&buf, 0, 0), Some('V'));
1213
1214 buf.clear();
1215 buf.draw_rect_outline(Rect::new(0, 0, 1, 1), Cell::from_char('O'));
1216 assert_eq!(char_at(&buf, 0, 0), Some('O'));
1217
1218 buf.clear();
1219 buf.draw_rect_filled(Rect::new(0, 0, 1, 1), Cell::from_char('F'));
1220 assert_eq!(char_at(&buf, 0, 0), Some('F'));
1221
1222 buf.clear();
1223 buf.draw_border(
1224 Rect::new(0, 0, 1, 1),
1225 BorderChars::SQUARE,
1226 Cell::from_char(' '),
1227 );
1228 assert_eq!(char_at(&buf, 0, 0), Some('┌'));
1229
1230 buf.clear();
1231 buf.draw_box(
1232 Rect::new(0, 0, 1, 1),
1233 BorderChars::ASCII,
1234 Cell::from_char(' '),
1235 Cell::from_char('.'),
1236 );
1237 assert_eq!(char_at(&buf, 0, 0), Some('+'));
1238
1239 buf.clear();
1240 let end = buf.print_text(0, 0, "X", Cell::from_char(' '));
1241 assert_eq!(end, 1);
1242 assert_eq!(char_at(&buf, 0, 0), Some('X'));
1243 }
1244}