Skip to main content

louie/core/
style.rs

1use serde::{Deserialize, Serialize};
2use std::fmt;
3
4/// Terminal color.
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
6pub enum Color {
7    #[default]
8    Reset,
9    Black,
10    Red,
11    Green,
12    Yellow,
13    Blue,
14    Magenta,
15    Cyan,
16    Gray,
17    DarkGray,
18    LightRed,
19    LightGreen,
20    LightYellow,
21    LightBlue,
22    LightMagenta,
23    LightCyan,
24    White,
25    /// 256-color palette index.
26    Indexed(u8),
27    /// True color (24-bit RGB).
28    Rgb(u8, u8, u8),
29}
30
31impl fmt::Display for Color {
32    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33        match self {
34            Self::Rgb(r, g, b) => write!(f, "#{r:02X}{g:02X}{b:02X}"),
35            Self::Indexed(i) => write!(f, "color({i})"),
36            other => write!(f, "{other:?}"),
37        }
38    }
39}
40
41/// Text modifiers (bitflags).
42#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
43pub struct Modifier(u16);
44
45impl Modifier {
46    pub const NONE: Self = Self(0);
47    pub const BOLD: Self = Self(1 << 0);
48    pub const DIM: Self = Self(1 << 1);
49    pub const ITALIC: Self = Self(1 << 2);
50    pub const UNDERLINED: Self = Self(1 << 3);
51    pub const SLOW_BLINK: Self = Self(1 << 4);
52    pub const RAPID_BLINK: Self = Self(1 << 5);
53    pub const REVERSED: Self = Self(1 << 6);
54    pub const HIDDEN: Self = Self(1 << 7);
55    pub const CROSSED_OUT: Self = Self(1 << 8);
56    pub const DOUBLE_UNDERLINED: Self = Self(1 << 9);
57    pub const UNDERCURLED: Self = Self(1 << 10);
58    pub const UNDERDOTTED: Self = Self(1 << 11);
59    pub const UNDERDASHED: Self = Self(1 << 12);
60    pub const OVERLINED: Self = Self(1 << 13);
61    pub const SUPERSCRIPT: Self = Self(1 << 14);
62    pub const SUBSCRIPT: Self = Self(1 << 15);
63
64    pub const fn empty() -> Self {
65        Self(0)
66    }
67
68    pub const fn contains(self, other: Self) -> bool {
69        self.0 & other.0 == other.0
70    }
71
72    pub const fn union(self, other: Self) -> Self {
73        Self(self.0 | other.0)
74    }
75
76    pub const fn difference(self, other: Self) -> Self {
77        Self(self.0 & !other.0)
78    }
79
80    pub const fn is_empty(self) -> bool {
81        self.0 == 0
82    }
83}
84
85impl std::ops::BitOr for Modifier {
86    type Output = Self;
87    fn bitor(self, rhs: Self) -> Self::Output {
88        Self(self.0 | rhs.0)
89    }
90}
91
92impl std::ops::BitAnd for Modifier {
93    type Output = Self;
94    fn bitand(self, rhs: Self) -> Self::Output {
95        Self(self.0 & rhs.0)
96    }
97}
98
99impl std::ops::Not for Modifier {
100    type Output = Self;
101    fn not(self) -> Self::Output {
102        Self(!self.0)
103    }
104}
105
106/// Complete styling specification for a terminal cell.
107#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
108pub struct Style {
109    pub fg: Option<Color>,
110    pub bg: Option<Color>,
111    pub underline_color: Option<Color>,
112    pub add_modifier: Modifier,
113    pub sub_modifier: Modifier,
114}
115
116impl Style {
117    pub const fn new() -> Self {
118        Self {
119            fg: None,
120            bg: None,
121            underline_color: None,
122            add_modifier: Modifier::NONE,
123            sub_modifier: Modifier::NONE,
124        }
125    }
126
127    /// Reset all style fields.
128    pub const fn reset() -> Self {
129        Self {
130            fg: Some(Color::Reset),
131            bg: Some(Color::Reset),
132            underline_color: Some(Color::Reset),
133            add_modifier: Modifier::NONE,
134            sub_modifier: Modifier::NONE,
135        }
136    }
137
138    pub const fn fg(mut self, color: Color) -> Self {
139        self.fg = Some(color);
140        self
141    }
142
143    pub const fn bg(mut self, color: Color) -> Self {
144        self.bg = Some(color);
145        self
146    }
147
148    pub const fn underline_color(mut self, color: Color) -> Self {
149        self.underline_color = Some(color);
150        self
151    }
152
153    pub const fn bold(mut self) -> Self {
154        self.add_modifier = self.add_modifier.union(Modifier::BOLD);
155        self
156    }
157
158    pub const fn dim(mut self) -> Self {
159        self.add_modifier = self.add_modifier.union(Modifier::DIM);
160        self
161    }
162
163    pub const fn italic(mut self) -> Self {
164        self.add_modifier = self.add_modifier.union(Modifier::ITALIC);
165        self
166    }
167
168    pub const fn underlined(mut self) -> Self {
169        self.add_modifier = self.add_modifier.union(Modifier::UNDERLINED);
170        self
171    }
172
173    pub const fn reversed(mut self) -> Self {
174        self.add_modifier = self.add_modifier.union(Modifier::REVERSED);
175        self
176    }
177
178    pub const fn crossed_out(mut self) -> Self {
179        self.add_modifier = self.add_modifier.union(Modifier::CROSSED_OUT);
180        self
181    }
182
183    pub const fn hidden(mut self) -> Self {
184        self.add_modifier = self.add_modifier.union(Modifier::HIDDEN);
185        self
186    }
187
188    pub const fn slow_blink(mut self) -> Self {
189        self.add_modifier = self.add_modifier.union(Modifier::SLOW_BLINK);
190        self
191    }
192
193    pub const fn rapid_blink(mut self) -> Self {
194        self.add_modifier = self.add_modifier.union(Modifier::RAPID_BLINK);
195        self
196    }
197
198    pub const fn overlined(mut self) -> Self {
199        self.add_modifier = self.add_modifier.union(Modifier::OVERLINED);
200        self
201    }
202
203    pub const fn not_bold(mut self) -> Self {
204        self.sub_modifier = self.sub_modifier.union(Modifier::BOLD);
205        self
206    }
207
208    pub const fn not_italic(mut self) -> Self {
209        self.sub_modifier = self.sub_modifier.union(Modifier::ITALIC);
210        self
211    }
212
213    pub const fn not_underlined(mut self) -> Self {
214        self.sub_modifier = self.sub_modifier.union(Modifier::UNDERLINED);
215        self
216    }
217
218    /// Merge another style on top of this one.
219    /// `other`'s non-None fields override `self`'s.
220    pub fn patch(mut self, other: Style) -> Self {
221        if other.fg.is_some() {
222            self.fg = other.fg;
223        }
224        if other.bg.is_some() {
225            self.bg = other.bg;
226        }
227        if other.underline_color.is_some() {
228            self.underline_color = other.underline_color;
229        }
230        self.add_modifier = self
231            .add_modifier
232            .difference(other.sub_modifier)
233            .union(other.add_modifier);
234        self.sub_modifier = self
235            .sub_modifier
236            .difference(other.add_modifier)
237            .union(other.sub_modifier);
238        self
239    }
240}
241
242/// [Stylize] trait enables a fluent shorthand API.
243///
244/// ```
245/// use louie::core::style::*;
246/// let style = Style::new().red().on_blue().bold();
247/// ```
248pub trait Stylize: Sized {
249    fn style(self, style: Style) -> Self;
250
251    fn red(self) -> Self {
252        self.style(Style::new().fg(Color::Red))
253    }
254    fn green(self) -> Self {
255        self.style(Style::new().fg(Color::Green))
256    }
257    fn yellow(self) -> Self {
258        self.style(Style::new().fg(Color::Yellow))
259    }
260    fn blue(self) -> Self {
261        self.style(Style::new().fg(Color::Blue))
262    }
263    fn magenta(self) -> Self {
264        self.style(Style::new().fg(Color::Magenta))
265    }
266    fn cyan(self) -> Self {
267        self.style(Style::new().fg(Color::Cyan))
268    }
269    fn white(self) -> Self {
270        self.style(Style::new().fg(Color::White))
271    }
272    fn gray(self) -> Self {
273        self.style(Style::new().fg(Color::Gray))
274    }
275
276    fn on_red(self) -> Self {
277        self.style(Style::new().bg(Color::Red))
278    }
279    fn on_green(self) -> Self {
280        self.style(Style::new().bg(Color::Green))
281    }
282    fn on_yellow(self) -> Self {
283        self.style(Style::new().bg(Color::Yellow))
284    }
285    fn on_blue(self) -> Self {
286        self.style(Style::new().bg(Color::Blue))
287    }
288    fn on_magenta(self) -> Self {
289        self.style(Style::new().bg(Color::Magenta))
290    }
291    fn on_cyan(self) -> Self {
292        self.style(Style::new().bg(Color::Cyan))
293    }
294    fn on_white(self) -> Self {
295        self.style(Style::new().bg(Color::White))
296    }
297
298    fn bold(self) -> Self {
299        self.style(Style::new().bold())
300    }
301    fn dim(self) -> Self {
302        self.style(Style::new().dim())
303    }
304    fn italic(self) -> Self {
305        self.style(Style::new().italic())
306    }
307    fn underlined(self) -> Self {
308        self.style(Style::new().underlined())
309    }
310    fn reversed(self) -> Self {
311        self.style(Style::new().reversed())
312    }
313    fn crossed_out(self) -> Self {
314        self.style(Style::new().crossed_out())
315    }
316}
317
318impl Stylize for Style {
319    fn style(self, other: Style) -> Self {
320        self.patch(other)
321    }
322}