Skip to main content

lv_tui/
stylize.rs

1use crate::style::{Color, Style};
2use crate::text::Span;
3
4/// Trait for chaining style methods on strings.
5///
6/// Returns [`Span`] objects that can be collected into [`Line`](crate::text::Line)
7/// and [`Text`](crate::text::Text).
8///
9/// ```rust
10/// use lv_tui::stylize::Stylize;
11/// let s = "hello".red().bold();
12/// assert!(s.style.bold);
13/// ```
14pub trait Stylize {
15    fn style(self, style: Style) -> Span;
16    fn fg(self, color: Color) -> Span;
17    fn bg(self, color: Color) -> Span;
18
19    fn red(self) -> Span where Self: Sized { self.fg(Color::Red) }
20    fn green(self) -> Span where Self: Sized { self.fg(Color::Green) }
21    fn blue(self) -> Span where Self: Sized { self.fg(Color::Blue) }
22    fn cyan(self) -> Span where Self: Sized { self.fg(Color::Cyan) }
23    fn yellow(self) -> Span where Self: Sized { self.fg(Color::Yellow) }
24    fn magenta(self) -> Span where Self: Sized { self.fg(Color::Magenta) }
25    fn white(self) -> Span where Self: Sized { self.fg(Color::White) }
26    fn gray(self) -> Span where Self: Sized { self.fg(Color::Gray) }
27    fn black(self) -> Span where Self: Sized { self.fg(Color::Black) }
28
29    fn on_red(self) -> Span where Self: Sized { self.bg(Color::Red) }
30    fn on_green(self) -> Span where Self: Sized { self.bg(Color::Green) }
31    fn on_blue(self) -> Span where Self: Sized { self.bg(Color::Blue) }
32    fn on_cyan(self) -> Span where Self: Sized { self.bg(Color::Cyan) }
33    fn on_yellow(self) -> Span where Self: Sized { self.bg(Color::Yellow) }
34    fn on_magenta(self) -> Span where Self: Sized { self.bg(Color::Magenta) }
35    fn on_white(self) -> Span where Self: Sized { self.bg(Color::White) }
36    fn on_gray(self) -> Span where Self: Sized { self.bg(Color::Gray) }
37    fn on_black(self) -> Span where Self: Sized { self.bg(Color::Black) }
38
39    fn rgb(self, r: u8, g: u8, b: u8) -> Span where Self: Sized { self.fg(Color::Rgb(r, g, b)) }
40    fn on_rgb(self, r: u8, g: u8, b: u8) -> Span where Self: Sized { self.bg(Color::Rgb(r, g, b)) }
41    fn hex(self, hex: &str) -> Span where Self: Sized {
42        self.fg(Color::hex(hex).unwrap_or(Color::White))
43    }
44    fn on_hex(self, hex: &str) -> Span where Self: Sized {
45        self.bg(Color::hex(hex).unwrap_or(Color::White))
46    }
47
48    fn bold(self) -> Span where Self: Sized;
49    fn italic(self) -> Span where Self: Sized;
50    fn underline(self) -> Span where Self: Sized;
51}
52
53impl Stylize for &str {
54    fn style(self, style: Style) -> Span { Span { text: self.to_string(), style } }
55    fn fg(self, color: Color) -> Span { Span { text: self.to_string(), style: Style::default().fg(color) } }
56    fn bg(self, color: Color) -> Span { Span { text: self.to_string(), style: Style::default().bg(color) } }
57    fn bold(self) -> Span { Span { text: self.to_string(), style: Style::default().bold() } }
58    fn italic(self) -> Span { Span { text: self.to_string(), style: Style::default().italic() } }
59    fn underline(self) -> Span { Span { text: self.to_string(), style: Style::default().underline() } }
60}
61
62impl Stylize for String {
63    fn style(self, style: Style) -> Span { Span { text: self, style } }
64    fn fg(self, color: Color) -> Span { Span { text: self, style: Style::default().fg(color) } }
65    fn bg(self, color: Color) -> Span { Span { text: self, style: Style::default().bg(color) } }
66    fn bold(self) -> Span { Span { text: self, style: Style::default().bold() } }
67    fn italic(self) -> Span { Span { text: self, style: Style::default().italic() } }
68    fn underline(self) -> Span { Span { text: self, style: Style::default().underline() } }
69}
70
71/// Merge styles for chained calls like `.red().bold()`.
72impl Stylize for Span {
73    fn style(mut self, style: Style) -> Span {
74        self.style = crate::style_parser::merge_styles(self.style, &style);
75        self
76    }
77    fn fg(self, color: Color) -> Span { self.style(Style::default().fg(color)) }
78    fn bg(self, color: Color) -> Span { self.style(Style::default().bg(color)) }
79    fn bold(self) -> Span { self.style(Style::default().bold()) }
80    fn italic(self) -> Span { self.style(Style::default().italic()) }
81    fn underline(self) -> Span { self.style(Style::default().underline()) }
82}
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87
88    #[test]
89    fn test_red() {
90        let s = "hello".red();
91        assert_eq!(s.text, "hello");
92        assert_eq!(s.style.fg, Some(Color::Red));
93    }
94
95    #[test]
96    fn test_red_bold() {
97        let s = "hello".red().bold();
98        assert_eq!(s.text, "hello");
99        assert_eq!(s.style.fg, Some(Color::Red));
100        assert!(s.style.bold);
101    }
102
103    #[test]
104    fn test_on_blue_white() {
105        let s = "text".on_blue().white();
106        assert_eq!(s.style.bg, Some(Color::Blue));
107        assert_eq!(s.style.fg, Some(Color::White));
108    }
109
110    #[test]
111    fn test_chained_style() {
112        let s = "x".red().on_black().bold().italic();
113        assert_eq!(s.style.fg, Some(Color::Red));
114        assert_eq!(s.style.bg, Some(Color::Black));
115        assert!(s.style.bold);
116        assert!(s.style.italic);
117    }
118
119    #[test]
120    fn test_string_impl() {
121        let s = String::from("hi").green();
122        assert_eq!(s.text, "hi");
123        assert_eq!(s.style.fg, Some(Color::Green));
124    }
125
126    #[test]
127    fn test_gray() {
128        let s = "dim".gray();
129        assert_eq!(s.style.fg, Some(Color::Gray));
130    }
131
132    #[test]
133    fn test_bg_methods() {
134        let s = "x".on_red().on_green();
135        assert_eq!(s.style.bg, Some(Color::Green));
136    }
137}