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