Skip to main content

slt/
style.rs

1/// Terminal color.
2///
3/// Covers the standard 16 named colors, 256-color palette indices, and
4/// 24-bit RGB true color. Use [`Color::Reset`] to restore the terminal's
5/// default foreground or background.
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
7pub enum Color {
8    /// Reset to the terminal's default color.
9    Reset,
10    /// Standard black (color index 0).
11    Black,
12    /// Standard red (color index 1).
13    Red,
14    /// Standard green (color index 2).
15    Green,
16    /// Standard yellow (color index 3).
17    Yellow,
18    /// Standard blue (color index 4).
19    Blue,
20    /// Standard magenta (color index 5).
21    Magenta,
22    /// Standard cyan (color index 6).
23    Cyan,
24    /// Standard white (color index 7).
25    White,
26    /// 24-bit true color.
27    Rgb(u8, u8, u8),
28    /// 256-color palette index.
29    Indexed(u8),
30}
31
32/// A color theme that flows through all widgets automatically.
33///
34/// Construct with [`Theme::dark()`] or [`Theme::light()`], or build a custom
35/// theme by filling in the fields directly. Pass the theme via [`crate::RunConfig`]
36/// and every widget will pick up the colors without any extra wiring.
37#[derive(Debug, Clone, Copy)]
38pub struct Theme {
39    /// Primary accent color, used for focused borders and highlights.
40    pub primary: Color,
41    /// Secondary accent color, used for less prominent highlights.
42    pub secondary: Color,
43    /// Accent color for decorative elements.
44    pub accent: Color,
45    /// Default foreground text color.
46    pub text: Color,
47    /// Dimmed text color for secondary labels and hints.
48    pub text_dim: Color,
49    /// Border color for unfocused containers.
50    pub border: Color,
51    /// Background color. Typically [`Color::Reset`] to inherit the terminal background.
52    pub bg: Color,
53    /// Color for success states (e.g., toast notifications).
54    pub success: Color,
55    /// Color for warning states.
56    pub warning: Color,
57    /// Color for error states.
58    pub error: Color,
59    /// Background color for selected list/table rows.
60    pub selected_bg: Color,
61    /// Foreground color for selected list/table rows.
62    pub selected_fg: Color,
63}
64
65impl Theme {
66    /// Create a dark theme with cyan primary and white text.
67    pub fn dark() -> Self {
68        Self {
69            primary: Color::Cyan,
70            secondary: Color::Blue,
71            accent: Color::Magenta,
72            text: Color::White,
73            text_dim: Color::Indexed(245),
74            border: Color::Indexed(240),
75            bg: Color::Reset,
76            success: Color::Green,
77            warning: Color::Yellow,
78            error: Color::Red,
79            selected_bg: Color::Cyan,
80            selected_fg: Color::Black,
81        }
82    }
83
84    /// Create a light theme with blue primary and black text.
85    pub fn light() -> Self {
86        Self {
87            primary: Color::Blue,
88            secondary: Color::Cyan,
89            accent: Color::Magenta,
90            text: Color::Black,
91            text_dim: Color::Indexed(240),
92            border: Color::Indexed(245),
93            bg: Color::Reset,
94            success: Color::Green,
95            warning: Color::Yellow,
96            error: Color::Red,
97            selected_bg: Color::Blue,
98            selected_fg: Color::White,
99        }
100    }
101}
102
103impl Default for Theme {
104    fn default() -> Self {
105        Self::dark()
106    }
107}
108
109/// Border style for containers.
110///
111/// Pass to `Context::bordered()` to draw a box around a container.
112/// Each variant uses a different set of Unicode box-drawing characters.
113#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
114pub enum Border {
115    /// Single-line box: `┌─┐│└─┘`
116    Single,
117    /// Double-line box: `╔═╗║╚═╝`
118    Double,
119    /// Rounded corners: `╭─╮│╰─╯`
120    Rounded,
121    /// Thick single-line box: `┏━┓┃┗━┛`
122    Thick,
123}
124
125/// Character set for a specific border style.
126///
127/// Returned by [`Border::chars`]. Contains the six box-drawing characters
128/// needed to render a complete border: four corners and two line segments.
129#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
130pub struct BorderChars {
131    /// Top-left corner character.
132    pub tl: char,
133    /// Top-right corner character.
134    pub tr: char,
135    /// Bottom-left corner character.
136    pub bl: char,
137    /// Bottom-right corner character.
138    pub br: char,
139    /// Horizontal line character.
140    pub h: char,
141    /// Vertical line character.
142    pub v: char,
143}
144
145impl Border {
146    /// Return the [`BorderChars`] for this border style.
147    pub const fn chars(self) -> BorderChars {
148        match self {
149            Self::Single => BorderChars {
150                tl: '┌',
151                tr: '┐',
152                bl: '└',
153                br: '┘',
154                h: '─',
155                v: '│',
156            },
157            Self::Double => BorderChars {
158                tl: '╔',
159                tr: '╗',
160                bl: '╚',
161                br: '╝',
162                h: '═',
163                v: '║',
164            },
165            Self::Rounded => BorderChars {
166                tl: '╭',
167                tr: '╮',
168                bl: '╰',
169                br: '╯',
170                h: '─',
171                v: '│',
172            },
173            Self::Thick => BorderChars {
174                tl: '┏',
175                tr: '┓',
176                bl: '┗',
177                br: '┛',
178                h: '━',
179                v: '┃',
180            },
181        }
182    }
183}
184
185/// Padding inside a container border.
186///
187/// Shrinks the content area inward from each edge. All values are in terminal
188/// columns/rows.
189#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
190pub struct Padding {
191    /// Padding on the top edge.
192    pub top: u32,
193    /// Padding on the right edge.
194    pub right: u32,
195    /// Padding on the bottom edge.
196    pub bottom: u32,
197    /// Padding on the left edge.
198    pub left: u32,
199}
200
201impl Padding {
202    /// Create uniform padding on all four sides.
203    pub const fn all(v: u32) -> Self {
204        Self::new(v, v, v, v)
205    }
206
207    /// Create padding with `x` on left/right and `y` on top/bottom.
208    pub const fn xy(x: u32, y: u32) -> Self {
209        Self::new(y, x, y, x)
210    }
211
212    /// Create padding with explicit values for each side.
213    pub const fn new(top: u32, right: u32, bottom: u32, left: u32) -> Self {
214        Self {
215            top,
216            right,
217            bottom,
218            left,
219        }
220    }
221
222    /// Total horizontal padding (`left + right`).
223    pub const fn horizontal(self) -> u32 {
224        self.left + self.right
225    }
226
227    /// Total vertical padding (`top + bottom`).
228    pub const fn vertical(self) -> u32 {
229        self.top + self.bottom
230    }
231}
232
233/// Margin outside a container.
234///
235/// Adds space around the outside of a container's border. All values are in
236/// terminal columns/rows.
237#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
238pub struct Margin {
239    /// Margin on the top edge.
240    pub top: u32,
241    /// Margin on the right edge.
242    pub right: u32,
243    /// Margin on the bottom edge.
244    pub bottom: u32,
245    /// Margin on the left edge.
246    pub left: u32,
247}
248
249impl Margin {
250    /// Create uniform margin on all four sides.
251    pub const fn all(v: u32) -> Self {
252        Self::new(v, v, v, v)
253    }
254
255    /// Create margin with `x` on left/right and `y` on top/bottom.
256    pub const fn xy(x: u32, y: u32) -> Self {
257        Self::new(y, x, y, x)
258    }
259
260    /// Create margin with explicit values for each side.
261    pub const fn new(top: u32, right: u32, bottom: u32, left: u32) -> Self {
262        Self {
263            top,
264            right,
265            bottom,
266            left,
267        }
268    }
269
270    /// Total horizontal margin (`left + right`).
271    pub const fn horizontal(self) -> u32 {
272        self.left + self.right
273    }
274
275    /// Total vertical margin (`top + bottom`).
276    pub const fn vertical(self) -> u32 {
277        self.top + self.bottom
278    }
279}
280
281/// Size constraints for layout computation.
282///
283/// All fields are optional. Unset constraints are unconstrained. Use the
284/// builder methods to set individual bounds in a fluent style.
285///
286/// # Example
287///
288/// ```
289/// use slt::Constraints;
290///
291/// let c = Constraints::default().min_w(10).max_w(40);
292/// ```
293#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
294pub struct Constraints {
295    /// Minimum width in terminal columns, if any.
296    pub min_width: Option<u32>,
297    /// Maximum width in terminal columns, if any.
298    pub max_width: Option<u32>,
299    /// Minimum height in terminal rows, if any.
300    pub min_height: Option<u32>,
301    /// Maximum height in terminal rows, if any.
302    pub max_height: Option<u32>,
303}
304
305impl Constraints {
306    /// Set the minimum width constraint.
307    pub const fn min_w(mut self, min_width: u32) -> Self {
308        self.min_width = Some(min_width);
309        self
310    }
311
312    /// Set the maximum width constraint.
313    pub const fn max_w(mut self, max_width: u32) -> Self {
314        self.max_width = Some(max_width);
315        self
316    }
317
318    /// Set the minimum height constraint.
319    pub const fn min_h(mut self, min_height: u32) -> Self {
320        self.min_height = Some(min_height);
321        self
322    }
323
324    /// Set the maximum height constraint.
325    pub const fn max_h(mut self, max_height: u32) -> Self {
326        self.max_height = Some(max_height);
327        self
328    }
329}
330
331/// Cross-axis alignment within a container.
332///
333/// Controls how children are positioned along the axis perpendicular to the
334/// container's main axis. For a `row()`, this is vertical alignment; for a
335/// `col()`, this is horizontal alignment.
336#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
337pub enum Align {
338    /// Align children to the start of the cross axis (default).
339    #[default]
340    Start,
341    /// Center children on the cross axis.
342    Center,
343    /// Align children to the end of the cross axis.
344    End,
345}
346
347/// Text modifier bitflags stored as a `u8`.
348///
349/// Combine modifiers with `|` or [`Modifiers::insert`]. Check membership with
350/// [`Modifiers::contains`].
351#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
352pub struct Modifiers(pub u8);
353
354impl Modifiers {
355    /// No modifiers set.
356    pub const NONE: Self = Self(0);
357    /// Bold text.
358    pub const BOLD: Self = Self(1 << 0);
359    /// Dimmed/faint text.
360    pub const DIM: Self = Self(1 << 1);
361    /// Italic text.
362    pub const ITALIC: Self = Self(1 << 2);
363    /// Underlined text.
364    pub const UNDERLINE: Self = Self(1 << 3);
365    /// Reversed foreground/background colors.
366    pub const REVERSED: Self = Self(1 << 4);
367    /// Strikethrough text.
368    pub const STRIKETHROUGH: Self = Self(1 << 5);
369
370    /// Returns `true` if all bits in `other` are set in `self`.
371    #[inline]
372    pub fn contains(self, other: Self) -> bool {
373        (self.0 & other.0) == other.0
374    }
375
376    /// Set all bits from `other` into `self`.
377    #[inline]
378    pub fn insert(&mut self, other: Self) {
379        self.0 |= other.0;
380    }
381
382    /// Returns `true` if no modifiers are set.
383    #[inline]
384    pub fn is_empty(self) -> bool {
385        self.0 == 0
386    }
387}
388
389impl std::ops::BitOr for Modifiers {
390    type Output = Self;
391    #[inline]
392    fn bitor(self, rhs: Self) -> Self {
393        Self(self.0 | rhs.0)
394    }
395}
396
397impl std::ops::BitOrAssign for Modifiers {
398    #[inline]
399    fn bitor_assign(&mut self, rhs: Self) {
400        self.0 |= rhs.0;
401    }
402}
403
404/// Visual style for a terminal cell (foreground, background, modifiers).
405///
406/// Styles are applied to text via the builder methods on `Context` widget
407/// calls (e.g., `.bold()`, `.fg(Color::Cyan)`). All fields are optional;
408/// `None` means "inherit from the terminal default."
409///
410/// # Example
411///
412/// ```
413/// use slt::{Style, Color};
414///
415/// let style = Style::new().fg(Color::Cyan).bold();
416/// ```
417#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
418pub struct Style {
419    /// Foreground color, or `None` to use the terminal default.
420    pub fg: Option<Color>,
421    /// Background color, or `None` to use the terminal default.
422    pub bg: Option<Color>,
423    /// Text modifiers (bold, italic, underline, etc.).
424    pub modifiers: Modifiers,
425}
426
427impl Style {
428    /// Create a new style with no color or modifiers set.
429    pub const fn new() -> Self {
430        Self {
431            fg: None,
432            bg: None,
433            modifiers: Modifiers::NONE,
434        }
435    }
436
437    /// Set the foreground color.
438    pub const fn fg(mut self, color: Color) -> Self {
439        self.fg = Some(color);
440        self
441    }
442
443    /// Set the background color.
444    pub const fn bg(mut self, color: Color) -> Self {
445        self.bg = Some(color);
446        self
447    }
448
449    /// Add the bold modifier.
450    pub fn bold(mut self) -> Self {
451        self.modifiers |= Modifiers::BOLD;
452        self
453    }
454
455    /// Add the dim modifier.
456    pub fn dim(mut self) -> Self {
457        self.modifiers |= Modifiers::DIM;
458        self
459    }
460
461    /// Add the italic modifier.
462    pub fn italic(mut self) -> Self {
463        self.modifiers |= Modifiers::ITALIC;
464        self
465    }
466
467    /// Add the underline modifier.
468    pub fn underline(mut self) -> Self {
469        self.modifiers |= Modifiers::UNDERLINE;
470        self
471    }
472
473    /// Add the reversed (inverted colors) modifier.
474    pub fn reversed(mut self) -> Self {
475        self.modifiers |= Modifiers::REVERSED;
476        self
477    }
478
479    /// Add the strikethrough modifier.
480    pub fn strikethrough(mut self) -> Self {
481        self.modifiers |= Modifiers::STRIKETHROUGH;
482        self
483    }
484}