1use derive_more::{Deref, DerefMut};
77use ratatui::{buffer::Buffer, layout::Rect};
78
79pub trait Border {
87 fn get_border_set(&self) -> BorderSet;
89
90 fn get_borders(&self) -> Borders;
92
93 fn render_left(&self, area: Rect, buffer: &mut Buffer, symbol: char) {
95 for y in area.top()..area.bottom() {
96 buffer[(area.left(), y)].set_char(symbol);
97 }
98 }
99
100 fn render_top(&self, area: Rect, buffer: &mut Buffer, symbol: char) {
102 for x in area.left()..area.right() {
103 buffer[(x, area.top())].set_char(symbol);
104 }
105 }
106
107 fn render_right(&self, area: Rect, buffer: &mut Buffer, symbol: char) {
109 let x = area.right().saturating_sub(1);
110 for y in area.top()..area.bottom() {
111 buffer[(x, y)].set_char(symbol);
112 }
113 }
114
115 fn render_bottom(&self, area: Rect, buffer: &mut Buffer, symbol: char) {
117 let y = area.bottom().saturating_sub(1);
118 for x in area.left()..area.right() {
119 buffer[(x, y)].set_char(symbol);
120 }
121 }
122
123 fn render_corners(&self, area: Rect, buffer: &mut Buffer, charset: &BorderSet) {
125 let borders = self.get_borders();
126 let (right, bottom) = (
127 area.right().saturating_sub(1),
128 area.bottom().saturating_sub(1),
129 );
130
131 if borders.contains(Borders::RIGHT | Borders::BOTTOM) {
132 buffer[(right, bottom)].set_char(charset.bottom_right);
133 }
134 if borders.contains(Borders::RIGHT | Borders::TOP) {
135 buffer[(right, area.top())].set_char(charset.top_right);
136 }
137 if borders.contains(Borders::LEFT | Borders::BOTTOM) {
138 buffer[(area.left(), bottom)].set_char(charset.bottom_left);
139 }
140 if borders.contains(Borders::LEFT | Borders::TOP) {
141 buffer[(area.left(), area.top())].set_char(charset.top_left);
142 }
143 }
144}
145
146impl<T: Border> crate::RenderModifier for T {
147 fn before_render(&self, area: Rect, buffer: &mut Buffer) {
148 let borders = self.get_borders();
149 let border_set = self.get_border_set();
150
151 if borders.contains(Borders::LEFT) {
152 self.render_left(area, buffer, border_set.left);
153 }
154 if borders.contains(Borders::TOP) {
155 self.render_top(area, buffer, border_set.top);
156 }
157 if borders.contains(Borders::RIGHT) {
158 self.render_right(area, buffer, border_set.right);
159 }
160 if borders.contains(Borders::BOTTOM) {
161 self.render_bottom(area, buffer, border_set.bottom);
162 }
163
164 self.render_corners(area, buffer, &border_set);
165 }
166
167 fn modify_area(&self, area: Rect) -> Rect {
168 let mut inner = area;
169 let borders = self.get_borders();
170
171 if borders.contains(Borders::LEFT) {
172 inner.x = inner.x.saturating_add(1).min(inner.right());
173 inner.width = inner.width.saturating_sub(1);
174 }
175 if borders.contains(Borders::TOP) {
176 inner.y = inner.y.saturating_add(1).min(inner.bottom());
177 inner.height = inner.height.saturating_sub(1);
178 }
179 if borders.contains(Borders::RIGHT) {
180 inner.width = inner.width.saturating_sub(1);
181 }
182 if borders.contains(Borders::BOTTOM) {
183 inner.height = inner.height.saturating_sub(1);
184 }
185 inner
186 }
187}
188
189#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
212#[cfg_attr(feature = "serde", serde(default))]
213#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash)]
214pub struct BorderSet {
215 pub top_left: char,
216 pub top_right: char,
217 pub bottom_left: char,
218 pub bottom_right: char,
219 pub left: char,
220 pub right: char,
221 pub top: char,
222 pub bottom: char,
223}
224
225impl BorderSet {
226 #[must_use = "constructor returns a new instance"]
238 pub const fn new(symbol: char) -> Self {
239 Self {
240 top: symbol,
241 bottom: symbol,
242 left: symbol,
243 right: symbol,
244 top_left: symbol,
245 top_right: symbol,
246 bottom_left: symbol,
247 bottom_right: symbol,
248 }
249 }
250
251 #[must_use = "constructor returns a new instance"]
259 pub const fn plain() -> Self {
260 Self {
261 top_left: '┌',
262 top_right: '┐',
263 bottom_left: '└',
264 bottom_right: '┘',
265 left: '│',
266 right: '│',
267 top: '─',
268 bottom: '─',
269 }
270 }
271
272 #[must_use = "constructor returns a new instance"]
280 pub const fn dashed() -> Self {
281 Self::plain().horizontals('╌').verticals('┊')
282 }
283
284 #[must_use = "constructor returns a new instance"]
292 pub const fn rounded() -> Self {
293 Self {
294 top_left: '╭',
295 top_right: '╮',
296 bottom_left: '╰',
297 bottom_right: '╯',
298 left: '│',
299 right: '│',
300 top: '─',
301 bottom: '─',
302 }
303 }
304
305 #[must_use = "constructor returns a new instance"]
313 pub const fn rounded_dashed() -> Self {
314 Self::rounded().horizontals('╌').verticals('┊')
315 }
316
317 #[must_use = "constructor returns a new instance"]
325 pub const fn double() -> Self {
326 Self {
327 top_left: '╔',
328 top_right: '╗',
329 bottom_left: '╚',
330 bottom_right: '╝',
331 left: '║',
332 right: '║',
333 top: '═',
334 bottom: '═',
335 }
336 }
337
338 #[must_use = "constructor returns a new instance"]
346 pub const fn thick() -> Self {
347 Self {
348 top_left: '┏',
349 top_right: '┓',
350 bottom_left: '┗',
351 bottom_right: '┛',
352 left: '┃',
353 right: '┃',
354 top: '━',
355 bottom: '━',
356 }
357 }
358
359 #[must_use = "constructor returns a new instance"]
367 pub const fn thick_dashed() -> Self {
368 Self::rounded().horizontals('╍').verticals('┋')
369 }
370
371 #[must_use = "constructor returns a new instance"]
380 pub const fn quadrant_inside() -> Self {
381 Self {
382 top_left: '▗',
383 top_right: '▖',
384 bottom_left: '▝',
385 bottom_right: '▘',
386 left: '▐',
387 right: '▌',
388 top: '▄',
389 bottom: '▀',
390 }
391 }
392
393 #[must_use = "constructor returns a new instance"]
402 pub const fn quadrant_outside() -> Self {
403 Self {
404 top_left: '▛',
405 top_right: '▜',
406 bottom_left: '▙',
407 bottom_right: '▟',
408 left: '▌',
409 right: '▐',
410 top: '▀',
411 bottom: '▄',
412 }
413 }
414
415 #[must_use = "constructor returns a new instance"]
424 pub const fn fat_inside() -> Self {
425 Self {
426 top_left: '▄',
427 top_right: '▄',
428 bottom_left: '▀',
429 bottom_right: '▀',
430 left: '█',
431 right: '█',
432 top: '▄',
433 bottom: '▀',
434 }
435 }
436
437 #[must_use = "constructor returns a new instance"]
446 pub const fn fat_outside() -> Self {
447 Self {
448 top_left: '█',
449 top_right: '█',
450 bottom_left: '█',
451 bottom_right: '█',
452 left: '█',
453 right: '█',
454 top: '▀',
455 bottom: '▄',
456 }
457 }
458
459 #[must_use = "method returns a new instance and does not mutate the original"]
468 pub const fn corners(mut self, symbol: char) -> Self {
469 self.top_left = symbol;
470 self.top_right = symbol;
471 self.bottom_left = symbol;
472 self.bottom_right = symbol;
473 self
474 }
475
476 #[must_use = "method returns a new instance and does not mutate the original"]
485 pub const fn verticals(mut self, symbol: char) -> Self {
486 self.left = symbol;
487 self.right = symbol;
488 self
489 }
490
491 #[must_use = "method returns a new instance and does not mutate the original"]
500 pub const fn horizontals(mut self, symbol: char) -> Self {
501 self.top = symbol;
502 self.bottom = symbol;
503 self
504 }
505
506 #[must_use = "method returns a new instance and does not mutate the original"]
515 pub const fn sides(mut self, symbol: char) -> Self {
516 self.top = symbol;
517 self.bottom = symbol;
518 self.left = symbol;
519 self.right = symbol;
520 self
521 }
522}
523
524macro_rules! standard_border {
528 ($name:ident, $border_set_fn:ident, $doc:literal) => {
529 #[doc = $doc]
530 #[doc = concat!("use ratatui_garnish::border::{Borders, ", stringify!($name), "};")]
538 #[doc = concat!("let border = ", stringify!($name), "::default();")]
540 #[doc = concat!("let custom = ", stringify!($name), "::new(Borders::TOP | Borders::BOTTOM);")]
541 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
543 #[cfg_attr(feature = "serde", serde(transparent))]
544 #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Deref, DerefMut)]
545 pub struct $name(
546 pub Borders
547 );
548
549 impl $name {
550 #[must_use = "constructor returns a new instance"]
552 pub const fn new(borders: Borders) -> Self {
553 Self(borders)
554 }
555 }
556
557 impl Default for $name {
558 fn default() -> Self {
560 Self(Borders::ALL)
561 }
562 }
563
564 impl Border for $name {
565 fn get_borders(&self) -> Borders {
566 self.0
567 }
568
569 fn get_border_set(&self) -> BorderSet {
570 BorderSet::$border_set_fn()
571 }
572 }
573 };
574}
575
576standard_border!(
577 PlainBorder,
578 plain,
579 "A plain border with standard box-drawing characters."
580);
581standard_border!(DashedBorder, dashed, "A dashed border.");
582standard_border!(
583 RoundedDashedBorder,
584 rounded_dashed,
585 "A dashed border with rounded corners."
586);
587standard_border!(ThickDashedBorder, thick_dashed, "A thick dashed border.");
588standard_border!(RoundedBorder, rounded, "A border with rounded corners.");
589standard_border!(DoubleBorder, double, "A double-line border.");
590standard_border!(ThickBorder, thick, "A thick border.");
591standard_border!(
592 QuadrantInsideBorder,
593 quadrant_inside,
594 "A quadrant-style inside border."
595);
596standard_border!(
597 QuadrantOutsideBorder,
598 quadrant_outside,
599 "A quadrant-style outside border."
600);
601standard_border!(FatInsideBorder, fat_inside, "A fat inside border.");
602standard_border!(FatOutsideBorder, fat_outside, "A fat outside border.");
603
604#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
615#[cfg_attr(feature = "serde", serde(default))]
616#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
617pub struct CharBorder {
618 pub symbol: char,
619 pub borders: Borders,
620}
621
622impl Default for CharBorder {
623 fn default() -> Self {
625 Self {
626 symbol: ' ',
627 borders: Borders::ALL,
628 }
629 }
630}
631
632impl CharBorder {
633 #[must_use = "constructor returns a new instance"]
645 pub fn new(symbol: char) -> Self {
646 Self {
647 symbol,
648 ..Default::default()
649 }
650 }
651
652 #[must_use = "method returns a new instance and does not mutate the original"]
663 pub const fn borders(mut self, borders: Borders) -> Self {
664 self.borders = borders;
665 self
666 }
667}
668
669impl Border for CharBorder {
670 fn get_borders(&self) -> Borders {
671 self.borders
672 }
673
674 fn get_border_set(&self) -> BorderSet {
675 BorderSet::new(self.symbol)
676 }
677}
678
679#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
693#[cfg_attr(feature = "serde", serde(default))]
694#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
695pub struct CustomBorder {
696 pub char_set: BorderSet,
698 pub borders: Borders,
700}
701
702impl Default for CustomBorder {
703 fn default() -> Self {
705 Self {
706 char_set: BorderSet::plain(),
707 borders: Borders::ALL,
708 }
709 }
710}
711
712impl CustomBorder {
713 #[must_use = "constructor returns a new instance"]
717 pub fn new(char_set: BorderSet) -> Self {
718 Self {
719 char_set,
720 ..Default::default()
721 }
722 }
723
724 #[must_use = "method returns a new instance and does not mutate the original"]
726 pub const fn borders(mut self, borders: Borders) -> Self {
727 self.borders = borders;
728 self
729 }
730}
731
732impl Border for CustomBorder {
733 fn get_borders(&self) -> Borders {
734 self.borders
735 }
736
737 fn get_border_set(&self) -> BorderSet {
738 self.char_set
739 }
740}
741
742bitflags::bitflags! {
743 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
745 #[cfg_attr(feature = "serde", serde(transparent))]
746 #[cfg_attr(feature = "serde", serde(default))]
747 #[derive(Default, Debug, Clone, Copy, Eq, PartialEq, Hash)]
748 pub struct Borders: u8 {
749 const NONE = 0b0000;
750 const TOP = 0b0001;
751 const RIGHT = 0b0010;
752 const BOTTOM = 0b0100;
753 const LEFT = 0b1000;
754 const ALL = Self::TOP.bits() | Self::RIGHT.bits() | Self::BOTTOM.bits() | Self::LEFT.bits();
755 }
756}
757
758#[cfg(test)]
759mod tests {
760 use super::*;
761 use crate::RenderModifier;
762
763 fn create_test_buffer(width: u16, height: u16) -> Buffer {
764 Buffer::empty(Rect::new(0, 0, width, height))
765 }
766
767 #[test]
768 fn border_set_new_creates_uniform_set() {
769 let border_set = BorderSet::new('*');
770
771 assert_eq!(border_set.top_left, '*');
772 assert_eq!(border_set.top_right, '*');
773 assert_eq!(border_set.bottom_left, '*');
774 assert_eq!(border_set.bottom_right, '*');
775 assert_eq!(border_set.left, '*');
776 assert_eq!(border_set.right, '*');
777 assert_eq!(border_set.top, '*');
778 assert_eq!(border_set.bottom, '*');
779 }
780
781 #[test]
782 fn border_set_modifiers_work_correctly() {
783 let border_set = BorderSet::plain()
784 .corners('*')
785 .verticals('|')
786 .horizontals('-');
787
788 assert_eq!(border_set.top_left, '*');
789 assert_eq!(border_set.top_right, '*');
790 assert_eq!(border_set.bottom_left, '*');
791 assert_eq!(border_set.bottom_right, '*');
792 assert_eq!(border_set.left, '|');
793 assert_eq!(border_set.right, '|');
794 assert_eq!(border_set.top, '-');
795 assert_eq!(border_set.bottom, '-');
796 }
797
798 #[test]
799 fn char_border_creates_correct_border() {
800 let border = CharBorder::new('*');
801 assert_eq!(border.symbol, '*');
802 assert_eq!(border.borders, Borders::ALL);
803
804 let partial_border = CharBorder::new('#').borders(Borders::TOP | Borders::BOTTOM);
805 assert_eq!(partial_border.symbol, '#');
806 assert_eq!(partial_border.borders, Borders::TOP | Borders::BOTTOM);
807 }
808
809 #[test]
810 fn standard_borders_have_correct_defaults() {
811 assert_eq!(PlainBorder::default().get_border_set(), BorderSet::plain());
812 assert_eq!(PlainBorder::default().get_borders(), Borders::ALL);
813 assert_eq!(
814 RoundedBorder::default().get_border_set(),
815 BorderSet::rounded()
816 );
817 assert_eq!(RoundedBorder::default().get_borders(), Borders::ALL);
818 assert_eq!(
819 DoubleBorder::default().get_border_set(),
820 BorderSet::double()
821 );
822 assert_eq!(DoubleBorder::default().get_borders(), Borders::ALL);
823 assert_eq!(ThickBorder::default().get_border_set(), BorderSet::thick());
824 assert_eq!(ThickBorder::default().get_borders(), Borders::ALL);
825 }
826
827 #[test]
828 fn complete_border_renders_correctly() {
829 let mut buffer = create_test_buffer(5, 5);
830 let area = Rect::new(0, 0, 5, 5);
831 let border = PlainBorder::default();
832
833 border.before_render(area, &mut buffer);
834
835 assert_eq!(buffer[(0, 0)].symbol(), "┌");
837 assert_eq!(buffer[(4, 0)].symbol(), "┐");
838 assert_eq!(buffer[(0, 4)].symbol(), "└");
839 assert_eq!(buffer[(4, 4)].symbol(), "┘");
840
841 assert_eq!(buffer[(0, 1)].symbol(), "│"); assert_eq!(buffer[(4, 1)].symbol(), "│"); assert_eq!(buffer[(1, 0)].symbol(), "─"); assert_eq!(buffer[(1, 4)].symbol(), "─"); }
847
848 #[test]
849 fn partial_border_renders_only_specified_sides() {
850 let mut buffer = create_test_buffer(5, 5);
851 let area = Rect::new(0, 0, 5, 5);
852 let border = PlainBorder::new(Borders::TOP | Borders::LEFT);
853
854 border.before_render(area, &mut buffer);
855
856 assert_eq!(buffer[(0, 0)].symbol(), "┌");
858 assert_eq!(buffer[(0, 1)].symbol(), "│");
859
860 assert_eq!(buffer[(0, 4)].symbol(), "│");
862 assert_eq!(buffer[(4, 0)].symbol(), "─");
863 assert_eq!(buffer[(4, 1)].symbol(), " ");
864 assert_eq!(buffer[(1, 4)].symbol(), " ");
865 }
866
867 #[test]
868 fn area_modification_accounts_for_all_borders() {
869 let area = Rect::new(0, 0, 10, 10);
870 let border = PlainBorder::default();
871
872 let inner_area = border.modify_area(area);
873
874 assert_eq!(inner_area.x, 1);
875 assert_eq!(inner_area.y, 1);
876 assert_eq!(inner_area.width, 8);
877 assert_eq!(inner_area.height, 8);
878 }
879
880 #[test]
881 fn area_modification_accounts_for_partial_borders() {
882 let area = Rect::new(0, 0, 10, 10);
883 let border = PlainBorder::new(Borders::TOP | Borders::LEFT);
884
885 let inner_area = border.modify_area(area);
886
887 assert_eq!(inner_area.x, 1); assert_eq!(inner_area.y, 1); assert_eq!(inner_area.width, 9); assert_eq!(inner_area.height, 9); }
892
893 #[test]
894 fn border_deref_provides_border_access() {
895 let mut border = PlainBorder::new(Borders::TOP);
896 assert_eq!(*border, Borders::TOP);
897
898 *border |= Borders::LEFT;
900 assert_eq!(border.get_borders(), Borders::TOP | Borders::LEFT);
901 }
902
903 #[test]
904 fn custom_border_works_with_modified_border_set() {
905 let custom_border_set = BorderSet::plain().corners('*').horizontals('=');
906 let custom_border =
907 CustomBorder::new(custom_border_set).borders(Borders::TOP | Borders::LEFT);
908
909 assert_eq!(custom_border.get_borders(), Borders::TOP | Borders::LEFT);
910
911 let border_set = custom_border.get_border_set();
912 assert_eq!(border_set.top_left, '*');
913 assert_eq!(border_set.top, '=');
914 assert_eq!(border_set.left, '│'); }
916
917 #[test]
918 fn edge_case_single_cell_area() {
919 let mut buffer = create_test_buffer(1, 1);
920 let area = Rect::new(0, 0, 1, 1);
921 let border = PlainBorder::default();
922
923 border.before_render(area, &mut buffer);
924
925 assert_eq!(buffer[(0, 0)].symbol(), "┌");
928 }
929
930 #[test]
931 fn zero_area_handling() {
932 let area = Rect::new(0, 0, 0, 0);
933 let border = PlainBorder::default();
934
935 let inner_area = border.modify_area(area);
936
937 assert_eq!(inner_area.width, 0);
939 assert_eq!(inner_area.height, 0);
940 }
941
942 #[cfg(feature = "serde")]
943 #[test]
944 fn plain_border_serialization() {
945 let border = PlainBorder::default();
946 let json = serde_json::to_string_pretty(&border).unwrap();
947
948 let restored: PlainBorder = serde_json::from_str(&json).unwrap();
949 assert_eq!(restored, border);
950 }
951
952 #[cfg(feature = "serde")]
953 #[test]
954 fn char_border_serialization() {
955 let border = CharBorder::new('*');
956 let json = serde_json::to_string_pretty(&border).unwrap();
957
958 let restored: CharBorder = serde_json::from_str(&json).unwrap();
959 assert_eq!(restored, border);
960 }
961
962 #[cfg(feature = "serde")]
963 #[test]
964 fn custom_border_serialization() {
965 let custom_border_set = BorderSet::plain().corners('*').horizontals('=');
966 let border = CustomBorder::new(custom_border_set).borders(Borders::TOP | Borders::LEFT);
967 let json = serde_json::to_string_pretty(&border).unwrap();
968
969 let restored: CustomBorder = serde_json::from_str(&json).unwrap();
970 assert_eq!(restored, border);
971 }
972}