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)]
327#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
328#[non_exhaustive]
329pub enum WidthSpec {
330 Auto,
332 Fixed(u32),
334 Pct(u8),
336 Ratio(u16, u16),
342 MinMax {
354 min: u32,
356 max: u32,
358 },
359}
360
361impl Default for WidthSpec {
362 #[inline]
363 fn default() -> Self {
364 Self::Auto
365 }
366}
367
368#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
373#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
374#[non_exhaustive]
375pub enum HeightSpec {
376 Auto,
378 Fixed(u32),
380 Pct(u8),
382 Ratio(u16, u16),
388 MinMax {
391 min: u32,
393 max: u32,
395 },
396}
397
398impl Default for HeightSpec {
399 #[inline]
400 fn default() -> Self {
401 Self::Auto
402 }
403}
404
405#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
419#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
420#[must_use = "configure constraints using the returned value"]
421pub struct Constraints {
422 pub width: WidthSpec,
424 pub height: HeightSpec,
426}
427
428const _ASSERT_CONSTRAINTS_SIZE: () = assert!(
435 std::mem::size_of::<Constraints>() == 24,
436 "Constraints must be 24 bytes"
437);
438
439impl Constraints {
440 pub const fn min_w(mut self, min_width: u32) -> Self {
448 let max = match self.width {
449 WidthSpec::MinMax { max, .. } => max,
450 WidthSpec::Fixed(v) => v,
451 _ => u32::MAX,
452 };
453 self.width = WidthSpec::MinMax {
454 min: min_width,
455 max,
456 };
457 self
458 }
459
460 pub const fn max_w(mut self, max_width: u32) -> Self {
466 let min = match self.width {
467 WidthSpec::MinMax { min, .. } => min,
468 WidthSpec::Fixed(v) => v,
469 _ => 0,
470 };
471 self.width = WidthSpec::MinMax {
472 min,
473 max: max_width,
474 };
475 self
476 }
477
478 pub const fn min_h(mut self, min_height: u32) -> Self {
484 let max = match self.height {
485 HeightSpec::MinMax { max, .. } => max,
486 HeightSpec::Fixed(v) => v,
487 _ => u32::MAX,
488 };
489 self.height = HeightSpec::MinMax {
490 min: min_height,
491 max,
492 };
493 self
494 }
495
496 pub const fn max_h(mut self, max_height: u32) -> Self {
502 let min = match self.height {
503 HeightSpec::MinMax { min, .. } => min,
504 HeightSpec::Fixed(v) => v,
505 _ => 0,
506 };
507 self.height = HeightSpec::MinMax {
508 min,
509 max: max_height,
510 };
511 self
512 }
513
514 pub const fn w_minmax(mut self, min: u32, max: u32) -> Self {
519 self.width = WidthSpec::MinMax { min, max };
520 self
521 }
522
523 pub const fn h_minmax(mut self, min: u32, max: u32) -> Self {
525 self.height = HeightSpec::MinMax { min, max };
526 self
527 }
528
529 pub const fn w(mut self, width: u32) -> Self {
531 self.width = WidthSpec::Fixed(width);
532 self
533 }
534
535 pub const fn h(mut self, height: u32) -> Self {
537 self.height = HeightSpec::Fixed(height);
538 self
539 }
540
541 pub const fn w_pct(mut self, pct: u8) -> Self {
543 self.width = WidthSpec::Pct(pct);
544 self
545 }
546
547 pub const fn h_pct(mut self, pct: u8) -> Self {
549 self.height = HeightSpec::Pct(pct);
550 self
551 }
552
553 pub const fn w_ratio(mut self, num: u16, den: u16) -> Self {
558 self.width = WidthSpec::Ratio(num, den);
559 self
560 }
561
562 pub const fn h_ratio(mut self, num: u16, den: u16) -> Self {
566 self.height = HeightSpec::Ratio(num, den);
567 self
568 }
569
570 pub const fn min_width(&self) -> Option<u32> {
580 match self.width {
581 WidthSpec::Fixed(v) => Some(v),
582 WidthSpec::MinMax { min, .. } if min > 0 => Some(min),
583 _ => None,
584 }
585 }
586
587 pub const fn max_width(&self) -> Option<u32> {
593 match self.width {
594 WidthSpec::Fixed(v) => Some(v),
595 WidthSpec::MinMax { max, .. } if max < u32::MAX => Some(max),
596 _ => None,
597 }
598 }
599
600 pub const fn min_height(&self) -> Option<u32> {
604 match self.height {
605 HeightSpec::Fixed(v) => Some(v),
606 HeightSpec::MinMax { min, .. } if min > 0 => Some(min),
607 _ => None,
608 }
609 }
610
611 pub const fn max_height(&self) -> Option<u32> {
615 match self.height {
616 HeightSpec::Fixed(v) => Some(v),
617 HeightSpec::MinMax { max, .. } if max < u32::MAX => Some(max),
618 _ => None,
619 }
620 }
621
622 pub const fn width_pct(&self) -> Option<u8> {
624 match self.width {
625 WidthSpec::Pct(p) => Some(p),
626 _ => None,
627 }
628 }
629
630 pub const fn height_pct(&self) -> Option<u8> {
632 match self.height {
633 HeightSpec::Pct(p) => Some(p),
634 _ => None,
635 }
636 }
637
638 pub fn set_min_width(&mut self, value: Option<u32>) {
662 let max = match self.width {
663 WidthSpec::MinMax { max, .. } => max,
664 WidthSpec::Fixed(v) => v,
665 _ => u32::MAX,
666 };
667 let min = value.unwrap_or(0);
668 self.width = if min == 0 && max == u32::MAX {
669 WidthSpec::Auto
670 } else {
671 WidthSpec::MinMax { min, max }
672 };
673 }
674
675 pub fn set_max_width(&mut self, value: Option<u32>) {
677 let min = match self.width {
678 WidthSpec::MinMax { min, .. } => min,
679 WidthSpec::Fixed(v) => v,
680 _ => 0,
681 };
682 let max = value.unwrap_or(u32::MAX);
683 self.width = if min == 0 && max == u32::MAX {
684 WidthSpec::Auto
685 } else {
686 WidthSpec::MinMax { min, max }
687 };
688 }
689
690 pub fn set_min_height(&mut self, value: Option<u32>) {
692 let max = match self.height {
693 HeightSpec::MinMax { max, .. } => max,
694 HeightSpec::Fixed(v) => v,
695 _ => u32::MAX,
696 };
697 let min = value.unwrap_or(0);
698 self.height = if min == 0 && max == u32::MAX {
699 HeightSpec::Auto
700 } else {
701 HeightSpec::MinMax { min, max }
702 };
703 }
704
705 pub fn set_max_height(&mut self, value: Option<u32>) {
707 let min = match self.height {
708 HeightSpec::MinMax { min, .. } => min,
709 HeightSpec::Fixed(v) => v,
710 _ => 0,
711 };
712 let max = value.unwrap_or(u32::MAX);
713 self.height = if min == 0 && max == u32::MAX {
714 HeightSpec::Auto
715 } else {
716 HeightSpec::MinMax { min, max }
717 };
718 }
719
720 pub fn set_width_pct(&mut self, value: Option<u8>) {
722 self.width = match value {
723 Some(p) => WidthSpec::Pct(p),
724 None => WidthSpec::Auto,
725 };
726 }
727
728 pub fn set_height_pct(&mut self, value: Option<u8>) {
730 self.height = match value {
731 Some(p) => HeightSpec::Pct(p),
732 None => HeightSpec::Auto,
733 };
734 }
735}
736
737#[non_exhaustive]
743#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
744#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
745pub enum Align {
746 #[default]
753 Start,
754 Center,
756 End,
758}
759
760#[non_exhaustive]
769#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
770#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
771pub enum Justify {
772 #[default]
774 Start,
775 Center,
777 End,
779 SpaceBetween,
781 SpaceAround,
783 SpaceEvenly,
785}
786
787#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
792#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
793#[cfg_attr(feature = "serde", serde(transparent))]
794pub struct Modifiers(pub u8);
795
796impl Modifiers {
797 pub const NONE: Self = Self(0);
799 pub const BOLD: Self = Self(1 << 0);
801 pub const DIM: Self = Self(1 << 1);
803 pub const ITALIC: Self = Self(1 << 2);
805 pub const UNDERLINE: Self = Self(1 << 3);
807 pub const REVERSED: Self = Self(1 << 4);
809 pub const STRIKETHROUGH: Self = Self(1 << 5);
811
812 #[inline]
814 pub fn contains(self, other: Self) -> bool {
815 (self.0 & other.0) == other.0
816 }
817
818 #[inline]
820 pub fn insert(&mut self, other: Self) {
821 self.0 |= other.0;
822 }
823
824 #[inline]
837 pub fn remove(&mut self, other: Self) {
838 self.0 &= !other.0;
839 }
840
841 #[inline]
843 pub fn is_empty(self) -> bool {
844 self.0 == 0
845 }
846}
847
848impl std::ops::BitOr for Modifiers {
849 type Output = Self;
850 #[inline]
851 fn bitor(self, rhs: Self) -> Self {
852 Self(self.0 | rhs.0)
853 }
854}
855
856impl std::ops::BitOrAssign for Modifiers {
857 #[inline]
858 fn bitor_assign(&mut self, rhs: Self) {
859 self.0 |= rhs.0;
860 }
861}
862
863#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
877#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
878#[must_use = "build and pass the returned Style value"]
879pub struct Style {
880 pub fg: Option<Color>,
882 pub bg: Option<Color>,
884 pub modifiers: Modifiers,
886}
887
888impl Style {
889 pub const fn new() -> Self {
891 Self {
892 fg: None,
893 bg: None,
894 modifiers: Modifiers::NONE,
895 }
896 }
897
898 pub const fn fg(mut self, color: Color) -> Self {
900 self.fg = Some(color);
901 self
902 }
903
904 pub const fn bg(mut self, color: Color) -> Self {
906 self.bg = Some(color);
907 self
908 }
909
910 pub fn bold(mut self) -> Self {
912 self.modifiers |= Modifiers::BOLD;
913 self
914 }
915
916 pub fn dim(mut self) -> Self {
918 self.modifiers |= Modifiers::DIM;
919 self
920 }
921
922 pub fn italic(mut self) -> Self {
924 self.modifiers |= Modifiers::ITALIC;
925 self
926 }
927
928 pub fn underline(mut self) -> Self {
930 self.modifiers |= Modifiers::UNDERLINE;
931 self
932 }
933
934 pub fn reversed(mut self) -> Self {
936 self.modifiers |= Modifiers::REVERSED;
937 self
938 }
939
940 pub fn strikethrough(mut self) -> Self {
942 self.modifiers |= Modifiers::STRIKETHROUGH;
943 self
944 }
945}
946
947#[derive(Debug, Clone, Copy, Default)]
971pub struct ContainerStyle {
972 pub border: Option<Border>,
974 pub border_sides: Option<BorderSides>,
976 pub border_style: Option<Style>,
978 pub bg: Option<Color>,
980 pub text_color: Option<Color>,
982 pub dark_bg: Option<Color>,
984 pub dark_border_style: Option<Style>,
986 pub padding: Option<Padding>,
988 pub margin: Option<Margin>,
990 pub gap: Option<u32>,
992 pub row_gap: Option<u32>,
994 pub col_gap: Option<u32>,
996 pub grow: Option<u16>,
998 pub align: Option<Align>,
1000 pub align_self: Option<Align>,
1002 pub justify: Option<Justify>,
1004 pub w: Option<u32>,
1006 pub h: Option<u32>,
1008 pub min_w: Option<u32>,
1010 pub max_w: Option<u32>,
1012 pub min_h: Option<u32>,
1014 pub max_h: Option<u32>,
1016 pub w_pct: Option<u8>,
1018 pub h_pct: Option<u8>,
1020 pub theme_bg: Option<ThemeColor>,
1022 pub theme_text_color: Option<ThemeColor>,
1024 pub theme_border_fg: Option<ThemeColor>,
1027 pub extends: Option<&'static ContainerStyle>,
1032}
1033
1034impl ContainerStyle {
1035 pub const fn new() -> Self {
1037 Self {
1038 border: None,
1039 border_sides: None,
1040 border_style: None,
1041 bg: None,
1042 text_color: None,
1043 dark_bg: None,
1044 dark_border_style: None,
1045 padding: None,
1046 margin: None,
1047 gap: None,
1048 row_gap: None,
1049 col_gap: None,
1050 grow: None,
1051 align: None,
1052 align_self: None,
1053 justify: None,
1054 w: None,
1055 h: None,
1056 min_w: None,
1057 max_w: None,
1058 min_h: None,
1059 max_h: None,
1060 w_pct: None,
1061 h_pct: None,
1062 theme_bg: None,
1063 theme_text_color: None,
1064 theme_border_fg: None,
1065 extends: None,
1066 }
1067 }
1068
1069 pub const fn extending(base: &'static ContainerStyle) -> Self {
1087 let mut s = Self::new();
1088 s.extends = Some(base);
1089 s
1090 }
1091
1092 pub const fn border(mut self, border: Border) -> Self {
1094 self.border = Some(border);
1095 self
1096 }
1097
1098 pub const fn border_sides(mut self, sides: BorderSides) -> Self {
1100 self.border_sides = Some(sides);
1101 self
1102 }
1103
1104 pub const fn bg(mut self, color: Color) -> Self {
1106 self.bg = Some(color);
1107 self
1108 }
1109
1110 pub const fn text_color(mut self, color: Color) -> Self {
1112 self.text_color = Some(color);
1113 self
1114 }
1115
1116 pub const fn dark_bg(mut self, color: Color) -> Self {
1118 self.dark_bg = Some(color);
1119 self
1120 }
1121
1122 pub const fn p(mut self, value: u32) -> Self {
1124 self.padding = Some(Padding {
1125 top: value,
1126 bottom: value,
1127 left: value,
1128 right: value,
1129 });
1130 self
1131 }
1132
1133 pub const fn px(mut self, value: u32) -> Self {
1135 let p = match self.padding {
1136 Some(p) => Padding {
1137 left: value,
1138 right: value,
1139 ..p
1140 },
1141 None => Padding {
1142 top: 0,
1143 bottom: 0,
1144 left: value,
1145 right: value,
1146 },
1147 };
1148 self.padding = Some(p);
1149 self
1150 }
1151
1152 pub const fn py(mut self, value: u32) -> Self {
1154 let p = match self.padding {
1155 Some(p) => Padding {
1156 top: value,
1157 bottom: value,
1158 ..p
1159 },
1160 None => Padding {
1161 top: value,
1162 bottom: value,
1163 left: 0,
1164 right: 0,
1165 },
1166 };
1167 self.padding = Some(p);
1168 self
1169 }
1170
1171 pub const fn m(mut self, value: u32) -> Self {
1173 self.margin = Some(Margin {
1174 top: value,
1175 bottom: value,
1176 left: value,
1177 right: value,
1178 });
1179 self
1180 }
1181
1182 pub const fn mx(mut self, value: u32) -> Self {
1193 let m = match self.margin {
1194 Some(m) => Margin {
1195 left: value,
1196 right: value,
1197 ..m
1198 },
1199 None => Margin {
1200 top: 0,
1201 bottom: 0,
1202 left: value,
1203 right: value,
1204 },
1205 };
1206 self.margin = Some(m);
1207 self
1208 }
1209
1210 pub const fn my(mut self, value: u32) -> Self {
1213 let m = match self.margin {
1214 Some(m) => Margin {
1215 top: value,
1216 bottom: value,
1217 ..m
1218 },
1219 None => Margin {
1220 top: value,
1221 bottom: value,
1222 left: 0,
1223 right: 0,
1224 },
1225 };
1226 self.margin = Some(m);
1227 self
1228 }
1229
1230 pub const fn gap(mut self, value: u32) -> Self {
1232 self.gap = Some(value);
1233 self
1234 }
1235
1236 pub const fn row_gap(mut self, value: u32) -> Self {
1238 self.row_gap = Some(value);
1239 self
1240 }
1241
1242 pub const fn col_gap(mut self, value: u32) -> Self {
1244 self.col_gap = Some(value);
1245 self
1246 }
1247
1248 pub const fn grow(mut self, value: u16) -> Self {
1250 self.grow = Some(value);
1251 self
1252 }
1253
1254 pub const fn w(mut self, value: u32) -> Self {
1256 self.w = Some(value);
1257 self
1258 }
1259
1260 pub const fn h(mut self, value: u32) -> Self {
1262 self.h = Some(value);
1263 self
1264 }
1265
1266 pub const fn min_w(mut self, value: u32) -> Self {
1268 self.min_w = Some(value);
1269 self
1270 }
1271
1272 pub const fn max_w(mut self, value: u32) -> Self {
1274 self.max_w = Some(value);
1275 self
1276 }
1277
1278 pub const fn align(mut self, value: Align) -> Self {
1280 self.align = Some(value);
1281 self
1282 }
1283
1284 pub const fn align_self(mut self, value: Align) -> Self {
1286 self.align_self = Some(value);
1287 self
1288 }
1289
1290 pub const fn justify(mut self, value: Justify) -> Self {
1292 self.justify = Some(value);
1293 self
1294 }
1295
1296 pub const fn min_h(mut self, value: u32) -> Self {
1298 self.min_h = Some(value);
1299 self
1300 }
1301
1302 pub const fn max_h(mut self, value: u32) -> Self {
1304 self.max_h = Some(value);
1305 self
1306 }
1307
1308 pub const fn w_pct(mut self, value: u8) -> Self {
1310 self.w_pct = Some(value);
1311 self
1312 }
1313
1314 pub const fn h_pct(mut self, value: u8) -> Self {
1316 self.h_pct = Some(value);
1317 self
1318 }
1319
1320 pub const fn theme_bg(mut self, color: ThemeColor) -> Self {
1325 self.theme_bg = Some(color);
1326 self
1327 }
1328
1329 pub const fn theme_text_color(mut self, color: ThemeColor) -> Self {
1333 self.theme_text_color = Some(color);
1334 self
1335 }
1336
1337 pub const fn theme_border_fg(mut self, color: ThemeColor) -> Self {
1341 self.theme_border_fg = Some(color);
1342 self
1343 }
1344}
1345
1346#[derive(Debug, Clone, Copy, Default)]
1347#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1348pub struct WidgetColors {
1353 pub fg: Option<Color>,
1355 pub bg: Option<Color>,
1357 pub border: Option<Color>,
1359 pub accent: Option<Color>,
1361 pub theme_fg: Option<ThemeColor>,
1363 pub theme_bg: Option<ThemeColor>,
1365 pub theme_border: Option<ThemeColor>,
1367 pub theme_accent: Option<ThemeColor>,
1369}
1370
1371impl WidgetColors {
1372 pub const fn new() -> Self {
1374 Self {
1375 fg: None,
1376 bg: None,
1377 border: None,
1378 accent: None,
1379 theme_fg: None,
1380 theme_bg: None,
1381 theme_border: None,
1382 theme_accent: None,
1383 }
1384 }
1385
1386 pub const fn fg(mut self, color: Color) -> Self {
1388 self.fg = Some(color);
1389 self
1390 }
1391
1392 pub const fn bg(mut self, color: Color) -> Self {
1394 self.bg = Some(color);
1395 self
1396 }
1397
1398 pub const fn border(mut self, color: Color) -> Self {
1400 self.border = Some(color);
1401 self
1402 }
1403
1404 pub const fn accent(mut self, color: Color) -> Self {
1406 self.accent = Some(color);
1407 self
1408 }
1409
1410 pub const fn theme_fg(mut self, color: ThemeColor) -> Self {
1412 self.theme_fg = Some(color);
1413 self
1414 }
1415
1416 pub const fn theme_bg(mut self, color: ThemeColor) -> Self {
1418 self.theme_bg = Some(color);
1419 self
1420 }
1421
1422 pub const fn theme_border(mut self, color: ThemeColor) -> Self {
1424 self.theme_border = Some(color);
1425 self
1426 }
1427
1428 pub const fn theme_accent(mut self, color: ThemeColor) -> Self {
1430 self.theme_accent = Some(color);
1431 self
1432 }
1433
1434 pub fn resolve_fg(&self, theme: &Theme, fallback: Color) -> Color {
1436 self.theme_fg
1437 .map(|tc| theme.resolve(tc))
1438 .or(self.fg)
1439 .unwrap_or(fallback)
1440 }
1441
1442 pub fn resolve_bg(&self, theme: &Theme, fallback: Color) -> Color {
1444 self.theme_bg
1445 .map(|tc| theme.resolve(tc))
1446 .or(self.bg)
1447 .unwrap_or(fallback)
1448 }
1449
1450 pub fn resolve_border(&self, theme: &Theme, fallback: Color) -> Color {
1452 self.theme_border
1453 .map(|tc| theme.resolve(tc))
1454 .or(self.border)
1455 .unwrap_or(fallback)
1456 }
1457
1458 pub fn resolve_accent(&self, theme: &Theme, fallback: Color) -> Color {
1460 self.theme_accent
1461 .map(|tc| theme.resolve(tc))
1462 .or(self.accent)
1463 .unwrap_or(fallback)
1464 }
1465}
1466
1467#[derive(Debug, Clone, Copy, Default)]
1482pub struct WidgetTheme {
1483 pub button: WidgetColors,
1485 pub table: WidgetColors,
1487 pub list: WidgetColors,
1489 pub tabs: WidgetColors,
1491 pub select: WidgetColors,
1493 pub radio: WidgetColors,
1495 pub checkbox: WidgetColors,
1497 pub toggle: WidgetColors,
1499 pub text_input: WidgetColors,
1501}
1502
1503impl WidgetTheme {
1504 pub const fn new() -> Self {
1506 Self {
1507 button: WidgetColors::new(),
1508 table: WidgetColors::new(),
1509 list: WidgetColors::new(),
1510 tabs: WidgetColors::new(),
1511 select: WidgetColors::new(),
1512 radio: WidgetColors::new(),
1513 checkbox: WidgetColors::new(),
1514 toggle: WidgetColors::new(),
1515 text_input: WidgetColors::new(),
1516 }
1517 }
1518
1519 pub const fn button(mut self, colors: WidgetColors) -> Self {
1521 self.button = colors;
1522 self
1523 }
1524
1525 pub const fn table(mut self, colors: WidgetColors) -> Self {
1527 self.table = colors;
1528 self
1529 }
1530
1531 pub const fn list(mut self, colors: WidgetColors) -> Self {
1533 self.list = colors;
1534 self
1535 }
1536
1537 pub const fn tabs(mut self, colors: WidgetColors) -> Self {
1539 self.tabs = colors;
1540 self
1541 }
1542
1543 pub const fn select(mut self, colors: WidgetColors) -> Self {
1545 self.select = colors;
1546 self
1547 }
1548
1549 pub const fn radio(mut self, colors: WidgetColors) -> Self {
1551 self.radio = colors;
1552 self
1553 }
1554
1555 pub const fn checkbox(mut self, colors: WidgetColors) -> Self {
1557 self.checkbox = colors;
1558 self
1559 }
1560
1561 pub const fn toggle(mut self, colors: WidgetColors) -> Self {
1563 self.toggle = colors;
1564 self
1565 }
1566
1567 pub const fn text_input(mut self, colors: WidgetColors) -> Self {
1569 self.text_input = colors;
1570 self
1571 }
1572}
1573
1574#[cfg(test)]
1575mod tests {
1576 use super::*;
1577
1578 #[test]
1579 fn style_new_is_default() {
1580 let style = Style::new();
1581 assert_eq!(style.fg, None);
1582 assert_eq!(style.bg, None);
1583 assert_eq!(style.modifiers, Modifiers::NONE);
1584 assert_eq!(style, Style::default());
1585 }
1586
1587 #[test]
1588 fn style_bold_and_fg_set_expected_fields() {
1589 let style = Style::new().bold().fg(Color::Red);
1590 assert_eq!(style.fg, Some(Color::Red));
1591 assert_eq!(style.bg, None);
1592 assert!(style.modifiers.contains(Modifiers::BOLD));
1593 }
1594
1595 #[test]
1596 fn style_multiple_modifiers_accumulate() {
1597 let style = Style::new().italic().underline().dim();
1598 assert!(style.modifiers.contains(Modifiers::ITALIC));
1599 assert!(style.modifiers.contains(Modifiers::UNDERLINE));
1600 assert!(style.modifiers.contains(Modifiers::DIM));
1601 }
1602
1603 #[test]
1604 fn style_repeated_fg_overrides_previous_color() {
1605 let style = Style::new().fg(Color::Blue).fg(Color::Green);
1606 assert_eq!(style.fg, Some(Color::Green));
1607 }
1608
1609 #[test]
1610 fn style_repeated_bg_overrides_previous_color() {
1611 let style = Style::new().bg(Color::Blue).bg(Color::Green);
1612 assert_eq!(style.bg, Some(Color::Green));
1613 }
1614
1615 #[test]
1616 fn style_override_preserves_existing_modifiers() {
1617 let style = Style::new().bold().fg(Color::Red).fg(Color::Yellow);
1618 assert_eq!(style.fg, Some(Color::Yellow));
1619 assert!(style.modifiers.contains(Modifiers::BOLD));
1620 }
1621
1622 #[test]
1623 fn padding_all_sets_all_sides() {
1624 let p = Padding::all(3);
1625 assert_eq!(p.top, 3);
1626 assert_eq!(p.right, 3);
1627 assert_eq!(p.bottom, 3);
1628 assert_eq!(p.left, 3);
1629 }
1630
1631 #[test]
1632 fn padding_xy_sets_axis_values() {
1633 let p = Padding::xy(4, 2);
1634 assert_eq!(p.top, 2);
1635 assert_eq!(p.bottom, 2);
1636 assert_eq!(p.left, 4);
1637 assert_eq!(p.right, 4);
1638 }
1639
1640 #[test]
1641 fn padding_new_and_totals_are_correct() {
1642 let p = Padding::new(1, 2, 3, 4);
1643 assert_eq!(p.top, 1);
1644 assert_eq!(p.right, 2);
1645 assert_eq!(p.bottom, 3);
1646 assert_eq!(p.left, 4);
1647 assert_eq!(p.horizontal(), 6);
1648 assert_eq!(p.vertical(), 4);
1649 }
1650
1651 #[test]
1652 fn margin_all_and_xy_are_correct() {
1653 let all = Margin::all(5);
1654 assert_eq!(all, Margin::new(5, 5, 5, 5));
1655
1656 let xy = Margin::xy(7, 1);
1657 assert_eq!(xy.top, 1);
1658 assert_eq!(xy.bottom, 1);
1659 assert_eq!(xy.left, 7);
1660 assert_eq!(xy.right, 7);
1661 }
1662
1663 #[test]
1664 fn margin_new_and_totals_are_correct() {
1665 let m = Margin::new(2, 4, 6, 8);
1666 assert_eq!(m.horizontal(), 12);
1667 assert_eq!(m.vertical(), 8);
1668 }
1669
1670 #[test]
1671 fn constraints_min_max_builder_sets_values() {
1672 let c = Constraints::default()
1673 .min_w(10)
1674 .max_w(40)
1675 .min_h(5)
1676 .max_h(20);
1677 assert_eq!(c.min_width(), Some(10));
1678 assert_eq!(c.max_width(), Some(40));
1679 assert_eq!(c.min_height(), Some(5));
1680 assert_eq!(c.max_height(), Some(20));
1681 assert_eq!(c.width, WidthSpec::MinMax { min: 10, max: 40 });
1682 }
1683
1684 #[test]
1685 fn constraints_percentage_builder_sets_values() {
1686 let c = Constraints::default().w_pct(50).h_pct(80);
1687 assert_eq!(c.width_pct(), Some(50));
1688 assert_eq!(c.height_pct(), Some(80));
1689 assert_eq!(c.width, WidthSpec::Pct(50));
1690 assert_eq!(c.height, HeightSpec::Pct(80));
1691 }
1692
1693 #[test]
1694 fn constraints_default_is_auto() {
1695 let c = Constraints::default();
1696 assert_eq!(c.width, WidthSpec::Auto);
1697 assert_eq!(c.height, HeightSpec::Auto);
1698 }
1699
1700 #[test]
1701 fn constraints_fixed_w_h() {
1702 let c = Constraints::default().w(20).h(10);
1703 assert_eq!(c.width, WidthSpec::Fixed(20));
1704 assert_eq!(c.height, HeightSpec::Fixed(10));
1705 assert_eq!(c.min_width(), Some(20));
1706 assert_eq!(c.max_width(), Some(20));
1707 }
1708
1709 #[test]
1710 fn constraints_size_24_bytes() {
1711 assert_eq!(std::mem::size_of::<Constraints>(), 24);
1712 }
1713
1714 #[test]
1715 fn constraints_set_min_width_promotes_to_minmax() {
1716 let mut c = Constraints::default();
1717 c.set_min_width(Some(10));
1718 assert_eq!(
1719 c.width,
1720 WidthSpec::MinMax {
1721 min: 10,
1722 max: u32::MAX,
1723 }
1724 );
1725 c.set_max_width(Some(40));
1726 assert_eq!(c.width, WidthSpec::MinMax { min: 10, max: 40 });
1727 }
1728
1729 #[test]
1730 fn constraints_w_ratio_builder() {
1731 let c = Constraints::default().w_ratio(1, 3);
1732 assert_eq!(c.width, WidthSpec::Ratio(1, 3));
1733 }
1734
1735 #[test]
1736 fn border_sides_all_has_both_axes() {
1737 let sides = BorderSides::all();
1738 assert!(sides.top && sides.right && sides.bottom && sides.left);
1739 assert!(sides.has_horizontal());
1740 assert!(sides.has_vertical());
1741 }
1742
1743 #[test]
1744 fn border_sides_none_has_no_axes() {
1745 let sides = BorderSides::none();
1746 assert!(!sides.top && !sides.right && !sides.bottom && !sides.left);
1747 assert!(!sides.has_horizontal());
1748 assert!(!sides.has_vertical());
1749 }
1750
1751 #[test]
1752 fn border_sides_horizontal_only() {
1753 let sides = BorderSides::horizontal();
1754 assert!(sides.top);
1755 assert!(sides.bottom);
1756 assert!(!sides.left);
1757 assert!(!sides.right);
1758 assert!(sides.has_horizontal());
1759 assert!(!sides.has_vertical());
1760 }
1761
1762 #[test]
1763 fn border_sides_vertical_only() {
1764 let sides = BorderSides::vertical();
1765 assert!(!sides.top);
1766 assert!(!sides.bottom);
1767 assert!(sides.left);
1768 assert!(sides.right);
1769 assert!(!sides.has_horizontal());
1770 assert!(sides.has_vertical());
1771 }
1772
1773 #[test]
1774 fn container_style_new_is_empty() {
1775 let s = ContainerStyle::new();
1776 assert_eq!(s.border, None);
1777 assert_eq!(s.bg, None);
1778 assert_eq!(s.padding, None);
1779 assert_eq!(s.margin, None);
1780 assert_eq!(s.gap, None);
1781 assert_eq!(s.align, None);
1782 assert_eq!(s.justify, None);
1783 }
1784
1785 #[test]
1786 fn container_style_const_construction_and_fields() {
1787 const CARD: ContainerStyle = ContainerStyle::new()
1788 .border(Border::Rounded)
1789 .border_sides(BorderSides::horizontal())
1790 .p(2)
1791 .m(1)
1792 .gap(3)
1793 .align(Align::Center)
1794 .justify(Justify::SpaceBetween)
1795 .w(60)
1796 .h(20);
1797
1798 assert_eq!(CARD.border, Some(Border::Rounded));
1799 assert_eq!(CARD.border_sides, Some(BorderSides::horizontal()));
1800 assert_eq!(CARD.padding, Some(Padding::all(2)));
1801 assert_eq!(CARD.margin, Some(Margin::all(1)));
1802 assert_eq!(CARD.gap, Some(3));
1803 assert_eq!(CARD.align, Some(Align::Center));
1804 assert_eq!(CARD.justify, Some(Justify::SpaceBetween));
1805 assert_eq!(CARD.w, Some(60));
1806 assert_eq!(CARD.h, Some(20));
1807 }
1808
1809 #[test]
1810 fn widget_colors_new_is_empty() {
1811 let colors = WidgetColors::new();
1812 assert_eq!(colors.fg, None);
1813 assert_eq!(colors.bg, None);
1814 assert_eq!(colors.border, None);
1815 assert_eq!(colors.accent, None);
1816
1817 let defaults = WidgetColors::default();
1818 assert_eq!(defaults.fg, None);
1819 assert_eq!(defaults.bg, None);
1820 assert_eq!(defaults.border, None);
1821 assert_eq!(defaults.accent, None);
1822 }
1823
1824 #[test]
1825 fn widget_colors_builder_sets_all_fields() {
1826 let colors = WidgetColors::new()
1827 .fg(Color::White)
1828 .bg(Color::Black)
1829 .border(Color::Cyan)
1830 .accent(Color::Yellow);
1831
1832 assert_eq!(colors.fg, Some(Color::White));
1833 assert_eq!(colors.bg, Some(Color::Black));
1834 assert_eq!(colors.border, Some(Color::Cyan));
1835 assert_eq!(colors.accent, Some(Color::Yellow));
1836 }
1837
1838 #[test]
1839 fn align_default_is_start() {
1840 assert_eq!(Align::default(), Align::Start);
1841 }
1842
1843 #[test]
1844 fn justify_default_is_start() {
1845 assert_eq!(Justify::default(), Justify::Start);
1846 }
1847
1848 #[test]
1849 fn align_and_justify_variants_are_distinct() {
1850 assert_ne!(Align::Start, Align::Center);
1851 assert_ne!(Align::Center, Align::End);
1852
1853 assert_ne!(Justify::Start, Justify::Center);
1854 assert_ne!(Justify::Center, Justify::End);
1855 assert_ne!(Justify::SpaceBetween, Justify::SpaceAround);
1856 assert_ne!(Justify::SpaceAround, Justify::SpaceEvenly);
1857 }
1858}