1use itertools::Itertools;
9use strum::{Display, EnumString};
10
11use crate::{
12 buffer::Buffer,
13 layout::{Alignment, Rect},
14 style::{Style, Styled},
15 symbols::border,
16 text::Line,
17 widgets::{Borders, Widget, WidgetRef},
18};
19
20mod padding;
21pub mod title;
22
23pub use padding::Padding;
24pub use title::{Position, Title};
25
26#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
113pub struct Block<'a> {
114 titles: Vec<(Option<Position>, Line<'a>)>,
116 titles_style: Style,
118 titles_alignment: Alignment,
120 titles_position: Position,
122 borders: Borders,
124 border_style: Style,
126 border_set: border::Set,
129 style: Style,
131 padding: Padding,
133}
134
135#[derive(Debug, Default, Display, EnumString, Clone, Copy, Eq, PartialEq, Hash)]
139pub enum BorderType {
140 #[default]
152 Plain,
153 Rounded,
163 Double,
175 Thick,
185 QuadrantInside,
195
196 QuadrantOutside,
206}
207
208impl<'a> Block<'a> {
209 pub const fn new() -> Self {
211 Self {
212 titles: Vec::new(),
213 titles_style: Style::new(),
214 titles_alignment: Alignment::Left,
215 titles_position: Position::Top,
216 borders: Borders::NONE,
217 border_style: Style::new(),
218 border_set: BorderType::Plain.to_border_set(),
219 style: Style::new(),
220 padding: Padding::ZERO,
221 }
222 }
223
224 pub const fn bordered() -> Self {
232 let mut block = Self::new();
233 block.borders = Borders::ALL;
234 block
235 }
236
237 #[must_use = "method moves the value of self and returns the modified value"]
305 pub fn title<T>(mut self, title: T) -> Self
306 where
307 T: Into<Title<'a>>,
308 {
309 let title = title.into();
310 let position = title.position;
311 let mut content = title.content;
312 if let Some(alignment) = title.alignment {
313 content = content.alignment(alignment);
314 }
315 self.titles.push((position, content));
316 self
317 }
318
319 #[must_use = "method moves the value of self and returns the modified value"]
342 pub fn title_top<T: Into<Line<'a>>>(mut self, title: T) -> Self {
343 let line = title.into();
344 self.titles.push((Some(Position::Top), line));
345 self
346 }
347
348 #[must_use = "method moves the value of self and returns the modified value"]
371 pub fn title_bottom<T: Into<Line<'a>>>(mut self, title: T) -> Self {
372 let line = title.into();
373 self.titles.push((Some(Position::Bottom), line));
374 self
375 }
376
377 #[must_use = "method moves the value of self and returns the modified value"]
390 pub fn title_style<S: Into<Style>>(mut self, style: S) -> Self {
391 self.titles_style = style.into();
392 self
393 }
394
395 #[must_use = "method moves the value of self and returns the modified value"]
414 pub const fn title_alignment(mut self, alignment: Alignment) -> Self {
415 self.titles_alignment = alignment;
416 self
417 }
418
419 #[must_use = "method moves the value of self and returns the modified value"]
438 pub const fn title_position(mut self, position: Position) -> Self {
439 self.titles_position = position;
440 self
441 }
442
443 #[must_use = "method moves the value of self and returns the modified value"]
466 pub fn border_style<S: Into<Style>>(mut self, style: S) -> Self {
467 self.border_style = style.into();
468 self
469 }
470
471 #[must_use = "method moves the value of self and returns the modified value"]
510 pub fn style<S: Into<Style>>(mut self, style: S) -> Self {
511 self.style = style.into();
512 self
513 }
514
515 #[must_use = "method moves the value of self and returns the modified value"]
529 pub const fn borders(mut self, flag: Borders) -> Self {
530 self.borders = flag;
531 self
532 }
533
534 #[must_use = "method moves the value of self and returns the modified value"]
554 pub const fn border_type(mut self, border_type: BorderType) -> Self {
555 self.border_set = border_type.to_border_set();
556 self
557 }
558
559 #[must_use = "method moves the value of self and returns the modified value"]
574 pub const fn border_set(mut self, border_set: border::Set) -> Self {
575 self.border_set = border_set;
576 self
577 }
578
579 #[must_use = "method moves the value of self and returns the modified value"]
608 pub const fn padding(mut self, padding: Padding) -> Self {
609 self.padding = padding;
610 self
611 }
612
613 pub fn inner(&self, area: Rect) -> Rect {
639 let mut inner = area;
640 if self.borders.intersects(Borders::LEFT) {
641 inner.x = inner.x.saturating_add(1).min(inner.right());
642 inner.width = inner.width.saturating_sub(1);
643 }
644 if self.borders.intersects(Borders::TOP) || self.has_title_at_position(Position::Top) {
645 inner.y = inner.y.saturating_add(1).min(inner.bottom());
646 inner.height = inner.height.saturating_sub(1);
647 }
648 if self.borders.intersects(Borders::RIGHT) {
649 inner.width = inner.width.saturating_sub(1);
650 }
651 if self.borders.intersects(Borders::BOTTOM) || self.has_title_at_position(Position::Bottom)
652 {
653 inner.height = inner.height.saturating_sub(1);
654 }
655
656 inner.x = inner.x.saturating_add(self.padding.left);
657 inner.y = inner.y.saturating_add(self.padding.top);
658
659 inner.width = inner
660 .width
661 .saturating_sub(self.padding.left + self.padding.right);
662 inner.height = inner
663 .height
664 .saturating_sub(self.padding.top + self.padding.bottom);
665
666 inner
667 }
668
669 fn has_title_at_position(&self, position: Position) -> bool {
670 self.titles
671 .iter()
672 .any(|(pos, _)| pos.unwrap_or(self.titles_position) == position)
673 }
674}
675
676impl BorderType {
677 pub const fn border_symbols(border_type: Self) -> border::Set {
679 match border_type {
680 Self::Plain => border::PLAIN,
681 Self::Rounded => border::ROUNDED,
682 Self::Double => border::DOUBLE,
683 Self::Thick => border::THICK,
684 Self::QuadrantInside => border::QUADRANT_INSIDE,
685 Self::QuadrantOutside => border::QUADRANT_OUTSIDE,
686 }
687 }
688
689 pub const fn to_border_set(self) -> border::Set {
691 Self::border_symbols(self)
692 }
693}
694
695impl Widget for Block<'_> {
696 fn render(self, area: Rect, buf: &mut Buffer) {
697 self.render_ref(area, buf);
698 }
699}
700
701impl WidgetRef for Block<'_> {
702 fn render_ref(&self, area: Rect, buf: &mut Buffer) {
703 let area = area.intersection(buf.area);
704 if area.is_empty() {
705 return;
706 }
707 buf.set_style(area, self.style);
708 self.render_borders(area, buf);
709 self.render_titles(area, buf);
710 }
711}
712
713impl Block<'_> {
714 fn render_borders(&self, area: Rect, buf: &mut Buffer) {
715 self.render_left_side(area, buf);
716 self.render_top_side(area, buf);
717 self.render_right_side(area, buf);
718 self.render_bottom_side(area, buf);
719
720 self.render_bottom_right_corner(buf, area);
721 self.render_top_right_corner(buf, area);
722 self.render_bottom_left_corner(buf, area);
723 self.render_top_left_corner(buf, area);
724 }
725
726 fn render_titles(&self, area: Rect, buf: &mut Buffer) {
727 self.render_title_position(Position::Top, area, buf);
728 self.render_title_position(Position::Bottom, area, buf);
729 }
730
731 fn render_title_position(&self, position: Position, area: Rect, buf: &mut Buffer) {
732 self.render_right_titles(position, area, buf);
734 self.render_center_titles(position, area, buf);
735 self.render_left_titles(position, area, buf);
736 }
737
738 fn render_left_side(&self, area: Rect, buf: &mut Buffer) {
739 if self.borders.contains(Borders::LEFT) {
740 for y in area.top()..area.bottom() {
741 buf[(area.left(), y)]
742 .set_symbol(self.border_set.vertical_left)
743 .set_style(self.border_style);
744 }
745 }
746 }
747
748 fn render_top_side(&self, area: Rect, buf: &mut Buffer) {
749 if self.borders.contains(Borders::TOP) {
750 for x in area.left()..area.right() {
751 buf[(x, area.top())]
752 .set_symbol(self.border_set.horizontal_top)
753 .set_style(self.border_style);
754 }
755 }
756 }
757
758 fn render_right_side(&self, area: Rect, buf: &mut Buffer) {
759 if self.borders.contains(Borders::RIGHT) {
760 let x = area.right() - 1;
761 for y in area.top()..area.bottom() {
762 buf[(x, y)]
763 .set_symbol(self.border_set.vertical_right)
764 .set_style(self.border_style);
765 }
766 }
767 }
768
769 fn render_bottom_side(&self, area: Rect, buf: &mut Buffer) {
770 if self.borders.contains(Borders::BOTTOM) {
771 let y = area.bottom() - 1;
772 for x in area.left()..area.right() {
773 buf[(x, y)]
774 .set_symbol(self.border_set.horizontal_bottom)
775 .set_style(self.border_style);
776 }
777 }
778 }
779
780 fn render_bottom_right_corner(&self, buf: &mut Buffer, area: Rect) {
781 if self.borders.contains(Borders::RIGHT | Borders::BOTTOM) {
782 buf[(area.right() - 1, area.bottom() - 1)]
783 .set_symbol(self.border_set.bottom_right)
784 .set_style(self.border_style);
785 }
786 }
787
788 fn render_top_right_corner(&self, buf: &mut Buffer, area: Rect) {
789 if self.borders.contains(Borders::RIGHT | Borders::TOP) {
790 buf[(area.right() - 1, area.top())]
791 .set_symbol(self.border_set.top_right)
792 .set_style(self.border_style);
793 }
794 }
795
796 fn render_bottom_left_corner(&self, buf: &mut Buffer, area: Rect) {
797 if self.borders.contains(Borders::LEFT | Borders::BOTTOM) {
798 buf[(area.left(), area.bottom() - 1)]
799 .set_symbol(self.border_set.bottom_left)
800 .set_style(self.border_style);
801 }
802 }
803
804 fn render_top_left_corner(&self, buf: &mut Buffer, area: Rect) {
805 if self.borders.contains(Borders::LEFT | Borders::TOP) {
806 buf[(area.left(), area.top())]
807 .set_symbol(self.border_set.top_left)
808 .set_style(self.border_style);
809 }
810 }
811
812 #[allow(clippy::similar_names)]
819 fn render_right_titles(&self, position: Position, area: Rect, buf: &mut Buffer) {
820 let titles = self.filtered_titles(position, Alignment::Right);
821 let mut titles_area = self.titles_area(area, position);
822
823 for title in titles.rev() {
825 if titles_area.is_empty() {
826 break;
827 }
828 let title_width = title.width() as u16;
829 let title_area = Rect {
830 x: titles_area
831 .right()
832 .saturating_sub(title_width)
833 .max(titles_area.left()),
834 width: title_width.min(titles_area.width),
835 ..titles_area
836 };
837 buf.set_style(title_area, self.titles_style);
838 title.render_ref(title_area, buf);
839
840 titles_area.width = titles_area
842 .width
843 .saturating_sub(title_width)
844 .saturating_sub(1); }
846 }
847
848 #[allow(clippy::similar_names)]
854 fn render_center_titles(&self, position: Position, area: Rect, buf: &mut Buffer) {
855 let titles = self
856 .filtered_titles(position, Alignment::Center)
857 .collect_vec();
858 let total_width = titles
859 .iter()
860 .map(|title| title.width() as u16 + 1) .sum::<u16>()
862 .saturating_sub(1); let titles_area = self.titles_area(area, position);
865 let mut titles_area = Rect {
866 x: titles_area.left() + (titles_area.width.saturating_sub(total_width) / 2),
867 ..titles_area
868 };
869 for title in titles {
870 if titles_area.is_empty() {
871 break;
872 }
873 let title_width = title.width() as u16;
874 let title_area = Rect {
875 width: title_width.min(titles_area.width),
876 ..titles_area
877 };
878 buf.set_style(title_area, self.titles_style);
879 title.render_ref(title_area, buf);
880
881 titles_area.x = titles_area.x.saturating_add(title_width + 1);
883 titles_area.width = titles_area.width.saturating_sub(title_width + 1);
884 }
885 }
886
887 #[allow(clippy::similar_names)]
889 fn render_left_titles(&self, position: Position, area: Rect, buf: &mut Buffer) {
890 let titles = self.filtered_titles(position, Alignment::Left);
891 let mut titles_area = self.titles_area(area, position);
892 for title in titles {
893 if titles_area.is_empty() {
894 break;
895 }
896 let title_width = title.width() as u16;
897 let title_area = Rect {
898 width: title_width.min(titles_area.width),
899 ..titles_area
900 };
901 buf.set_style(title_area, self.titles_style);
902 title.render_ref(title_area, buf);
903
904 titles_area.x = titles_area.x.saturating_add(title_width + 1);
906 titles_area.width = titles_area.width.saturating_sub(title_width + 1);
907 }
908 }
909
910 fn filtered_titles(
912 &self,
913 position: Position,
914 alignment: Alignment,
915 ) -> impl DoubleEndedIterator<Item = &Line> {
916 self.titles
917 .iter()
918 .filter(move |(pos, _)| pos.unwrap_or(self.titles_position) == position)
919 .filter(move |(_, line)| line.alignment.unwrap_or(self.titles_alignment) == alignment)
920 .map(|(_, line)| line)
921 }
922
923 fn titles_area(&self, area: Rect, position: Position) -> Rect {
926 let left_border = u16::from(self.borders.contains(Borders::LEFT));
927 let right_border = u16::from(self.borders.contains(Borders::RIGHT));
928 Rect {
929 x: area.left() + left_border,
930 y: match position {
931 Position::Top => area.top(),
932 Position::Bottom => area.bottom() - 1,
933 },
934 width: area
935 .width
936 .saturating_sub(left_border)
937 .saturating_sub(right_border),
938 height: 1,
939 }
940 }
941
942 pub(crate) fn horizontal_space(&self) -> (u16, u16) {
946 let left = self
947 .padding
948 .left
949 .saturating_add(u16::from(self.borders.contains(Borders::LEFT)));
950 let right = self
951 .padding
952 .right
953 .saturating_add(u16::from(self.borders.contains(Borders::RIGHT)));
954 (left, right)
955 }
956
957 pub(crate) fn vertical_space(&self) -> (u16, u16) {
962 let has_top =
963 self.borders.contains(Borders::TOP) || self.has_title_at_position(Position::Top);
964 let top = self.padding.top + u16::from(has_top);
965 let has_bottom =
966 self.borders.contains(Borders::BOTTOM) || self.has_title_at_position(Position::Bottom);
967 let bottom = self.padding.bottom + u16::from(has_bottom);
968 (top, bottom)
969 }
970}
971
972pub trait BlockExt {
977 fn inner_if_some(&self, area: Rect) -> Rect;
981}
982
983impl BlockExt for Option<Block<'_>> {
984 fn inner_if_some(&self, area: Rect) -> Rect {
985 self.as_ref().map_or(area, |block| block.inner(area))
986 }
987}
988
989impl<'a> Styled for Block<'a> {
990 type Item = Self;
991
992 fn style(&self) -> Style {
993 self.style
994 }
995
996 fn set_style<S: Into<Style>>(self, style: S) -> Self::Item {
997 self.style(style)
998 }
999}
1000
1001#[cfg(test)]
1002mod tests {
1003 use rstest::rstest;
1004 use strum::ParseError;
1005
1006 use super::*;
1007 use crate::style::{Color, Modifier, Stylize};
1008
1009 #[test]
1010 fn create_with_all_borders() {
1011 let block = Block::bordered();
1012 assert_eq!(block.borders, Borders::all());
1013 }
1014
1015 #[rstest]
1016 #[case::none_0(Borders::NONE, Rect::ZERO, Rect::ZERO)]
1017 #[case::none_1(Borders::NONE, Rect::new(0, 0, 1, 1), Rect::new(0, 0, 1, 1))]
1018 #[case::left_0(Borders::LEFT, Rect::ZERO, Rect::ZERO)]
1019 #[case::left_w1(Borders::LEFT, Rect::new(0, 0, 0, 1), Rect::new(0, 0, 0, 1))]
1020 #[case::left_w2(Borders::LEFT, Rect::new(0, 0, 1, 1), Rect::new(1, 0, 0, 1))]
1021 #[case::left_w3(Borders::LEFT, Rect::new(0, 0, 2, 1), Rect::new(1, 0, 1, 1))]
1022 #[case::top_0(Borders::TOP, Rect::ZERO, Rect::ZERO)]
1023 #[case::top_h1(Borders::TOP, Rect::new(0, 0, 1, 0), Rect::new(0, 0, 1, 0))]
1024 #[case::top_h2(Borders::TOP, Rect::new(0, 0, 1, 1), Rect::new(0, 1, 1, 0))]
1025 #[case::top_h3(Borders::TOP, Rect::new(0, 0, 1, 2), Rect::new(0, 1, 1, 1))]
1026 #[case::right_0(Borders::RIGHT, Rect::ZERO, Rect::ZERO)]
1027 #[case::right_w1(Borders::RIGHT, Rect::new(0, 0, 0, 1), Rect::new(0, 0, 0, 1))]
1028 #[case::right_w2(Borders::RIGHT, Rect::new(0, 0, 1, 1), Rect::new(0, 0, 0, 1))]
1029 #[case::right_w3(Borders::RIGHT, Rect::new(0, 0, 2, 1), Rect::new(0, 0, 1, 1))]
1030 #[case::bottom_0(Borders::BOTTOM, Rect::ZERO, Rect::ZERO)]
1031 #[case::bottom_h1(Borders::BOTTOM, Rect::new(0, 0, 1, 0), Rect::new(0, 0, 1, 0))]
1032 #[case::bottom_h2(Borders::BOTTOM, Rect::new(0, 0, 1, 1), Rect::new(0, 0, 1, 0))]
1033 #[case::bottom_h3(Borders::BOTTOM, Rect::new(0, 0, 1, 2), Rect::new(0, 0, 1, 1))]
1034 #[case::all_0(Borders::ALL, Rect::ZERO, Rect::ZERO)]
1035 #[case::all_1(Borders::ALL, Rect::new(0, 0, 1, 1), Rect::new(1, 1, 0, 0))]
1036 #[case::all_2(Borders::ALL, Rect::new(0, 0, 2, 2), Rect::new(1, 1, 0, 0))]
1037 #[case::all_3(Borders::ALL, Rect::new(0, 0, 3, 3), Rect::new(1, 1, 1, 1))]
1038 fn inner_takes_into_account_the_borders(
1039 #[case] borders: Borders,
1040 #[case] area: Rect,
1041 #[case] expected: Rect,
1042 ) {
1043 let block = Block::new().borders(borders);
1044 assert_eq!(block.inner(area), expected);
1045 }
1046
1047 #[rstest]
1048 #[case::left(Alignment::Left)]
1049 #[case::center(Alignment::Center)]
1050 #[case::right(Alignment::Right)]
1051 fn inner_takes_into_account_the_title(#[case] alignment: Alignment) {
1052 let area = Rect::new(0, 0, 0, 1);
1053 let expected = Rect::new(0, 1, 0, 0);
1054
1055 let block = Block::new().title(Line::from("Test").alignment(alignment));
1056 assert_eq!(block.inner(area), expected);
1057 }
1058
1059 #[rstest]
1060 #[case::top_top(Block::new().title_top("Test").borders(Borders::TOP), Rect::new(0, 1, 0, 1))]
1061 #[case::top_bot(Block::new().title_top("Test").borders(Borders::BOTTOM), Rect::new(0, 1, 0, 0))]
1062 #[case::bot_top(Block::new().title_bottom("Test").borders(Borders::TOP), Rect::new(0, 1, 0, 0))]
1063 #[case::bot_bot(Block::new().title_bottom("Test").borders(Borders::BOTTOM), Rect::new(0, 0, 0, 1))]
1064 fn inner_takes_into_account_border_and_title(#[case] block: Block, #[case] expected: Rect) {
1065 let area = Rect::new(0, 0, 0, 2);
1066 assert_eq!(block.inner(area), expected);
1067 }
1068
1069 #[test]
1070 fn has_title_at_position_takes_into_account_all_positioning_declarations() {
1071 let block = Block::new();
1072 assert!(!block.has_title_at_position(Position::Top));
1073 assert!(!block.has_title_at_position(Position::Bottom));
1074
1075 let block = Block::new().title_top("test");
1076 assert!(block.has_title_at_position(Position::Top));
1077 assert!(!block.has_title_at_position(Position::Bottom));
1078
1079 let block = Block::new().title_bottom("test");
1080 assert!(!block.has_title_at_position(Position::Top));
1081 assert!(block.has_title_at_position(Position::Bottom));
1082
1083 #[allow(deprecated)] let block = Block::new()
1085 .title(Title::from("Test").position(Position::Top))
1086 .title_position(Position::Bottom);
1087 assert!(block.has_title_at_position(Position::Top));
1088 assert!(!block.has_title_at_position(Position::Bottom));
1089
1090 #[allow(deprecated)] let block = Block::new()
1092 .title(Title::from("Test").position(Position::Bottom))
1093 .title_position(Position::Top);
1094 assert!(!block.has_title_at_position(Position::Top));
1095 assert!(block.has_title_at_position(Position::Bottom));
1096
1097 let block = Block::new().title_top("test").title_bottom("test");
1098 assert!(block.has_title_at_position(Position::Top));
1099 assert!(block.has_title_at_position(Position::Bottom));
1100
1101 #[allow(deprecated)] let block = Block::new()
1103 .title(Title::from("Test").position(Position::Top))
1104 .title(Title::from("Test"))
1105 .title_position(Position::Bottom);
1106 assert!(block.has_title_at_position(Position::Top));
1107 assert!(block.has_title_at_position(Position::Bottom));
1108
1109 #[allow(deprecated)] let block = Block::new()
1111 .title(Title::from("Test"))
1112 .title(Title::from("Test").position(Position::Bottom))
1113 .title_position(Position::Top);
1114 assert!(block.has_title_at_position(Position::Top));
1115 assert!(block.has_title_at_position(Position::Bottom));
1116 }
1117
1118 #[rstest]
1119 #[case::none(Borders::NONE, (0, 0))]
1120 #[case::top(Borders::TOP, (1, 0))]
1121 #[case::right(Borders::RIGHT, (0, 0))]
1122 #[case::bottom(Borders::BOTTOM, (0, 1))]
1123 #[case::left(Borders::LEFT, (0, 0))]
1124 #[case::top_right(Borders::TOP | Borders::RIGHT, (1, 0))]
1125 #[case::top_bottom(Borders::TOP | Borders::BOTTOM, (1, 1))]
1126 #[case::top_left(Borders::TOP | Borders::LEFT, (1, 0))]
1127 #[case::bottom_right(Borders::BOTTOM | Borders::RIGHT, (0, 1))]
1128 #[case::bottom_left(Borders::BOTTOM | Borders::LEFT, (0, 1))]
1129 #[case::left_right(Borders::LEFT | Borders::RIGHT, (0, 0))]
1130 fn vertical_space_takes_into_account_borders(
1131 #[case] borders: Borders,
1132 #[case] vertical_space: (u16, u16),
1133 ) {
1134 let block = Block::new().borders(borders);
1135 assert_eq!(block.vertical_space(), vertical_space);
1136 }
1137
1138 #[rstest]
1139 #[case::top_border_top_p1(Borders::TOP, Padding::new(0, 0, 1, 0), (2, 0))]
1140 #[case::right_border_top_p1(Borders::RIGHT, Padding::new(0, 0, 1, 0), (1, 0))]
1141 #[case::bottom_border_top_p1(Borders::BOTTOM, Padding::new(0, 0, 1, 0), (1, 1))]
1142 #[case::left_border_top_p1(Borders::LEFT, Padding::new(0, 0, 1, 0), (1, 0))]
1143 #[case::top_bottom_border_all_p3(Borders::TOP | Borders::BOTTOM, Padding::new(100, 100, 4, 5), (5, 6))]
1144 #[case::no_border(Borders::NONE, Padding::new(100, 100, 10, 13), (10, 13))]
1145 #[case::all(Borders::ALL, Padding::new(100, 100, 1, 3), (2, 4))]
1146 fn vertical_space_takes_into_account_padding(
1147 #[case] borders: Borders,
1148 #[case] padding: Padding,
1149 #[case] vertical_space: (u16, u16),
1150 ) {
1151 let block = Block::new().borders(borders).padding(padding);
1152 assert_eq!(block.vertical_space(), vertical_space);
1153 }
1154
1155 #[test]
1156 fn vertical_space_takes_into_account_titles() {
1157 let block = Block::new().title_top("Test");
1158 assert_eq!(block.vertical_space(), (1, 0));
1159
1160 let block = Block::new().title_bottom("Test");
1161 assert_eq!(block.vertical_space(), (0, 1));
1162 }
1163
1164 #[rstest]
1165 #[case::top_border_top_title(Block::new(), Borders::TOP, Position::Top, (1, 0))]
1166 #[case::right_border_top_title(Block::new(), Borders::RIGHT, Position::Top, (1, 0))]
1167 #[case::bottom_border_top_title(Block::new(), Borders::BOTTOM, Position::Top, (1, 1))]
1168 #[case::left_border_top_title(Block::new(), Borders::LEFT, Position::Top, (1, 0))]
1169 #[case::top_border_top_title(Block::new(), Borders::TOP, Position::Bottom, (1, 1))]
1170 #[case::right_border_top_title(Block::new(), Borders::RIGHT, Position::Bottom, (0, 1))]
1171 #[case::bottom_border_top_title(Block::new(), Borders::BOTTOM, Position::Bottom, (0, 1))]
1172 #[case::left_border_top_title(Block::new(), Borders::LEFT, Position::Bottom, (0, 1))]
1173 fn vertical_space_takes_into_account_borders_and_title(
1174 #[case] block: Block,
1175 #[case] borders: Borders,
1176 #[case] pos: Position,
1177 #[case] vertical_space: (u16, u16),
1178 ) {
1179 let block = block.borders(borders).title_position(pos).title("Test");
1180 assert_eq!(block.vertical_space(), vertical_space);
1181 }
1182
1183 #[test]
1184 fn horizontal_space_takes_into_account_borders() {
1185 let block = Block::bordered();
1186 assert_eq!(block.horizontal_space(), (1, 1));
1187
1188 let block = Block::new().borders(Borders::LEFT);
1189 assert_eq!(block.horizontal_space(), (1, 0));
1190
1191 let block = Block::new().borders(Borders::RIGHT);
1192 assert_eq!(block.horizontal_space(), (0, 1));
1193 }
1194
1195 #[test]
1196 fn horizontal_space_takes_into_account_padding() {
1197 let block = Block::new().padding(Padding::new(1, 1, 100, 100));
1198 assert_eq!(block.horizontal_space(), (1, 1));
1199
1200 let block = Block::new().padding(Padding::new(3, 5, 0, 0));
1201 assert_eq!(block.horizontal_space(), (3, 5));
1202
1203 let block = Block::new().padding(Padding::new(0, 1, 100, 100));
1204 assert_eq!(block.horizontal_space(), (0, 1));
1205
1206 let block = Block::new().padding(Padding::new(1, 0, 100, 100));
1207 assert_eq!(block.horizontal_space(), (1, 0));
1208 }
1209
1210 #[rstest]
1211 #[case::all_bordered_all_padded(Block::bordered(), Padding::new(1, 1, 1, 1), (2, 2))]
1212 #[case::all_bordered_left_padded(Block::bordered(), Padding::new(1, 0, 0, 0), (2, 1))]
1213 #[case::all_bordered_right_padded(Block::bordered(), Padding::new(0, 1, 0, 0), (1, 2))]
1214 #[case::all_bordered_top_padded(Block::bordered(), Padding::new(0, 0, 1, 0), (1, 1))]
1215 #[case::all_bordered_bottom_padded(Block::bordered(), Padding::new(0, 0, 0, 1), (1, 1))]
1216 #[case::left_bordered_left_padded(Block::new().borders(Borders::LEFT), Padding::new(1, 0, 0, 0), (2, 0))]
1217 #[case::left_bordered_right_padded(Block::new().borders(Borders::LEFT), Padding::new(0, 1, 0, 0), (1, 1))]
1218 #[case::right_bordered_right_padded(Block::new().borders(Borders::RIGHT), Padding::new(0, 1, 0, 0), (0, 2))]
1219 #[case::right_bordered_left_padded(Block::new().borders(Borders::RIGHT), Padding::new(1, 0, 0, 0), (1, 1))]
1220 fn horizontal_space_takes_into_account_borders_and_padding(
1221 #[case] block: Block,
1222 #[case] padding: Padding,
1223 #[case] horizontal_space: (u16, u16),
1224 ) {
1225 let block = block.padding(padding);
1226 assert_eq!(block.horizontal_space(), horizontal_space);
1227 }
1228
1229 #[test]
1230 const fn border_type_can_be_const() {
1231 const _PLAIN: border::Set = BorderType::border_symbols(BorderType::Plain);
1232 }
1233
1234 #[test]
1235 fn block_new() {
1236 assert_eq!(
1237 Block::new(),
1238 Block {
1239 titles: Vec::new(),
1240 titles_style: Style::new(),
1241 titles_alignment: Alignment::Left,
1242 titles_position: Position::Top,
1243 borders: Borders::NONE,
1244 border_style: Style::new(),
1245 border_set: BorderType::Plain.to_border_set(),
1246 style: Style::new(),
1247 padding: Padding::ZERO,
1248 }
1249 );
1250 }
1251
1252 #[test]
1253 const fn block_can_be_const() {
1254 const _DEFAULT_STYLE: Style = Style::new();
1255 const _DEFAULT_PADDING: Padding = Padding::uniform(1);
1256 const _DEFAULT_BLOCK: Block = Block::bordered()
1257 .title_alignment(Alignment::Left)
1262 .title_position(Position::Top)
1263 .padding(_DEFAULT_PADDING);
1264 }
1265
1266 #[test]
1268 fn style_into_works_from_user_view() {
1269 let block = Block::new().style(Style::new().red());
1271 assert_eq!(block.style, Style::new().red());
1272
1273 let block = Block::new().style(Color::Red);
1275 assert_eq!(block.style, Style::new().red());
1276
1277 let block = Block::new().style((Color::Red, Color::Blue));
1279 assert_eq!(block.style, Style::new().red().on_blue());
1280
1281 let block = Block::new().style(Modifier::BOLD | Modifier::ITALIC);
1283 assert_eq!(block.style, Style::new().bold().italic());
1284
1285 let block = Block::new().style((Modifier::BOLD | Modifier::ITALIC, Modifier::DIM));
1287 assert_eq!(block.style, Style::new().bold().italic().not_dim());
1288
1289 let block = Block::new().style((Color::Red, Modifier::BOLD));
1291 assert_eq!(block.style, Style::new().red().bold());
1292
1293 let block = Block::new().style((Color::Red, Color::Blue, Modifier::BOLD));
1295 assert_eq!(block.style, Style::new().red().on_blue().bold());
1296
1297 let block = Block::new().style((
1299 Color::Red,
1300 Color::Blue,
1301 Modifier::BOLD | Modifier::ITALIC,
1302 Modifier::DIM,
1303 ));
1304 assert_eq!(
1305 block.style,
1306 Style::new().red().on_blue().bold().italic().not_dim()
1307 );
1308 }
1309
1310 #[test]
1311 fn can_be_stylized() {
1312 let block = Block::new().black().on_white().bold().not_dim();
1313 assert_eq!(
1314 block.style,
1315 Style::default()
1316 .fg(Color::Black)
1317 .bg(Color::White)
1318 .add_modifier(Modifier::BOLD)
1319 .remove_modifier(Modifier::DIM)
1320 );
1321 }
1322
1323 #[test]
1324 fn title() {
1325 use Alignment::*;
1326 use Position::*;
1327 let mut buffer = Buffer::empty(Rect::new(0, 0, 11, 3));
1328 #[allow(deprecated)] Block::bordered()
1330 .title(Title::from("A").position(Top).alignment(Left))
1331 .title(Title::from("B").position(Top).alignment(Center))
1332 .title(Title::from("C").position(Top).alignment(Right))
1333 .title(Title::from("D").position(Bottom).alignment(Left))
1334 .title(Title::from("E").position(Bottom).alignment(Center))
1335 .title(Title::from("F").position(Bottom).alignment(Right))
1336 .render(buffer.area, &mut buffer);
1337 #[rustfmt::skip]
1338 let expected = Buffer::with_lines([
1339 "┌A───B───C┐",
1340 "│ │",
1341 "└D───E───F┘",
1342 ]);
1343 assert_eq!(buffer, expected);
1344 }
1345
1346 #[test]
1347 fn title_top_bottom() {
1348 let mut buffer = Buffer::empty(Rect::new(0, 0, 11, 3));
1349 Block::bordered()
1350 .title_top(Line::raw("A").left_aligned())
1351 .title_top(Line::raw("B").centered())
1352 .title_top(Line::raw("C").right_aligned())
1353 .title_bottom(Line::raw("D").left_aligned())
1354 .title_bottom(Line::raw("E").centered())
1355 .title_bottom(Line::raw("F").right_aligned())
1356 .render(buffer.area, &mut buffer);
1357 #[rustfmt::skip]
1358 let expected = Buffer::with_lines([
1359 "┌A───B───C┐",
1360 "│ │",
1361 "└D───E───F┘",
1362 ]);
1363 assert_eq!(buffer, expected);
1364 }
1365
1366 #[test]
1367 fn title_alignment() {
1368 let tests = vec![
1369 (Alignment::Left, "test "),
1370 (Alignment::Center, " test "),
1371 (Alignment::Right, " test"),
1372 ];
1373 for (alignment, expected) in tests {
1374 let mut buffer = Buffer::empty(Rect::new(0, 0, 8, 1));
1375 Block::new()
1376 .title_alignment(alignment)
1377 .title("test")
1378 .render(buffer.area, &mut buffer);
1379 assert_eq!(buffer, Buffer::with_lines([expected]));
1380 }
1381 }
1382
1383 #[test]
1384 fn title_alignment_overrides_block_title_alignment() {
1385 let tests = vec![
1386 (Alignment::Right, Alignment::Left, "test "),
1387 (Alignment::Left, Alignment::Center, " test "),
1388 (Alignment::Center, Alignment::Right, " test"),
1389 ];
1390 for (block_title_alignment, alignment, expected) in tests {
1391 let mut buffer = Buffer::empty(Rect::new(0, 0, 8, 1));
1392 Block::new()
1393 .title_alignment(block_title_alignment)
1394 .title(Line::from("test").alignment(alignment))
1395 .render(buffer.area, &mut buffer);
1396 assert_eq!(buffer, Buffer::with_lines([expected]));
1397 }
1398 }
1399
1400 #[test]
1402 fn render_right_aligned_empty_title() {
1403 let mut buffer = Buffer::empty(Rect::new(0, 0, 15, 3));
1404 Block::new()
1405 .title_alignment(Alignment::Right)
1406 .title("")
1407 .render(buffer.area, &mut buffer);
1408 assert_eq!(buffer, Buffer::with_lines([" "; 3]));
1409 }
1410
1411 #[test]
1412 fn title_position() {
1413 let mut buffer = Buffer::empty(Rect::new(0, 0, 4, 2));
1414 Block::new()
1415 .title_position(Position::Bottom)
1416 .title("test")
1417 .render(buffer.area, &mut buffer);
1418 assert_eq!(buffer, Buffer::with_lines([" ", "test"]));
1419 }
1420
1421 #[test]
1422 fn title_content_style() {
1423 for alignment in [Alignment::Left, Alignment::Center, Alignment::Right] {
1424 let mut buffer = Buffer::empty(Rect::new(0, 0, 4, 1));
1425 Block::new()
1426 .title_alignment(alignment)
1427 .title("test".yellow())
1428 .render(buffer.area, &mut buffer);
1429 assert_eq!(buffer, Buffer::with_lines(["test".yellow()]));
1430 }
1431 }
1432
1433 #[test]
1434 fn block_title_style() {
1435 for alignment in [Alignment::Left, Alignment::Center, Alignment::Right] {
1436 let mut buffer = Buffer::empty(Rect::new(0, 0, 4, 1));
1437 Block::new()
1438 .title_alignment(alignment)
1439 .title_style(Style::new().yellow())
1440 .title("test")
1441 .render(buffer.area, &mut buffer);
1442 assert_eq!(buffer, Buffer::with_lines(["test".yellow()]));
1443 }
1444 }
1445
1446 #[test]
1447 fn title_style_overrides_block_title_style() {
1448 for alignment in [Alignment::Left, Alignment::Center, Alignment::Right] {
1449 let mut buffer = Buffer::empty(Rect::new(0, 0, 4, 1));
1450 Block::new()
1451 .title_alignment(alignment)
1452 .title_style(Style::new().green().on_red())
1453 .title("test".yellow())
1454 .render(buffer.area, &mut buffer);
1455 assert_eq!(buffer, Buffer::with_lines(["test".yellow().on_red()]));
1456 }
1457 }
1458
1459 #[test]
1460 fn title_border_style() {
1461 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1462 Block::bordered()
1463 .title("test")
1464 .border_style(Style::new().yellow())
1465 .render(buffer.area, &mut buffer);
1466 #[rustfmt::skip]
1467 let mut expected = Buffer::with_lines([
1468 "┌test────┐",
1469 "│ │",
1470 "└────────┘",
1471 ]);
1472 expected.set_style(Rect::new(0, 0, 10, 3), Style::new().yellow());
1473 expected.set_style(Rect::new(1, 1, 8, 1), Style::reset());
1474 assert_eq!(buffer, expected);
1475 }
1476
1477 #[test]
1478 fn border_type_to_string() {
1479 assert_eq!(format!("{}", BorderType::Plain), "Plain");
1480 assert_eq!(format!("{}", BorderType::Rounded), "Rounded");
1481 assert_eq!(format!("{}", BorderType::Double), "Double");
1482 assert_eq!(format!("{}", BorderType::Thick), "Thick");
1483 }
1484
1485 #[test]
1486 fn border_type_from_str() {
1487 assert_eq!("Plain".parse(), Ok(BorderType::Plain));
1488 assert_eq!("Rounded".parse(), Ok(BorderType::Rounded));
1489 assert_eq!("Double".parse(), Ok(BorderType::Double));
1490 assert_eq!("Thick".parse(), Ok(BorderType::Thick));
1491 assert_eq!("".parse::<BorderType>(), Err(ParseError::VariantNotFound));
1492 }
1493
1494 #[test]
1495 fn render_plain_border() {
1496 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1497 Block::bordered()
1498 .border_type(BorderType::Plain)
1499 .render(buffer.area, &mut buffer);
1500 #[rustfmt::skip]
1501 let expected = Buffer::with_lines([
1502 "┌────────┐",
1503 "│ │",
1504 "└────────┘",
1505 ]);
1506 assert_eq!(buffer, expected);
1507 }
1508
1509 #[test]
1510 fn render_rounded_border() {
1511 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1512 Block::bordered()
1513 .border_type(BorderType::Rounded)
1514 .render(buffer.area, &mut buffer);
1515 #[rustfmt::skip]
1516 let expected = Buffer::with_lines([
1517 "╭────────╮",
1518 "│ │",
1519 "╰────────╯",
1520 ]);
1521 assert_eq!(buffer, expected);
1522 }
1523
1524 #[test]
1525 fn render_double_border() {
1526 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1527 Block::bordered()
1528 .border_type(BorderType::Double)
1529 .render(buffer.area, &mut buffer);
1530 #[rustfmt::skip]
1531 let expected = Buffer::with_lines([
1532 "╔════════╗",
1533 "║ ║",
1534 "╚════════╝",
1535 ]);
1536 assert_eq!(buffer, expected);
1537 }
1538
1539 #[test]
1540 fn render_quadrant_inside() {
1541 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1542 Block::bordered()
1543 .border_type(BorderType::QuadrantInside)
1544 .render(buffer.area, &mut buffer);
1545 #[rustfmt::skip]
1546 let expected = Buffer::with_lines([
1547 "▗▄▄▄▄▄▄▄▄▖",
1548 "▐ ▌",
1549 "▝▀▀▀▀▀▀▀▀▘",
1550 ]);
1551 assert_eq!(buffer, expected);
1552 }
1553
1554 #[test]
1555 fn render_border_quadrant_outside() {
1556 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1557 Block::bordered()
1558 .border_type(BorderType::QuadrantOutside)
1559 .render(buffer.area, &mut buffer);
1560 #[rustfmt::skip]
1561 let expected = Buffer::with_lines([
1562 "▛▀▀▀▀▀▀▀▀▜",
1563 "▌ ▐",
1564 "▙▄▄▄▄▄▄▄▄▟",
1565 ]);
1566 assert_eq!(buffer, expected);
1567 }
1568
1569 #[test]
1570 fn render_solid_border() {
1571 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1572 Block::bordered()
1573 .border_type(BorderType::Thick)
1574 .render(buffer.area, &mut buffer);
1575 #[rustfmt::skip]
1576 let expected = Buffer::with_lines([
1577 "┏━━━━━━━━┓",
1578 "┃ ┃",
1579 "┗━━━━━━━━┛",
1580 ]);
1581 assert_eq!(buffer, expected);
1582 }
1583
1584 #[test]
1585 fn render_custom_border_set() {
1586 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1587 Block::bordered()
1588 .border_set(border::Set {
1589 top_left: "1",
1590 top_right: "2",
1591 bottom_left: "3",
1592 bottom_right: "4",
1593 vertical_left: "L",
1594 vertical_right: "R",
1595 horizontal_top: "T",
1596 horizontal_bottom: "B",
1597 })
1598 .render(buffer.area, &mut buffer);
1599 #[rustfmt::skip]
1600 let expected = Buffer::with_lines([
1601 "1TTTTTTTT2",
1602 "L R",
1603 "3BBBBBBBB4",
1604 ]);
1605 assert_eq!(buffer, expected);
1606 }
1607}