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)]
7#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
8pub enum Color {
9    /// Reset to the terminal's default color.
10    Reset,
11    /// Standard black (color index 0).
12    Black,
13    /// Standard red (color index 1).
14    Red,
15    /// Standard green (color index 2).
16    Green,
17    /// Standard yellow (color index 3).
18    Yellow,
19    /// Standard blue (color index 4).
20    Blue,
21    /// Standard magenta (color index 5).
22    Magenta,
23    /// Standard cyan (color index 6).
24    Cyan,
25    /// Standard white (color index 7).
26    White,
27    /// 24-bit true color.
28    Rgb(u8, u8, u8),
29    /// 256-color palette index.
30    Indexed(u8),
31}
32
33/// A color theme that flows through all widgets automatically.
34///
35/// Construct with [`Theme::dark()`] or [`Theme::light()`], or build a custom
36/// theme by filling in the fields directly. Pass the theme via [`crate::RunConfig`]
37/// and every widget will pick up the colors without any extra wiring.
38#[derive(Debug, Clone, Copy)]
39#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
40pub struct Theme {
41    /// Primary accent color, used for focused borders and highlights.
42    pub primary: Color,
43    /// Secondary accent color, used for less prominent highlights.
44    pub secondary: Color,
45    /// Accent color for decorative elements.
46    pub accent: Color,
47    /// Default foreground text color.
48    pub text: Color,
49    /// Dimmed text color for secondary labels and hints.
50    pub text_dim: Color,
51    /// Border color for unfocused containers.
52    pub border: Color,
53    /// Background color. Typically [`Color::Reset`] to inherit the terminal background.
54    pub bg: Color,
55    /// Color for success states (e.g., toast notifications).
56    pub success: Color,
57    /// Color for warning states.
58    pub warning: Color,
59    /// Color for error states.
60    pub error: Color,
61    /// Background color for selected list/table rows.
62    pub selected_bg: Color,
63    /// Foreground color for selected list/table rows.
64    pub selected_fg: Color,
65}
66
67impl Theme {
68    /// Create a dark theme with cyan primary and white text.
69    pub fn dark() -> Self {
70        Self {
71            primary: Color::Cyan,
72            secondary: Color::Blue,
73            accent: Color::Magenta,
74            text: Color::White,
75            text_dim: Color::Indexed(245),
76            border: Color::Indexed(240),
77            bg: Color::Reset,
78            success: Color::Green,
79            warning: Color::Yellow,
80            error: Color::Red,
81            selected_bg: Color::Cyan,
82            selected_fg: Color::Black,
83        }
84    }
85
86    /// Create a light theme with blue primary and black text.
87    pub fn light() -> Self {
88        Self {
89            primary: Color::Blue,
90            secondary: Color::Cyan,
91            accent: Color::Magenta,
92            text: Color::Black,
93            text_dim: Color::Indexed(240),
94            border: Color::Indexed(245),
95            bg: Color::Reset,
96            success: Color::Green,
97            warning: Color::Yellow,
98            error: Color::Red,
99            selected_bg: Color::Blue,
100            selected_fg: Color::White,
101        }
102    }
103}
104
105impl Default for Theme {
106    fn default() -> Self {
107        Self::dark()
108    }
109}
110
111/// Border style for containers.
112///
113/// Pass to `Context::bordered()` to draw a box around a container.
114/// Each variant uses a different set of Unicode box-drawing characters.
115#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
116#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
117pub enum Border {
118    /// Single-line box: `┌─┐│└─┘`
119    Single,
120    /// Double-line box: `╔═╗║╚═╝`
121    Double,
122    /// Rounded corners: `╭─╮│╰─╯`
123    Rounded,
124    /// Thick single-line box: `┏━┓┃┗━┛`
125    Thick,
126}
127
128/// Character set for a specific border style.
129///
130/// Returned by [`Border::chars`]. Contains the six box-drawing characters
131/// needed to render a complete border: four corners and two line segments.
132#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
133pub struct BorderChars {
134    /// Top-left corner character.
135    pub tl: char,
136    /// Top-right corner character.
137    pub tr: char,
138    /// Bottom-left corner character.
139    pub bl: char,
140    /// Bottom-right corner character.
141    pub br: char,
142    /// Horizontal line character.
143    pub h: char,
144    /// Vertical line character.
145    pub v: char,
146}
147
148impl Border {
149    /// Return the [`BorderChars`] for this border style.
150    pub const fn chars(self) -> BorderChars {
151        match self {
152            Self::Single => BorderChars {
153                tl: '┌',
154                tr: '┐',
155                bl: '└',
156                br: '┘',
157                h: '─',
158                v: '│',
159            },
160            Self::Double => BorderChars {
161                tl: '╔',
162                tr: '╗',
163                bl: '╚',
164                br: '╝',
165                h: '═',
166                v: '║',
167            },
168            Self::Rounded => BorderChars {
169                tl: '╭',
170                tr: '╮',
171                bl: '╰',
172                br: '╯',
173                h: '─',
174                v: '│',
175            },
176            Self::Thick => 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}
311
312impl Constraints {
313    /// Set the minimum width constraint.
314    pub const fn min_w(mut self, min_width: u32) -> Self {
315        self.min_width = Some(min_width);
316        self
317    }
318
319    /// Set the maximum width constraint.
320    pub const fn max_w(mut self, max_width: u32) -> Self {
321        self.max_width = Some(max_width);
322        self
323    }
324
325    /// Set the minimum height constraint.
326    pub const fn min_h(mut self, min_height: u32) -> Self {
327        self.min_height = Some(min_height);
328        self
329    }
330
331    /// Set the maximum height constraint.
332    pub const fn max_h(mut self, max_height: u32) -> Self {
333        self.max_height = Some(max_height);
334        self
335    }
336}
337
338/// Cross-axis alignment within a container.
339///
340/// Controls how children are positioned along the axis perpendicular to the
341/// container's main axis. For a `row()`, this is vertical alignment; for a
342/// `col()`, this is horizontal alignment.
343#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
344#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
345pub enum Align {
346    /// Align children to the start of the cross axis (default).
347    #[default]
348    Start,
349    /// Center children on the cross axis.
350    Center,
351    /// Align children to the end of the cross axis.
352    End,
353}
354
355/// Text modifier bitflags stored as a `u8`.
356///
357/// Combine modifiers with `|` or [`Modifiers::insert`]. Check membership with
358/// [`Modifiers::contains`].
359#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
360#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
361#[cfg_attr(feature = "serde", serde(transparent))]
362pub struct Modifiers(pub u8);
363
364impl Modifiers {
365    /// No modifiers set.
366    pub const NONE: Self = Self(0);
367    /// Bold text.
368    pub const BOLD: Self = Self(1 << 0);
369    /// Dimmed/faint text.
370    pub const DIM: Self = Self(1 << 1);
371    /// Italic text.
372    pub const ITALIC: Self = Self(1 << 2);
373    /// Underlined text.
374    pub const UNDERLINE: Self = Self(1 << 3);
375    /// Reversed foreground/background colors.
376    pub const REVERSED: Self = Self(1 << 4);
377    /// Strikethrough text.
378    pub const STRIKETHROUGH: Self = Self(1 << 5);
379
380    /// Returns `true` if all bits in `other` are set in `self`.
381    #[inline]
382    pub fn contains(self, other: Self) -> bool {
383        (self.0 & other.0) == other.0
384    }
385
386    /// Set all bits from `other` into `self`.
387    #[inline]
388    pub fn insert(&mut self, other: Self) {
389        self.0 |= other.0;
390    }
391
392    /// Returns `true` if no modifiers are set.
393    #[inline]
394    pub fn is_empty(self) -> bool {
395        self.0 == 0
396    }
397}
398
399impl std::ops::BitOr for Modifiers {
400    type Output = Self;
401    #[inline]
402    fn bitor(self, rhs: Self) -> Self {
403        Self(self.0 | rhs.0)
404    }
405}
406
407impl std::ops::BitOrAssign for Modifiers {
408    #[inline]
409    fn bitor_assign(&mut self, rhs: Self) {
410        self.0 |= rhs.0;
411    }
412}
413
414/// Visual style for a terminal cell (foreground, background, modifiers).
415///
416/// Styles are applied to text via the builder methods on `Context` widget
417/// calls (e.g., `.bold()`, `.fg(Color::Cyan)`). All fields are optional;
418/// `None` means "inherit from the terminal default."
419///
420/// # Example
421///
422/// ```
423/// use slt::{Style, Color};
424///
425/// let style = Style::new().fg(Color::Cyan).bold();
426/// ```
427#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
428#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
429#[must_use = "build and pass the returned Style value"]
430pub struct Style {
431    /// Foreground color, or `None` to use the terminal default.
432    pub fg: Option<Color>,
433    /// Background color, or `None` to use the terminal default.
434    pub bg: Option<Color>,
435    /// Text modifiers (bold, italic, underline, etc.).
436    pub modifiers: Modifiers,
437}
438
439impl Style {
440    /// Create a new style with no color or modifiers set.
441    pub const fn new() -> Self {
442        Self {
443            fg: None,
444            bg: None,
445            modifiers: Modifiers::NONE,
446        }
447    }
448
449    /// Set the foreground color.
450    pub const fn fg(mut self, color: Color) -> Self {
451        self.fg = Some(color);
452        self
453    }
454
455    /// Set the background color.
456    pub const fn bg(mut self, color: Color) -> Self {
457        self.bg = Some(color);
458        self
459    }
460
461    /// Add the bold modifier.
462    pub fn bold(mut self) -> Self {
463        self.modifiers |= Modifiers::BOLD;
464        self
465    }
466
467    /// Add the dim modifier.
468    pub fn dim(mut self) -> Self {
469        self.modifiers |= Modifiers::DIM;
470        self
471    }
472
473    /// Add the italic modifier.
474    pub fn italic(mut self) -> Self {
475        self.modifiers |= Modifiers::ITALIC;
476        self
477    }
478
479    /// Add the underline modifier.
480    pub fn underline(mut self) -> Self {
481        self.modifiers |= Modifiers::UNDERLINE;
482        self
483    }
484
485    /// Add the reversed (inverted colors) modifier.
486    pub fn reversed(mut self) -> Self {
487        self.modifiers |= Modifiers::REVERSED;
488        self
489    }
490
491    /// Add the strikethrough modifier.
492    pub fn strikethrough(mut self) -> Self {
493        self.modifiers |= Modifiers::STRIKETHROUGH;
494        self
495    }
496}