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 let clipped = self.current_scissor().intersection(&rect);
362 if clipped.is_empty() {
363 return;
364 }
365
366 let opacity = self.current_opacity();
367
368 for y in clipped.y..clipped.bottom() {
369 self.mark_dirty_span(y, clipped.x, clipped.right());
370 for x in clipped.x..clipped.right() {
371 let idx = self.index_unchecked(x, y);
372 let cell = self.cell_mut_unchecked(idx);
373
374 if let Some(fg_color) = fg {
375 if opacity < 1.0 {
376 cell.fg = fg_color.with_opacity(opacity);
377 } else {
378 cell.fg = fg_color;
379 }
380 }
381 if let Some(bg_color) = bg {
382 if opacity < 1.0 {
383 cell.bg = bg_color.with_opacity(opacity).over(cell.bg);
384 } else {
385 cell.bg = bg_color;
386 }
387 }
388 }
389 }
390 }
391}
392
393#[cfg(test)]
394mod tests {
395 use super::*;
396 use crate::cell::PackedRgba;
397
398 fn char_at(buf: &Buffer, x: u16, y: u16) -> Option<char> {
401 buf.get(x, y).and_then(|c| {
402 if c.is_empty() {
403 None
404 } else {
405 c.content.as_char()
406 }
407 })
408 }
409
410 #[test]
413 fn horizontal_line_basic() {
414 let mut buf = Buffer::new(10, 1);
415 let cell = Cell::from_char('─');
416 buf.draw_horizontal_line(2, 0, 5, cell);
417 assert_eq!(char_at(&buf, 1, 0), None);
418 assert_eq!(char_at(&buf, 2, 0), Some('─'));
419 assert_eq!(char_at(&buf, 6, 0), Some('─'));
420 assert_eq!(char_at(&buf, 7, 0), None);
421 }
422
423 #[test]
424 fn horizontal_line_zero_width() {
425 let mut buf = Buffer::new(10, 1);
426 buf.draw_horizontal_line(0, 0, 0, Cell::from_char('x'));
427 assert!(buf.get(0, 0).unwrap().is_empty());
429 }
430
431 #[test]
432 fn horizontal_line_clipped_by_scissor() {
433 let mut buf = Buffer::new(10, 1);
434 buf.push_scissor(Rect::new(0, 0, 3, 1));
435 buf.draw_horizontal_line(0, 0, 10, Cell::from_char('x'));
436 assert_eq!(char_at(&buf, 0, 0), Some('x'));
437 assert_eq!(char_at(&buf, 2, 0), Some('x'));
438 assert!(buf.get(3, 0).unwrap().is_empty());
440 }
441
442 #[test]
445 fn vertical_line_basic() {
446 let mut buf = Buffer::new(1, 10);
447 let cell = Cell::from_char('│');
448 buf.draw_vertical_line(0, 1, 4, cell);
449 assert!(buf.get(0, 0).unwrap().is_empty());
450 assert_eq!(char_at(&buf, 0, 1), Some('│'));
451 assert_eq!(char_at(&buf, 0, 4), Some('│'));
452 assert!(buf.get(0, 5).unwrap().is_empty());
453 }
454
455 #[test]
456 fn vertical_line_zero_height() {
457 let mut buf = Buffer::new(1, 10);
458 buf.draw_vertical_line(0, 0, 0, Cell::from_char('x'));
459 assert!(buf.get(0, 0).unwrap().is_empty());
460 }
461
462 #[test]
465 fn rect_filled() {
466 let mut buf = Buffer::new(5, 5);
467 let cell = Cell::from_char('█');
468 buf.draw_rect_filled(Rect::new(1, 1, 3, 3), cell);
469 assert_eq!(char_at(&buf, 1, 1), Some('█'));
471 assert_eq!(char_at(&buf, 3, 3), Some('█'));
472 assert!(buf.get(0, 0).unwrap().is_empty());
474 assert!(buf.get(4, 4).unwrap().is_empty());
475 }
476
477 #[test]
478 fn rect_filled_empty() {
479 let mut buf = Buffer::new(5, 5);
480 buf.draw_rect_filled(Rect::new(0, 0, 0, 0), Cell::from_char('x'));
481 assert!(buf.get(0, 0).unwrap().is_empty());
482 }
483
484 #[test]
487 fn rect_outline_basic() {
488 let mut buf = Buffer::new(5, 5);
489 let cell = Cell::from_char('#');
490 buf.draw_rect_outline(Rect::new(0, 0, 5, 5), cell);
491
492 assert_eq!(char_at(&buf, 0, 0), Some('#'));
494 assert_eq!(char_at(&buf, 4, 0), Some('#'));
495 assert_eq!(char_at(&buf, 0, 4), Some('#'));
496 assert_eq!(char_at(&buf, 4, 4), Some('#'));
497
498 assert_eq!(char_at(&buf, 2, 0), Some('#'));
500 assert_eq!(char_at(&buf, 0, 2), Some('#'));
501
502 assert!(buf.get(2, 2).unwrap().is_empty());
504 }
505
506 #[test]
507 fn rect_outline_1x1() {
508 let mut buf = Buffer::new(5, 5);
509 buf.draw_rect_outline(Rect::new(1, 1, 1, 1), Cell::from_char('o'));
510 assert_eq!(char_at(&buf, 1, 1), Some('o'));
511 }
512
513 #[test]
514 fn rect_outline_2x2() {
515 let mut buf = Buffer::new(5, 5);
516 buf.draw_rect_outline(Rect::new(0, 0, 2, 2), Cell::from_char('#'));
517 assert_eq!(char_at(&buf, 0, 0), Some('#'));
518 assert_eq!(char_at(&buf, 1, 0), Some('#'));
519 assert_eq!(char_at(&buf, 0, 1), Some('#'));
520 assert_eq!(char_at(&buf, 1, 1), Some('#'));
521 }
522
523 #[test]
526 fn print_text_basic() {
527 let mut buf = Buffer::new(20, 1);
528 let cell = Cell::from_char(' '); let end_x = buf.print_text(2, 0, "Hello", cell);
530 assert_eq!(char_at(&buf, 2, 0), Some('H'));
531 assert_eq!(char_at(&buf, 3, 0), Some('e'));
532 assert_eq!(char_at(&buf, 6, 0), Some('o'));
533 assert_eq!(end_x, 7);
534 }
535
536 #[test]
537 fn print_text_preserves_style() {
538 let mut buf = Buffer::new(10, 1);
539 let cell = Cell::from_char(' ')
540 .with_fg(PackedRgba::rgb(255, 0, 0))
541 .with_bg(PackedRgba::rgb(0, 0, 255));
542 buf.print_text(0, 0, "AB", cell);
543 let a = buf.get(0, 0).unwrap();
544 assert_eq!(a.fg, PackedRgba::rgb(255, 0, 0));
545 assert_eq!(a.bg, PackedRgba::rgb(0, 0, 255));
546 }
547
548 #[test]
549 fn print_text_clips_at_buffer_edge() {
550 let mut buf = Buffer::new(5, 1);
551 let end_x = buf.print_text(0, 0, "Hello World", Cell::from_char(' '));
552 assert_eq!(char_at(&buf, 4, 0), Some('o'));
553 assert_eq!(end_x, 5);
554 }
555
556 #[test]
557 fn print_text_clipped_stops_at_max_x() {
558 let mut buf = Buffer::new(20, 1);
559 let end_x = buf.print_text_clipped(0, 0, "Hello World", Cell::from_char(' '), 5);
560 assert_eq!(char_at(&buf, 4, 0), Some('o'));
561 assert_eq!(end_x, 5);
562 assert!(buf.get(5, 0).unwrap().is_empty());
564 }
565
566 #[test]
567 fn print_text_multi_codepoint_grapheme_fills_width() {
568 let mut buf = Buffer::new(4, 1);
572
573 buf.set_raw(1, 0, Cell::from_char('|'));
575
576 let base = Cell::from_char(' ')
577 .with_fg(PackedRgba::rgb(255, 0, 0))
578 .with_bg(PackedRgba::rgb(0, 0, 255));
579
580 let end_x = buf.print_text_clipped(0, 0, "👍🏽", base, 4);
581 assert_eq!(end_x, 2);
582 assert_eq!(char_at(&buf, 0, 0), Some('👍'));
583
584 let c1 = buf.get(1, 0).unwrap();
586 assert!(c1.is_continuation());
587 }
588
589 #[test]
590 fn print_text_wide_chars() {
591 let mut buf = Buffer::new(10, 1);
592 let end_x = buf.print_text(0, 0, "AB", Cell::from_char(' '));
593 assert_eq!(end_x, 2);
595 assert_eq!(char_at(&buf, 0, 0), Some('A'));
596 assert_eq!(char_at(&buf, 1, 0), Some('B'));
597 }
598
599 #[test]
600 fn print_text_wide_char_clipped() {
601 let mut buf = Buffer::new(10, 1);
602 let end_x = buf.print_text_clipped(4, 0, "中", Cell::from_char(' '), 5);
604 assert_eq!(end_x, 4);
606 }
607
608 #[test]
609 fn print_text_empty_string() {
610 let mut buf = Buffer::new(10, 1);
611 let end_x = buf.print_text(0, 0, "", Cell::from_char(' '));
612 assert_eq!(end_x, 0);
613 }
614
615 #[test]
618 fn draw_border_square() {
619 let mut buf = Buffer::new(5, 3);
620 buf.draw_border(
621 Rect::new(0, 0, 5, 3),
622 BorderChars::SQUARE,
623 Cell::from_char(' '),
624 );
625
626 assert_eq!(char_at(&buf, 0, 0), Some('┌'));
628 assert_eq!(char_at(&buf, 4, 0), Some('┐'));
629 assert_eq!(char_at(&buf, 0, 2), Some('└'));
630 assert_eq!(char_at(&buf, 4, 2), Some('┘'));
631
632 assert_eq!(char_at(&buf, 1, 0), Some('─'));
634 assert_eq!(char_at(&buf, 2, 0), Some('─'));
635 assert_eq!(char_at(&buf, 3, 0), Some('─'));
636
637 assert_eq!(char_at(&buf, 0, 1), Some('│'));
639 assert_eq!(char_at(&buf, 4, 1), Some('│'));
640
641 assert!(buf.get(2, 1).unwrap().is_empty());
643 }
644
645 #[test]
646 fn draw_border_rounded() {
647 let mut buf = Buffer::new(4, 3);
648 buf.draw_border(
649 Rect::new(0, 0, 4, 3),
650 BorderChars::ROUNDED,
651 Cell::from_char(' '),
652 );
653 assert_eq!(char_at(&buf, 0, 0), Some('╭'));
654 assert_eq!(char_at(&buf, 3, 0), Some('╮'));
655 assert_eq!(char_at(&buf, 0, 2), Some('╰'));
656 assert_eq!(char_at(&buf, 3, 2), Some('╯'));
657 }
658
659 #[test]
660 fn draw_border_1x1() {
661 let mut buf = Buffer::new(5, 5);
662 buf.draw_border(
663 Rect::new(1, 1, 1, 1),
664 BorderChars::SQUARE,
665 Cell::from_char(' '),
666 );
667 assert_eq!(char_at(&buf, 1, 1), Some('┌'));
669 }
670
671 #[test]
672 fn draw_border_2x2() {
673 let mut buf = Buffer::new(5, 5);
674 buf.draw_border(
675 Rect::new(0, 0, 2, 2),
676 BorderChars::SQUARE,
677 Cell::from_char(' '),
678 );
679 assert_eq!(char_at(&buf, 0, 0), Some('┌'));
680 assert_eq!(char_at(&buf, 1, 0), Some('┐'));
681 assert_eq!(char_at(&buf, 0, 1), Some('└'));
682 assert_eq!(char_at(&buf, 1, 1), Some('┘'));
683 }
684
685 #[test]
686 fn draw_border_empty_rect() {
687 let mut buf = Buffer::new(5, 5);
688 buf.draw_border(
689 Rect::new(0, 0, 0, 0),
690 BorderChars::SQUARE,
691 Cell::from_char(' '),
692 );
693 assert!(buf.get(0, 0).unwrap().is_empty());
695 }
696
697 #[test]
698 fn draw_border_preserves_style() {
699 let mut buf = Buffer::new(5, 3);
700 let cell = Cell::from_char(' ')
701 .with_fg(PackedRgba::rgb(0, 255, 0))
702 .with_bg(PackedRgba::rgb(0, 0, 128));
703 buf.draw_border(Rect::new(0, 0, 5, 3), BorderChars::SQUARE, cell);
704
705 let corner = buf.get(0, 0).unwrap();
706 assert_eq!(corner.fg, PackedRgba::rgb(0, 255, 0));
707 assert_eq!(corner.bg, PackedRgba::rgb(0, 0, 128));
708
709 let edge = buf.get(2, 0).unwrap();
710 assert_eq!(edge.fg, PackedRgba::rgb(0, 255, 0));
711 }
712
713 #[test]
714 fn draw_border_clipped_by_scissor() {
715 let mut buf = Buffer::new(10, 5);
716 buf.push_scissor(Rect::new(0, 0, 3, 3));
717 buf.draw_border(
718 Rect::new(0, 0, 6, 4),
719 BorderChars::SQUARE,
720 Cell::from_char(' '),
721 );
722
723 assert_eq!(char_at(&buf, 0, 0), Some('┌'));
725 assert_eq!(char_at(&buf, 2, 0), Some('─'));
726
727 assert!(buf.get(5, 0).unwrap().is_empty());
729 assert!(buf.get(0, 3).unwrap().is_empty());
730 }
731
732 #[test]
735 fn draw_box_basic() {
736 let mut buf = Buffer::new(5, 4);
737 let border = Cell::from_char(' ').with_fg(PackedRgba::rgb(255, 255, 255));
738 let fill = Cell::from_char('.').with_bg(PackedRgba::rgb(50, 50, 50));
739 buf.draw_box(Rect::new(0, 0, 5, 4), BorderChars::SQUARE, border, fill);
740
741 assert_eq!(char_at(&buf, 0, 0), Some('┌'));
743 assert_eq!(char_at(&buf, 4, 3), Some('┘'));
744
745 assert_eq!(char_at(&buf, 1, 1), Some('.'));
747 assert_eq!(char_at(&buf, 3, 2), Some('.'));
748 assert_eq!(buf.get(2, 1).unwrap().bg, PackedRgba::rgb(50, 50, 50));
749 }
750
751 #[test]
752 fn draw_box_too_small_for_interior() {
753 let mut buf = Buffer::new(5, 5);
754 let border = Cell::from_char(' ');
755 let fill = Cell::from_char('X');
756 buf.draw_box(Rect::new(0, 0, 2, 2), BorderChars::SQUARE, border, fill);
757
758 assert_eq!(char_at(&buf, 0, 0), Some('┌'));
760 assert_eq!(char_at(&buf, 1, 0), Some('┐'));
761 }
762
763 #[test]
764 fn draw_box_empty() {
765 let mut buf = Buffer::new(5, 5);
766 buf.draw_box(
767 Rect::new(0, 0, 0, 0),
768 BorderChars::SQUARE,
769 Cell::from_char(' '),
770 Cell::from_char('.'),
771 );
772 assert!(buf.get(0, 0).unwrap().is_empty());
773 }
774
775 #[test]
778 fn paint_area_sets_colors() {
779 let mut buf = Buffer::new(5, 3);
780 buf.set(1, 1, Cell::from_char('X'));
782 buf.set(2, 1, Cell::from_char('Y'));
783
784 buf.paint_area(
785 Rect::new(0, 0, 5, 3),
786 None,
787 Some(PackedRgba::rgb(30, 30, 30)),
788 );
789
790 assert_eq!(char_at(&buf, 1, 1), Some('X'));
792 assert_eq!(buf.get(1, 1).unwrap().bg, PackedRgba::rgb(30, 30, 30));
794 assert_eq!(buf.get(0, 0).unwrap().bg, PackedRgba::rgb(30, 30, 30));
795 }
796
797 #[test]
798 fn paint_area_sets_fg() {
799 let mut buf = Buffer::new(3, 1);
800 buf.set(0, 0, Cell::from_char('A'));
801
802 buf.paint_area(
803 Rect::new(0, 0, 3, 1),
804 Some(PackedRgba::rgb(200, 100, 50)),
805 None,
806 );
807
808 assert_eq!(buf.get(0, 0).unwrap().fg, PackedRgba::rgb(200, 100, 50));
809 }
810
811 #[test]
812 fn paint_area_empty_rect() {
813 let mut buf = Buffer::new(5, 5);
814 buf.set(0, 0, Cell::from_char('A'));
815 let original_fg = buf.get(0, 0).unwrap().fg;
816
817 buf.paint_area(
818 Rect::new(0, 0, 0, 0),
819 Some(PackedRgba::rgb(255, 0, 0)),
820 None,
821 );
822
823 assert_eq!(buf.get(0, 0).unwrap().fg, original_fg);
825 }
826
827 #[test]
830 fn all_border_presets() {
831 let mut buf = Buffer::new(6, 4);
832 let cell = Cell::from_char(' ');
833 let rect = Rect::new(0, 0, 6, 4);
834
835 for chars in [
836 BorderChars::SQUARE,
837 BorderChars::ROUNDED,
838 BorderChars::DOUBLE,
839 BorderChars::HEAVY,
840 BorderChars::ASCII,
841 ] {
842 buf.clear();
843 buf.draw_border(rect, chars, cell);
844 assert!(buf.get(0, 0).unwrap().content.as_char().is_some());
846 assert!(buf.get(5, 3).unwrap().content.as_char().is_some());
847 }
848 }
849
850 #[test]
853 fn draw_border_then_print_title() {
854 let mut buf = Buffer::new(12, 3);
855 let cell = Cell::from_char(' ');
856
857 buf.draw_border(Rect::new(0, 0, 12, 3), BorderChars::SQUARE, cell);
859
860 buf.print_text(1, 0, "Title", cell);
862
863 assert_eq!(char_at(&buf, 0, 0), Some('┌'));
864 assert_eq!(char_at(&buf, 1, 0), Some('T'));
865 assert_eq!(char_at(&buf, 5, 0), Some('e'));
866 assert_eq!(char_at(&buf, 6, 0), Some('─'));
867 assert_eq!(char_at(&buf, 11, 0), Some('┐'));
868 }
869
870 #[test]
871 fn draw_nested_borders() {
872 let mut buf = Buffer::new(10, 6);
873 let cell = Cell::from_char(' ');
874
875 buf.draw_border(Rect::new(0, 0, 10, 6), BorderChars::DOUBLE, cell);
876 buf.draw_border(Rect::new(1, 1, 8, 4), BorderChars::SQUARE, cell);
877
878 assert_eq!(char_at(&buf, 0, 0), Some('╔'));
880 assert_eq!(char_at(&buf, 9, 5), Some('╝'));
881
882 assert_eq!(char_at(&buf, 1, 1), Some('┌'));
884 assert_eq!(char_at(&buf, 8, 4), Some('┘'));
885 }
886
887 #[test]
890 fn border_chars_debug_clone_copy_eq() {
891 let a = BorderChars::SQUARE;
892 let dbg = format!("{:?}", a);
893 assert!(dbg.contains("BorderChars"), "Debug: {dbg}");
894 let copied: BorderChars = a; assert_eq!(a, copied);
896 assert_ne!(a, BorderChars::ROUNDED);
897 assert_ne!(BorderChars::DOUBLE, BorderChars::HEAVY);
898 }
899
900 #[test]
901 fn border_chars_double_characters() {
902 let d = BorderChars::DOUBLE;
903 assert_eq!(d.top_left, '╔');
904 assert_eq!(d.top_right, '╗');
905 assert_eq!(d.bottom_left, '╚');
906 assert_eq!(d.bottom_right, '╝');
907 assert_eq!(d.horizontal, '═');
908 assert_eq!(d.vertical, '║');
909 }
910
911 #[test]
912 fn border_chars_heavy_characters() {
913 let h = BorderChars::HEAVY;
914 assert_eq!(h.top_left, '┏');
915 assert_eq!(h.top_right, '┓');
916 assert_eq!(h.bottom_left, '┗');
917 assert_eq!(h.bottom_right, '┛');
918 assert_eq!(h.horizontal, '━');
919 assert_eq!(h.vertical, '┃');
920 }
921
922 #[test]
923 fn border_chars_ascii_characters() {
924 let a = BorderChars::ASCII;
925 assert_eq!(a.top_left, '+');
926 assert_eq!(a.top_right, '+');
927 assert_eq!(a.bottom_left, '+');
928 assert_eq!(a.bottom_right, '+');
929 assert_eq!(a.horizontal, '-');
930 assert_eq!(a.vertical, '|');
931 }
932
933 #[test]
936 fn rect_outline_empty_rect() {
937 let mut buf = Buffer::new(5, 5);
938 buf.draw_rect_outline(Rect::new(0, 0, 0, 0), Cell::from_char('#'));
939 assert!(buf.get(0, 0).unwrap().is_empty());
941 }
942
943 #[test]
944 fn rect_outline_1xn_tall() {
945 let mut buf = Buffer::new(5, 5);
946 buf.draw_rect_outline(Rect::new(1, 0, 1, 4), Cell::from_char('#'));
947 assert_eq!(char_at(&buf, 1, 0), Some('#'));
949 assert_eq!(char_at(&buf, 1, 3), Some('#'));
950 assert_eq!(char_at(&buf, 1, 1), Some('#'));
952 assert_eq!(char_at(&buf, 1, 2), Some('#'));
953 }
954
955 #[test]
956 fn rect_outline_nx1_wide() {
957 let mut buf = Buffer::new(5, 5);
958 buf.draw_rect_outline(Rect::new(0, 1, 4, 1), Cell::from_char('#'));
959 for x in 0..4 {
961 assert_eq!(char_at(&buf, x, 1), Some('#'));
962 }
963 assert!(buf.get(0, 2).unwrap().is_empty());
965 }
966
967 #[test]
968 fn rect_outline_3x3() {
969 let mut buf = Buffer::new(5, 5);
970 buf.draw_rect_outline(Rect::new(0, 0, 3, 3), Cell::from_char('#'));
971 for &(x, y) in &[
973 (0, 0),
974 (1, 0),
975 (2, 0),
976 (0, 1),
977 (2, 1),
978 (0, 2),
979 (1, 2),
980 (2, 2),
981 ] {
982 assert_eq!(char_at(&buf, x, y), Some('#'), "({x},{y})");
983 }
984 assert!(buf.get(1, 1).unwrap().is_empty());
986 }
987
988 #[test]
991 fn draw_border_1x3_narrow() {
992 let mut buf = Buffer::new(5, 5);
994 buf.draw_border(
995 Rect::new(1, 0, 1, 3),
996 BorderChars::SQUARE,
997 Cell::from_char(' '),
998 );
999 assert_eq!(char_at(&buf, 1, 0), Some('┌'));
1000 assert_eq!(char_at(&buf, 1, 1), Some('│'));
1001 assert_eq!(char_at(&buf, 1, 2), Some('└'));
1002 }
1003
1004 #[test]
1005 fn draw_border_3x1_flat() {
1006 let mut buf = Buffer::new(5, 5);
1008 buf.draw_border(
1009 Rect::new(0, 0, 3, 1),
1010 BorderChars::SQUARE,
1011 Cell::from_char(' '),
1012 );
1013 assert_eq!(char_at(&buf, 0, 0), Some('┌'));
1014 assert_eq!(char_at(&buf, 1, 0), Some('─'));
1015 assert_eq!(char_at(&buf, 2, 0), Some('┐'));
1016 }
1017
1018 #[test]
1019 fn draw_border_2x1() {
1020 let mut buf = Buffer::new(5, 5);
1022 buf.draw_border(
1023 Rect::new(0, 0, 2, 1),
1024 BorderChars::SQUARE,
1025 Cell::from_char(' '),
1026 );
1027 assert_eq!(char_at(&buf, 0, 0), Some('┌'));
1028 assert_eq!(char_at(&buf, 1, 0), Some('┐'));
1029 }
1030
1031 #[test]
1032 fn draw_border_1x2() {
1033 let mut buf = Buffer::new(5, 5);
1035 buf.draw_border(
1036 Rect::new(0, 0, 1, 2),
1037 BorderChars::SQUARE,
1038 Cell::from_char(' '),
1039 );
1040 assert_eq!(char_at(&buf, 0, 0), Some('┌'));
1041 assert_eq!(char_at(&buf, 0, 1), Some('└'));
1042 }
1043
1044 #[test]
1047 fn draw_box_3x3_minimal_interior() {
1048 let mut buf = Buffer::new(5, 5);
1049 let border = Cell::from_char(' ');
1050 let fill = Cell::from_char('.');
1051 buf.draw_box(Rect::new(0, 0, 3, 3), BorderChars::SQUARE, border, fill);
1052 assert_eq!(char_at(&buf, 0, 0), Some('┌'));
1054 assert_eq!(char_at(&buf, 2, 2), Some('┘'));
1055 assert_eq!(char_at(&buf, 1, 1), Some('.'));
1057 }
1058
1059 #[test]
1060 fn draw_box_1x1() {
1061 let mut buf = Buffer::new(5, 5);
1062 let border = Cell::from_char(' ');
1063 let fill = Cell::from_char('X');
1064 buf.draw_box(Rect::new(1, 1, 1, 1), BorderChars::SQUARE, border, fill);
1065 assert_eq!(char_at(&buf, 1, 1), Some('┌'));
1067 }
1068
1069 #[test]
1070 fn draw_box_border_overwrites_fill() {
1071 let mut buf = Buffer::new(5, 5);
1073 let border = Cell::from_char(' ').with_fg(PackedRgba::rgb(255, 0, 0));
1074 let fill = Cell::from_char('.').with_fg(PackedRgba::rgb(0, 255, 0));
1075 buf.draw_box(Rect::new(0, 0, 4, 4), BorderChars::SQUARE, border, fill);
1076 assert_eq!(char_at(&buf, 0, 0), Some('┌'));
1078 assert_eq!(buf.get(0, 0).unwrap().fg, PackedRgba::rgb(255, 0, 0));
1079 assert_eq!(char_at(&buf, 1, 1), Some('.'));
1081 assert_eq!(buf.get(1, 1).unwrap().fg, PackedRgba::rgb(0, 255, 0));
1082 }
1083
1084 #[test]
1087 fn paint_area_sets_both_fg_and_bg() {
1088 let mut buf = Buffer::new(3, 3);
1089 buf.set(1, 1, Cell::from_char('X'));
1090 buf.paint_area(
1091 Rect::new(0, 0, 3, 3),
1092 Some(PackedRgba::rgb(100, 200, 50)),
1093 Some(PackedRgba::rgb(10, 20, 30)),
1094 );
1095 let cell = buf.get(1, 1).unwrap();
1096 assert_eq!(cell.content.as_char(), Some('X'));
1097 assert_eq!(cell.fg, PackedRgba::rgb(100, 200, 50));
1098 assert_eq!(cell.bg, PackedRgba::rgb(10, 20, 30));
1099 }
1100
1101 #[test]
1102 fn paint_area_beyond_buffer() {
1103 let mut buf = Buffer::new(3, 3);
1104 buf.paint_area(
1106 Rect::new(0, 0, 100, 100),
1107 Some(PackedRgba::rgb(255, 0, 0)),
1108 None,
1109 );
1110 assert_eq!(buf.get(0, 0).unwrap().fg, PackedRgba::rgb(255, 0, 0));
1112 assert_eq!(buf.get(2, 2).unwrap().fg, PackedRgba::rgb(255, 0, 0));
1113 }
1114
1115 #[test]
1116 fn paint_area_no_colors() {
1117 let mut buf = Buffer::new(3, 1);
1118 let cell = Cell::from_char('A').with_fg(PackedRgba::rgb(10, 20, 30));
1119 buf.set(0, 0, cell);
1120 buf.paint_area(Rect::new(0, 0, 3, 1), None, None);
1122 assert_eq!(buf.get(0, 0).unwrap().fg, PackedRgba::rgb(10, 20, 30));
1123 }
1124
1125 #[test]
1128 fn print_text_max_x_zero() {
1129 let mut buf = Buffer::new(10, 1);
1130 let end_x = buf.print_text_clipped(0, 0, "Hello", Cell::from_char(' '), 0);
1131 assert_eq!(end_x, 0);
1132 assert!(buf.get(0, 0).unwrap().is_empty());
1133 }
1134
1135 #[test]
1136 fn print_text_start_past_max_x() {
1137 let mut buf = Buffer::new(10, 1);
1138 let end_x = buf.print_text_clipped(5, 0, "Hello", Cell::from_char(' '), 3);
1139 assert_eq!(end_x, 5); }
1141
1142 #[test]
1143 fn print_text_single_char() {
1144 let mut buf = Buffer::new(10, 1);
1145 let end_x = buf.print_text(0, 0, "X", Cell::from_char(' '));
1146 assert_eq!(end_x, 1);
1147 assert_eq!(char_at(&buf, 0, 0), Some('X'));
1148 }
1149
1150 #[test]
1153 fn horizontal_line_at_buffer_bottom() {
1154 let mut buf = Buffer::new(5, 3);
1155 buf.draw_horizontal_line(0, 2, 5, Cell::from_char('='));
1156 for x in 0..5 {
1157 assert_eq!(char_at(&buf, x, 2), Some('='));
1158 }
1159 }
1160
1161 #[test]
1162 fn vertical_line_at_buffer_right_edge() {
1163 let mut buf = Buffer::new(5, 5);
1164 buf.draw_vertical_line(4, 0, 5, Cell::from_char('|'));
1165 for y in 0..5 {
1166 assert_eq!(char_at(&buf, 4, y), Some('|'));
1167 }
1168 }
1169
1170 #[test]
1171 fn horizontal_line_exceeds_buffer() {
1172 let mut buf = Buffer::new(3, 1);
1174 buf.draw_horizontal_line(0, 0, 100, Cell::from_char('-'));
1175 for x in 0..3 {
1176 assert_eq!(char_at(&buf, x, 0), Some('-'));
1177 }
1178 }
1179
1180 #[test]
1181 fn vertical_line_exceeds_buffer() {
1182 let mut buf = Buffer::new(1, 3);
1183 buf.draw_vertical_line(0, 0, 100, Cell::from_char('|'));
1184 for y in 0..3 {
1185 assert_eq!(char_at(&buf, 0, y), Some('|'));
1186 }
1187 }
1188
1189 #[test]
1192 fn rect_filled_clipped_by_scissor() {
1193 let mut buf = Buffer::new(10, 10);
1194 buf.push_scissor(Rect::new(2, 2, 3, 3));
1195 buf.draw_rect_filled(Rect::new(0, 0, 10, 10), Cell::from_char('#'));
1196 assert_eq!(char_at(&buf, 2, 2), Some('#'));
1198 assert_eq!(char_at(&buf, 4, 4), Some('#'));
1199 assert!(buf.get(0, 0).unwrap().is_empty());
1201 assert!(buf.get(5, 5).unwrap().is_empty());
1202 buf.pop_scissor();
1203 }
1204
1205 #[test]
1206 fn vertical_line_clipped_by_scissor() {
1207 let mut buf = Buffer::new(5, 10);
1208 buf.push_scissor(Rect::new(0, 2, 5, 3));
1209 buf.draw_vertical_line(2, 0, 10, Cell::from_char('|'));
1210 assert_eq!(char_at(&buf, 2, 2), Some('|'));
1212 assert_eq!(char_at(&buf, 2, 4), Some('|'));
1213 assert!(buf.get(2, 0).unwrap().is_empty());
1215 assert!(buf.get(2, 5).unwrap().is_empty());
1216 buf.pop_scissor();
1217 }
1218
1219 #[test]
1222 fn drawing_on_1x1_buffer() {
1223 let mut buf = Buffer::new(1, 1);
1224 buf.draw_horizontal_line(0, 0, 1, Cell::from_char('H'));
1225 assert_eq!(char_at(&buf, 0, 0), Some('H'));
1226
1227 buf.clear();
1228 buf.draw_vertical_line(0, 0, 1, Cell::from_char('V'));
1229 assert_eq!(char_at(&buf, 0, 0), Some('V'));
1230
1231 buf.clear();
1232 buf.draw_rect_outline(Rect::new(0, 0, 1, 1), Cell::from_char('O'));
1233 assert_eq!(char_at(&buf, 0, 0), Some('O'));
1234
1235 buf.clear();
1236 buf.draw_rect_filled(Rect::new(0, 0, 1, 1), Cell::from_char('F'));
1237 assert_eq!(char_at(&buf, 0, 0), Some('F'));
1238
1239 buf.clear();
1240 buf.draw_border(
1241 Rect::new(0, 0, 1, 1),
1242 BorderChars::SQUARE,
1243 Cell::from_char(' '),
1244 );
1245 assert_eq!(char_at(&buf, 0, 0), Some('┌'));
1246
1247 buf.clear();
1248 buf.draw_box(
1249 Rect::new(0, 0, 1, 1),
1250 BorderChars::ASCII,
1251 Cell::from_char(' '),
1252 Cell::from_char('.'),
1253 );
1254 assert_eq!(char_at(&buf, 0, 0), Some('+'));
1255
1256 buf.clear();
1257 let end = buf.print_text(0, 0, "X", Cell::from_char(' '));
1258 assert_eq!(end, 1);
1259 assert_eq!(char_at(&buf, 0, 0), Some('X'));
1260 }
1261}