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}