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
33impl Color {
34 /// Resolve to `(r, g, b)` for luminance and blending operations.
35 ///
36 /// Named colors map to their typical terminal palette values.
37 /// [`Color::Reset`] maps to black; [`Color::Indexed`] maps to the xterm-256 palette.
38 fn to_rgb(self) -> (u8, u8, u8) {
39 match self {
40 Color::Rgb(r, g, b) => (r, g, b),
41 Color::Black => (0, 0, 0),
42 Color::Red => (205, 49, 49),
43 Color::Green => (13, 188, 121),
44 Color::Yellow => (229, 229, 16),
45 Color::Blue => (36, 114, 200),
46 Color::Magenta => (188, 63, 188),
47 Color::Cyan => (17, 168, 205),
48 Color::White => (229, 229, 229),
49 Color::Reset => (0, 0, 0),
50 Color::Indexed(idx) => xterm256_to_rgb(idx),
51 }
52 }
53
54 /// Compute relative luminance using ITU-R BT.709 coefficients.
55 ///
56 /// Returns a value in `[0.0, 1.0]` where 0 is darkest and 1 is brightest.
57 /// Use this to determine whether text on a given background should be
58 /// light or dark.
59 ///
60 /// # Example
61 ///
62 /// ```
63 /// use slt::Color;
64 ///
65 /// let dark = Color::Rgb(30, 30, 46);
66 /// assert!(dark.luminance() < 0.15);
67 ///
68 /// let light = Color::Rgb(205, 214, 244);
69 /// assert!(light.luminance() > 0.6);
70 /// ```
71 pub fn luminance(self) -> f32 {
72 let (r, g, b) = self.to_rgb();
73 let rf = r as f32 / 255.0;
74 let gf = g as f32 / 255.0;
75 let bf = b as f32 / 255.0;
76 0.2126 * rf + 0.7152 * gf + 0.0722 * bf
77 }
78
79 /// Return a contrasting foreground color for the given background.
80 ///
81 /// Uses the BT.709 luminance threshold (0.5) to decide between white
82 /// and black text. For theme-aware contrast, prefer using this over
83 /// hardcoding `theme.bg` as the foreground.
84 ///
85 /// # Example
86 ///
87 /// ```
88 /// use slt::Color;
89 ///
90 /// let bg = Color::Rgb(189, 147, 249); // Dracula purple
91 /// let fg = Color::contrast_fg(bg);
92 /// // Purple is mid-bright → returns black for readable text
93 /// ```
94 pub fn contrast_fg(bg: Color) -> Color {
95 if bg.luminance() > 0.5 {
96 Color::Rgb(0, 0, 0)
97 } else {
98 Color::Rgb(255, 255, 255)
99 }
100 }
101
102 /// Blend this color over another with the given alpha.
103 ///
104 /// `alpha` is in `[0.0, 1.0]` where 0.0 returns `other` unchanged and
105 /// 1.0 returns `self` unchanged. Both colors are resolved to RGB.
106 ///
107 /// # Example
108 ///
109 /// ```
110 /// use slt::Color;
111 ///
112 /// let white = Color::Rgb(255, 255, 255);
113 /// let black = Color::Rgb(0, 0, 0);
114 /// let gray = white.blend(black, 0.5);
115 /// // ≈ Rgb(128, 128, 128)
116 /// ```
117 pub fn blend(self, other: Color, alpha: f32) -> Color {
118 let alpha = alpha.clamp(0.0, 1.0);
119 let (r1, g1, b1) = self.to_rgb();
120 let (r2, g2, b2) = other.to_rgb();
121 let r = (r1 as f32 * alpha + r2 as f32 * (1.0 - alpha)) as u8;
122 let g = (g1 as f32 * alpha + g2 as f32 * (1.0 - alpha)) as u8;
123 let b = (b1 as f32 * alpha + b2 as f32 * (1.0 - alpha)) as u8;
124 Color::Rgb(r, g, b)
125 }
126
127 /// Lighten this color by the given amount (0.0–1.0).
128 ///
129 /// Blends toward white. `amount = 0.0` returns the original color;
130 /// `amount = 1.0` returns white.
131 pub fn lighten(self, amount: f32) -> Color {
132 Color::Rgb(255, 255, 255).blend(self, 1.0 - amount.clamp(0.0, 1.0))
133 }
134
135 /// Darken this color by the given amount (0.0–1.0).
136 ///
137 /// Blends toward black. `amount = 0.0` returns the original color;
138 /// `amount = 1.0` returns black.
139 pub fn darken(self, amount: f32) -> Color {
140 Color::Rgb(0, 0, 0).blend(self, 1.0 - amount.clamp(0.0, 1.0))
141 }
142}
143
144fn xterm256_to_rgb(idx: u8) -> (u8, u8, u8) {
145 match idx {
146 0 => (0, 0, 0),
147 1 => (128, 0, 0),
148 2 => (0, 128, 0),
149 3 => (128, 128, 0),
150 4 => (0, 0, 128),
151 5 => (128, 0, 128),
152 6 => (0, 128, 128),
153 7 => (192, 192, 192),
154 8 => (128, 128, 128),
155 9 => (255, 0, 0),
156 10 => (0, 255, 0),
157 11 => (255, 255, 0),
158 12 => (0, 0, 255),
159 13 => (255, 0, 255),
160 14 => (0, 255, 255),
161 15 => (255, 255, 255),
162 16..=231 => {
163 let n = idx - 16;
164 let b_idx = n % 6;
165 let g_idx = (n / 6) % 6;
166 let r_idx = n / 36;
167 let to_val = |i: u8| if i == 0 { 0u8 } else { 55 + 40 * i };
168 (to_val(r_idx), to_val(g_idx), to_val(b_idx))
169 }
170 232..=255 => {
171 let v = 8 + 10 * (idx - 232);
172 (v, v, v)
173 }
174 }
175}
176
177/// A color theme that flows through all widgets automatically.
178///
179/// Construct with [`Theme::dark()`] or [`Theme::light()`], or build a custom
180/// theme by filling in the fields directly. Pass the theme via [`crate::RunConfig`]
181/// and every widget will pick up the colors without any extra wiring.
182#[derive(Debug, Clone, Copy)]
183#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
184pub struct Theme {
185 /// Primary accent color, used for focused borders and highlights.
186 pub primary: Color,
187 /// Secondary accent color, used for less prominent highlights.
188 pub secondary: Color,
189 /// Accent color for decorative elements.
190 pub accent: Color,
191 /// Default foreground text color.
192 pub text: Color,
193 /// Dimmed text color for secondary labels and hints.
194 pub text_dim: Color,
195 /// Border color for unfocused containers.
196 pub border: Color,
197 /// Background color. Typically [`Color::Reset`] to inherit the terminal background.
198 pub bg: Color,
199 /// Color for success states (e.g., toast notifications).
200 pub success: Color,
201 /// Color for warning states.
202 pub warning: Color,
203 /// Color for error states.
204 pub error: Color,
205 /// Background color for selected list/table rows.
206 pub selected_bg: Color,
207 /// Foreground color for selected list/table rows.
208 pub selected_fg: Color,
209 /// Subtle surface color for card backgrounds and elevated containers.
210 pub surface: Color,
211 /// Hover/active surface color, one step brighter than `surface`.
212 ///
213 /// Used for interactive element hover states. Should be visually
214 /// distinguishable from both `surface` and `border`.
215 pub surface_hover: Color,
216 /// Secondary text color guaranteed readable on `surface` backgrounds.
217 ///
218 /// Use this instead of `text_dim` when rendering on `surface`-colored
219 /// containers. `text_dim` is tuned for the main `bg`; on `surface` it
220 /// may lack contrast.
221 pub surface_text: Color,
222}
223
224impl Theme {
225 /// Create a dark theme with cyan primary and white text.
226 pub fn dark() -> Self {
227 Self {
228 primary: Color::Cyan,
229 secondary: Color::Blue,
230 accent: Color::Magenta,
231 text: Color::White,
232 text_dim: Color::Indexed(245),
233 border: Color::Indexed(240),
234 bg: Color::Reset,
235 success: Color::Green,
236 warning: Color::Yellow,
237 error: Color::Red,
238 selected_bg: Color::Cyan,
239 selected_fg: Color::Black,
240 surface: Color::Indexed(236),
241 surface_hover: Color::Indexed(238),
242 surface_text: Color::Indexed(250),
243 }
244 }
245
246 /// Create a light theme with blue primary and black text.
247 pub fn light() -> Self {
248 Self {
249 primary: Color::Blue,
250 secondary: Color::Cyan,
251 accent: Color::Magenta,
252 text: Color::Black,
253 text_dim: Color::Indexed(240),
254 border: Color::Indexed(245),
255 bg: Color::Reset,
256 success: Color::Green,
257 warning: Color::Yellow,
258 error: Color::Red,
259 selected_bg: Color::Blue,
260 selected_fg: Color::White,
261 surface: Color::Indexed(254),
262 surface_hover: Color::Indexed(252),
263 surface_text: Color::Indexed(238),
264 }
265 }
266
267 /// Dracula theme — purple primary on dark gray.
268 pub fn dracula() -> Self {
269 Self {
270 primary: Color::Rgb(189, 147, 249),
271 secondary: Color::Rgb(139, 233, 253),
272 accent: Color::Rgb(255, 121, 198),
273 text: Color::Rgb(248, 248, 242),
274 text_dim: Color::Rgb(98, 114, 164),
275 border: Color::Rgb(68, 71, 90),
276 bg: Color::Rgb(40, 42, 54),
277 success: Color::Rgb(80, 250, 123),
278 warning: Color::Rgb(241, 250, 140),
279 error: Color::Rgb(255, 85, 85),
280 selected_bg: Color::Rgb(189, 147, 249),
281 selected_fg: Color::Rgb(40, 42, 54),
282 surface: Color::Rgb(68, 71, 90),
283 surface_hover: Color::Rgb(98, 100, 120),
284 surface_text: Color::Rgb(191, 194, 210),
285 }
286 }
287
288 /// Catppuccin Mocha theme — lavender primary on dark base.
289 pub fn catppuccin() -> Self {
290 Self {
291 primary: Color::Rgb(180, 190, 254),
292 secondary: Color::Rgb(137, 180, 250),
293 accent: Color::Rgb(245, 194, 231),
294 text: Color::Rgb(205, 214, 244),
295 text_dim: Color::Rgb(127, 132, 156),
296 border: Color::Rgb(88, 91, 112),
297 bg: Color::Rgb(30, 30, 46),
298 success: Color::Rgb(166, 227, 161),
299 warning: Color::Rgb(249, 226, 175),
300 error: Color::Rgb(243, 139, 168),
301 selected_bg: Color::Rgb(180, 190, 254),
302 selected_fg: Color::Rgb(30, 30, 46),
303 surface: Color::Rgb(49, 50, 68),
304 surface_hover: Color::Rgb(69, 71, 90),
305 surface_text: Color::Rgb(166, 173, 200),
306 }
307 }
308
309 /// Nord theme — frost blue primary on polar night.
310 pub fn nord() -> Self {
311 Self {
312 primary: Color::Rgb(136, 192, 208),
313 secondary: Color::Rgb(129, 161, 193),
314 accent: Color::Rgb(180, 142, 173),
315 text: Color::Rgb(236, 239, 244),
316 text_dim: Color::Rgb(76, 86, 106),
317 border: Color::Rgb(76, 86, 106),
318 bg: Color::Rgb(46, 52, 64),
319 success: Color::Rgb(163, 190, 140),
320 warning: Color::Rgb(235, 203, 139),
321 error: Color::Rgb(191, 97, 106),
322 selected_bg: Color::Rgb(136, 192, 208),
323 selected_fg: Color::Rgb(46, 52, 64),
324 surface: Color::Rgb(59, 66, 82),
325 surface_hover: Color::Rgb(67, 76, 94),
326 surface_text: Color::Rgb(216, 222, 233),
327 }
328 }
329
330 /// Solarized Dark theme — blue primary on dark base.
331 pub fn solarized_dark() -> Self {
332 Self {
333 primary: Color::Rgb(38, 139, 210),
334 secondary: Color::Rgb(42, 161, 152),
335 accent: Color::Rgb(211, 54, 130),
336 text: Color::Rgb(131, 148, 150),
337 text_dim: Color::Rgb(88, 110, 117),
338 border: Color::Rgb(88, 110, 117),
339 bg: Color::Rgb(0, 43, 54),
340 success: Color::Rgb(133, 153, 0),
341 warning: Color::Rgb(181, 137, 0),
342 error: Color::Rgb(220, 50, 47),
343 selected_bg: Color::Rgb(38, 139, 210),
344 selected_fg: Color::Rgb(253, 246, 227),
345 surface: Color::Rgb(7, 54, 66),
346 surface_hover: Color::Rgb(23, 72, 85),
347 surface_text: Color::Rgb(147, 161, 161),
348 }
349 }
350
351 /// Tokyo Night theme — blue primary on dark storm base.
352 pub fn tokyo_night() -> Self {
353 Self {
354 primary: Color::Rgb(122, 162, 247),
355 secondary: Color::Rgb(125, 207, 255),
356 accent: Color::Rgb(187, 154, 247),
357 text: Color::Rgb(169, 177, 214),
358 text_dim: Color::Rgb(86, 95, 137),
359 border: Color::Rgb(54, 58, 79),
360 bg: Color::Rgb(26, 27, 38),
361 success: Color::Rgb(158, 206, 106),
362 warning: Color::Rgb(224, 175, 104),
363 error: Color::Rgb(247, 118, 142),
364 selected_bg: Color::Rgb(122, 162, 247),
365 selected_fg: Color::Rgb(26, 27, 38),
366 surface: Color::Rgb(36, 40, 59),
367 surface_hover: Color::Rgb(41, 46, 66),
368 surface_text: Color::Rgb(192, 202, 245),
369 }
370 }
371}
372
373impl Default for Theme {
374 fn default() -> Self {
375 Self::dark()
376 }
377}
378
379/// Border style for containers.
380///
381/// Pass to `Context::bordered()` to draw a box around a container.
382/// Each variant uses a different set of Unicode box-drawing characters.
383#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
384#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
385pub enum Border {
386 /// Single-line box: `┌─┐│└─┘`
387 Single,
388 /// Double-line box: `╔═╗║╚═╝`
389 Double,
390 /// Rounded corners: `╭─╮│╰─╯`
391 Rounded,
392 /// Thick single-line box: `┏━┓┃┗━┛`
393 Thick,
394}
395
396/// Character set for a specific border style.
397///
398/// Returned by [`Border::chars`]. Contains the six box-drawing characters
399/// needed to render a complete border: four corners and two line segments.
400#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
401pub struct BorderChars {
402 /// Top-left corner character.
403 pub tl: char,
404 /// Top-right corner character.
405 pub tr: char,
406 /// Bottom-left corner character.
407 pub bl: char,
408 /// Bottom-right corner character.
409 pub br: char,
410 /// Horizontal line character.
411 pub h: char,
412 /// Vertical line character.
413 pub v: char,
414}
415
416impl Border {
417 /// Return the [`BorderChars`] for this border style.
418 pub const fn chars(self) -> BorderChars {
419 match self {
420 Self::Single => BorderChars {
421 tl: '┌',
422 tr: '┐',
423 bl: '└',
424 br: '┘',
425 h: '─',
426 v: '│',
427 },
428 Self::Double => BorderChars {
429 tl: '╔',
430 tr: '╗',
431 bl: '╚',
432 br: '╝',
433 h: '═',
434 v: '║',
435 },
436 Self::Rounded => BorderChars {
437 tl: '╭',
438 tr: '╮',
439 bl: '╰',
440 br: '╯',
441 h: '─',
442 v: '│',
443 },
444 Self::Thick => BorderChars {
445 tl: '┏',
446 tr: '┓',
447 bl: '┗',
448 br: '┛',
449 h: '━',
450 v: '┃',
451 },
452 }
453 }
454}
455
456/// Padding inside a container border.
457///
458/// Shrinks the content area inward from each edge. All values are in terminal
459/// columns/rows.
460#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
461#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
462pub struct Padding {
463 /// Padding on the top edge.
464 pub top: u32,
465 /// Padding on the right edge.
466 pub right: u32,
467 /// Padding on the bottom edge.
468 pub bottom: u32,
469 /// Padding on the left edge.
470 pub left: u32,
471}
472
473impl Padding {
474 /// Create uniform padding on all four sides.
475 pub const fn all(v: u32) -> Self {
476 Self::new(v, v, v, v)
477 }
478
479 /// Create padding with `x` on left/right and `y` on top/bottom.
480 pub const fn xy(x: u32, y: u32) -> Self {
481 Self::new(y, x, y, x)
482 }
483
484 /// Create padding with explicit values for each side.
485 pub const fn new(top: u32, right: u32, bottom: u32, left: u32) -> Self {
486 Self {
487 top,
488 right,
489 bottom,
490 left,
491 }
492 }
493
494 /// Total horizontal padding (`left + right`).
495 pub const fn horizontal(self) -> u32 {
496 self.left + self.right
497 }
498
499 /// Total vertical padding (`top + bottom`).
500 pub const fn vertical(self) -> u32 {
501 self.top + self.bottom
502 }
503}
504
505/// Margin outside a container.
506///
507/// Adds space around the outside of a container's border. All values are in
508/// terminal columns/rows.
509#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
510#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
511pub struct Margin {
512 /// Margin on the top edge.
513 pub top: u32,
514 /// Margin on the right edge.
515 pub right: u32,
516 /// Margin on the bottom edge.
517 pub bottom: u32,
518 /// Margin on the left edge.
519 pub left: u32,
520}
521
522impl Margin {
523 /// Create uniform margin on all four sides.
524 pub const fn all(v: u32) -> Self {
525 Self::new(v, v, v, v)
526 }
527
528 /// Create margin with `x` on left/right and `y` on top/bottom.
529 pub const fn xy(x: u32, y: u32) -> Self {
530 Self::new(y, x, y, x)
531 }
532
533 /// Create margin with explicit values for each side.
534 pub const fn new(top: u32, right: u32, bottom: u32, left: u32) -> Self {
535 Self {
536 top,
537 right,
538 bottom,
539 left,
540 }
541 }
542
543 /// Total horizontal margin (`left + right`).
544 pub const fn horizontal(self) -> u32 {
545 self.left + self.right
546 }
547
548 /// Total vertical margin (`top + bottom`).
549 pub const fn vertical(self) -> u32 {
550 self.top + self.bottom
551 }
552}
553
554/// Size constraints for layout computation.
555///
556/// All fields are optional. Unset constraints are unconstrained. Use the
557/// builder methods to set individual bounds in a fluent style.
558///
559/// # Example
560///
561/// ```
562/// use slt::Constraints;
563///
564/// let c = Constraints::default().min_w(10).max_w(40);
565/// ```
566#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
567#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
568#[must_use = "configure constraints using the returned value"]
569pub struct Constraints {
570 /// Minimum width in terminal columns, if any.
571 pub min_width: Option<u32>,
572 /// Maximum width in terminal columns, if any.
573 pub max_width: Option<u32>,
574 /// Minimum height in terminal rows, if any.
575 pub min_height: Option<u32>,
576 /// Maximum height in terminal rows, if any.
577 pub max_height: Option<u32>,
578}
579
580impl Constraints {
581 /// Set the minimum width constraint.
582 pub const fn min_w(mut self, min_width: u32) -> Self {
583 self.min_width = Some(min_width);
584 self
585 }
586
587 /// Set the maximum width constraint.
588 pub const fn max_w(mut self, max_width: u32) -> Self {
589 self.max_width = Some(max_width);
590 self
591 }
592
593 /// Set the minimum height constraint.
594 pub const fn min_h(mut self, min_height: u32) -> Self {
595 self.min_height = Some(min_height);
596 self
597 }
598
599 /// Set the maximum height constraint.
600 pub const fn max_h(mut self, max_height: u32) -> Self {
601 self.max_height = Some(max_height);
602 self
603 }
604}
605
606/// Cross-axis alignment within a container.
607///
608/// Controls how children are positioned along the axis perpendicular to the
609/// container's main axis. For a `row()`, this is vertical alignment; for a
610/// `col()`, this is horizontal alignment.
611#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
612#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
613pub enum Align {
614 /// Align children to the start of the cross axis (default).
615 #[default]
616 Start,
617 /// Center children on the cross axis.
618 Center,
619 /// Align children to the end of the cross axis.
620 End,
621}
622
623/// Main-axis content distribution within a container.
624///
625/// Controls how children are distributed along the main axis. For a `row()`,
626/// this is horizontal distribution; for a `col()`, this is vertical.
627///
628/// When children have `grow > 0`, they consume remaining space before justify
629/// distribution applies. Justify modes only affect the leftover space after
630/// flex-grow allocation.
631#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
632#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
633pub enum Justify {
634 /// Pack children at the start (default). Uses `gap` for spacing.
635 #[default]
636 Start,
637 /// Center children along the main axis with `gap` spacing.
638 Center,
639 /// Pack children at the end with `gap` spacing.
640 End,
641 /// First child at start, last at end, equal space between.
642 SpaceBetween,
643 /// Equal space around each child (half-size space at edges).
644 SpaceAround,
645 /// Equal space between all children and at both edges.
646 SpaceEvenly,
647}
648
649/// Text modifier bitflags stored as a `u8`.
650///
651/// Combine modifiers with `|` or [`Modifiers::insert`]. Check membership with
652/// [`Modifiers::contains`].
653#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
654#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
655#[cfg_attr(feature = "serde", serde(transparent))]
656pub struct Modifiers(pub u8);
657
658impl Modifiers {
659 /// No modifiers set.
660 pub const NONE: Self = Self(0);
661 /// Bold text.
662 pub const BOLD: Self = Self(1 << 0);
663 /// Dimmed/faint text.
664 pub const DIM: Self = Self(1 << 1);
665 /// Italic text.
666 pub const ITALIC: Self = Self(1 << 2);
667 /// Underlined text.
668 pub const UNDERLINE: Self = Self(1 << 3);
669 /// Reversed foreground/background colors.
670 pub const REVERSED: Self = Self(1 << 4);
671 /// Strikethrough text.
672 pub const STRIKETHROUGH: Self = Self(1 << 5);
673
674 /// Returns `true` if all bits in `other` are set in `self`.
675 #[inline]
676 pub fn contains(self, other: Self) -> bool {
677 (self.0 & other.0) == other.0
678 }
679
680 /// Set all bits from `other` into `self`.
681 #[inline]
682 pub fn insert(&mut self, other: Self) {
683 self.0 |= other.0;
684 }
685
686 /// Returns `true` if no modifiers are set.
687 #[inline]
688 pub fn is_empty(self) -> bool {
689 self.0 == 0
690 }
691}
692
693impl std::ops::BitOr for Modifiers {
694 type Output = Self;
695 #[inline]
696 fn bitor(self, rhs: Self) -> Self {
697 Self(self.0 | rhs.0)
698 }
699}
700
701impl std::ops::BitOrAssign for Modifiers {
702 #[inline]
703 fn bitor_assign(&mut self, rhs: Self) {
704 self.0 |= rhs.0;
705 }
706}
707
708/// Visual style for a terminal cell (foreground, background, modifiers).
709///
710/// Styles are applied to text via the builder methods on `Context` widget
711/// calls (e.g., `.bold()`, `.fg(Color::Cyan)`). All fields are optional;
712/// `None` means "inherit from the terminal default."
713///
714/// # Example
715///
716/// ```
717/// use slt::{Style, Color};
718///
719/// let style = Style::new().fg(Color::Cyan).bold();
720/// ```
721#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
722#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
723#[must_use = "build and pass the returned Style value"]
724pub struct Style {
725 /// Foreground color, or `None` to use the terminal default.
726 pub fg: Option<Color>,
727 /// Background color, or `None` to use the terminal default.
728 pub bg: Option<Color>,
729 /// Text modifiers (bold, italic, underline, etc.).
730 pub modifiers: Modifiers,
731}
732
733impl Style {
734 /// Create a new style with no color or modifiers set.
735 pub const fn new() -> Self {
736 Self {
737 fg: None,
738 bg: None,
739 modifiers: Modifiers::NONE,
740 }
741 }
742
743 /// Set the foreground color.
744 pub const fn fg(mut self, color: Color) -> Self {
745 self.fg = Some(color);
746 self
747 }
748
749 /// Set the background color.
750 pub const fn bg(mut self, color: Color) -> Self {
751 self.bg = Some(color);
752 self
753 }
754
755 /// Add the bold modifier.
756 pub fn bold(mut self) -> Self {
757 self.modifiers |= Modifiers::BOLD;
758 self
759 }
760
761 /// Add the dim modifier.
762 pub fn dim(mut self) -> Self {
763 self.modifiers |= Modifiers::DIM;
764 self
765 }
766
767 /// Add the italic modifier.
768 pub fn italic(mut self) -> Self {
769 self.modifiers |= Modifiers::ITALIC;
770 self
771 }
772
773 /// Add the underline modifier.
774 pub fn underline(mut self) -> Self {
775 self.modifiers |= Modifiers::UNDERLINE;
776 self
777 }
778
779 /// Add the reversed (inverted colors) modifier.
780 pub fn reversed(mut self) -> Self {
781 self.modifiers |= Modifiers::REVERSED;
782 self
783 }
784
785 /// Add the strikethrough modifier.
786 pub fn strikethrough(mut self) -> Self {
787 self.modifiers |= Modifiers::STRIKETHROUGH;
788 self
789 }
790}