Skip to main content

slt/
style.rs

1//! Visual styling primitives.
2//!
3//! Colors, themes, borders, padding, margin, constraints, alignment, and
4//! text modifiers. Every widget inherits these through [`Theme`] automatically.
5
6mod color;
7mod theme;
8pub use color::{Color, ColorDepth};
9pub use theme::{Theme, ThemeBuilder};
10
11/// Terminal size breakpoint for responsive layouts.
12///
13/// Based on the current terminal width. Use [`crate::Context::breakpoint`] to
14/// get the active breakpoint.
15#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
16pub enum Breakpoint {
17    /// Width < 40 columns (phone-sized)
18    Xs,
19    /// Width 40-79 columns (small terminal)
20    Sm,
21    /// Width 80-119 columns (standard terminal)
22    Md,
23    /// Width 120-159 columns (wide terminal)
24    Lg,
25    /// Width >= 160 columns (ultra-wide)
26    Xl,
27}
28
29/// Border style for containers.
30///
31/// Pass to `Context::bordered()` to draw a box around a container.
32/// Each variant uses a different set of Unicode box-drawing characters.
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
34#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
35pub enum Border {
36    /// Single-line box: `┌─┐│└─┘`
37    Single,
38    /// Double-line box: `╔═╗║╚═╝`
39    Double,
40    /// Rounded corners: `╭─╮│╰─╯`
41    Rounded,
42    /// Thick single-line box: `┏━┓┃┗━┛`
43    Thick,
44    /// Dashed border using light dash characters: ┄╌┄╌
45    Dashed,
46    /// Heavy dashed border: ┅╍┅╍
47    DashedThick,
48}
49
50/// Character set for a specific border style.
51///
52/// Returned by [`Border::chars`]. Contains the six box-drawing characters
53/// needed to render a complete border: four corners and two line segments.
54#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
55pub struct BorderChars {
56    /// Top-left corner character.
57    pub tl: char,
58    /// Top-right corner character.
59    pub tr: char,
60    /// Bottom-left corner character.
61    pub bl: char,
62    /// Bottom-right corner character.
63    pub br: char,
64    /// Horizontal line character.
65    pub h: char,
66    /// Vertical line character.
67    pub v: char,
68}
69
70/// Controls which sides of a border are visible.
71#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
72#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
73pub struct BorderSides {
74    pub top: bool,
75    pub right: bool,
76    pub bottom: bool,
77    pub left: bool,
78}
79
80impl BorderSides {
81    pub const fn all() -> Self {
82        Self {
83            top: true,
84            right: true,
85            bottom: true,
86            left: true,
87        }
88    }
89
90    pub const fn none() -> Self {
91        Self {
92            top: false,
93            right: false,
94            bottom: false,
95            left: false,
96        }
97    }
98
99    pub const fn horizontal() -> Self {
100        Self {
101            top: true,
102            right: false,
103            bottom: true,
104            left: false,
105        }
106    }
107
108    pub const fn vertical() -> Self {
109        Self {
110            top: false,
111            right: true,
112            bottom: false,
113            left: true,
114        }
115    }
116
117    pub fn has_horizontal(&self) -> bool {
118        self.top || self.bottom
119    }
120
121    pub fn has_vertical(&self) -> bool {
122        self.left || self.right
123    }
124}
125
126impl Default for BorderSides {
127    fn default() -> Self {
128        Self::all()
129    }
130}
131
132impl Border {
133    /// Return the [`BorderChars`] for this border style.
134    pub const fn chars(self) -> BorderChars {
135        match self {
136            Self::Single => BorderChars {
137                tl: '┌',
138                tr: '┐',
139                bl: '└',
140                br: '┘',
141                h: '─',
142                v: '│',
143            },
144            Self::Double => BorderChars {
145                tl: '╔',
146                tr: '╗',
147                bl: '╚',
148                br: '╝',
149                h: '═',
150                v: '║',
151            },
152            Self::Rounded => BorderChars {
153                tl: '╭',
154                tr: '╮',
155                bl: '╰',
156                br: '╯',
157                h: '─',
158                v: '│',
159            },
160            Self::Thick => BorderChars {
161                tl: '┏',
162                tr: '┓',
163                bl: '┗',
164                br: '┛',
165                h: '━',
166                v: '┃',
167            },
168            Self::Dashed => BorderChars {
169                tl: '┌',
170                tr: '┐',
171                bl: '└',
172                br: '┘',
173                h: '┄',
174                v: '┆',
175            },
176            Self::DashedThick => BorderChars {
177                tl: '┏',
178                tr: '┓',
179                bl: '┗',
180                br: '┛',
181                h: '┅',
182                v: '┇',
183            },
184        }
185    }
186}
187
188/// Padding inside a container border.
189///
190/// Shrinks the content area inward from each edge. All values are in terminal
191/// columns/rows.
192#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
193#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
194pub struct Padding {
195    /// Padding on the top edge.
196    pub top: u32,
197    /// Padding on the right edge.
198    pub right: u32,
199    /// Padding on the bottom edge.
200    pub bottom: u32,
201    /// Padding on the left edge.
202    pub left: u32,
203}
204
205impl Padding {
206    /// Create uniform padding on all four sides.
207    pub const fn all(v: u32) -> Self {
208        Self::new(v, v, v, v)
209    }
210
211    /// Create padding with `x` on left/right and `y` on top/bottom.
212    pub const fn xy(x: u32, y: u32) -> Self {
213        Self::new(y, x, y, x)
214    }
215
216    /// Create padding with explicit values for each side.
217    pub const fn new(top: u32, right: u32, bottom: u32, left: u32) -> Self {
218        Self {
219            top,
220            right,
221            bottom,
222            left,
223        }
224    }
225
226    /// Total horizontal padding (`left + right`).
227    pub const fn horizontal(self) -> u32 {
228        self.left + self.right
229    }
230
231    /// Total vertical padding (`top + bottom`).
232    pub const fn vertical(self) -> u32 {
233        self.top + self.bottom
234    }
235}
236
237/// Margin outside a container.
238///
239/// Adds space around the outside of a container's border. All values are in
240/// terminal columns/rows.
241#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
242#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
243pub struct Margin {
244    /// Margin on the top edge.
245    pub top: u32,
246    /// Margin on the right edge.
247    pub right: u32,
248    /// Margin on the bottom edge.
249    pub bottom: u32,
250    /// Margin on the left edge.
251    pub left: u32,
252}
253
254impl Margin {
255    /// Create uniform margin on all four sides.
256    pub const fn all(v: u32) -> Self {
257        Self::new(v, v, v, v)
258    }
259
260    /// Create margin with `x` on left/right and `y` on top/bottom.
261    pub const fn xy(x: u32, y: u32) -> Self {
262        Self::new(y, x, y, x)
263    }
264
265    /// Create margin with explicit values for each side.
266    pub const fn new(top: u32, right: u32, bottom: u32, left: u32) -> Self {
267        Self {
268            top,
269            right,
270            bottom,
271            left,
272        }
273    }
274
275    /// Total horizontal margin (`left + right`).
276    pub const fn horizontal(self) -> u32 {
277        self.left + self.right
278    }
279
280    /// Total vertical margin (`top + bottom`).
281    pub const fn vertical(self) -> u32 {
282        self.top + self.bottom
283    }
284}
285
286/// Size constraints for layout computation.
287///
288/// All fields are optional. Unset constraints are unconstrained. Use the
289/// builder methods to set individual bounds in a fluent style.
290///
291/// # Example
292///
293/// ```
294/// use slt::Constraints;
295///
296/// let c = Constraints::default().min_w(10).max_w(40);
297/// ```
298#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
299#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
300#[must_use = "configure constraints using the returned value"]
301pub struct Constraints {
302    /// Minimum width in terminal columns, if any.
303    pub min_width: Option<u32>,
304    /// Maximum width in terminal columns, if any.
305    pub max_width: Option<u32>,
306    /// Minimum height in terminal rows, if any.
307    pub min_height: Option<u32>,
308    /// Maximum height in terminal rows, if any.
309    pub max_height: Option<u32>,
310    /// Width as a percentage (1-100) of the parent container.
311    pub width_pct: Option<u8>,
312    /// Height as a percentage (1-100) of the parent container.
313    pub height_pct: Option<u8>,
314}
315
316impl Constraints {
317    /// Set the minimum width constraint.
318    pub const fn min_w(mut self, min_width: u32) -> Self {
319        self.min_width = Some(min_width);
320        self
321    }
322
323    /// Set the maximum width constraint.
324    pub const fn max_w(mut self, max_width: u32) -> Self {
325        self.max_width = Some(max_width);
326        self
327    }
328
329    /// Set the minimum height constraint.
330    pub const fn min_h(mut self, min_height: u32) -> Self {
331        self.min_height = Some(min_height);
332        self
333    }
334
335    /// Set the maximum height constraint.
336    pub const fn max_h(mut self, max_height: u32) -> Self {
337        self.max_height = Some(max_height);
338        self
339    }
340
341    /// Set width as a percentage (1-100) of the parent container.
342    pub const fn w_pct(mut self, pct: u8) -> Self {
343        self.width_pct = Some(pct);
344        self
345    }
346
347    /// Set height as a percentage (1-100) of the parent container.
348    pub const fn h_pct(mut self, pct: u8) -> Self {
349        self.height_pct = Some(pct);
350        self
351    }
352}
353
354/// Cross-axis alignment within a container.
355///
356/// Controls how children are positioned along the axis perpendicular to the
357/// container's main axis. For a `row()`, this is vertical alignment; for a
358/// `col()`, this is horizontal alignment.
359#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
360#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
361pub enum Align {
362    /// Align children to the start of the cross axis (default).
363    #[default]
364    Start,
365    /// Center children on the cross axis.
366    Center,
367    /// Align children to the end of the cross axis.
368    End,
369}
370
371/// Main-axis content distribution within a container.
372///
373/// Controls how children are distributed along the main axis. For a `row()`,
374/// this is horizontal distribution; for a `col()`, this is vertical.
375///
376/// When children have `grow > 0`, they consume remaining space before justify
377/// distribution applies. Justify modes only affect the leftover space after
378/// flex-grow allocation.
379#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
380#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
381pub enum Justify {
382    /// Pack children at the start (default). Uses `gap` for spacing.
383    #[default]
384    Start,
385    /// Center children along the main axis with `gap` spacing.
386    Center,
387    /// Pack children at the end with `gap` spacing.
388    End,
389    /// First child at start, last at end, equal space between.
390    SpaceBetween,
391    /// Equal space around each child (half-size space at edges).
392    SpaceAround,
393    /// Equal space between all children and at both edges.
394    SpaceEvenly,
395}
396
397/// Text modifier bitflags stored as a `u8`.
398///
399/// Combine modifiers with `|` or [`Modifiers::insert`]. Check membership with
400/// [`Modifiers::contains`].
401#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
402#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
403#[cfg_attr(feature = "serde", serde(transparent))]
404pub struct Modifiers(pub u8);
405
406impl Modifiers {
407    /// No modifiers set.
408    pub const NONE: Self = Self(0);
409    /// Enable bold text.
410    pub const BOLD: Self = Self(1 << 0);
411    /// Enable dimmed/faint text.
412    pub const DIM: Self = Self(1 << 1);
413    /// Enable italic text.
414    pub const ITALIC: Self = Self(1 << 2);
415    /// Enable underlined text.
416    pub const UNDERLINE: Self = Self(1 << 3);
417    /// Enable reversed foreground/background colors.
418    pub const REVERSED: Self = Self(1 << 4);
419    /// Enable strikethrough text.
420    pub const STRIKETHROUGH: Self = Self(1 << 5);
421
422    /// Returns `true` if all bits in `other` are set in `self`.
423    #[inline]
424    pub fn contains(self, other: Self) -> bool {
425        (self.0 & other.0) == other.0
426    }
427
428    /// Set all bits from `other` into `self`.
429    #[inline]
430    pub fn insert(&mut self, other: Self) {
431        self.0 |= other.0;
432    }
433
434    /// Returns `true` if no modifiers are set.
435    #[inline]
436    pub fn is_empty(self) -> bool {
437        self.0 == 0
438    }
439}
440
441impl std::ops::BitOr for Modifiers {
442    type Output = Self;
443    #[inline]
444    fn bitor(self, rhs: Self) -> Self {
445        Self(self.0 | rhs.0)
446    }
447}
448
449impl std::ops::BitOrAssign for Modifiers {
450    #[inline]
451    fn bitor_assign(&mut self, rhs: Self) {
452        self.0 |= rhs.0;
453    }
454}
455
456/// Visual style for a terminal cell (foreground, background, modifiers).
457///
458/// Styles are applied to text via the builder methods on `Context` widget
459/// calls (e.g., `.bold()`, `.fg(Color::Cyan)`). All fields are optional;
460/// `None` means "inherit from the terminal default."
461///
462/// # Example
463///
464/// ```
465/// use slt::{Style, Color};
466///
467/// let style = Style::new().fg(Color::Cyan).bold();
468/// ```
469#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
470#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
471#[must_use = "build and pass the returned Style value"]
472pub struct Style {
473    /// Foreground color, or `None` to use the terminal default.
474    pub fg: Option<Color>,
475    /// Background color, or `None` to use the terminal default.
476    pub bg: Option<Color>,
477    /// Text modifiers (bold, italic, underline, etc.).
478    pub modifiers: Modifiers,
479}
480
481impl Style {
482    /// Create a new style with no color or modifiers set.
483    pub const fn new() -> Self {
484        Self {
485            fg: None,
486            bg: None,
487            modifiers: Modifiers::NONE,
488        }
489    }
490
491    /// Set the foreground color.
492    pub const fn fg(mut self, color: Color) -> Self {
493        self.fg = Some(color);
494        self
495    }
496
497    /// Set the background color.
498    pub const fn bg(mut self, color: Color) -> Self {
499        self.bg = Some(color);
500        self
501    }
502
503    /// Add the bold modifier.
504    pub fn bold(mut self) -> Self {
505        self.modifiers |= Modifiers::BOLD;
506        self
507    }
508
509    /// Add the dim modifier.
510    pub fn dim(mut self) -> Self {
511        self.modifiers |= Modifiers::DIM;
512        self
513    }
514
515    /// Add the italic modifier.
516    pub fn italic(mut self) -> Self {
517        self.modifiers |= Modifiers::ITALIC;
518        self
519    }
520
521    /// Add the underline modifier.
522    pub fn underline(mut self) -> Self {
523        self.modifiers |= Modifiers::UNDERLINE;
524        self
525    }
526
527    /// Add the reversed (inverted colors) modifier.
528    pub fn reversed(mut self) -> Self {
529        self.modifiers |= Modifiers::REVERSED;
530        self
531    }
532
533    /// Add the strikethrough modifier.
534    pub fn strikethrough(mut self) -> Self {
535        self.modifiers |= Modifiers::STRIKETHROUGH;
536        self
537    }
538}
539
540/// Reusable container style recipe.
541///
542/// Define once, apply anywhere with [`crate::ContainerBuilder::apply`]. All fields
543/// are optional — only set fields override the builder's current values.
544/// Styles compose: apply multiple recipes in sequence, last write wins.
545///
546/// # Example
547///
548/// ```ignore
549/// use slt::{ContainerStyle, Border, Color};
550///
551/// const CARD: ContainerStyle = ContainerStyle::new()
552///     .border(Border::Rounded)
553///     .p(1)
554///     .bg(Color::Indexed(236));
555///
556/// const DANGER: ContainerStyle = ContainerStyle::new()
557///     .bg(Color::Red);
558///
559/// // Apply one or compose multiple:
560/// ui.container().apply(&CARD).col(|ui| { ... });
561/// ui.container().apply(&CARD).apply(&DANGER).col(|ui| { ... });
562/// ```
563#[derive(Debug, Clone, Copy, Default)]
564pub struct ContainerStyle {
565    /// Border style for the container.
566    pub border: Option<Border>,
567    /// Which sides of the border are visible.
568    pub border_sides: Option<BorderSides>,
569    /// Style (color and modifiers) for the border.
570    pub border_style: Option<Style>,
571    /// Background color.
572    pub bg: Option<Color>,
573    /// Foreground (text) color.
574    pub text_color: Option<Color>,
575    /// Background color in dark mode.
576    pub dark_bg: Option<Color>,
577    /// Border style in dark mode.
578    pub dark_border_style: Option<Style>,
579    /// Padding inside the container.
580    pub padding: Option<Padding>,
581    /// Margin outside the container.
582    pub margin: Option<Margin>,
583    /// Gap between children (both row and column).
584    pub gap: Option<u32>,
585    /// Gap between rows.
586    pub row_gap: Option<u32>,
587    /// Gap between columns.
588    pub col_gap: Option<u32>,
589    /// Flex grow factor.
590    pub grow: Option<u16>,
591    /// Cross-axis alignment.
592    pub align: Option<Align>,
593    /// Self alignment (overrides parent align).
594    pub align_self: Option<Align>,
595    /// Main-axis content distribution.
596    pub justify: Option<Justify>,
597    /// Fixed width.
598    pub w: Option<u32>,
599    /// Fixed height.
600    pub h: Option<u32>,
601    /// Minimum width.
602    pub min_w: Option<u32>,
603    /// Maximum width.
604    pub max_w: Option<u32>,
605    /// Minimum height.
606    pub min_h: Option<u32>,
607    /// Maximum height.
608    pub max_h: Option<u32>,
609    /// Width as percentage of parent.
610    pub w_pct: Option<u8>,
611    /// Height as percentage of parent.
612    pub h_pct: Option<u8>,
613}
614
615impl ContainerStyle {
616    /// Create an empty container style with no overrides.
617    pub const fn new() -> Self {
618        Self {
619            border: None,
620            border_sides: None,
621            border_style: None,
622            bg: None,
623            text_color: None,
624            dark_bg: None,
625            dark_border_style: None,
626            padding: None,
627            margin: None,
628            gap: None,
629            row_gap: None,
630            col_gap: None,
631            grow: None,
632            align: None,
633            align_self: None,
634            justify: None,
635            w: None,
636            h: None,
637            min_w: None,
638            max_w: None,
639            min_h: None,
640            max_h: None,
641            w_pct: None,
642            h_pct: None,
643        }
644    }
645
646    /// Set the border style.
647    pub const fn border(mut self, border: Border) -> Self {
648        self.border = Some(border);
649        self
650    }
651
652    /// Set which border sides to render.
653    pub const fn border_sides(mut self, sides: BorderSides) -> Self {
654        self.border_sides = Some(sides);
655        self
656    }
657
658    /// Set the background color.
659    pub const fn bg(mut self, color: Color) -> Self {
660        self.bg = Some(color);
661        self
662    }
663
664    /// Set default text color inherited by child text widgets.
665    pub const fn text_color(mut self, color: Color) -> Self {
666        self.text_color = Some(color);
667        self
668    }
669
670    /// Set the dark-mode background color.
671    pub const fn dark_bg(mut self, color: Color) -> Self {
672        self.dark_bg = Some(color);
673        self
674    }
675
676    /// Set uniform padding on all sides.
677    pub const fn p(mut self, value: u32) -> Self {
678        self.padding = Some(Padding {
679            top: value,
680            bottom: value,
681            left: value,
682            right: value,
683        });
684        self
685    }
686
687    /// Set horizontal padding.
688    pub const fn px(mut self, value: u32) -> Self {
689        let p = match self.padding {
690            Some(p) => Padding {
691                left: value,
692                right: value,
693                ..p
694            },
695            None => Padding {
696                top: 0,
697                bottom: 0,
698                left: value,
699                right: value,
700            },
701        };
702        self.padding = Some(p);
703        self
704    }
705
706    /// Set vertical padding.
707    pub const fn py(mut self, value: u32) -> Self {
708        let p = match self.padding {
709            Some(p) => Padding {
710                top: value,
711                bottom: value,
712                ..p
713            },
714            None => Padding {
715                top: value,
716                bottom: value,
717                left: 0,
718                right: 0,
719            },
720        };
721        self.padding = Some(p);
722        self
723    }
724
725    /// Set uniform margin on all sides.
726    pub const fn m(mut self, value: u32) -> Self {
727        self.margin = Some(Margin {
728            top: value,
729            bottom: value,
730            left: value,
731            right: value,
732        });
733        self
734    }
735
736    /// Set the gap between children.
737    pub const fn gap(mut self, value: u32) -> Self {
738        self.gap = Some(value);
739        self
740    }
741
742    /// Set row gap for column layouts.
743    pub const fn row_gap(mut self, value: u32) -> Self {
744        self.row_gap = Some(value);
745        self
746    }
747
748    /// Set column gap for row layouts.
749    pub const fn col_gap(mut self, value: u32) -> Self {
750        self.col_gap = Some(value);
751        self
752    }
753
754    /// Set the flex-grow factor.
755    pub const fn grow(mut self, value: u16) -> Self {
756        self.grow = Some(value);
757        self
758    }
759
760    /// Set fixed width.
761    pub const fn w(mut self, value: u32) -> Self {
762        self.w = Some(value);
763        self
764    }
765
766    /// Set fixed height.
767    pub const fn h(mut self, value: u32) -> Self {
768        self.h = Some(value);
769        self
770    }
771
772    /// Set minimum width.
773    pub const fn min_w(mut self, value: u32) -> Self {
774        self.min_w = Some(value);
775        self
776    }
777
778    /// Set maximum width.
779    pub const fn max_w(mut self, value: u32) -> Self {
780        self.max_w = Some(value);
781        self
782    }
783
784    /// Set cross-axis alignment.
785    pub const fn align(mut self, value: Align) -> Self {
786        self.align = Some(value);
787        self
788    }
789
790    /// Set per-child cross-axis alignment override.
791    pub const fn align_self(mut self, value: Align) -> Self {
792        self.align_self = Some(value);
793        self
794    }
795
796    /// Set main-axis justification.
797    pub const fn justify(mut self, value: Justify) -> Self {
798        self.justify = Some(value);
799        self
800    }
801
802    /// Set minimum height.
803    pub const fn min_h(mut self, value: u32) -> Self {
804        self.min_h = Some(value);
805        self
806    }
807
808    /// Set maximum height.
809    pub const fn max_h(mut self, value: u32) -> Self {
810        self.max_h = Some(value);
811        self
812    }
813
814    /// Set width as percentage of parent (1-100).
815    pub const fn w_pct(mut self, value: u8) -> Self {
816        self.w_pct = Some(value);
817        self
818    }
819
820    /// Set height as percentage of parent (1-100).
821    pub const fn h_pct(mut self, value: u8) -> Self {
822        self.h_pct = Some(value);
823        self
824    }
825}
826
827#[derive(Debug, Clone, Copy, Default)]
828#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
829pub struct WidgetColors {
830    /// Foreground color override.
831    pub fg: Option<Color>,
832    /// Background color override.
833    pub bg: Option<Color>,
834    /// Border color override.
835    pub border: Option<Color>,
836    /// Accent color override.
837    pub accent: Option<Color>,
838}
839
840impl WidgetColors {
841    /// Create a new WidgetColors with all fields set to None (theme defaults).
842    pub const fn new() -> Self {
843        Self {
844            fg: None,
845            bg: None,
846            border: None,
847            accent: None,
848        }
849    }
850
851    /// Set the foreground color override.
852    pub const fn fg(mut self, color: Color) -> Self {
853        self.fg = Some(color);
854        self
855    }
856
857    /// Set the background color override.
858    pub const fn bg(mut self, color: Color) -> Self {
859        self.bg = Some(color);
860        self
861    }
862
863    /// Set the border color override.
864    pub const fn border(mut self, color: Color) -> Self {
865        self.border = Some(color);
866        self
867    }
868
869    /// Set the accent color override.
870    pub const fn accent(mut self, color: Color) -> Self {
871        self.accent = Some(color);
872        self
873    }
874}
875
876#[cfg(test)]
877mod tests {
878    use super::*;
879
880    #[test]
881    fn style_new_is_default() {
882        let style = Style::new();
883        assert_eq!(style.fg, None);
884        assert_eq!(style.bg, None);
885        assert_eq!(style.modifiers, Modifiers::NONE);
886        assert_eq!(style, Style::default());
887    }
888
889    #[test]
890    fn style_bold_and_fg_set_expected_fields() {
891        let style = Style::new().bold().fg(Color::Red);
892        assert_eq!(style.fg, Some(Color::Red));
893        assert_eq!(style.bg, None);
894        assert!(style.modifiers.contains(Modifiers::BOLD));
895    }
896
897    #[test]
898    fn style_multiple_modifiers_accumulate() {
899        let style = Style::new().italic().underline().dim();
900        assert!(style.modifiers.contains(Modifiers::ITALIC));
901        assert!(style.modifiers.contains(Modifiers::UNDERLINE));
902        assert!(style.modifiers.contains(Modifiers::DIM));
903    }
904
905    #[test]
906    fn style_repeated_fg_overrides_previous_color() {
907        let style = Style::new().fg(Color::Blue).fg(Color::Green);
908        assert_eq!(style.fg, Some(Color::Green));
909    }
910
911    #[test]
912    fn style_repeated_bg_overrides_previous_color() {
913        let style = Style::new().bg(Color::Blue).bg(Color::Green);
914        assert_eq!(style.bg, Some(Color::Green));
915    }
916
917    #[test]
918    fn style_override_preserves_existing_modifiers() {
919        let style = Style::new().bold().fg(Color::Red).fg(Color::Yellow);
920        assert_eq!(style.fg, Some(Color::Yellow));
921        assert!(style.modifiers.contains(Modifiers::BOLD));
922    }
923
924    #[test]
925    fn padding_all_sets_all_sides() {
926        let p = Padding::all(3);
927        assert_eq!(p.top, 3);
928        assert_eq!(p.right, 3);
929        assert_eq!(p.bottom, 3);
930        assert_eq!(p.left, 3);
931    }
932
933    #[test]
934    fn padding_xy_sets_axis_values() {
935        let p = Padding::xy(4, 2);
936        assert_eq!(p.top, 2);
937        assert_eq!(p.bottom, 2);
938        assert_eq!(p.left, 4);
939        assert_eq!(p.right, 4);
940    }
941
942    #[test]
943    fn padding_new_and_totals_are_correct() {
944        let p = Padding::new(1, 2, 3, 4);
945        assert_eq!(p.top, 1);
946        assert_eq!(p.right, 2);
947        assert_eq!(p.bottom, 3);
948        assert_eq!(p.left, 4);
949        assert_eq!(p.horizontal(), 6);
950        assert_eq!(p.vertical(), 4);
951    }
952
953    #[test]
954    fn margin_all_and_xy_are_correct() {
955        let all = Margin::all(5);
956        assert_eq!(all, Margin::new(5, 5, 5, 5));
957
958        let xy = Margin::xy(7, 1);
959        assert_eq!(xy.top, 1);
960        assert_eq!(xy.bottom, 1);
961        assert_eq!(xy.left, 7);
962        assert_eq!(xy.right, 7);
963    }
964
965    #[test]
966    fn margin_new_and_totals_are_correct() {
967        let m = Margin::new(2, 4, 6, 8);
968        assert_eq!(m.horizontal(), 12);
969        assert_eq!(m.vertical(), 8);
970    }
971
972    #[test]
973    fn constraints_min_max_builder_sets_values() {
974        let c = Constraints::default()
975            .min_w(10)
976            .max_w(40)
977            .min_h(5)
978            .max_h(20);
979        assert_eq!(c.min_width, Some(10));
980        assert_eq!(c.max_width, Some(40));
981        assert_eq!(c.min_height, Some(5));
982        assert_eq!(c.max_height, Some(20));
983    }
984
985    #[test]
986    fn constraints_percentage_builder_sets_values() {
987        let c = Constraints::default().w_pct(50).h_pct(80);
988        assert_eq!(c.width_pct, Some(50));
989        assert_eq!(c.height_pct, Some(80));
990    }
991
992    #[test]
993    fn border_sides_all_has_both_axes() {
994        let sides = BorderSides::all();
995        assert!(sides.top && sides.right && sides.bottom && sides.left);
996        assert!(sides.has_horizontal());
997        assert!(sides.has_vertical());
998    }
999
1000    #[test]
1001    fn border_sides_none_has_no_axes() {
1002        let sides = BorderSides::none();
1003        assert!(!sides.top && !sides.right && !sides.bottom && !sides.left);
1004        assert!(!sides.has_horizontal());
1005        assert!(!sides.has_vertical());
1006    }
1007
1008    #[test]
1009    fn border_sides_horizontal_only() {
1010        let sides = BorderSides::horizontal();
1011        assert!(sides.top);
1012        assert!(sides.bottom);
1013        assert!(!sides.left);
1014        assert!(!sides.right);
1015        assert!(sides.has_horizontal());
1016        assert!(!sides.has_vertical());
1017    }
1018
1019    #[test]
1020    fn border_sides_vertical_only() {
1021        let sides = BorderSides::vertical();
1022        assert!(!sides.top);
1023        assert!(!sides.bottom);
1024        assert!(sides.left);
1025        assert!(sides.right);
1026        assert!(!sides.has_horizontal());
1027        assert!(sides.has_vertical());
1028    }
1029
1030    #[test]
1031    fn container_style_new_is_empty() {
1032        let s = ContainerStyle::new();
1033        assert_eq!(s.border, None);
1034        assert_eq!(s.bg, None);
1035        assert_eq!(s.padding, None);
1036        assert_eq!(s.margin, None);
1037        assert_eq!(s.gap, None);
1038        assert_eq!(s.align, None);
1039        assert_eq!(s.justify, None);
1040    }
1041
1042    #[test]
1043    fn container_style_const_construction_and_fields() {
1044        const CARD: ContainerStyle = ContainerStyle::new()
1045            .border(Border::Rounded)
1046            .border_sides(BorderSides::horizontal())
1047            .p(2)
1048            .m(1)
1049            .gap(3)
1050            .align(Align::Center)
1051            .justify(Justify::SpaceBetween)
1052            .w(60)
1053            .h(20);
1054
1055        assert_eq!(CARD.border, Some(Border::Rounded));
1056        assert_eq!(CARD.border_sides, Some(BorderSides::horizontal()));
1057        assert_eq!(CARD.padding, Some(Padding::all(2)));
1058        assert_eq!(CARD.margin, Some(Margin::all(1)));
1059        assert_eq!(CARD.gap, Some(3));
1060        assert_eq!(CARD.align, Some(Align::Center));
1061        assert_eq!(CARD.justify, Some(Justify::SpaceBetween));
1062        assert_eq!(CARD.w, Some(60));
1063        assert_eq!(CARD.h, Some(20));
1064    }
1065
1066    #[test]
1067    fn widget_colors_new_is_empty() {
1068        let colors = WidgetColors::new();
1069        assert_eq!(colors.fg, None);
1070        assert_eq!(colors.bg, None);
1071        assert_eq!(colors.border, None);
1072        assert_eq!(colors.accent, None);
1073
1074        let defaults = WidgetColors::default();
1075        assert_eq!(defaults.fg, None);
1076        assert_eq!(defaults.bg, None);
1077        assert_eq!(defaults.border, None);
1078        assert_eq!(defaults.accent, None);
1079    }
1080
1081    #[test]
1082    fn widget_colors_builder_sets_all_fields() {
1083        let colors = WidgetColors::new()
1084            .fg(Color::White)
1085            .bg(Color::Black)
1086            .border(Color::Cyan)
1087            .accent(Color::Yellow);
1088
1089        assert_eq!(colors.fg, Some(Color::White));
1090        assert_eq!(colors.bg, Some(Color::Black));
1091        assert_eq!(colors.border, Some(Color::Cyan));
1092        assert_eq!(colors.accent, Some(Color::Yellow));
1093    }
1094
1095    #[test]
1096    fn align_default_is_start() {
1097        assert_eq!(Align::default(), Align::Start);
1098    }
1099
1100    #[test]
1101    fn justify_default_is_start() {
1102        assert_eq!(Justify::default(), Justify::Start);
1103    }
1104
1105    #[test]
1106    fn align_and_justify_variants_are_distinct() {
1107        assert_ne!(Align::Start, Align::Center);
1108        assert_ne!(Align::Center, Align::End);
1109
1110        assert_ne!(Justify::Start, Justify::Center);
1111        assert_ne!(Justify::Center, Justify::End);
1112        assert_ne!(Justify::SpaceBetween, Justify::SpaceAround);
1113        assert_ne!(Justify::SpaceAround, Justify::SpaceEvenly);
1114    }
1115}