1use alloc::vec::Vec;
9
10use itertools::Itertools;
11use ratatui_core::buffer::Buffer;
12use ratatui_core::layout::{Alignment, Rect};
13use ratatui_core::style::{Style, Styled};
14use ratatui_core::symbols::border;
15use ratatui_core::symbols::merge::MergeStrategy;
16use ratatui_core::text::Line;
17use ratatui_core::widgets::Widget;
18use strum::{Display, EnumString};
19
20pub use self::padding::Padding;
21pub use self::shadow::{CellEffect, Dimmed, Shadow, dimmed};
22use crate::borders::{BorderType, Borders};
23
24mod padding;
25mod shadow;
26
27#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
217pub struct Block<'a> {
218 titles: Vec<(Option<TitlePosition>, Line<'a>)>,
220 titles_style: Style,
222 titles_alignment: Alignment,
224 titles_position: TitlePosition,
226 borders: Borders,
228 border_style: Style,
230 border_set: border::Set<'a>,
233 style: Style,
235 padding: Padding,
237 merge_borders: MergeStrategy,
239 shadow: Option<Shadow>,
241}
242
243#[derive(Debug, Default, Display, EnumString, Clone, Copy, PartialEq, Eq, Hash)]
260pub enum TitlePosition {
261 #[default]
263 Top,
264 Bottom,
266}
267
268impl<'a> Block<'a> {
269 pub const fn new() -> Self {
271 Self {
272 titles: Vec::new(),
273 titles_style: Style::new(),
274 titles_alignment: Alignment::Left,
275 titles_position: TitlePosition::Top,
276 borders: Borders::NONE,
277 border_style: Style::new(),
278 border_set: BorderType::Plain.to_border_set(),
279 style: Style::new(),
280 padding: Padding::ZERO,
281 merge_borders: MergeStrategy::Replace,
282 shadow: None,
283 }
284 }
285
286 pub const fn bordered() -> Self {
294 let mut block = Self::new();
295 block.borders = Borders::ALL;
296 block
297 }
298
299 #[must_use = "method moves the value of self and returns the modified value"]
366 pub fn title<T>(mut self, title: T) -> Self
367 where
368 T: Into<Line<'a>>,
369 {
370 self.titles.push((None, title.into()));
371 self
372 }
373
374 #[must_use = "method moves the value of self and returns the modified value"]
397 pub fn title_top<T: Into<Line<'a>>>(mut self, title: T) -> Self {
398 let line = title.into();
399 self.titles.push((Some(TitlePosition::Top), line));
400 self
401 }
402
403 #[must_use = "method moves the value of self and returns the modified value"]
426 pub fn title_bottom<T: Into<Line<'a>>>(mut self, title: T) -> Self {
427 let line = title.into();
428 self.titles.push((Some(TitlePosition::Bottom), line));
429 self
430 }
431
432 #[must_use = "method moves the value of self and returns the modified value"]
445 pub fn title_style<S: Into<Style>>(mut self, style: S) -> Self {
446 self.titles_style = style.into();
447 self
448 }
449
450 #[must_use = "method moves the value of self and returns the modified value"]
471 pub const fn title_alignment(mut self, alignment: Alignment) -> Self {
472 self.titles_alignment = alignment;
473 self
474 }
475
476 #[must_use = "method moves the value of self and returns the modified value"]
494 pub const fn title_position(mut self, position: TitlePosition) -> Self {
495 self.titles_position = position;
496 self
497 }
498
499 #[must_use = "method moves the value of self and returns the modified value"]
520 pub fn border_style<S: Into<Style>>(mut self, style: S) -> Self {
521 self.border_style = style.into();
522 self
523 }
524
525 #[must_use = "method moves the value of self and returns the modified value"]
562 pub fn style<S: Into<Style>>(mut self, style: S) -> Self {
563 self.style = style.into();
564 self
565 }
566
567 #[must_use = "method moves the value of self and returns the modified value"]
581 pub const fn borders(mut self, flag: Borders) -> Self {
582 self.borders = flag;
583 self
584 }
585
586 #[must_use = "method moves the value of self and returns the modified value"]
606 pub const fn border_type(mut self, border_type: BorderType) -> Self {
607 self.border_set = border_type.to_border_set();
608 self
609 }
610
611 #[must_use = "method moves the value of self and returns the modified value"]
626 pub const fn border_set(mut self, border_set: border::Set<'a>) -> Self {
627 self.border_set = border_set;
628 self
629 }
630
631 #[must_use = "method moves the value of self and returns the modified value"]
660 pub const fn padding(mut self, padding: Padding) -> Self {
661 self.padding = padding;
662 self
663 }
664
665 #[must_use = "method moves the value of self and returns the modified value"]
707 pub const fn merge_borders(mut self, strategy: MergeStrategy) -> Self {
708 self.merge_borders = strategy;
709 self
710 }
711
712 #[must_use]
731 pub fn shadow(mut self, shadow: Shadow) -> Self {
732 self.shadow = Some(shadow);
733 self
734 }
735
736 pub fn inner(&self, area: Rect) -> Rect {
763 let mut inner = area;
764 if self.borders.intersects(Borders::LEFT) {
765 inner.x = inner.x.saturating_add(1).min(inner.right());
766 inner.width = inner.width.saturating_sub(1);
767 }
768 if self.borders.intersects(Borders::TOP) || self.has_title_at_position(TitlePosition::Top) {
769 inner.y = inner.y.saturating_add(1).min(inner.bottom());
770 inner.height = inner.height.saturating_sub(1);
771 }
772 if self.borders.intersects(Borders::RIGHT) {
773 inner.width = inner.width.saturating_sub(1);
774 }
775 if self.borders.intersects(Borders::BOTTOM)
776 || self.has_title_at_position(TitlePosition::Bottom)
777 {
778 inner.height = inner.height.saturating_sub(1);
779 }
780
781 inner.x = inner.x.saturating_add(self.padding.left);
782 inner.y = inner.y.saturating_add(self.padding.top);
783
784 let horizontal_padding = self.padding.left.saturating_add(self.padding.right);
785 let vertical_padding = self.padding.top.saturating_add(self.padding.bottom);
786 inner.width = inner.width.saturating_sub(horizontal_padding);
787 inner.height = inner.height.saturating_sub(vertical_padding);
788
789 inner
790 }
791
792 fn has_title_at_position(&self, position: TitlePosition) -> bool {
793 self.titles
794 .iter()
795 .any(|(pos, _)| pos.unwrap_or(self.titles_position) == position)
796 }
797}
798
799impl Widget for Block<'_> {
800 fn render(self, area: Rect, buf: &mut Buffer) {
801 Widget::render(&self, area, buf);
802 }
803}
804
805impl Widget for &Block<'_> {
806 fn render(self, area: Rect, buf: &mut Buffer) {
807 let area = area.intersection(buf.area);
808 if area.is_empty() {
809 return;
810 }
811 buf.set_style(area, self.style);
812 self.render_borders(area, buf);
813 self.render_titles(area, buf);
814 self.render_shadow(area, buf);
815 }
816}
817
818impl Block<'_> {
819 fn render_borders(&self, area: Rect, buf: &mut Buffer) {
820 self.render_sides(area, buf);
821 self.render_corners(area, buf);
822 }
823
824 fn render_sides(&self, area: Rect, buf: &mut Buffer) {
825 let left = area.left();
826 let top = area.top();
827 let right = area.right().saturating_sub(1);
829 let bottom = area.bottom().saturating_sub(1);
830
831 let is_replace = self.merge_borders != MergeStrategy::Replace;
836 let left_inset_amount = u16::from(is_replace && self.borders.contains(Borders::LEFT));
837 let top_inset_amount = u16::from(is_replace && self.borders.contains(Borders::TOP));
838 let right_inset_amount = u16::from(is_replace && self.borders.contains(Borders::RIGHT));
839 let bottom_inset_amount = u16::from(is_replace && self.borders.contains(Borders::BOTTOM));
840 let left_inset = left.saturating_add(left_inset_amount);
841 let top_inset = top.saturating_add(top_inset_amount);
842 let right_inset = right.saturating_sub(right_inset_amount);
843 let bottom_inset = bottom.saturating_sub(bottom_inset_amount);
844
845 let sides = [
846 (
847 Borders::LEFT,
848 left..=left,
849 top_inset..=bottom_inset,
850 self.border_set.vertical_left,
851 ),
852 (
853 Borders::TOP,
854 left_inset..=right_inset,
855 top..=top,
856 self.border_set.horizontal_top,
857 ),
858 (
859 Borders::RIGHT,
860 right..=right,
861 top_inset..=bottom_inset,
862 self.border_set.vertical_right,
863 ),
864 (
865 Borders::BOTTOM,
866 left_inset..=right_inset,
867 bottom..=bottom,
868 self.border_set.horizontal_bottom,
869 ),
870 ];
871 for (border, x_range, y_range, symbol) in sides {
872 if self.borders.contains(border) {
873 for x in x_range {
874 for y in y_range.clone() {
875 buf[(x, y)]
876 .merge_symbol(symbol, self.merge_borders)
877 .set_style(self.border_style);
878 }
879 }
880 }
881 }
882 }
883
884 fn render_corners(&self, area: Rect, buf: &mut Buffer) {
885 let corners = [
886 (
887 Borders::RIGHT | Borders::BOTTOM,
888 area.right().saturating_sub(1),
889 area.bottom().saturating_sub(1),
890 self.border_set.bottom_right,
891 ),
892 (
893 Borders::RIGHT | Borders::TOP,
894 area.right().saturating_sub(1),
895 area.top(),
896 self.border_set.top_right,
897 ),
898 (
899 Borders::LEFT | Borders::BOTTOM,
900 area.left(),
901 area.bottom().saturating_sub(1),
902 self.border_set.bottom_left,
903 ),
904 (
905 Borders::LEFT | Borders::TOP,
906 area.left(),
907 area.top(),
908 self.border_set.top_left,
909 ),
910 ];
911
912 for (border, x, y, symbol) in corners {
913 if self.borders.contains(border) {
914 buf[(x, y)]
915 .merge_symbol(symbol, self.merge_borders)
916 .set_style(self.border_style);
917 }
918 }
919 }
920 fn render_titles(&self, area: Rect, buf: &mut Buffer) {
921 self.render_title_position(TitlePosition::Top, area, buf);
922 self.render_title_position(TitlePosition::Bottom, area, buf);
923 }
924
925 fn render_title_position(&self, position: TitlePosition, area: Rect, buf: &mut Buffer) {
926 self.render_left_titles(position, area, buf);
928 self.render_center_titles(position, area, buf);
929 self.render_right_titles(position, area, buf);
930 }
931
932 #[expect(clippy::similar_names)]
939 fn render_right_titles(&self, position: TitlePosition, area: Rect, buf: &mut Buffer) {
940 let titles = self.filtered_titles(position, Alignment::Right);
941 let mut titles_area = self.titles_area(area, position);
942
943 for title in titles.rev() {
945 if titles_area.is_empty() {
946 break;
947 }
948 let title_width = Self::line_width_u16(title);
949 let title_area = Rect {
950 x: titles_area
951 .right()
952 .saturating_sub(title_width)
953 .max(titles_area.left()),
954 width: title_width.min(titles_area.width),
955 ..titles_area
956 };
957 buf.set_style(title_area, self.titles_style);
958 title.render(title_area, buf);
959
960 titles_area.width = titles_area
962 .width
963 .saturating_sub(title_width)
964 .saturating_sub(1); }
966 }
967
968 fn render_center_titles(&self, position: TitlePosition, area: Rect, buf: &mut Buffer) {
970 let area = self.titles_area(area, position);
971 let titles = self
972 .filtered_titles(position, Alignment::Center)
973 .collect_vec();
974 let total_width = titles
976 .iter()
977 .map(|title| Self::line_width_u16(title).saturating_add(1))
978 .fold(0, u16::saturating_add)
979 .saturating_sub(1);
980
981 if total_width <= area.width {
982 self.render_centered_titles_without_truncation(titles, total_width, area, buf);
983 } else {
984 self.render_centered_titles_with_truncation(titles, total_width, area, buf);
985 }
986 }
987
988 fn render_centered_titles_without_truncation(
989 &self,
990 titles: Vec<&Line<'_>>,
991 total_width: u16,
992 area: Rect,
993 buf: &mut Buffer,
994 ) {
995 let x = area
997 .left()
998 .saturating_add(area.width.saturating_sub(total_width) / 2);
999 let mut area = Rect { x, ..area };
1000 for title in titles {
1001 let width = Self::line_width_u16(title);
1002 let title_area = Rect { width, ..area };
1003 buf.set_style(title_area, self.titles_style);
1004 title.render(title_area, buf);
1005 let advance = width.saturating_add(1);
1007 area.x = area.x.saturating_add(advance);
1008 area.width = area.width.saturating_sub(advance);
1009 }
1010 }
1011
1012 fn render_centered_titles_with_truncation(
1013 &self,
1014 titles: Vec<&Line<'_>>,
1015 total_width: u16,
1016 mut area: Rect,
1017 buf: &mut Buffer,
1018 ) {
1019 let mut offset = total_width.saturating_sub(area.width) / 2;
1022 for title in titles {
1023 if area.is_empty() {
1024 break;
1025 }
1026 let width = area
1027 .width
1028 .min(Self::line_width_u16(title))
1029 .saturating_sub(offset);
1030 let title_area = Rect { width, ..area };
1031 buf.set_style(title_area, self.titles_style);
1032 if offset > 0 {
1033 title.clone().right_aligned().render(title_area, buf);
1035 offset = offset.saturating_sub(width).saturating_sub(1);
1036 } else {
1037 title.clone().left_aligned().render(title_area, buf);
1039 }
1040 let advance = width.saturating_add(1);
1042 area.x = area.x.saturating_add(advance);
1043 area.width = area.width.saturating_sub(advance);
1044 }
1045 }
1046
1047 #[expect(clippy::similar_names)]
1049 fn render_left_titles(&self, position: TitlePosition, area: Rect, buf: &mut Buffer) {
1050 let titles = self.filtered_titles(position, Alignment::Left);
1051 let mut titles_area = self.titles_area(area, position);
1052 for title in titles {
1053 if titles_area.is_empty() {
1054 break;
1055 }
1056 let title_width = Self::line_width_u16(title);
1057 let title_area = Rect {
1058 width: title_width.min(titles_area.width),
1059 ..titles_area
1060 };
1061 buf.set_style(title_area, self.titles_style);
1062 title.render(title_area, buf);
1063
1064 let advance = title_width.saturating_add(1);
1066 titles_area.x = titles_area.x.saturating_add(advance);
1067 titles_area.width = titles_area.width.saturating_sub(advance);
1068 }
1069 }
1070
1071 fn render_shadow(&self, base_area: Rect, buf: &mut Buffer) {
1072 if let Some(shadow) = &self.shadow {
1073 shadow.render(base_area, buf);
1074 }
1075 }
1076
1077 fn filtered_titles(
1079 &self,
1080 position: TitlePosition,
1081 alignment: Alignment,
1082 ) -> impl DoubleEndedIterator<Item = &Line<'_>> {
1083 self.titles
1084 .iter()
1085 .filter(move |(pos, _)| pos.unwrap_or(self.titles_position) == position)
1086 .filter(move |(_, line)| line.alignment.unwrap_or(self.titles_alignment) == alignment)
1087 .map(|(_, line)| line)
1088 }
1089
1090 fn line_width_u16(line: &Line<'_>) -> u16 {
1092 line.width().min(u16::MAX as usize) as u16
1093 }
1094
1095 fn titles_area(&self, area: Rect, position: TitlePosition) -> Rect {
1098 let left_border = u16::from(self.borders.contains(Borders::LEFT));
1099 let right_border = u16::from(self.borders.contains(Borders::RIGHT));
1100 Rect {
1101 x: area.left().saturating_add(left_border),
1102 y: match position {
1103 TitlePosition::Top => area.top(),
1104 TitlePosition::Bottom => area.bottom().saturating_sub(1),
1105 },
1106 width: area
1107 .width
1108 .saturating_sub(left_border)
1109 .saturating_sub(right_border),
1110 height: 1,
1111 }
1112 }
1113
1114 pub(crate) fn horizontal_space(&self) -> (u16, u16) {
1118 let left = self
1119 .padding
1120 .left
1121 .saturating_add(u16::from(self.borders.contains(Borders::LEFT)));
1122 let right = self
1123 .padding
1124 .right
1125 .saturating_add(u16::from(self.borders.contains(Borders::RIGHT)));
1126 (left, right)
1127 }
1128
1129 pub(crate) fn vertical_space(&self) -> (u16, u16) {
1134 let has_top =
1135 self.borders.contains(Borders::TOP) || self.has_title_at_position(TitlePosition::Top);
1136 let top = self.padding.top.saturating_add(u16::from(has_top));
1137 let has_bottom = self.borders.contains(Borders::BOTTOM)
1138 || self.has_title_at_position(TitlePosition::Bottom);
1139 let bottom = self.padding.bottom.saturating_add(u16::from(has_bottom));
1140 (top, bottom)
1141 }
1142}
1143
1144pub trait BlockExt {
1149 fn inner_if_some(&self, area: Rect) -> Rect;
1153}
1154
1155impl BlockExt for Option<Block<'_>> {
1156 fn inner_if_some(&self, area: Rect) -> Rect {
1157 self.as_ref().map_or(area, |block| block.inner(area))
1158 }
1159}
1160
1161impl Styled for Block<'_> {
1162 type Item = Self;
1163
1164 fn style(&self) -> Style {
1165 self.style
1166 }
1167
1168 fn set_style<S: Into<Style>>(self, style: S) -> Self::Item {
1169 self.style(style)
1170 }
1171}
1172
1173#[cfg(test)]
1174mod tests {
1175 use alloc::{format, vec};
1176
1177 use itertools::iproduct;
1178 use ratatui_core::layout::Offset;
1179 use ratatui_core::style::{Color, Modifier, Stylize};
1180 use rstest::rstest;
1181 use strum::ParseError;
1182
1183 use super::*;
1184
1185 #[test]
1186 fn create_with_all_borders() {
1187 let block = Block::bordered();
1188 assert_eq!(block.borders, Borders::all());
1189 }
1190
1191 #[rstest]
1192 #[case::none_0(Borders::NONE, Rect::ZERO, Rect::ZERO)]
1193 #[case::none_1(Borders::NONE, Rect::new(0, 0, 1, 1), Rect::new(0, 0, 1, 1))]
1194 #[case::left_0(Borders::LEFT, Rect::ZERO, Rect::ZERO)]
1195 #[case::left_w1(Borders::LEFT, Rect::new(0, 0, 0, 1), Rect::new(0, 0, 0, 1))]
1196 #[case::left_w2(Borders::LEFT, Rect::new(0, 0, 1, 1), Rect::new(1, 0, 0, 1))]
1197 #[case::left_w3(Borders::LEFT, Rect::new(0, 0, 2, 1), Rect::new(1, 0, 1, 1))]
1198 #[case::top_0(Borders::TOP, Rect::ZERO, Rect::ZERO)]
1199 #[case::top_h1(Borders::TOP, Rect::new(0, 0, 1, 0), Rect::new(0, 0, 1, 0))]
1200 #[case::top_h2(Borders::TOP, Rect::new(0, 0, 1, 1), Rect::new(0, 1, 1, 0))]
1201 #[case::top_h3(Borders::TOP, Rect::new(0, 0, 1, 2), Rect::new(0, 1, 1, 1))]
1202 #[case::right_0(Borders::RIGHT, Rect::ZERO, Rect::ZERO)]
1203 #[case::right_w1(Borders::RIGHT, Rect::new(0, 0, 0, 1), Rect::new(0, 0, 0, 1))]
1204 #[case::right_w2(Borders::RIGHT, Rect::new(0, 0, 1, 1), Rect::new(0, 0, 0, 1))]
1205 #[case::right_w3(Borders::RIGHT, Rect::new(0, 0, 2, 1), Rect::new(0, 0, 1, 1))]
1206 #[case::bottom_0(Borders::BOTTOM, Rect::ZERO, Rect::ZERO)]
1207 #[case::bottom_h1(Borders::BOTTOM, Rect::new(0, 0, 1, 0), Rect::new(0, 0, 1, 0))]
1208 #[case::bottom_h2(Borders::BOTTOM, Rect::new(0, 0, 1, 1), Rect::new(0, 0, 1, 0))]
1209 #[case::bottom_h3(Borders::BOTTOM, Rect::new(0, 0, 1, 2), Rect::new(0, 0, 1, 1))]
1210 #[case::all_0(Borders::ALL, Rect::ZERO, Rect::ZERO)]
1211 #[case::all_1(Borders::ALL, Rect::new(0, 0, 1, 1), Rect::new(1, 1, 0, 0))]
1212 #[case::all_2(Borders::ALL, Rect::new(0, 0, 2, 2), Rect::new(1, 1, 0, 0))]
1213 #[case::all_3(Borders::ALL, Rect::new(0, 0, 3, 3), Rect::new(1, 1, 1, 1))]
1214 fn inner_takes_into_account_the_borders(
1215 #[case] borders: Borders,
1216 #[case] area: Rect,
1217 #[case] expected: Rect,
1218 ) {
1219 let block = Block::new().borders(borders);
1220 assert_eq!(block.inner(area), expected);
1221 }
1222
1223 #[rstest]
1224 #[case::left(Alignment::Left)]
1225 #[case::center(Alignment::Center)]
1226 #[case::right(Alignment::Right)]
1227 fn inner_takes_into_account_the_title(#[case] alignment: Alignment) {
1228 let area = Rect::new(0, 0, 0, 1);
1229 let expected = Rect::new(0, 1, 0, 0);
1230
1231 let block = Block::new().title(Line::from("Test").alignment(alignment));
1232 assert_eq!(block.inner(area), expected);
1233 }
1234
1235 #[rstest]
1236 #[case::top_top(Block::new().title_top("Test").borders(Borders::TOP), Rect::new(0, 1, 0, 1))]
1237 #[case::top_bot(Block::new().title_top("Test").borders(Borders::BOTTOM), Rect::new(0, 1, 0, 0))]
1238 #[case::bot_top(Block::new().title_bottom("Test").borders(Borders::TOP), Rect::new(0, 1, 0, 0))]
1239 #[case::bot_bot(Block::new().title_bottom("Test").borders(Borders::BOTTOM), Rect::new(0, 0, 0, 1))]
1240 fn inner_takes_into_account_border_and_title(#[case] block: Block, #[case] expected: Rect) {
1241 let area = Rect::new(0, 0, 0, 2);
1242 assert_eq!(block.inner(area), expected);
1243 }
1244
1245 #[test]
1246 fn has_title_at_position_takes_into_account_all_positioning_declarations() {
1247 let block = Block::new();
1248 assert!(!block.has_title_at_position(TitlePosition::Top));
1249 assert!(!block.has_title_at_position(TitlePosition::Bottom));
1250
1251 let block = Block::new().title_top("test");
1252 assert!(block.has_title_at_position(TitlePosition::Top));
1253 assert!(!block.has_title_at_position(TitlePosition::Bottom));
1254
1255 let block = Block::new().title_bottom("test");
1256 assert!(!block.has_title_at_position(TitlePosition::Top));
1257 assert!(block.has_title_at_position(TitlePosition::Bottom));
1258
1259 let block = Block::new().title_top("test").title_bottom("test");
1260 assert!(block.has_title_at_position(TitlePosition::Top));
1261 assert!(block.has_title_at_position(TitlePosition::Bottom));
1262 }
1263
1264 #[rstest]
1265 #[case::none(Borders::NONE, (0, 0))]
1266 #[case::top(Borders::TOP, (1, 0))]
1267 #[case::right(Borders::RIGHT, (0, 0))]
1268 #[case::bottom(Borders::BOTTOM, (0, 1))]
1269 #[case::left(Borders::LEFT, (0, 0))]
1270 #[case::top_right(Borders::TOP | Borders::RIGHT, (1, 0))]
1271 #[case::top_bottom(Borders::TOP | Borders::BOTTOM, (1, 1))]
1272 #[case::top_left(Borders::TOP | Borders::LEFT, (1, 0))]
1273 #[case::bottom_right(Borders::BOTTOM | Borders::RIGHT, (0, 1))]
1274 #[case::bottom_left(Borders::BOTTOM | Borders::LEFT, (0, 1))]
1275 #[case::left_right(Borders::LEFT | Borders::RIGHT, (0, 0))]
1276 fn vertical_space_takes_into_account_borders(
1277 #[case] borders: Borders,
1278 #[case] vertical_space: (u16, u16),
1279 ) {
1280 let block = Block::new().borders(borders);
1281 assert_eq!(block.vertical_space(), vertical_space);
1282 }
1283
1284 #[rstest]
1285 #[case::top_border_top_p1(Borders::TOP, Padding::new(0, 0, 1, 0), (2, 0))]
1286 #[case::right_border_top_p1(Borders::RIGHT, Padding::new(0, 0, 1, 0), (1, 0))]
1287 #[case::bottom_border_top_p1(Borders::BOTTOM, Padding::new(0, 0, 1, 0), (1, 1))]
1288 #[case::left_border_top_p1(Borders::LEFT, Padding::new(0, 0, 1, 0), (1, 0))]
1289 #[case::top_bottom_border_all_p3(Borders::TOP | Borders::BOTTOM, Padding::new(100, 100, 4, 5), (5, 6))]
1290 #[case::no_border(Borders::NONE, Padding::new(100, 100, 10, 13), (10, 13))]
1291 #[case::all(Borders::ALL, Padding::new(100, 100, 1, 3), (2, 4))]
1292 fn vertical_space_takes_into_account_padding(
1293 #[case] borders: Borders,
1294 #[case] padding: Padding,
1295 #[case] vertical_space: (u16, u16),
1296 ) {
1297 let block = Block::new().borders(borders).padding(padding);
1298 assert_eq!(block.vertical_space(), vertical_space);
1299 }
1300
1301 #[test]
1302 fn vertical_space_takes_into_account_titles() {
1303 let block = Block::new().title_top("Test");
1304 assert_eq!(block.vertical_space(), (1, 0));
1305
1306 let block = Block::new().title_bottom("Test");
1307 assert_eq!(block.vertical_space(), (0, 1));
1308 }
1309
1310 #[rstest]
1311 #[case::top_border_top_title(Block::new(), Borders::TOP, TitlePosition::Top, (1, 0))]
1312 #[case::right_border_top_title(Block::new(), Borders::RIGHT, TitlePosition::Top, (1, 0))]
1313 #[case::bottom_border_top_title(Block::new(), Borders::BOTTOM, TitlePosition::Top, (1, 1))]
1314 #[case::left_border_top_title(Block::new(), Borders::LEFT, TitlePosition::Top, (1, 0))]
1315 #[case::top_border_top_title(Block::new(), Borders::TOP, TitlePosition::Bottom, (1, 1))]
1316 #[case::right_border_top_title(Block::new(), Borders::RIGHT, TitlePosition::Bottom, (0, 1))]
1317 #[case::bottom_border_top_title(Block::new(), Borders::BOTTOM, TitlePosition::Bottom, (0, 1))]
1318 #[case::left_border_top_title(Block::new(), Borders::LEFT, TitlePosition::Bottom, (0, 1))]
1319 fn vertical_space_takes_into_account_borders_and_title(
1320 #[case] block: Block,
1321 #[case] borders: Borders,
1322 #[case] pos: TitlePosition,
1323 #[case] vertical_space: (u16, u16),
1324 ) {
1325 let block = block.borders(borders).title_position(pos).title("Test");
1326 assert_eq!(block.vertical_space(), vertical_space);
1327 }
1328
1329 #[test]
1330 fn horizontal_space_takes_into_account_borders() {
1331 let block = Block::bordered();
1332 assert_eq!(block.horizontal_space(), (1, 1));
1333
1334 let block = Block::new().borders(Borders::LEFT);
1335 assert_eq!(block.horizontal_space(), (1, 0));
1336
1337 let block = Block::new().borders(Borders::RIGHT);
1338 assert_eq!(block.horizontal_space(), (0, 1));
1339 }
1340
1341 #[test]
1342 fn horizontal_space_takes_into_account_padding() {
1343 let block = Block::new().padding(Padding::new(1, 1, 100, 100));
1344 assert_eq!(block.horizontal_space(), (1, 1));
1345
1346 let block = Block::new().padding(Padding::new(3, 5, 0, 0));
1347 assert_eq!(block.horizontal_space(), (3, 5));
1348
1349 let block = Block::new().padding(Padding::new(0, 1, 100, 100));
1350 assert_eq!(block.horizontal_space(), (0, 1));
1351
1352 let block = Block::new().padding(Padding::new(1, 0, 100, 100));
1353 assert_eq!(block.horizontal_space(), (1, 0));
1354 }
1355
1356 #[rstest]
1357 #[case::all_bordered_all_padded(Block::bordered(), Padding::new(1, 1, 1, 1), (2, 2))]
1358 #[case::all_bordered_left_padded(Block::bordered(), Padding::new(1, 0, 0, 0), (2, 1))]
1359 #[case::all_bordered_right_padded(Block::bordered(), Padding::new(0, 1, 0, 0), (1, 2))]
1360 #[case::all_bordered_top_padded(Block::bordered(), Padding::new(0, 0, 1, 0), (1, 1))]
1361 #[case::all_bordered_bottom_padded(Block::bordered(), Padding::new(0, 0, 0, 1), (1, 1))]
1362 #[case::left_bordered_left_padded(Block::new().borders(Borders::LEFT), Padding::new(1, 0, 0, 0), (2, 0))]
1363 #[case::left_bordered_right_padded(Block::new().borders(Borders::LEFT), Padding::new(0, 1, 0, 0), (1, 1))]
1364 #[case::right_bordered_right_padded(Block::new().borders(Borders::RIGHT), Padding::new(0, 1, 0, 0), (0, 2))]
1365 #[case::right_bordered_left_padded(Block::new().borders(Borders::RIGHT), Padding::new(1, 0, 0, 0), (1, 1))]
1366 fn horizontal_space_takes_into_account_borders_and_padding(
1367 #[case] block: Block,
1368 #[case] padding: Padding,
1369 #[case] horizontal_space: (u16, u16),
1370 ) {
1371 let block = block.padding(padding);
1372 assert_eq!(block.horizontal_space(), horizontal_space);
1373 }
1374
1375 #[test]
1376 const fn border_type_can_be_const() {
1377 const _PLAIN: border::Set = BorderType::border_symbols(BorderType::Plain);
1378 }
1379
1380 #[test]
1381 fn block_new() {
1382 assert_eq!(
1383 Block::new(),
1384 Block {
1385 titles: Vec::new(),
1386 titles_style: Style::new(),
1387 titles_alignment: Alignment::Left,
1388 titles_position: TitlePosition::Top,
1389 borders: Borders::NONE,
1390 border_style: Style::new(),
1391 border_set: BorderType::Plain.to_border_set(),
1392 style: Style::new(),
1393 padding: Padding::ZERO,
1394 merge_borders: MergeStrategy::Replace,
1395 shadow: None,
1396 }
1397 );
1398 }
1399
1400 #[test]
1401 const fn block_can_be_const() {
1402 const _DEFAULT_STYLE: Style = Style::new();
1403 const _DEFAULT_PADDING: Padding = Padding::uniform(1);
1404 const _DEFAULT_BLOCK: Block = Block::bordered()
1405 .title_alignment(Alignment::Left)
1410 .title_position(TitlePosition::Top)
1411 .padding(_DEFAULT_PADDING);
1412 }
1413
1414 #[test]
1416 fn style_into_works_from_user_view() {
1417 let block = Block::new().style(Style::new().red());
1419 assert_eq!(block.style, Style::new().red());
1420
1421 let block = Block::new().style(Color::Red);
1423 assert_eq!(block.style, Style::new().red());
1424
1425 let block = Block::new().style((Color::Red, Color::Blue));
1427 assert_eq!(block.style, Style::new().red().on_blue());
1428
1429 let block = Block::new().style(Modifier::BOLD | Modifier::ITALIC);
1431 assert_eq!(block.style, Style::new().bold().italic());
1432
1433 let block = Block::new().style((Modifier::BOLD | Modifier::ITALIC, Modifier::DIM));
1435 assert_eq!(block.style, Style::new().bold().italic().not_dim());
1436
1437 let block = Block::new().style((Color::Red, Modifier::BOLD));
1439 assert_eq!(block.style, Style::new().red().bold());
1440
1441 let block = Block::new().style((Color::Red, Color::Blue, Modifier::BOLD));
1443 assert_eq!(block.style, Style::new().red().on_blue().bold());
1444
1445 let block = Block::new().style((
1447 Color::Red,
1448 Color::Blue,
1449 Modifier::BOLD | Modifier::ITALIC,
1450 Modifier::DIM,
1451 ));
1452 assert_eq!(
1453 block.style,
1454 Style::new().red().on_blue().bold().italic().not_dim()
1455 );
1456 }
1457
1458 #[test]
1459 fn can_be_stylized() {
1460 let block = Block::new().black().on_white().bold().not_dim();
1461 assert_eq!(
1462 block.style,
1463 Style::default()
1464 .fg(Color::Black)
1465 .bg(Color::White)
1466 .add_modifier(Modifier::BOLD)
1467 .remove_modifier(Modifier::DIM)
1468 );
1469 }
1470
1471 #[test]
1472 fn title_top_bottom() {
1473 let mut buffer = Buffer::empty(Rect::new(0, 0, 11, 3));
1474 Block::bordered()
1475 .title_top(Line::raw("A").left_aligned())
1476 .title_top(Line::raw("B").centered())
1477 .title_top(Line::raw("C").right_aligned())
1478 .title_bottom(Line::raw("D").left_aligned())
1479 .title_bottom(Line::raw("E").centered())
1480 .title_bottom(Line::raw("F").right_aligned())
1481 .render(buffer.area, &mut buffer);
1482 #[rustfmt::skip]
1483 let expected = Buffer::with_lines([
1484 "┌A───B───C┐",
1485 "│ │",
1486 "└D───E───F┘",
1487 ]);
1488 assert_eq!(buffer, expected);
1489 }
1490
1491 #[test]
1492 fn title_alignment() {
1493 let tests = vec![
1494 (Alignment::Left, "test "),
1495 (Alignment::Center, " test "),
1496 (Alignment::Right, " test"),
1497 ];
1498 for (alignment, expected) in tests {
1499 let mut buffer = Buffer::empty(Rect::new(0, 0, 8, 1));
1500 Block::new()
1501 .title_alignment(alignment)
1502 .title("test")
1503 .render(buffer.area, &mut buffer);
1504 assert_eq!(buffer, Buffer::with_lines([expected]));
1505 }
1506 }
1507
1508 #[test]
1509 fn title_alignment_overrides_block_title_alignment() {
1510 let tests = vec![
1511 (Alignment::Right, Alignment::Left, "test "),
1512 (Alignment::Left, Alignment::Center, " test "),
1513 (Alignment::Center, Alignment::Right, " test"),
1514 ];
1515 for (block_title_alignment, alignment, expected) in tests {
1516 let mut buffer = Buffer::empty(Rect::new(0, 0, 8, 1));
1517 Block::new()
1518 .title_alignment(block_title_alignment)
1519 .title(Line::from("test").alignment(alignment))
1520 .render(buffer.area, &mut buffer);
1521 assert_eq!(buffer, Buffer::with_lines([expected]));
1522 }
1523 }
1524
1525 #[test]
1527 fn render_right_aligned_empty_title() {
1528 let mut buffer = Buffer::empty(Rect::new(0, 0, 15, 3));
1529 Block::new()
1530 .title_alignment(Alignment::Right)
1531 .title("")
1532 .render(buffer.area, &mut buffer);
1533 assert_eq!(buffer, Buffer::with_lines([" "; 3]));
1534 }
1535
1536 #[test]
1537 fn title_position() {
1538 let mut buffer = Buffer::empty(Rect::new(0, 0, 4, 2));
1539 Block::new()
1540 .title_position(TitlePosition::Bottom)
1541 .title("test")
1542 .render(buffer.area, &mut buffer);
1543 assert_eq!(buffer, Buffer::with_lines([" ", "test"]));
1544 }
1545
1546 #[test]
1547 fn title_content_style() {
1548 for alignment in [Alignment::Left, Alignment::Center, Alignment::Right] {
1549 let mut buffer = Buffer::empty(Rect::new(0, 0, 4, 1));
1550 Block::new()
1551 .title_alignment(alignment)
1552 .title("test".yellow())
1553 .render(buffer.area, &mut buffer);
1554 assert_eq!(buffer, Buffer::with_lines(["test".yellow()]));
1555 }
1556 }
1557
1558 #[test]
1559 fn block_title_style() {
1560 for alignment in [Alignment::Left, Alignment::Center, Alignment::Right] {
1561 let mut buffer = Buffer::empty(Rect::new(0, 0, 4, 1));
1562 Block::new()
1563 .title_alignment(alignment)
1564 .title_style(Style::new().yellow())
1565 .title("test")
1566 .render(buffer.area, &mut buffer);
1567 assert_eq!(buffer, Buffer::with_lines(["test".yellow()]));
1568 }
1569 }
1570
1571 #[test]
1572 fn title_style_overrides_block_title_style() {
1573 for alignment in [Alignment::Left, Alignment::Center, Alignment::Right] {
1574 let mut buffer = Buffer::empty(Rect::new(0, 0, 4, 1));
1575 Block::new()
1576 .title_alignment(alignment)
1577 .title_style(Style::new().green().on_red())
1578 .title("test".yellow())
1579 .render(buffer.area, &mut buffer);
1580 assert_eq!(buffer, Buffer::with_lines(["test".yellow().on_red()]));
1581 }
1582 }
1583
1584 #[test]
1585 fn title_border_style() {
1586 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1587 Block::bordered()
1588 .title("test")
1589 .border_style(Style::new().yellow())
1590 .render(buffer.area, &mut buffer);
1591 #[rustfmt::skip]
1592 let mut expected = Buffer::with_lines([
1593 "┌test────┐",
1594 "│ │",
1595 "└────────┘",
1596 ]);
1597 expected.set_style(Rect::new(0, 0, 10, 3), Style::new().yellow());
1598 expected.set_style(Rect::new(1, 1, 8, 1), Style::reset());
1599 assert_eq!(buffer, expected);
1600 }
1601
1602 #[test]
1603 fn border_type_to_string() {
1604 assert_eq!(format!("{}", BorderType::Plain), "Plain");
1605 assert_eq!(format!("{}", BorderType::Rounded), "Rounded");
1606 assert_eq!(format!("{}", BorderType::Double), "Double");
1607 assert_eq!(format!("{}", BorderType::Thick), "Thick");
1608 assert_eq!(
1609 format!("{}", BorderType::LightDoubleDashed),
1610 "LightDoubleDashed"
1611 );
1612 assert_eq!(
1613 format!("{}", BorderType::HeavyDoubleDashed),
1614 "HeavyDoubleDashed"
1615 );
1616 assert_eq!(
1617 format!("{}", BorderType::LightTripleDashed),
1618 "LightTripleDashed"
1619 );
1620 assert_eq!(
1621 format!("{}", BorderType::HeavyTripleDashed),
1622 "HeavyTripleDashed"
1623 );
1624 assert_eq!(
1625 format!("{}", BorderType::LightQuadrupleDashed),
1626 "LightQuadrupleDashed"
1627 );
1628 assert_eq!(
1629 format!("{}", BorderType::HeavyQuadrupleDashed),
1630 "HeavyQuadrupleDashed"
1631 );
1632 }
1633
1634 #[test]
1635 fn border_type_from_str() {
1636 assert_eq!("Plain".parse(), Ok(BorderType::Plain));
1637 assert_eq!("Rounded".parse(), Ok(BorderType::Rounded));
1638 assert_eq!("Double".parse(), Ok(BorderType::Double));
1639 assert_eq!("Thick".parse(), Ok(BorderType::Thick));
1640 assert_eq!(
1641 "LightDoubleDashed".parse(),
1642 Ok(BorderType::LightDoubleDashed)
1643 );
1644 assert_eq!(
1645 "HeavyDoubleDashed".parse(),
1646 Ok(BorderType::HeavyDoubleDashed)
1647 );
1648 assert_eq!(
1649 "LightTripleDashed".parse(),
1650 Ok(BorderType::LightTripleDashed)
1651 );
1652 assert_eq!(
1653 "HeavyTripleDashed".parse(),
1654 Ok(BorderType::HeavyTripleDashed)
1655 );
1656 assert_eq!(
1657 "LightQuadrupleDashed".parse(),
1658 Ok(BorderType::LightQuadrupleDashed)
1659 );
1660 assert_eq!(
1661 "HeavyQuadrupleDashed".parse(),
1662 Ok(BorderType::HeavyQuadrupleDashed)
1663 );
1664 assert_eq!("".parse::<BorderType>(), Err(ParseError::VariantNotFound));
1665 }
1666
1667 #[test]
1668 fn render_plain_border() {
1669 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1670 Block::bordered()
1671 .border_type(BorderType::Plain)
1672 .render(buffer.area, &mut buffer);
1673 #[rustfmt::skip]
1674 let expected = Buffer::with_lines([
1675 "┌────────┐",
1676 "│ │",
1677 "└────────┘",
1678 ]);
1679 assert_eq!(buffer, expected);
1680 }
1681
1682 #[test]
1683 fn render_rounded_border() {
1684 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1685 Block::bordered()
1686 .border_type(BorderType::Rounded)
1687 .render(buffer.area, &mut buffer);
1688 #[rustfmt::skip]
1689 let expected = Buffer::with_lines([
1690 "╭────────╮",
1691 "│ │",
1692 "╰────────╯",
1693 ]);
1694 assert_eq!(buffer, expected);
1695 }
1696
1697 #[test]
1698 fn render_double_border() {
1699 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1700 Block::bordered()
1701 .border_type(BorderType::Double)
1702 .render(buffer.area, &mut buffer);
1703 #[rustfmt::skip]
1704 let expected = Buffer::with_lines([
1705 "╔════════╗",
1706 "║ ║",
1707 "╚════════╝",
1708 ]);
1709 assert_eq!(buffer, expected);
1710 }
1711
1712 #[test]
1713 fn render_quadrant_inside() {
1714 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1715 Block::bordered()
1716 .border_type(BorderType::QuadrantInside)
1717 .render(buffer.area, &mut buffer);
1718 #[rustfmt::skip]
1719 let expected = Buffer::with_lines([
1720 "▗▄▄▄▄▄▄▄▄▖",
1721 "▐ ▌",
1722 "▝▀▀▀▀▀▀▀▀▘",
1723 ]);
1724 assert_eq!(buffer, expected);
1725 }
1726
1727 #[test]
1728 fn render_border_quadrant_outside() {
1729 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1730 Block::bordered()
1731 .border_type(BorderType::QuadrantOutside)
1732 .render(buffer.area, &mut buffer);
1733 #[rustfmt::skip]
1734 let expected = Buffer::with_lines([
1735 "▛▀▀▀▀▀▀▀▀▜",
1736 "▌ ▐",
1737 "▙▄▄▄▄▄▄▄▄▟",
1738 ]);
1739 assert_eq!(buffer, expected);
1740 }
1741
1742 #[test]
1743 fn render_solid_border() {
1744 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1745 Block::bordered()
1746 .border_type(BorderType::Thick)
1747 .render(buffer.area, &mut buffer);
1748 #[rustfmt::skip]
1749 let expected = Buffer::with_lines([
1750 "┏━━━━━━━━┓",
1751 "┃ ┃",
1752 "┗━━━━━━━━┛",
1753 ]);
1754 assert_eq!(buffer, expected);
1755 }
1756
1757 #[test]
1758 fn render_light_double_dashed_border() {
1759 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1760 Block::bordered()
1761 .border_type(BorderType::LightDoubleDashed)
1762 .render(buffer.area, &mut buffer);
1763 #[rustfmt::skip]
1764 let expected = Buffer::with_lines([
1765 "┌╌╌╌╌╌╌╌╌┐",
1766 "╎ ╎",
1767 "└╌╌╌╌╌╌╌╌┘",
1768 ]);
1769 assert_eq!(buffer, expected);
1770 }
1771
1772 #[test]
1773 fn render_heavy_double_dashed_border() {
1774 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1775 Block::bordered()
1776 .border_type(BorderType::HeavyDoubleDashed)
1777 .render(buffer.area, &mut buffer);
1778 #[rustfmt::skip]
1779 let expected = Buffer::with_lines([
1780 "┏╍╍╍╍╍╍╍╍┓",
1781 "╏ ╏",
1782 "┗╍╍╍╍╍╍╍╍┛",
1783 ]);
1784 assert_eq!(buffer, expected);
1785 }
1786
1787 #[test]
1788 fn render_light_triple_dashed_border() {
1789 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1790 Block::bordered()
1791 .border_type(BorderType::LightTripleDashed)
1792 .render(buffer.area, &mut buffer);
1793 #[rustfmt::skip]
1794 let expected = Buffer::with_lines([
1795 "┌┄┄┄┄┄┄┄┄┐",
1796 "┆ ┆",
1797 "└┄┄┄┄┄┄┄┄┘",
1798 ]);
1799 assert_eq!(buffer, expected);
1800 }
1801
1802 #[test]
1803 fn render_heavy_triple_dashed_border() {
1804 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1805 Block::bordered()
1806 .border_type(BorderType::HeavyTripleDashed)
1807 .render(buffer.area, &mut buffer);
1808 #[rustfmt::skip]
1809 let expected = Buffer::with_lines([
1810 "┏┅┅┅┅┅┅┅┅┓",
1811 "┇ ┇",
1812 "┗┅┅┅┅┅┅┅┅┛",
1813 ]);
1814 assert_eq!(buffer, expected);
1815 }
1816
1817 #[test]
1818 fn render_light_quadruple_dashed_border() {
1819 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1820 Block::bordered()
1821 .border_type(BorderType::LightQuadrupleDashed)
1822 .render(buffer.area, &mut buffer);
1823 #[rustfmt::skip]
1824 let expected = Buffer::with_lines([
1825 "┌┈┈┈┈┈┈┈┈┐",
1826 "┊ ┊",
1827 "└┈┈┈┈┈┈┈┈┘",
1828 ]);
1829 assert_eq!(buffer, expected);
1830 }
1831
1832 #[test]
1833 fn render_heavy_quadruple_dashed_border() {
1834 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1835 Block::bordered()
1836 .border_type(BorderType::HeavyQuadrupleDashed)
1837 .render(buffer.area, &mut buffer);
1838 #[rustfmt::skip]
1839 let expected = Buffer::with_lines([
1840 "┏┉┉┉┉┉┉┉┉┓",
1841 "┋ ┋",
1842 "┗┉┉┉┉┉┉┉┉┛",
1843 ]);
1844 assert_eq!(buffer, expected);
1845 }
1846
1847 #[test]
1848 fn render_custom_border_set() {
1849 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1850 Block::bordered()
1851 .border_set(border::Set {
1852 top_left: "1",
1853 top_right: "2",
1854 bottom_left: "3",
1855 bottom_right: "4",
1856 vertical_left: "L",
1857 vertical_right: "R",
1858 horizontal_top: "T",
1859 horizontal_bottom: "B",
1860 })
1861 .render(buffer.area, &mut buffer);
1862 #[rustfmt::skip]
1863 let expected = Buffer::with_lines([
1864 "1TTTTTTTT2",
1865 "L R",
1866 "3BBBBBBBB4",
1867 ]);
1868 assert_eq!(buffer, expected);
1869 }
1870
1871 #[rstest]
1872 #[case::replace(MergeStrategy::Replace)]
1873 #[case::exact(MergeStrategy::Exact)]
1874 #[case::fuzzy(MergeStrategy::Fuzzy)]
1875 fn render_partial_borders(#[case] strategy: MergeStrategy) {
1876 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1877 Block::new()
1878 .borders(Borders::TOP | Borders::LEFT | Borders::RIGHT | Borders::BOTTOM)
1879 .merge_borders(strategy)
1880 .render(buffer.area, &mut buffer);
1881 #[rustfmt::skip]
1882 let expected = Buffer::with_lines([
1883 "┌────────┐",
1884 "│ │",
1885 "└────────┘",
1886 ]);
1887 assert_eq!(buffer, expected);
1888
1889 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1890 Block::new()
1891 .borders(Borders::TOP | Borders::LEFT)
1892 .merge_borders(strategy)
1893 .render(buffer.area, &mut buffer);
1894 #[rustfmt::skip]
1895 let expected = Buffer::with_lines([
1896 "┌─────────",
1897 "│ ",
1898 "│ ",
1899 ]);
1900 assert_eq!(buffer, expected);
1901
1902 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1903 Block::new()
1904 .borders(Borders::TOP | Borders::RIGHT)
1905 .merge_borders(strategy)
1906 .render(buffer.area, &mut buffer);
1907 #[rustfmt::skip]
1908 let expected = Buffer::with_lines([
1909 "─────────┐",
1910 " │",
1911 " │",
1912 ]);
1913 assert_eq!(buffer, expected);
1914
1915 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1916 Block::new()
1917 .borders(Borders::BOTTOM | Borders::LEFT)
1918 .merge_borders(strategy)
1919 .render(buffer.area, &mut buffer);
1920 #[rustfmt::skip]
1921 let expected = Buffer::with_lines([
1922 "│ ",
1923 "│ ",
1924 "└─────────",
1925 ]);
1926 assert_eq!(buffer, expected);
1927
1928 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1929 Block::new()
1930 .borders(Borders::BOTTOM | Borders::RIGHT)
1931 .merge_borders(strategy)
1932 .render(buffer.area, &mut buffer);
1933 #[rustfmt::skip]
1934 let expected = Buffer::with_lines([
1935 " │",
1936 " │",
1937 "─────────┘",
1938 ]);
1939 assert_eq!(buffer, expected);
1940
1941 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1942 Block::new()
1943 .borders(Borders::TOP | Borders::BOTTOM)
1944 .merge_borders(strategy)
1945 .render(buffer.area, &mut buffer);
1946 #[rustfmt::skip]
1947 let expected = Buffer::with_lines([
1948 "──────────",
1949 " ",
1950 "──────────",
1951 ]);
1952 assert_eq!(buffer, expected);
1953
1954 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
1955 Block::new()
1956 .borders(Borders::LEFT | Borders::RIGHT)
1957 .merge_borders(strategy)
1958 .render(buffer.area, &mut buffer);
1959 #[rustfmt::skip]
1960 let expected = Buffer::with_lines([
1961 "│ │",
1962 "│ │",
1963 "│ │",
1964 ]);
1965 assert_eq!(buffer, expected);
1966 }
1967
1968 #[rstest]
1975 #[case::replace(MergeStrategy::Replace, include_str!("../tests/block/merge_replace.txt"))]
1976 #[case::exact(MergeStrategy::Exact, include_str!("../tests/block/merge_exact.txt"))]
1977 #[case::fuzzy(MergeStrategy::Fuzzy, include_str!("../tests/block/merge_fuzzy.txt"))]
1978 fn render_merged_borders(#[case] strategy: MergeStrategy, #[case] expected: &'static str) {
1979 let border_types = [
1980 BorderType::Plain,
1981 BorderType::Rounded,
1982 BorderType::Thick,
1983 BorderType::Double,
1984 BorderType::LightDoubleDashed,
1985 BorderType::HeavyDoubleDashed,
1986 BorderType::LightTripleDashed,
1987 BorderType::HeavyTripleDashed,
1988 BorderType::LightQuadrupleDashed,
1989 BorderType::HeavyQuadrupleDashed,
1990 ];
1991 let rects = [
1992 (Rect::new(0, 0, 5, 5), Rect::new(4, 4, 5, 5)),
1994 (Rect::new(10, 0, 5, 5), Rect::new(12, 2, 5, 5)),
1996 (Rect::new(18, 0, 5, 5), Rect::new(22, 0, 5, 5)),
1998 (Rect::new(28, 0, 5, 5), Rect::new(28, 4, 5, 5)),
2000 ];
2001
2002 let mut buffer = Buffer::empty(Rect::new(0, 0, 43, 1000));
2003
2004 let mut offset = Offset::ZERO;
2005 for (border_type_1, border_type_2) in iproduct!(border_types, border_types) {
2006 let title = format!("{border_type_1} + {border_type_2}");
2007 let title_area = Rect::new(0, 0, 43, 1) + offset;
2008 title.render(title_area, &mut buffer);
2009 offset.y += 1;
2010 for (rect_1, rect_2) in rects {
2011 Block::bordered()
2012 .border_type(border_type_1)
2013 .merge_borders(strategy)
2014 .render(rect_1 + offset, &mut buffer);
2015 Block::bordered()
2016 .border_type(border_type_2)
2017 .merge_borders(strategy)
2018 .render(rect_2 + offset, &mut buffer);
2019 }
2020 offset.y += 9;
2021 }
2022 pretty_assertions::assert_eq!(Buffer::with_lines(expected.lines()), buffer);
2023 }
2024
2025 #[rstest]
2026 #[case::replace(MergeStrategy::Replace, Buffer::with_lines([
2027 "┏block top━━┓",
2028 "┃ ┃",
2029 "┗━━━━━━━━━━━┛",
2030 "│ │",
2031 "└───────────┘",
2032 ])
2033 )]
2034 #[case::replace(MergeStrategy::Exact, Buffer::with_lines([
2035 "┏block top━━┓",
2036 "┃ ┃",
2037 "┡block btm━━┩",
2038 "│ │",
2039 "└───────────┘",
2040 ])
2041 )]
2042 #[case::replace(MergeStrategy::Fuzzy, Buffer::with_lines([
2043 "┏block top━━┓",
2044 "┃ ┃",
2045 "┡block btm━━┩",
2046 "│ │",
2047 "└───────────┘",
2048 ])
2049 )]
2050 fn merged_titles_bottom_first(#[case] strategy: MergeStrategy, #[case] expected: Buffer) {
2051 let mut buffer = Buffer::empty(Rect::new(0, 0, 13, 5));
2052 Block::bordered()
2053 .title("block btm")
2054 .render(Rect::new(0, 2, 13, 3), &mut buffer);
2055 Block::bordered()
2056 .title("block top")
2057 .border_type(BorderType::Thick)
2058 .merge_borders(strategy)
2059 .render(Rect::new(0, 0, 13, 3), &mut buffer);
2060 assert_eq!(buffer, expected);
2061 }
2062
2063 #[rstest]
2064 #[case::replace(MergeStrategy::Replace, Buffer::with_lines([
2065 "┏block top━━┓",
2066 "┃ ┃",
2067 "┌block btm──┐",
2068 "│ │",
2069 "└───────────┘",
2070 ])
2071 )]
2072 #[case::replace(MergeStrategy::Exact, Buffer::with_lines([
2073 "┏block top━━┓",
2074 "┃ ┃",
2075 "┞block btm──┦",
2076 "│ │",
2077 "└───────────┘",
2078 ])
2079 )]
2080 #[case::replace(MergeStrategy::Fuzzy, Buffer::with_lines([
2081 "┏block top━━┓",
2082 "┃ ┃",
2083 "┞block btm──┦",
2084 "│ │",
2085 "└───────────┘",
2086 ])
2087 )]
2088 fn merged_titles_top_first(#[case] strategy: MergeStrategy, #[case] expected: Buffer) {
2089 let mut buffer = Buffer::empty(Rect::new(0, 0, 13, 5));
2090 Block::bordered()
2091 .title("block top")
2092 .border_type(BorderType::Thick)
2093 .render(Rect::new(0, 0, 13, 3), &mut buffer);
2094 Block::bordered()
2095 .title("block btm")
2096 .merge_borders(strategy)
2097 .render(Rect::new(0, 2, 13, 3), &mut buffer);
2098 assert_eq!(buffer, expected);
2099 }
2100
2101 #[test]
2102 fn left_titles() {
2103 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 1));
2104 Block::new()
2105 .title("L12")
2106 .title("L34")
2107 .render(buffer.area, &mut buffer);
2108 assert_eq!(buffer, Buffer::with_lines(["L12 L34 "]));
2109 }
2110
2111 #[test]
2112 fn left_titles_truncated() {
2113 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 1));
2114 Block::new()
2115 .title("L12345")
2116 .title("L67890")
2117 .render(buffer.area, &mut buffer);
2118 assert_eq!(buffer, Buffer::with_lines(["L12345 L67"]));
2119 }
2120
2121 #[test]
2122 fn center_titles() {
2123 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 1));
2124 Block::new()
2125 .title(Line::from("C12").centered())
2126 .title(Line::from("C34").centered())
2127 .render(buffer.area, &mut buffer);
2128 assert_eq!(buffer, Buffer::with_lines([" C12 C34 "]));
2129 }
2130
2131 #[test]
2132 fn center_titles_truncated() {
2133 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 1));
2134 Block::new()
2135 .title(Line::from("C12345").centered())
2136 .title(Line::from("C67890").centered())
2137 .render(buffer.area, &mut buffer);
2138 assert_eq!(buffer, Buffer::with_lines(["12345 C678"]));
2139 }
2140
2141 #[test]
2142 fn right_titles() {
2143 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 1));
2144 Block::new()
2145 .title(Line::from("R12").right_aligned())
2146 .title(Line::from("R34").right_aligned())
2147 .render(buffer.area, &mut buffer);
2148 assert_eq!(buffer, Buffer::with_lines([" R12 R34"]));
2149 }
2150
2151 #[test]
2152 fn right_titles_truncated() {
2153 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 1));
2154 Block::new()
2155 .title(Line::from("R12345").right_aligned())
2156 .title(Line::from("R67890").right_aligned())
2157 .render(buffer.area, &mut buffer);
2158 assert_eq!(buffer, Buffer::with_lines(["345 R67890"]));
2159 }
2160
2161 #[test]
2162 fn center_title_truncates_left_title() {
2163 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 1));
2164 Block::new()
2165 .title("L1234")
2166 .title(Line::from("C5678").centered())
2167 .render(buffer.area, &mut buffer);
2168 assert_eq!(buffer, Buffer::with_lines(["L1C5678 "]));
2169 }
2170
2171 #[test]
2172 fn right_title_truncates_left_title() {
2173 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 1));
2174 Block::new()
2175 .title("L12345")
2176 .title(Line::from("R67890").right_aligned())
2177 .render(buffer.area, &mut buffer);
2178 assert_eq!(buffer, Buffer::with_lines(["L123R67890"]));
2179 }
2180
2181 #[test]
2182 fn right_title_truncates_center_title() {
2183 let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 1));
2184 Block::new()
2185 .title(Line::from("C12345").centered())
2186 .title(Line::from("R67890").right_aligned())
2187 .render(buffer.area, &mut buffer);
2188 assert_eq!(buffer, Buffer::with_lines([" C1R67890"]));
2189 }
2190
2191 #[test]
2192 fn render_in_minimal_buffer() {
2193 let mut buffer = Buffer::empty(Rect::new(0, 0, 1, 1));
2194 Block::bordered()
2196 .title("I'm too big for this buffer")
2197 .padding(Padding::uniform(10))
2198 .render(buffer.area, &mut buffer);
2199 assert_eq!(buffer, Buffer::with_lines(["┌"]));
2200 }
2201
2202 #[test]
2203 fn render_in_zero_size_buffer() {
2204 let mut buffer = Buffer::empty(Rect::ZERO);
2205 Block::bordered()
2207 .title("I'm too big for this buffer")
2208 .padding(Padding::uniform(10))
2209 .render(buffer.area, &mut buffer);
2210 }
2211
2212 #[cfg(debug_assertions)]
2222 mod regression {
2223 use super::*;
2224
2225 #[rstest]
2227 #[case(Padding::new(u16::MAX, 1, 0, 0), Rect::new(u16::MAX, 0, 0, 1))]
2228 #[case(Padding::new(0, 0, u16::MAX, 1), Rect::new(0, u16::MAX, 1, 0))]
2229 fn inner_saturates_when_padding_sum_overflows(
2230 #[case] padding: Padding,
2231 #[case] expected: Rect,
2232 ) {
2233 let block = Block::new().padding(padding);
2234 assert_eq!(block.inner(Rect::new(0, 0, 1, 1)), expected);
2235 }
2236
2237 #[test]
2239 fn render_sides_handles_empty_area_without_panicking() {
2240 let block = Block::bordered();
2241 let mut buffer = Buffer::empty(Rect::new(0, 0, 1, 1));
2242 block.render_sides(Rect::ZERO, &mut buffer);
2243 assert_eq!(buffer, Buffer::with_lines(["─"]));
2244 }
2245
2246 #[test]
2248 fn render_corners_handles_empty_area_without_panicking() {
2249 let block = Block::bordered();
2250 let mut buffer = Buffer::empty(Rect::new(0, 0, 1, 1));
2251 block.render_corners(Rect::ZERO, &mut buffer);
2252 assert_eq!(buffer, Buffer::with_lines(["┌"]));
2253 }
2254
2255 #[rstest]
2257 #[case::exact(MergeStrategy::Exact)]
2258 #[case::fuzzy(MergeStrategy::Fuzzy)]
2259 fn render_merged_borders_in_minimal_buffer_does_not_panic(#[case] strategy: MergeStrategy) {
2260 let mut buffer = Buffer::empty(Rect::new(0, 0, 1, 1));
2261 Block::bordered()
2262 .merge_borders(strategy)
2263 .render(buffer.area, &mut buffer);
2264 assert_eq!(buffer, Buffer::with_lines(["┼"]));
2265 }
2266
2267 #[test]
2269 fn render_center_titles_handles_title_width_increment_overflow() {
2270 let block = Block::new().title(Line::from("a".repeat(u16::MAX as usize)).centered());
2271 let mut buffer = Buffer::empty(Rect::new(0, 0, 1, 1));
2272 block.render_center_titles(TitlePosition::Top, Rect::new(0, 0, 1, 1), &mut buffer);
2273 assert_eq!(buffer, Buffer::with_lines([" "]));
2274 }
2275
2276 #[test]
2278 fn render_center_titles_handles_total_width_overflow() {
2279 let block = Block::new()
2280 .title(Line::from("a".repeat(40_000)).centered())
2281 .title(Line::from("b".repeat(30_000)).centered());
2282 let mut buffer = Buffer::empty(Rect::new(0, 0, 1, 1));
2283 block.render_center_titles(TitlePosition::Top, Rect::new(0, 0, 1, 1), &mut buffer);
2284 assert_eq!(buffer, Buffer::with_lines([" "]));
2285 }
2286
2287 #[test]
2289 fn render_centered_titles_without_truncation_handles_maximum_x() {
2290 let block = Block::new();
2291 let mut buffer = Buffer::empty(Rect::new(0, 0, 1, 1));
2292 block.render_centered_titles_without_truncation(
2293 Vec::new(),
2294 0,
2295 Rect::new(u16::MAX - 1, 0, 1, 1),
2296 &mut buffer,
2297 );
2298 assert_eq!(buffer, Buffer::with_lines([" "]));
2299 }
2300
2301 #[test]
2303 fn render_centered_titles_without_truncation_handles_title_advance_overflow() {
2304 let block = Block::new();
2305 let title = Line::from("a".repeat(u16::MAX as usize)).centered();
2306 let mut buffer = Buffer::empty(Rect::new(0, 0, 1, 1));
2307 block.render_centered_titles_without_truncation(
2308 vec![&title],
2309 u16::MAX,
2310 Rect::new(0, 0, 1, 1),
2311 &mut buffer,
2312 );
2313 assert_eq!(buffer, Buffer::with_lines(["a"]));
2314 }
2315
2316 #[test]
2318 fn render_centered_titles_with_truncation_handles_title_advance_overflow() {
2319 let block = Block::new();
2320 let title = Line::from("a".repeat(u16::MAX as usize)).centered();
2321 let mut buffer = Buffer::empty(Rect::new(0, 0, 1, 1));
2322 block.render_centered_titles_with_truncation(
2323 vec![&title],
2324 u16::MAX,
2325 Rect::new(0, 0, u16::MAX, 1),
2326 &mut buffer,
2327 );
2328 assert_eq!(buffer, Buffer::with_lines(["a"]));
2329 }
2330
2331 #[test]
2333 fn render_left_titles_handles_title_advance_overflow() {
2334 let block = Block::new().title("a".repeat(u16::MAX as usize));
2335 let mut buffer = Buffer::empty(Rect::new(0, 0, 1, 1));
2336 block.render_left_titles(TitlePosition::Top, Rect::new(0, 0, 1, 1), &mut buffer);
2337 assert_eq!(buffer, Buffer::with_lines(["a"]));
2338 }
2339
2340 #[test]
2342 fn titles_area_saturates_when_left_border_offset_overflows() {
2343 let block = Block::new().borders(Borders::LEFT);
2344 assert_eq!(
2345 block.titles_area(Rect::new(u16::MAX, 0, 1, 1), TitlePosition::Top),
2346 Rect::new(u16::MAX, 0, 1, 1)
2347 );
2348 }
2349
2350 #[test]
2352 fn titles_area_handles_empty_area_without_panicking() {
2353 let block = Block::new();
2354 assert_eq!(
2355 block.titles_area(Rect::ZERO, TitlePosition::Bottom),
2356 Rect::new(0, 0, 0, 1)
2357 );
2358 }
2359
2360 #[rstest]
2362 #[case(Block::new().padding(Padding::new(0, 0, u16::MAX, 0)).title_top("T"), (u16::MAX, 0))]
2363 #[case(Block::new().padding(Padding::new(0, 0, 0, u16::MAX)).title_bottom("T"), (0, u16::MAX))]
2364 fn vertical_space_saturates_when_space_overflows(
2365 #[case] block: Block,
2366 #[case] expected: (u16, u16),
2367 ) {
2368 assert_eq!(block.vertical_space(), expected);
2369 }
2370 }
2371}