1mod color;
7mod theme;
8pub use color::{Color, ColorDepth};
9pub use theme::{Spacing, Theme, ThemeBuilder, ThemeColor};
10
11#[non_exhaustive]
16#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
17pub enum Breakpoint {
18 Xs,
20 Sm,
22 Md,
24 Lg,
26 Xl,
28}
29
30#[non_exhaustive]
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
36#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
37pub enum Border {
38 Single,
40 Double,
42 Rounded,
44 Thick,
46 Dashed,
48 DashedThick,
50}
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
57pub struct BorderChars {
58 pub tl: char,
60 pub tr: char,
62 pub bl: char,
64 pub br: char,
66 pub h: char,
68 pub v: char,
70}
71
72#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
74#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
75pub struct BorderSides {
76 pub top: bool,
78 pub right: bool,
80 pub bottom: bool,
82 pub left: bool,
84}
85
86impl BorderSides {
87 pub const fn all() -> Self {
89 Self {
90 top: true,
91 right: true,
92 bottom: true,
93 left: true,
94 }
95 }
96
97 pub const fn none() -> Self {
99 Self {
100 top: false,
101 right: false,
102 bottom: false,
103 left: false,
104 }
105 }
106
107 pub const fn horizontal() -> Self {
109 Self {
110 top: true,
111 right: false,
112 bottom: true,
113 left: false,
114 }
115 }
116
117 pub const fn vertical() -> Self {
119 Self {
120 top: false,
121 right: true,
122 bottom: false,
123 left: true,
124 }
125 }
126
127 pub fn has_horizontal(&self) -> bool {
129 self.top || self.bottom
130 }
131
132 pub fn has_vertical(&self) -> bool {
134 self.left || self.right
135 }
136}
137
138impl Default for BorderSides {
139 fn default() -> Self {
140 Self::all()
141 }
142}
143
144impl Border {
145 pub const fn chars(self) -> BorderChars {
147 match self {
148 Self::Single => BorderChars {
149 tl: '┌',
150 tr: '┐',
151 bl: '└',
152 br: '┘',
153 h: '─',
154 v: '│',
155 },
156 Self::Double => BorderChars {
157 tl: '╔',
158 tr: '╗',
159 bl: '╚',
160 br: '╝',
161 h: '═',
162 v: '║',
163 },
164 Self::Rounded => BorderChars {
165 tl: '╭',
166 tr: '╮',
167 bl: '╰',
168 br: '╯',
169 h: '─',
170 v: '│',
171 },
172 Self::Thick => BorderChars {
173 tl: '┏',
174 tr: '┓',
175 bl: '┗',
176 br: '┛',
177 h: '━',
178 v: '┃',
179 },
180 Self::Dashed => BorderChars {
181 tl: '┌',
182 tr: '┐',
183 bl: '└',
184 br: '┘',
185 h: '┄',
186 v: '┆',
187 },
188 Self::DashedThick => BorderChars {
189 tl: '┏',
190 tr: '┓',
191 bl: '┗',
192 br: '┛',
193 h: '┅',
194 v: '┇',
195 },
196 }
197 }
198}
199
200#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
205#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
206pub struct Padding {
207 pub top: u32,
209 pub right: u32,
211 pub bottom: u32,
213 pub left: u32,
215}
216
217impl Padding {
218 pub const fn all(v: u32) -> Self {
220 Self::new(v, v, v, v)
221 }
222
223 pub const fn xy(x: u32, y: u32) -> Self {
225 Self::new(y, x, y, x)
226 }
227
228 pub const fn new(top: u32, right: u32, bottom: u32, left: u32) -> Self {
230 Self {
231 top,
232 right,
233 bottom,
234 left,
235 }
236 }
237
238 pub const fn horizontal(self) -> u32 {
240 self.left + self.right
241 }
242
243 pub const fn vertical(self) -> u32 {
245 self.top + self.bottom
246 }
247}
248
249#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
254#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
255pub struct Margin {
256 pub top: u32,
258 pub right: u32,
260 pub bottom: u32,
262 pub left: u32,
264}
265
266impl Margin {
267 pub const fn all(v: u32) -> Self {
269 Self::new(v, v, v, v)
270 }
271
272 pub const fn xy(x: u32, y: u32) -> Self {
274 Self::new(y, x, y, x)
275 }
276
277 pub const fn new(top: u32, right: u32, bottom: u32, left: u32) -> Self {
279 Self {
280 top,
281 right,
282 bottom,
283 left,
284 }
285 }
286
287 pub const fn horizontal(self) -> u32 {
289 self.left + self.right
290 }
291
292 pub const fn vertical(self) -> u32 {
294 self.top + self.bottom
295 }
296}
297
298#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
311#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
312#[must_use = "configure constraints using the returned value"]
313pub struct Constraints {
314 pub min_width: Option<u32>,
316 pub max_width: Option<u32>,
318 pub min_height: Option<u32>,
320 pub max_height: Option<u32>,
322 pub width_pct: Option<u8>,
324 pub height_pct: Option<u8>,
326}
327
328impl Constraints {
329 pub const fn min_w(mut self, min_width: u32) -> Self {
331 self.min_width = Some(min_width);
332 self
333 }
334
335 pub const fn max_w(mut self, max_width: u32) -> Self {
337 self.max_width = Some(max_width);
338 self
339 }
340
341 pub const fn min_h(mut self, min_height: u32) -> Self {
343 self.min_height = Some(min_height);
344 self
345 }
346
347 pub const fn max_h(mut self, max_height: u32) -> Self {
349 self.max_height = Some(max_height);
350 self
351 }
352
353 pub const fn w_pct(mut self, pct: u8) -> Self {
355 self.width_pct = Some(pct);
356 self
357 }
358
359 pub const fn h_pct(mut self, pct: u8) -> Self {
361 self.height_pct = Some(pct);
362 self
363 }
364}
365
366#[non_exhaustive]
372#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
373#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
374pub enum Align {
375 #[default]
377 Start,
378 Center,
380 End,
382}
383
384#[non_exhaustive]
393#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
394#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
395pub enum Justify {
396 #[default]
398 Start,
399 Center,
401 End,
403 SpaceBetween,
405 SpaceAround,
407 SpaceEvenly,
409}
410
411#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
416#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
417#[cfg_attr(feature = "serde", serde(transparent))]
418pub struct Modifiers(pub u8);
419
420impl Modifiers {
421 pub const NONE: Self = Self(0);
423 pub const BOLD: Self = Self(1 << 0);
425 pub const DIM: Self = Self(1 << 1);
427 pub const ITALIC: Self = Self(1 << 2);
429 pub const UNDERLINE: Self = Self(1 << 3);
431 pub const REVERSED: Self = Self(1 << 4);
433 pub const STRIKETHROUGH: Self = Self(1 << 5);
435
436 #[inline]
438 pub fn contains(self, other: Self) -> bool {
439 (self.0 & other.0) == other.0
440 }
441
442 #[inline]
444 pub fn insert(&mut self, other: Self) {
445 self.0 |= other.0;
446 }
447
448 #[inline]
450 pub fn is_empty(self) -> bool {
451 self.0 == 0
452 }
453}
454
455impl std::ops::BitOr for Modifiers {
456 type Output = Self;
457 #[inline]
458 fn bitor(self, rhs: Self) -> Self {
459 Self(self.0 | rhs.0)
460 }
461}
462
463impl std::ops::BitOrAssign for Modifiers {
464 #[inline]
465 fn bitor_assign(&mut self, rhs: Self) {
466 self.0 |= rhs.0;
467 }
468}
469
470#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
484#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
485#[must_use = "build and pass the returned Style value"]
486pub struct Style {
487 pub fg: Option<Color>,
489 pub bg: Option<Color>,
491 pub modifiers: Modifiers,
493}
494
495impl Style {
496 pub const fn new() -> Self {
498 Self {
499 fg: None,
500 bg: None,
501 modifiers: Modifiers::NONE,
502 }
503 }
504
505 pub const fn fg(mut self, color: Color) -> Self {
507 self.fg = Some(color);
508 self
509 }
510
511 pub const fn bg(mut self, color: Color) -> Self {
513 self.bg = Some(color);
514 self
515 }
516
517 pub fn bold(mut self) -> Self {
519 self.modifiers |= Modifiers::BOLD;
520 self
521 }
522
523 pub fn dim(mut self) -> Self {
525 self.modifiers |= Modifiers::DIM;
526 self
527 }
528
529 pub fn italic(mut self) -> Self {
531 self.modifiers |= Modifiers::ITALIC;
532 self
533 }
534
535 pub fn underline(mut self) -> Self {
537 self.modifiers |= Modifiers::UNDERLINE;
538 self
539 }
540
541 pub fn reversed(mut self) -> Self {
543 self.modifiers |= Modifiers::REVERSED;
544 self
545 }
546
547 pub fn strikethrough(mut self) -> Self {
549 self.modifiers |= Modifiers::STRIKETHROUGH;
550 self
551 }
552}
553
554#[derive(Debug, Clone, Copy, Default)]
578pub struct ContainerStyle {
579 pub border: Option<Border>,
581 pub border_sides: Option<BorderSides>,
583 pub border_style: Option<Style>,
585 pub bg: Option<Color>,
587 pub text_color: Option<Color>,
589 pub dark_bg: Option<Color>,
591 pub dark_border_style: Option<Style>,
593 pub padding: Option<Padding>,
595 pub margin: Option<Margin>,
597 pub gap: Option<u32>,
599 pub row_gap: Option<u32>,
601 pub col_gap: Option<u32>,
603 pub grow: Option<u16>,
605 pub align: Option<Align>,
607 pub align_self: Option<Align>,
609 pub justify: Option<Justify>,
611 pub w: Option<u32>,
613 pub h: Option<u32>,
615 pub min_w: Option<u32>,
617 pub max_w: Option<u32>,
619 pub min_h: Option<u32>,
621 pub max_h: Option<u32>,
623 pub w_pct: Option<u8>,
625 pub h_pct: Option<u8>,
627 pub theme_bg: Option<ThemeColor>,
629 pub theme_text_color: Option<ThemeColor>,
631 pub theme_border_fg: Option<ThemeColor>,
634 pub extends: Option<&'static ContainerStyle>,
639}
640
641impl ContainerStyle {
642 pub const fn new() -> Self {
644 Self {
645 border: None,
646 border_sides: None,
647 border_style: None,
648 bg: None,
649 text_color: None,
650 dark_bg: None,
651 dark_border_style: None,
652 padding: None,
653 margin: None,
654 gap: None,
655 row_gap: None,
656 col_gap: None,
657 grow: None,
658 align: None,
659 align_self: None,
660 justify: None,
661 w: None,
662 h: None,
663 min_w: None,
664 max_w: None,
665 min_h: None,
666 max_h: None,
667 w_pct: None,
668 h_pct: None,
669 theme_bg: None,
670 theme_text_color: None,
671 theme_border_fg: None,
672 extends: None,
673 }
674 }
675
676 pub const fn extending(base: &'static ContainerStyle) -> Self {
694 let mut s = Self::new();
695 s.extends = Some(base);
696 s
697 }
698
699 pub const fn border(mut self, border: Border) -> Self {
701 self.border = Some(border);
702 self
703 }
704
705 pub const fn border_sides(mut self, sides: BorderSides) -> Self {
707 self.border_sides = Some(sides);
708 self
709 }
710
711 pub const fn bg(mut self, color: Color) -> Self {
713 self.bg = Some(color);
714 self
715 }
716
717 pub const fn text_color(mut self, color: Color) -> Self {
719 self.text_color = Some(color);
720 self
721 }
722
723 pub const fn dark_bg(mut self, color: Color) -> Self {
725 self.dark_bg = Some(color);
726 self
727 }
728
729 pub const fn p(mut self, value: u32) -> Self {
731 self.padding = Some(Padding {
732 top: value,
733 bottom: value,
734 left: value,
735 right: value,
736 });
737 self
738 }
739
740 pub const fn px(mut self, value: u32) -> Self {
742 let p = match self.padding {
743 Some(p) => Padding {
744 left: value,
745 right: value,
746 ..p
747 },
748 None => Padding {
749 top: 0,
750 bottom: 0,
751 left: value,
752 right: value,
753 },
754 };
755 self.padding = Some(p);
756 self
757 }
758
759 pub const fn py(mut self, value: u32) -> Self {
761 let p = match self.padding {
762 Some(p) => Padding {
763 top: value,
764 bottom: value,
765 ..p
766 },
767 None => Padding {
768 top: value,
769 bottom: value,
770 left: 0,
771 right: 0,
772 },
773 };
774 self.padding = Some(p);
775 self
776 }
777
778 pub const fn m(mut self, value: u32) -> Self {
780 self.margin = Some(Margin {
781 top: value,
782 bottom: value,
783 left: value,
784 right: value,
785 });
786 self
787 }
788
789 pub const fn gap(mut self, value: u32) -> Self {
791 self.gap = Some(value);
792 self
793 }
794
795 pub const fn row_gap(mut self, value: u32) -> Self {
797 self.row_gap = Some(value);
798 self
799 }
800
801 pub const fn col_gap(mut self, value: u32) -> Self {
803 self.col_gap = Some(value);
804 self
805 }
806
807 pub const fn grow(mut self, value: u16) -> Self {
809 self.grow = Some(value);
810 self
811 }
812
813 pub const fn w(mut self, value: u32) -> Self {
815 self.w = Some(value);
816 self
817 }
818
819 pub const fn h(mut self, value: u32) -> Self {
821 self.h = Some(value);
822 self
823 }
824
825 pub const fn min_w(mut self, value: u32) -> Self {
827 self.min_w = Some(value);
828 self
829 }
830
831 pub const fn max_w(mut self, value: u32) -> Self {
833 self.max_w = Some(value);
834 self
835 }
836
837 pub const fn align(mut self, value: Align) -> Self {
839 self.align = Some(value);
840 self
841 }
842
843 pub const fn align_self(mut self, value: Align) -> Self {
845 self.align_self = Some(value);
846 self
847 }
848
849 pub const fn justify(mut self, value: Justify) -> Self {
851 self.justify = Some(value);
852 self
853 }
854
855 pub const fn min_h(mut self, value: u32) -> Self {
857 self.min_h = Some(value);
858 self
859 }
860
861 pub const fn max_h(mut self, value: u32) -> Self {
863 self.max_h = Some(value);
864 self
865 }
866
867 pub const fn w_pct(mut self, value: u8) -> Self {
869 self.w_pct = Some(value);
870 self
871 }
872
873 pub const fn h_pct(mut self, value: u8) -> Self {
875 self.h_pct = Some(value);
876 self
877 }
878
879 pub const fn theme_bg(mut self, color: ThemeColor) -> Self {
884 self.theme_bg = Some(color);
885 self
886 }
887
888 pub const fn theme_text_color(mut self, color: ThemeColor) -> Self {
892 self.theme_text_color = Some(color);
893 self
894 }
895
896 pub const fn theme_border_fg(mut self, color: ThemeColor) -> Self {
900 self.theme_border_fg = Some(color);
901 self
902 }
903}
904
905#[derive(Debug, Clone, Copy, Default)]
906#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
907pub struct WidgetColors {
912 pub fg: Option<Color>,
914 pub bg: Option<Color>,
916 pub border: Option<Color>,
918 pub accent: Option<Color>,
920 pub theme_fg: Option<ThemeColor>,
922 pub theme_bg: Option<ThemeColor>,
924 pub theme_border: Option<ThemeColor>,
926 pub theme_accent: Option<ThemeColor>,
928}
929
930impl WidgetColors {
931 pub const fn new() -> Self {
933 Self {
934 fg: None,
935 bg: None,
936 border: None,
937 accent: None,
938 theme_fg: None,
939 theme_bg: None,
940 theme_border: None,
941 theme_accent: None,
942 }
943 }
944
945 pub const fn fg(mut self, color: Color) -> Self {
947 self.fg = Some(color);
948 self
949 }
950
951 pub const fn bg(mut self, color: Color) -> Self {
953 self.bg = Some(color);
954 self
955 }
956
957 pub const fn border(mut self, color: Color) -> Self {
959 self.border = Some(color);
960 self
961 }
962
963 pub const fn accent(mut self, color: Color) -> Self {
965 self.accent = Some(color);
966 self
967 }
968
969 pub const fn theme_fg(mut self, color: ThemeColor) -> Self {
971 self.theme_fg = Some(color);
972 self
973 }
974
975 pub const fn theme_bg(mut self, color: ThemeColor) -> Self {
977 self.theme_bg = Some(color);
978 self
979 }
980
981 pub const fn theme_border(mut self, color: ThemeColor) -> Self {
983 self.theme_border = Some(color);
984 self
985 }
986
987 pub const fn theme_accent(mut self, color: ThemeColor) -> Self {
989 self.theme_accent = Some(color);
990 self
991 }
992
993 pub fn resolve_fg(&self, theme: &Theme, fallback: Color) -> Color {
995 self.theme_fg
996 .map(|tc| theme.resolve(tc))
997 .or(self.fg)
998 .unwrap_or(fallback)
999 }
1000
1001 pub fn resolve_bg(&self, theme: &Theme, fallback: Color) -> Color {
1003 self.theme_bg
1004 .map(|tc| theme.resolve(tc))
1005 .or(self.bg)
1006 .unwrap_or(fallback)
1007 }
1008
1009 pub fn resolve_border(&self, theme: &Theme, fallback: Color) -> Color {
1011 self.theme_border
1012 .map(|tc| theme.resolve(tc))
1013 .or(self.border)
1014 .unwrap_or(fallback)
1015 }
1016
1017 pub fn resolve_accent(&self, theme: &Theme, fallback: Color) -> Color {
1019 self.theme_accent
1020 .map(|tc| theme.resolve(tc))
1021 .or(self.accent)
1022 .unwrap_or(fallback)
1023 }
1024}
1025
1026#[derive(Debug, Clone, Copy, Default)]
1041pub struct WidgetTheme {
1042 pub button: WidgetColors,
1044 pub table: WidgetColors,
1046 pub list: WidgetColors,
1048 pub tabs: WidgetColors,
1050 pub select: WidgetColors,
1052 pub radio: WidgetColors,
1054 pub checkbox: WidgetColors,
1056 pub toggle: WidgetColors,
1058 pub text_input: WidgetColors,
1060}
1061
1062impl WidgetTheme {
1063 pub const fn new() -> Self {
1065 Self {
1066 button: WidgetColors::new(),
1067 table: WidgetColors::new(),
1068 list: WidgetColors::new(),
1069 tabs: WidgetColors::new(),
1070 select: WidgetColors::new(),
1071 radio: WidgetColors::new(),
1072 checkbox: WidgetColors::new(),
1073 toggle: WidgetColors::new(),
1074 text_input: WidgetColors::new(),
1075 }
1076 }
1077
1078 pub const fn button(mut self, colors: WidgetColors) -> Self {
1080 self.button = colors;
1081 self
1082 }
1083
1084 pub const fn table(mut self, colors: WidgetColors) -> Self {
1086 self.table = colors;
1087 self
1088 }
1089
1090 pub const fn list(mut self, colors: WidgetColors) -> Self {
1092 self.list = colors;
1093 self
1094 }
1095
1096 pub const fn tabs(mut self, colors: WidgetColors) -> Self {
1098 self.tabs = colors;
1099 self
1100 }
1101
1102 pub const fn select(mut self, colors: WidgetColors) -> Self {
1104 self.select = colors;
1105 self
1106 }
1107
1108 pub const fn radio(mut self, colors: WidgetColors) -> Self {
1110 self.radio = colors;
1111 self
1112 }
1113
1114 pub const fn checkbox(mut self, colors: WidgetColors) -> Self {
1116 self.checkbox = colors;
1117 self
1118 }
1119
1120 pub const fn toggle(mut self, colors: WidgetColors) -> Self {
1122 self.toggle = colors;
1123 self
1124 }
1125
1126 pub const fn text_input(mut self, colors: WidgetColors) -> Self {
1128 self.text_input = colors;
1129 self
1130 }
1131}
1132
1133#[cfg(test)]
1134mod tests {
1135 use super::*;
1136
1137 #[test]
1138 fn style_new_is_default() {
1139 let style = Style::new();
1140 assert_eq!(style.fg, None);
1141 assert_eq!(style.bg, None);
1142 assert_eq!(style.modifiers, Modifiers::NONE);
1143 assert_eq!(style, Style::default());
1144 }
1145
1146 #[test]
1147 fn style_bold_and_fg_set_expected_fields() {
1148 let style = Style::new().bold().fg(Color::Red);
1149 assert_eq!(style.fg, Some(Color::Red));
1150 assert_eq!(style.bg, None);
1151 assert!(style.modifiers.contains(Modifiers::BOLD));
1152 }
1153
1154 #[test]
1155 fn style_multiple_modifiers_accumulate() {
1156 let style = Style::new().italic().underline().dim();
1157 assert!(style.modifiers.contains(Modifiers::ITALIC));
1158 assert!(style.modifiers.contains(Modifiers::UNDERLINE));
1159 assert!(style.modifiers.contains(Modifiers::DIM));
1160 }
1161
1162 #[test]
1163 fn style_repeated_fg_overrides_previous_color() {
1164 let style = Style::new().fg(Color::Blue).fg(Color::Green);
1165 assert_eq!(style.fg, Some(Color::Green));
1166 }
1167
1168 #[test]
1169 fn style_repeated_bg_overrides_previous_color() {
1170 let style = Style::new().bg(Color::Blue).bg(Color::Green);
1171 assert_eq!(style.bg, Some(Color::Green));
1172 }
1173
1174 #[test]
1175 fn style_override_preserves_existing_modifiers() {
1176 let style = Style::new().bold().fg(Color::Red).fg(Color::Yellow);
1177 assert_eq!(style.fg, Some(Color::Yellow));
1178 assert!(style.modifiers.contains(Modifiers::BOLD));
1179 }
1180
1181 #[test]
1182 fn padding_all_sets_all_sides() {
1183 let p = Padding::all(3);
1184 assert_eq!(p.top, 3);
1185 assert_eq!(p.right, 3);
1186 assert_eq!(p.bottom, 3);
1187 assert_eq!(p.left, 3);
1188 }
1189
1190 #[test]
1191 fn padding_xy_sets_axis_values() {
1192 let p = Padding::xy(4, 2);
1193 assert_eq!(p.top, 2);
1194 assert_eq!(p.bottom, 2);
1195 assert_eq!(p.left, 4);
1196 assert_eq!(p.right, 4);
1197 }
1198
1199 #[test]
1200 fn padding_new_and_totals_are_correct() {
1201 let p = Padding::new(1, 2, 3, 4);
1202 assert_eq!(p.top, 1);
1203 assert_eq!(p.right, 2);
1204 assert_eq!(p.bottom, 3);
1205 assert_eq!(p.left, 4);
1206 assert_eq!(p.horizontal(), 6);
1207 assert_eq!(p.vertical(), 4);
1208 }
1209
1210 #[test]
1211 fn margin_all_and_xy_are_correct() {
1212 let all = Margin::all(5);
1213 assert_eq!(all, Margin::new(5, 5, 5, 5));
1214
1215 let xy = Margin::xy(7, 1);
1216 assert_eq!(xy.top, 1);
1217 assert_eq!(xy.bottom, 1);
1218 assert_eq!(xy.left, 7);
1219 assert_eq!(xy.right, 7);
1220 }
1221
1222 #[test]
1223 fn margin_new_and_totals_are_correct() {
1224 let m = Margin::new(2, 4, 6, 8);
1225 assert_eq!(m.horizontal(), 12);
1226 assert_eq!(m.vertical(), 8);
1227 }
1228
1229 #[test]
1230 fn constraints_min_max_builder_sets_values() {
1231 let c = Constraints::default()
1232 .min_w(10)
1233 .max_w(40)
1234 .min_h(5)
1235 .max_h(20);
1236 assert_eq!(c.min_width, Some(10));
1237 assert_eq!(c.max_width, Some(40));
1238 assert_eq!(c.min_height, Some(5));
1239 assert_eq!(c.max_height, Some(20));
1240 }
1241
1242 #[test]
1243 fn constraints_percentage_builder_sets_values() {
1244 let c = Constraints::default().w_pct(50).h_pct(80);
1245 assert_eq!(c.width_pct, Some(50));
1246 assert_eq!(c.height_pct, Some(80));
1247 }
1248
1249 #[test]
1250 fn border_sides_all_has_both_axes() {
1251 let sides = BorderSides::all();
1252 assert!(sides.top && sides.right && sides.bottom && sides.left);
1253 assert!(sides.has_horizontal());
1254 assert!(sides.has_vertical());
1255 }
1256
1257 #[test]
1258 fn border_sides_none_has_no_axes() {
1259 let sides = BorderSides::none();
1260 assert!(!sides.top && !sides.right && !sides.bottom && !sides.left);
1261 assert!(!sides.has_horizontal());
1262 assert!(!sides.has_vertical());
1263 }
1264
1265 #[test]
1266 fn border_sides_horizontal_only() {
1267 let sides = BorderSides::horizontal();
1268 assert!(sides.top);
1269 assert!(sides.bottom);
1270 assert!(!sides.left);
1271 assert!(!sides.right);
1272 assert!(sides.has_horizontal());
1273 assert!(!sides.has_vertical());
1274 }
1275
1276 #[test]
1277 fn border_sides_vertical_only() {
1278 let sides = BorderSides::vertical();
1279 assert!(!sides.top);
1280 assert!(!sides.bottom);
1281 assert!(sides.left);
1282 assert!(sides.right);
1283 assert!(!sides.has_horizontal());
1284 assert!(sides.has_vertical());
1285 }
1286
1287 #[test]
1288 fn container_style_new_is_empty() {
1289 let s = ContainerStyle::new();
1290 assert_eq!(s.border, None);
1291 assert_eq!(s.bg, None);
1292 assert_eq!(s.padding, None);
1293 assert_eq!(s.margin, None);
1294 assert_eq!(s.gap, None);
1295 assert_eq!(s.align, None);
1296 assert_eq!(s.justify, None);
1297 }
1298
1299 #[test]
1300 fn container_style_const_construction_and_fields() {
1301 const CARD: ContainerStyle = ContainerStyle::new()
1302 .border(Border::Rounded)
1303 .border_sides(BorderSides::horizontal())
1304 .p(2)
1305 .m(1)
1306 .gap(3)
1307 .align(Align::Center)
1308 .justify(Justify::SpaceBetween)
1309 .w(60)
1310 .h(20);
1311
1312 assert_eq!(CARD.border, Some(Border::Rounded));
1313 assert_eq!(CARD.border_sides, Some(BorderSides::horizontal()));
1314 assert_eq!(CARD.padding, Some(Padding::all(2)));
1315 assert_eq!(CARD.margin, Some(Margin::all(1)));
1316 assert_eq!(CARD.gap, Some(3));
1317 assert_eq!(CARD.align, Some(Align::Center));
1318 assert_eq!(CARD.justify, Some(Justify::SpaceBetween));
1319 assert_eq!(CARD.w, Some(60));
1320 assert_eq!(CARD.h, Some(20));
1321 }
1322
1323 #[test]
1324 fn widget_colors_new_is_empty() {
1325 let colors = WidgetColors::new();
1326 assert_eq!(colors.fg, None);
1327 assert_eq!(colors.bg, None);
1328 assert_eq!(colors.border, None);
1329 assert_eq!(colors.accent, None);
1330
1331 let defaults = WidgetColors::default();
1332 assert_eq!(defaults.fg, None);
1333 assert_eq!(defaults.bg, None);
1334 assert_eq!(defaults.border, None);
1335 assert_eq!(defaults.accent, None);
1336 }
1337
1338 #[test]
1339 fn widget_colors_builder_sets_all_fields() {
1340 let colors = WidgetColors::new()
1341 .fg(Color::White)
1342 .bg(Color::Black)
1343 .border(Color::Cyan)
1344 .accent(Color::Yellow);
1345
1346 assert_eq!(colors.fg, Some(Color::White));
1347 assert_eq!(colors.bg, Some(Color::Black));
1348 assert_eq!(colors.border, Some(Color::Cyan));
1349 assert_eq!(colors.accent, Some(Color::Yellow));
1350 }
1351
1352 #[test]
1353 fn align_default_is_start() {
1354 assert_eq!(Align::default(), Align::Start);
1355 }
1356
1357 #[test]
1358 fn justify_default_is_start() {
1359 assert_eq!(Justify::default(), Justify::Start);
1360 }
1361
1362 #[test]
1363 fn align_and_justify_variants_are_distinct() {
1364 assert_ne!(Align::Start, Align::Center);
1365 assert_ne!(Align::Center, Align::End);
1366
1367 assert_ne!(Justify::Start, Justify::Center);
1368 assert_ne!(Justify::Center, Justify::End);
1369 assert_ne!(Justify::SpaceBetween, Justify::SpaceAround);
1370 assert_ne!(Justify::SpaceAround, Justify::SpaceEvenly);
1371 }
1372}