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