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}