1mod color;
7mod theme;
8#[cfg(feature = "serde")]
9mod theme_io;
10pub use color::{Color, ColorDepth, ColorParseError};
11pub use theme::{Spacing, SyntaxPalette, Theme, ThemeBuilder, ThemeColor};
12#[cfg(feature = "theme-watch")]
13#[cfg_attr(docsrs, doc(cfg(feature = "theme-watch")))]
14pub use theme_io::ThemeWatcher;
15#[cfg(feature = "serde")]
16#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
17pub use theme_io::{ThemeFile, ThemeLoadError};
18
19#[non_exhaustive]
24#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
25pub enum Breakpoint {
26 Xs,
28 Sm,
30 Md,
32 Lg,
34 Xl,
36}
37
38#[non_exhaustive]
43#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
44#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
45pub enum Border {
46 Single,
48 Double,
50 Rounded,
52 Thick,
54 Dashed,
56 DashedThick,
58}
59
60#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
65pub struct BorderChars {
66 pub tl: char,
68 pub tr: char,
70 pub bl: char,
72 pub br: char,
74 pub h: char,
76 pub v: char,
78}
79
80#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
82#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
83pub struct BorderSides {
84 pub top: bool,
86 pub right: bool,
88 pub bottom: bool,
90 pub left: bool,
92}
93
94impl BorderSides {
95 pub const fn all() -> Self {
97 Self {
98 top: true,
99 right: true,
100 bottom: true,
101 left: true,
102 }
103 }
104
105 pub const fn none() -> Self {
107 Self {
108 top: false,
109 right: false,
110 bottom: false,
111 left: false,
112 }
113 }
114
115 pub const fn horizontal() -> Self {
117 Self {
118 top: true,
119 right: false,
120 bottom: true,
121 left: false,
122 }
123 }
124
125 pub const fn vertical() -> Self {
127 Self {
128 top: false,
129 right: true,
130 bottom: false,
131 left: true,
132 }
133 }
134
135 pub fn has_horizontal(&self) -> bool {
137 self.top || self.bottom
138 }
139
140 pub fn has_vertical(&self) -> bool {
142 self.left || self.right
143 }
144}
145
146impl Default for BorderSides {
147 fn default() -> Self {
148 Self::all()
149 }
150}
151
152impl Border {
153 pub const fn chars(self) -> BorderChars {
155 match self {
156 Self::Single => BorderChars {
157 tl: '┌',
158 tr: '┐',
159 bl: '└',
160 br: '┘',
161 h: '─',
162 v: '│',
163 },
164 Self::Double => BorderChars {
165 tl: '╔',
166 tr: '╗',
167 bl: '╚',
168 br: '╝',
169 h: '═',
170 v: '║',
171 },
172 Self::Rounded => BorderChars {
173 tl: '╭',
174 tr: '╮',
175 bl: '╰',
176 br: '╯',
177 h: '─',
178 v: '│',
179 },
180 Self::Thick => BorderChars {
181 tl: '┏',
182 tr: '┓',
183 bl: '┗',
184 br: '┛',
185 h: '━',
186 v: '┃',
187 },
188 Self::Dashed => BorderChars {
189 tl: '┌',
190 tr: '┐',
191 bl: '└',
192 br: '┘',
193 h: '┄',
194 v: '┆',
195 },
196 Self::DashedThick => BorderChars {
197 tl: '┏',
198 tr: '┓',
199 bl: '┗',
200 br: '┛',
201 h: '┅',
202 v: '┇',
203 },
204 }
205 }
206}
207
208#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
213#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
214pub struct Padding {
215 pub top: u32,
217 pub right: u32,
219 pub bottom: u32,
221 pub left: u32,
223}
224
225impl Padding {
226 pub const fn all(v: u32) -> Self {
228 Self::new(v, v, v, v)
229 }
230
231 pub const fn xy(x: u32, y: u32) -> Self {
233 Self::new(y, x, y, x)
234 }
235
236 pub const fn new(top: u32, right: u32, bottom: u32, left: u32) -> Self {
238 Self {
239 top,
240 right,
241 bottom,
242 left,
243 }
244 }
245
246 pub const fn horizontal(self) -> u32 {
248 self.left + self.right
249 }
250
251 pub const fn vertical(self) -> u32 {
253 self.top + self.bottom
254 }
255}
256
257#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
262#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
263pub struct Margin {
264 pub top: u32,
266 pub right: u32,
268 pub bottom: u32,
270 pub left: u32,
272}
273
274impl Margin {
275 pub const fn all(v: u32) -> Self {
277 Self::new(v, v, v, v)
278 }
279
280 pub const fn xy(x: u32, y: u32) -> Self {
282 Self::new(y, x, y, x)
283 }
284
285 pub const fn new(top: u32, right: u32, bottom: u32, left: u32) -> Self {
287 Self {
288 top,
289 right,
290 bottom,
291 left,
292 }
293 }
294
295 pub const fn horizontal(self) -> u32 {
297 self.left + self.right
298 }
299
300 pub const fn vertical(self) -> u32 {
302 self.top + self.bottom
303 }
304}
305
306#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
335#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
336#[non_exhaustive]
337pub enum WidthSpec {
338 Auto,
340 Fixed(u32),
342 Pct(u8),
344 Ratio(u16, u16),
350 MinMax {
362 min: u32,
364 max: u32,
366 },
367}
368
369impl Default for WidthSpec {
370 #[inline]
371 fn default() -> Self {
372 Self::Auto
373 }
374}
375
376#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
381#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
382#[non_exhaustive]
383pub enum HeightSpec {
384 Auto,
386 Fixed(u32),
388 Pct(u8),
390 Ratio(u16, u16),
396 MinMax {
399 min: u32,
401 max: u32,
403 },
404}
405
406impl Default for HeightSpec {
407 #[inline]
408 fn default() -> Self {
409 Self::Auto
410 }
411}
412
413#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
427#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
428#[must_use = "configure constraints using the returned value"]
429pub struct Constraints {
430 pub width: WidthSpec,
432 pub height: HeightSpec,
434}
435
436const _ASSERT_CONSTRAINTS_SIZE: () = assert!(
443 std::mem::size_of::<Constraints>() == 24,
444 "Constraints must be 24 bytes"
445);
446
447impl Constraints {
448 pub const fn min_w(mut self, min_width: u32) -> Self {
456 let max = match self.width {
457 WidthSpec::MinMax { max, .. } => max,
458 WidthSpec::Fixed(v) => v,
459 _ => u32::MAX,
460 };
461 self.width = WidthSpec::MinMax {
462 min: min_width,
463 max,
464 };
465 self
466 }
467
468 pub const fn max_w(mut self, max_width: u32) -> Self {
474 let min = match self.width {
475 WidthSpec::MinMax { min, .. } => min,
476 WidthSpec::Fixed(v) => v,
477 _ => 0,
478 };
479 self.width = WidthSpec::MinMax {
480 min,
481 max: max_width,
482 };
483 self
484 }
485
486 pub const fn min_h(mut self, min_height: u32) -> Self {
492 let max = match self.height {
493 HeightSpec::MinMax { max, .. } => max,
494 HeightSpec::Fixed(v) => v,
495 _ => u32::MAX,
496 };
497 self.height = HeightSpec::MinMax {
498 min: min_height,
499 max,
500 };
501 self
502 }
503
504 pub const fn max_h(mut self, max_height: u32) -> Self {
510 let min = match self.height {
511 HeightSpec::MinMax { min, .. } => min,
512 HeightSpec::Fixed(v) => v,
513 _ => 0,
514 };
515 self.height = HeightSpec::MinMax {
516 min,
517 max: max_height,
518 };
519 self
520 }
521
522 pub const fn w_minmax(mut self, min: u32, max: u32) -> Self {
527 self.width = WidthSpec::MinMax { min, max };
528 self
529 }
530
531 pub const fn h_minmax(mut self, min: u32, max: u32) -> Self {
533 self.height = HeightSpec::MinMax { min, max };
534 self
535 }
536
537 pub const fn w(mut self, width: u32) -> Self {
539 self.width = WidthSpec::Fixed(width);
540 self
541 }
542
543 pub const fn h(mut self, height: u32) -> Self {
545 self.height = HeightSpec::Fixed(height);
546 self
547 }
548
549 pub const fn w_pct(mut self, pct: u8) -> Self {
551 self.width = WidthSpec::Pct(pct);
552 self
553 }
554
555 pub const fn h_pct(mut self, pct: u8) -> Self {
557 self.height = HeightSpec::Pct(pct);
558 self
559 }
560
561 pub const fn w_ratio(mut self, num: u16, den: u16) -> Self {
566 self.width = WidthSpec::Ratio(num, den);
567 self
568 }
569
570 pub const fn h_ratio(mut self, num: u16, den: u16) -> Self {
574 self.height = HeightSpec::Ratio(num, den);
575 self
576 }
577
578 pub const fn min_width(&self) -> Option<u32> {
588 match self.width {
589 WidthSpec::Fixed(v) => Some(v),
590 WidthSpec::MinMax { min, .. } if min > 0 => Some(min),
591 _ => None,
592 }
593 }
594
595 pub const fn max_width(&self) -> Option<u32> {
601 match self.width {
602 WidthSpec::Fixed(v) => Some(v),
603 WidthSpec::MinMax { max, .. } if max < u32::MAX => Some(max),
604 _ => None,
605 }
606 }
607
608 pub const fn min_height(&self) -> Option<u32> {
612 match self.height {
613 HeightSpec::Fixed(v) => Some(v),
614 HeightSpec::MinMax { min, .. } if min > 0 => Some(min),
615 _ => None,
616 }
617 }
618
619 pub const fn max_height(&self) -> Option<u32> {
623 match self.height {
624 HeightSpec::Fixed(v) => Some(v),
625 HeightSpec::MinMax { max, .. } if max < u32::MAX => Some(max),
626 _ => None,
627 }
628 }
629
630 pub const fn width_pct(&self) -> Option<u8> {
632 match self.width {
633 WidthSpec::Pct(p) => Some(p),
634 _ => None,
635 }
636 }
637
638 pub const fn height_pct(&self) -> Option<u8> {
640 match self.height {
641 HeightSpec::Pct(p) => Some(p),
642 _ => None,
643 }
644 }
645
646 pub fn set_min_width(&mut self, value: Option<u32>) {
670 let max = match self.width {
671 WidthSpec::MinMax { max, .. } => max,
672 WidthSpec::Fixed(v) => v,
673 _ => u32::MAX,
674 };
675 let min = value.unwrap_or(0);
676 self.width = if min == 0 && max == u32::MAX {
677 WidthSpec::Auto
678 } else {
679 WidthSpec::MinMax { min, max }
680 };
681 }
682
683 pub fn set_max_width(&mut self, value: Option<u32>) {
685 let min = match self.width {
686 WidthSpec::MinMax { min, .. } => min,
687 WidthSpec::Fixed(v) => v,
688 _ => 0,
689 };
690 let max = value.unwrap_or(u32::MAX);
691 self.width = if min == 0 && max == u32::MAX {
692 WidthSpec::Auto
693 } else {
694 WidthSpec::MinMax { min, max }
695 };
696 }
697
698 pub fn set_min_height(&mut self, value: Option<u32>) {
700 let max = match self.height {
701 HeightSpec::MinMax { max, .. } => max,
702 HeightSpec::Fixed(v) => v,
703 _ => u32::MAX,
704 };
705 let min = value.unwrap_or(0);
706 self.height = if min == 0 && max == u32::MAX {
707 HeightSpec::Auto
708 } else {
709 HeightSpec::MinMax { min, max }
710 };
711 }
712
713 pub fn set_max_height(&mut self, value: Option<u32>) {
715 let min = match self.height {
716 HeightSpec::MinMax { min, .. } => min,
717 HeightSpec::Fixed(v) => v,
718 _ => 0,
719 };
720 let max = value.unwrap_or(u32::MAX);
721 self.height = if min == 0 && max == u32::MAX {
722 HeightSpec::Auto
723 } else {
724 HeightSpec::MinMax { min, max }
725 };
726 }
727
728 pub fn set_width_pct(&mut self, value: Option<u8>) {
730 self.width = match value {
731 Some(p) => WidthSpec::Pct(p),
732 None => WidthSpec::Auto,
733 };
734 }
735
736 pub fn set_height_pct(&mut self, value: Option<u8>) {
738 self.height = match value {
739 Some(p) => HeightSpec::Pct(p),
740 None => HeightSpec::Auto,
741 };
742 }
743}
744
745#[non_exhaustive]
751#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
752#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
753pub enum Align {
754 #[default]
761 Start,
762 Center,
764 End,
766}
767
768#[non_exhaustive]
777#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
778#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
779pub enum Justify {
780 #[default]
782 Start,
783 Center,
785 End,
787 SpaceBetween,
789 SpaceAround,
791 SpaceEvenly,
793}
794
795#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
800#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
801#[cfg_attr(feature = "serde", serde(transparent))]
802pub struct Modifiers(pub u8);
803
804impl Modifiers {
805 pub const NONE: Self = Self(0);
807 pub const BOLD: Self = Self(1 << 0);
809 pub const DIM: Self = Self(1 << 1);
811 pub const ITALIC: Self = Self(1 << 2);
813 pub const UNDERLINE: Self = Self(1 << 3);
815 pub const REVERSED: Self = Self(1 << 4);
817 pub const STRIKETHROUGH: Self = Self(1 << 5);
819 pub const BLINK: Self = Self(1 << 6);
821 pub const OVERLINE: Self = Self(1 << 7);
823
824 #[inline]
826 pub fn contains(self, other: Self) -> bool {
827 (self.0 & other.0) == other.0
828 }
829
830 #[inline]
832 pub fn insert(&mut self, other: Self) {
833 self.0 |= other.0;
834 }
835
836 #[inline]
849 pub fn remove(&mut self, other: Self) {
850 self.0 &= !other.0;
851 }
852
853 #[inline]
855 pub fn is_empty(self) -> bool {
856 self.0 == 0
857 }
858}
859
860impl std::ops::BitOr for Modifiers {
861 type Output = Self;
862 #[inline]
863 fn bitor(self, rhs: Self) -> Self {
864 Self(self.0 | rhs.0)
865 }
866}
867
868impl std::ops::BitOrAssign for Modifiers {
869 #[inline]
870 fn bitor_assign(&mut self, rhs: Self) {
871 self.0 |= rhs.0;
872 }
873}
874
875#[non_exhaustive]
894#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
895#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
896pub enum UnderlineStyle {
897 #[default]
899 Straight,
900 Double,
902 Curly,
904 Dotted,
906 Dashed,
908}
909
910#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
924#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
925#[must_use = "build and pass the returned Style value"]
926pub struct Style {
927 pub fg: Option<Color>,
929 pub bg: Option<Color>,
931 pub modifiers: Modifiers,
933 pub underline_color: Option<Color>,
938 pub underline_style: UnderlineStyle,
943}
944
945impl Style {
946 pub const fn new() -> Self {
948 Self {
949 fg: None,
950 bg: None,
951 modifiers: Modifiers::NONE,
952 underline_color: None,
953 underline_style: UnderlineStyle::Straight,
954 }
955 }
956
957 pub const fn fg(mut self, color: Color) -> Self {
959 self.fg = Some(color);
960 self
961 }
962
963 pub const fn bg(mut self, color: Color) -> Self {
965 self.bg = Some(color);
966 self
967 }
968
969 pub fn bold(mut self) -> Self {
971 self.modifiers |= Modifiers::BOLD;
972 self
973 }
974
975 pub fn dim(mut self) -> Self {
977 self.modifiers |= Modifiers::DIM;
978 self
979 }
980
981 pub fn italic(mut self) -> Self {
983 self.modifiers |= Modifiers::ITALIC;
984 self
985 }
986
987 pub fn underline(mut self) -> Self {
989 self.modifiers |= Modifiers::UNDERLINE;
990 self
991 }
992
993 pub fn reversed(mut self) -> Self {
995 self.modifiers |= Modifiers::REVERSED;
996 self
997 }
998
999 pub fn strikethrough(mut self) -> Self {
1001 self.modifiers |= Modifiers::STRIKETHROUGH;
1002 self
1003 }
1004
1005 pub fn blink(mut self) -> Self {
1007 self.modifiers |= Modifiers::BLINK;
1008 self
1009 }
1010
1011 pub fn overline(mut self) -> Self {
1013 self.modifiers |= Modifiers::OVERLINE;
1014 self
1015 }
1016
1017 pub const fn underline_color(mut self, color: Color) -> Self {
1031 self.underline_color = Some(color);
1032 self.modifiers = Modifiers(self.modifiers.0 | Modifiers::UNDERLINE.0);
1033 self
1034 }
1035
1036 pub const fn underline_style(mut self, style: UnderlineStyle) -> Self {
1050 self.underline_style = style;
1051 self.modifiers = Modifiers(self.modifiers.0 | Modifiers::UNDERLINE.0);
1052 self
1053 }
1054}
1055
1056#[derive(Debug, Clone, Copy, Default)]
1080pub struct ContainerStyle {
1081 pub border: Option<Border>,
1083 pub border_sides: Option<BorderSides>,
1085 pub border_style: Option<Style>,
1087 pub bg: Option<Color>,
1089 pub text_color: Option<Color>,
1091 pub dark_bg: Option<Color>,
1093 pub dark_border_style: Option<Style>,
1095 pub padding: Option<Padding>,
1097 pub margin: Option<Margin>,
1099 pub gap: Option<u32>,
1101 pub row_gap: Option<u32>,
1103 pub col_gap: Option<u32>,
1105 pub grow: Option<u16>,
1107 pub align: Option<Align>,
1109 pub align_self: Option<Align>,
1111 pub justify: Option<Justify>,
1113 pub w: Option<u32>,
1115 pub h: Option<u32>,
1117 pub min_w: Option<u32>,
1119 pub max_w: Option<u32>,
1121 pub min_h: Option<u32>,
1123 pub max_h: Option<u32>,
1125 pub w_pct: Option<u8>,
1127 pub h_pct: Option<u8>,
1129 pub theme_bg: Option<ThemeColor>,
1131 pub theme_text_color: Option<ThemeColor>,
1133 pub theme_border_fg: Option<ThemeColor>,
1136 pub extends: Option<&'static ContainerStyle>,
1141}
1142
1143impl ContainerStyle {
1144 pub const fn new() -> Self {
1146 Self {
1147 border: None,
1148 border_sides: None,
1149 border_style: None,
1150 bg: None,
1151 text_color: None,
1152 dark_bg: None,
1153 dark_border_style: None,
1154 padding: None,
1155 margin: None,
1156 gap: None,
1157 row_gap: None,
1158 col_gap: None,
1159 grow: None,
1160 align: None,
1161 align_self: None,
1162 justify: None,
1163 w: None,
1164 h: None,
1165 min_w: None,
1166 max_w: None,
1167 min_h: None,
1168 max_h: None,
1169 w_pct: None,
1170 h_pct: None,
1171 theme_bg: None,
1172 theme_text_color: None,
1173 theme_border_fg: None,
1174 extends: None,
1175 }
1176 }
1177
1178 pub const fn extending(base: &'static ContainerStyle) -> Self {
1196 let mut s = Self::new();
1197 s.extends = Some(base);
1198 s
1199 }
1200
1201 pub const fn border(mut self, border: Border) -> Self {
1203 self.border = Some(border);
1204 self
1205 }
1206
1207 pub const fn border_sides(mut self, sides: BorderSides) -> Self {
1209 self.border_sides = Some(sides);
1210 self
1211 }
1212
1213 pub const fn bg(mut self, color: Color) -> Self {
1215 self.bg = Some(color);
1216 self
1217 }
1218
1219 pub const fn text_color(mut self, color: Color) -> Self {
1221 self.text_color = Some(color);
1222 self
1223 }
1224
1225 pub const fn dark_bg(mut self, color: Color) -> Self {
1227 self.dark_bg = Some(color);
1228 self
1229 }
1230
1231 pub const fn p(mut self, value: u32) -> Self {
1233 self.padding = Some(Padding {
1234 top: value,
1235 bottom: value,
1236 left: value,
1237 right: value,
1238 });
1239 self
1240 }
1241
1242 pub const fn px(mut self, value: u32) -> Self {
1244 let p = match self.padding {
1245 Some(p) => Padding {
1246 left: value,
1247 right: value,
1248 ..p
1249 },
1250 None => Padding {
1251 top: 0,
1252 bottom: 0,
1253 left: value,
1254 right: value,
1255 },
1256 };
1257 self.padding = Some(p);
1258 self
1259 }
1260
1261 pub const fn py(mut self, value: u32) -> Self {
1263 let p = match self.padding {
1264 Some(p) => Padding {
1265 top: value,
1266 bottom: value,
1267 ..p
1268 },
1269 None => Padding {
1270 top: value,
1271 bottom: value,
1272 left: 0,
1273 right: 0,
1274 },
1275 };
1276 self.padding = Some(p);
1277 self
1278 }
1279
1280 pub const fn m(mut self, value: u32) -> Self {
1282 self.margin = Some(Margin {
1283 top: value,
1284 bottom: value,
1285 left: value,
1286 right: value,
1287 });
1288 self
1289 }
1290
1291 pub const fn mx(mut self, value: u32) -> Self {
1302 let m = match self.margin {
1303 Some(m) => Margin {
1304 left: value,
1305 right: value,
1306 ..m
1307 },
1308 None => Margin {
1309 top: 0,
1310 bottom: 0,
1311 left: value,
1312 right: value,
1313 },
1314 };
1315 self.margin = Some(m);
1316 self
1317 }
1318
1319 pub const fn my(mut self, value: u32) -> Self {
1322 let m = match self.margin {
1323 Some(m) => Margin {
1324 top: value,
1325 bottom: value,
1326 ..m
1327 },
1328 None => Margin {
1329 top: value,
1330 bottom: value,
1331 left: 0,
1332 right: 0,
1333 },
1334 };
1335 self.margin = Some(m);
1336 self
1337 }
1338
1339 pub const fn gap(mut self, value: u32) -> Self {
1341 self.gap = Some(value);
1342 self
1343 }
1344
1345 pub const fn row_gap(mut self, value: u32) -> Self {
1347 self.row_gap = Some(value);
1348 self
1349 }
1350
1351 pub const fn col_gap(mut self, value: u32) -> Self {
1353 self.col_gap = Some(value);
1354 self
1355 }
1356
1357 pub const fn grow(mut self, value: u16) -> Self {
1359 self.grow = Some(value);
1360 self
1361 }
1362
1363 pub const fn w(mut self, value: u32) -> Self {
1365 self.w = Some(value);
1366 self
1367 }
1368
1369 pub const fn h(mut self, value: u32) -> Self {
1371 self.h = Some(value);
1372 self
1373 }
1374
1375 pub const fn min_w(mut self, value: u32) -> Self {
1377 self.min_w = Some(value);
1378 self
1379 }
1380
1381 pub const fn max_w(mut self, value: u32) -> Self {
1383 self.max_w = Some(value);
1384 self
1385 }
1386
1387 pub const fn align(mut self, value: Align) -> Self {
1389 self.align = Some(value);
1390 self
1391 }
1392
1393 pub const fn align_self(mut self, value: Align) -> Self {
1395 self.align_self = Some(value);
1396 self
1397 }
1398
1399 pub const fn justify(mut self, value: Justify) -> Self {
1401 self.justify = Some(value);
1402 self
1403 }
1404
1405 pub const fn min_h(mut self, value: u32) -> Self {
1407 self.min_h = Some(value);
1408 self
1409 }
1410
1411 pub const fn max_h(mut self, value: u32) -> Self {
1413 self.max_h = Some(value);
1414 self
1415 }
1416
1417 pub const fn w_pct(mut self, value: u8) -> Self {
1419 self.w_pct = Some(value);
1420 self
1421 }
1422
1423 pub const fn h_pct(mut self, value: u8) -> Self {
1425 self.h_pct = Some(value);
1426 self
1427 }
1428
1429 pub const fn theme_bg(mut self, color: ThemeColor) -> Self {
1434 self.theme_bg = Some(color);
1435 self
1436 }
1437
1438 pub const fn theme_text_color(mut self, color: ThemeColor) -> Self {
1442 self.theme_text_color = Some(color);
1443 self
1444 }
1445
1446 pub const fn theme_border_fg(mut self, color: ThemeColor) -> Self {
1450 self.theme_border_fg = Some(color);
1451 self
1452 }
1453}
1454
1455#[derive(Debug, Clone, Copy, Default)]
1456#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1457#[cfg_attr(feature = "serde", serde(default))]
1458pub struct WidgetColors {
1463 pub fg: Option<Color>,
1465 pub bg: Option<Color>,
1467 pub border: Option<Color>,
1469 pub accent: Option<Color>,
1471 pub theme_fg: Option<ThemeColor>,
1473 pub theme_bg: Option<ThemeColor>,
1475 pub theme_border: Option<ThemeColor>,
1477 pub theme_accent: Option<ThemeColor>,
1479}
1480
1481impl WidgetColors {
1482 pub const fn new() -> Self {
1484 Self {
1485 fg: None,
1486 bg: None,
1487 border: None,
1488 accent: None,
1489 theme_fg: None,
1490 theme_bg: None,
1491 theme_border: None,
1492 theme_accent: None,
1493 }
1494 }
1495
1496 pub const fn fg(mut self, color: Color) -> Self {
1498 self.fg = Some(color);
1499 self
1500 }
1501
1502 pub const fn bg(mut self, color: Color) -> Self {
1504 self.bg = Some(color);
1505 self
1506 }
1507
1508 pub const fn border(mut self, color: Color) -> Self {
1510 self.border = Some(color);
1511 self
1512 }
1513
1514 pub const fn accent(mut self, color: Color) -> Self {
1516 self.accent = Some(color);
1517 self
1518 }
1519
1520 pub const fn theme_fg(mut self, color: ThemeColor) -> Self {
1522 self.theme_fg = Some(color);
1523 self
1524 }
1525
1526 pub const fn theme_bg(mut self, color: ThemeColor) -> Self {
1528 self.theme_bg = Some(color);
1529 self
1530 }
1531
1532 pub const fn theme_border(mut self, color: ThemeColor) -> Self {
1534 self.theme_border = Some(color);
1535 self
1536 }
1537
1538 pub const fn theme_accent(mut self, color: ThemeColor) -> Self {
1540 self.theme_accent = Some(color);
1541 self
1542 }
1543
1544 pub fn resolve_fg(&self, theme: &Theme, fallback: Color) -> Color {
1546 self.theme_fg
1547 .map(|tc| theme.resolve(tc))
1548 .or(self.fg)
1549 .unwrap_or(fallback)
1550 }
1551
1552 pub fn resolve_bg(&self, theme: &Theme, fallback: Color) -> Color {
1554 self.theme_bg
1555 .map(|tc| theme.resolve(tc))
1556 .or(self.bg)
1557 .unwrap_or(fallback)
1558 }
1559
1560 pub fn resolve_border(&self, theme: &Theme, fallback: Color) -> Color {
1562 self.theme_border
1563 .map(|tc| theme.resolve(tc))
1564 .or(self.border)
1565 .unwrap_or(fallback)
1566 }
1567
1568 pub fn resolve_accent(&self, theme: &Theme, fallback: Color) -> Color {
1570 self.theme_accent
1571 .map(|tc| theme.resolve(tc))
1572 .or(self.accent)
1573 .unwrap_or(fallback)
1574 }
1575}
1576
1577#[derive(Debug, Clone, Copy, Default)]
1592#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1593#[cfg_attr(feature = "serde", serde(default))]
1594pub struct WidgetTheme {
1595 pub button: WidgetColors,
1597 pub table: WidgetColors,
1599 pub list: WidgetColors,
1601 pub tabs: WidgetColors,
1603 pub select: WidgetColors,
1605 pub radio: WidgetColors,
1607 pub checkbox: WidgetColors,
1609 pub toggle: WidgetColors,
1611 pub text_input: WidgetColors,
1613 pub color_picker: WidgetColors,
1615}
1616
1617impl WidgetTheme {
1618 pub const fn new() -> Self {
1620 Self {
1621 button: WidgetColors::new(),
1622 table: WidgetColors::new(),
1623 list: WidgetColors::new(),
1624 tabs: WidgetColors::new(),
1625 select: WidgetColors::new(),
1626 radio: WidgetColors::new(),
1627 checkbox: WidgetColors::new(),
1628 toggle: WidgetColors::new(),
1629 text_input: WidgetColors::new(),
1630 color_picker: WidgetColors::new(),
1631 }
1632 }
1633
1634 pub const fn button(mut self, colors: WidgetColors) -> Self {
1636 self.button = colors;
1637 self
1638 }
1639
1640 pub const fn table(mut self, colors: WidgetColors) -> Self {
1642 self.table = colors;
1643 self
1644 }
1645
1646 pub const fn list(mut self, colors: WidgetColors) -> Self {
1648 self.list = colors;
1649 self
1650 }
1651
1652 pub const fn tabs(mut self, colors: WidgetColors) -> Self {
1654 self.tabs = colors;
1655 self
1656 }
1657
1658 pub const fn select(mut self, colors: WidgetColors) -> Self {
1660 self.select = colors;
1661 self
1662 }
1663
1664 pub const fn radio(mut self, colors: WidgetColors) -> Self {
1666 self.radio = colors;
1667 self
1668 }
1669
1670 pub const fn checkbox(mut self, colors: WidgetColors) -> Self {
1672 self.checkbox = colors;
1673 self
1674 }
1675
1676 pub const fn toggle(mut self, colors: WidgetColors) -> Self {
1678 self.toggle = colors;
1679 self
1680 }
1681
1682 pub const fn text_input(mut self, colors: WidgetColors) -> Self {
1684 self.text_input = colors;
1685 self
1686 }
1687
1688 pub const fn color_picker(mut self, colors: WidgetColors) -> Self {
1690 self.color_picker = colors;
1691 self
1692 }
1693}
1694
1695#[cfg(test)]
1696mod tests {
1697 use super::*;
1698
1699 #[test]
1700 fn style_new_is_default() {
1701 let style = Style::new();
1702 assert_eq!(style.fg, None);
1703 assert_eq!(style.bg, None);
1704 assert_eq!(style.modifiers, Modifiers::NONE);
1705 assert_eq!(style, Style::default());
1706 }
1707
1708 #[test]
1709 fn style_bold_and_fg_set_expected_fields() {
1710 let style = Style::new().bold().fg(Color::Red);
1711 assert_eq!(style.fg, Some(Color::Red));
1712 assert_eq!(style.bg, None);
1713 assert!(style.modifiers.contains(Modifiers::BOLD));
1714 }
1715
1716 #[test]
1717 fn style_multiple_modifiers_accumulate() {
1718 let style = Style::new().italic().underline().dim();
1719 assert!(style.modifiers.contains(Modifiers::ITALIC));
1720 assert!(style.modifiers.contains(Modifiers::UNDERLINE));
1721 assert!(style.modifiers.contains(Modifiers::DIM));
1722 }
1723
1724 #[test]
1725 fn modifiers_blink_overline_occupy_high_bits() {
1726 assert_eq!(Modifiers::BLINK.0, 1 << 6);
1727 assert_eq!(Modifiers::OVERLINE.0, 1 << 7);
1728 let existing = [
1730 Modifiers::BOLD,
1731 Modifiers::DIM,
1732 Modifiers::ITALIC,
1733 Modifiers::UNDERLINE,
1734 Modifiers::REVERSED,
1735 Modifiers::STRIKETHROUGH,
1736 ];
1737 for m in existing {
1738 assert_eq!(m.0 & Modifiers::BLINK.0, 0);
1739 assert_eq!(m.0 & Modifiers::OVERLINE.0, 0);
1740 }
1741 assert_eq!(Modifiers::BLINK.0 & Modifiers::OVERLINE.0, 0);
1742 }
1743
1744 #[test]
1745 fn style_blink_and_overline_accumulate() {
1746 let style = Style::new().blink().overline();
1747 assert!(style.modifiers.contains(Modifiers::BLINK));
1748 assert!(style.modifiers.contains(Modifiers::OVERLINE));
1749 }
1750
1751 #[test]
1752 fn underline_style_default_is_straight() {
1753 assert_eq!(UnderlineStyle::default(), UnderlineStyle::Straight);
1754 let s = Style::default();
1755 assert_eq!(s.underline_style, UnderlineStyle::Straight);
1756 assert_eq!(s.underline_color, None);
1757 }
1758
1759 #[test]
1760 fn underline_color_sets_field_and_implies_underline() {
1761 let s = Style::new().underline_color(Color::Red);
1762 assert_eq!(s.underline_color, Some(Color::Red));
1763 assert!(s.modifiers.contains(Modifiers::UNDERLINE));
1764 }
1765
1766 #[test]
1767 fn underline_style_sets_field_and_implies_underline() {
1768 let s = Style::new().underline_style(UnderlineStyle::Curly);
1769 assert_eq!(s.underline_style, UnderlineStyle::Curly);
1770 assert!(s.modifiers.contains(Modifiers::UNDERLINE));
1771 }
1772
1773 #[test]
1774 fn style_repeated_fg_overrides_previous_color() {
1775 let style = Style::new().fg(Color::Blue).fg(Color::Green);
1776 assert_eq!(style.fg, Some(Color::Green));
1777 }
1778
1779 #[test]
1780 fn style_repeated_bg_overrides_previous_color() {
1781 let style = Style::new().bg(Color::Blue).bg(Color::Green);
1782 assert_eq!(style.bg, Some(Color::Green));
1783 }
1784
1785 #[test]
1786 fn style_override_preserves_existing_modifiers() {
1787 let style = Style::new().bold().fg(Color::Red).fg(Color::Yellow);
1788 assert_eq!(style.fg, Some(Color::Yellow));
1789 assert!(style.modifiers.contains(Modifiers::BOLD));
1790 }
1791
1792 #[test]
1793 fn padding_all_sets_all_sides() {
1794 let p = Padding::all(3);
1795 assert_eq!(p.top, 3);
1796 assert_eq!(p.right, 3);
1797 assert_eq!(p.bottom, 3);
1798 assert_eq!(p.left, 3);
1799 }
1800
1801 #[test]
1802 fn padding_xy_sets_axis_values() {
1803 let p = Padding::xy(4, 2);
1804 assert_eq!(p.top, 2);
1805 assert_eq!(p.bottom, 2);
1806 assert_eq!(p.left, 4);
1807 assert_eq!(p.right, 4);
1808 }
1809
1810 #[test]
1811 fn padding_new_and_totals_are_correct() {
1812 let p = Padding::new(1, 2, 3, 4);
1813 assert_eq!(p.top, 1);
1814 assert_eq!(p.right, 2);
1815 assert_eq!(p.bottom, 3);
1816 assert_eq!(p.left, 4);
1817 assert_eq!(p.horizontal(), 6);
1818 assert_eq!(p.vertical(), 4);
1819 }
1820
1821 #[test]
1822 fn margin_all_and_xy_are_correct() {
1823 let all = Margin::all(5);
1824 assert_eq!(all, Margin::new(5, 5, 5, 5));
1825
1826 let xy = Margin::xy(7, 1);
1827 assert_eq!(xy.top, 1);
1828 assert_eq!(xy.bottom, 1);
1829 assert_eq!(xy.left, 7);
1830 assert_eq!(xy.right, 7);
1831 }
1832
1833 #[test]
1834 fn margin_new_and_totals_are_correct() {
1835 let m = Margin::new(2, 4, 6, 8);
1836 assert_eq!(m.horizontal(), 12);
1837 assert_eq!(m.vertical(), 8);
1838 }
1839
1840 #[test]
1841 fn constraints_min_max_builder_sets_values() {
1842 let c = Constraints::default()
1843 .min_w(10)
1844 .max_w(40)
1845 .min_h(5)
1846 .max_h(20);
1847 assert_eq!(c.min_width(), Some(10));
1848 assert_eq!(c.max_width(), Some(40));
1849 assert_eq!(c.min_height(), Some(5));
1850 assert_eq!(c.max_height(), Some(20));
1851 assert_eq!(c.width, WidthSpec::MinMax { min: 10, max: 40 });
1852 }
1853
1854 #[test]
1855 fn constraints_percentage_builder_sets_values() {
1856 let c = Constraints::default().w_pct(50).h_pct(80);
1857 assert_eq!(c.width_pct(), Some(50));
1858 assert_eq!(c.height_pct(), Some(80));
1859 assert_eq!(c.width, WidthSpec::Pct(50));
1860 assert_eq!(c.height, HeightSpec::Pct(80));
1861 }
1862
1863 #[test]
1864 fn constraints_default_is_auto() {
1865 let c = Constraints::default();
1866 assert_eq!(c.width, WidthSpec::Auto);
1867 assert_eq!(c.height, HeightSpec::Auto);
1868 }
1869
1870 #[test]
1871 fn constraints_fixed_w_h() {
1872 let c = Constraints::default().w(20).h(10);
1873 assert_eq!(c.width, WidthSpec::Fixed(20));
1874 assert_eq!(c.height, HeightSpec::Fixed(10));
1875 assert_eq!(c.min_width(), Some(20));
1876 assert_eq!(c.max_width(), Some(20));
1877 }
1878
1879 #[test]
1880 fn constraints_size_24_bytes() {
1881 assert_eq!(std::mem::size_of::<Constraints>(), 24);
1882 }
1883
1884 #[test]
1885 fn constraints_set_min_width_promotes_to_minmax() {
1886 let mut c = Constraints::default();
1887 c.set_min_width(Some(10));
1888 assert_eq!(
1889 c.width,
1890 WidthSpec::MinMax {
1891 min: 10,
1892 max: u32::MAX,
1893 }
1894 );
1895 c.set_max_width(Some(40));
1896 assert_eq!(c.width, WidthSpec::MinMax { min: 10, max: 40 });
1897 }
1898
1899 #[test]
1900 fn constraints_w_ratio_builder() {
1901 let c = Constraints::default().w_ratio(1, 3);
1902 assert_eq!(c.width, WidthSpec::Ratio(1, 3));
1903 }
1904
1905 #[test]
1906 fn border_sides_all_has_both_axes() {
1907 let sides = BorderSides::all();
1908 assert!(sides.top && sides.right && sides.bottom && sides.left);
1909 assert!(sides.has_horizontal());
1910 assert!(sides.has_vertical());
1911 }
1912
1913 #[test]
1914 fn border_sides_none_has_no_axes() {
1915 let sides = BorderSides::none();
1916 assert!(!sides.top && !sides.right && !sides.bottom && !sides.left);
1917 assert!(!sides.has_horizontal());
1918 assert!(!sides.has_vertical());
1919 }
1920
1921 #[test]
1922 fn border_sides_horizontal_only() {
1923 let sides = BorderSides::horizontal();
1924 assert!(sides.top);
1925 assert!(sides.bottom);
1926 assert!(!sides.left);
1927 assert!(!sides.right);
1928 assert!(sides.has_horizontal());
1929 assert!(!sides.has_vertical());
1930 }
1931
1932 #[test]
1933 fn border_sides_vertical_only() {
1934 let sides = BorderSides::vertical();
1935 assert!(!sides.top);
1936 assert!(!sides.bottom);
1937 assert!(sides.left);
1938 assert!(sides.right);
1939 assert!(!sides.has_horizontal());
1940 assert!(sides.has_vertical());
1941 }
1942
1943 #[test]
1944 fn container_style_new_is_empty() {
1945 let s = ContainerStyle::new();
1946 assert_eq!(s.border, None);
1947 assert_eq!(s.bg, None);
1948 assert_eq!(s.padding, None);
1949 assert_eq!(s.margin, None);
1950 assert_eq!(s.gap, None);
1951 assert_eq!(s.align, None);
1952 assert_eq!(s.justify, None);
1953 }
1954
1955 #[test]
1956 fn container_style_const_construction_and_fields() {
1957 const CARD: ContainerStyle = ContainerStyle::new()
1958 .border(Border::Rounded)
1959 .border_sides(BorderSides::horizontal())
1960 .p(2)
1961 .m(1)
1962 .gap(3)
1963 .align(Align::Center)
1964 .justify(Justify::SpaceBetween)
1965 .w(60)
1966 .h(20);
1967
1968 assert_eq!(CARD.border, Some(Border::Rounded));
1969 assert_eq!(CARD.border_sides, Some(BorderSides::horizontal()));
1970 assert_eq!(CARD.padding, Some(Padding::all(2)));
1971 assert_eq!(CARD.margin, Some(Margin::all(1)));
1972 assert_eq!(CARD.gap, Some(3));
1973 assert_eq!(CARD.align, Some(Align::Center));
1974 assert_eq!(CARD.justify, Some(Justify::SpaceBetween));
1975 assert_eq!(CARD.w, Some(60));
1976 assert_eq!(CARD.h, Some(20));
1977 }
1978
1979 #[test]
1980 fn widget_colors_new_is_empty() {
1981 let colors = WidgetColors::new();
1982 assert_eq!(colors.fg, None);
1983 assert_eq!(colors.bg, None);
1984 assert_eq!(colors.border, None);
1985 assert_eq!(colors.accent, None);
1986
1987 let defaults = WidgetColors::default();
1988 assert_eq!(defaults.fg, None);
1989 assert_eq!(defaults.bg, None);
1990 assert_eq!(defaults.border, None);
1991 assert_eq!(defaults.accent, None);
1992 }
1993
1994 #[test]
1995 fn widget_colors_builder_sets_all_fields() {
1996 let colors = WidgetColors::new()
1997 .fg(Color::White)
1998 .bg(Color::Black)
1999 .border(Color::Cyan)
2000 .accent(Color::Yellow);
2001
2002 assert_eq!(colors.fg, Some(Color::White));
2003 assert_eq!(colors.bg, Some(Color::Black));
2004 assert_eq!(colors.border, Some(Color::Cyan));
2005 assert_eq!(colors.accent, Some(Color::Yellow));
2006 }
2007
2008 #[test]
2009 fn align_default_is_start() {
2010 assert_eq!(Align::default(), Align::Start);
2011 }
2012
2013 #[test]
2014 fn justify_default_is_start() {
2015 assert_eq!(Justify::default(), Justify::Start);
2016 }
2017
2018 #[test]
2019 fn align_and_justify_variants_are_distinct() {
2020 assert_ne!(Align::Start, Align::Center);
2021 assert_ne!(Align::Center, Align::End);
2022
2023 assert_ne!(Justify::Start, Justify::Center);
2024 assert_ne!(Justify::Center, Justify::End);
2025 assert_ne!(Justify::SpaceBetween, Justify::SpaceAround);
2026 assert_ne!(Justify::SpaceAround, Justify::SpaceEvenly);
2027 }
2028}