Skip to main content

altui_core/
style.rs

1//! `style` contains the primitives used to control how your user interface will look.
2
3use bitflags::bitflags;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
7pub enum Color {
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    Rgb(u8, u8, u8),
26    Indexed(u8),
27}
28
29bitflags! {
30    /// Modifier changes the way a piece of text is displayed.
31    ///
32    /// They are bitflags so they can easily be composed.
33    ///
34    /// ## Examples
35    ///
36    /// ```rust
37    /// # use altui_core::style::Modifier;
38    ///
39    /// let m = Modifier::BOLD | Modifier::ITALIC;
40    /// ```
41    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
42    #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
43    pub struct Modifier: u16 {
44        const BOLD              = 0b0000_0000_0001;
45        const DIM               = 0b0000_0000_0010;
46        const ITALIC            = 0b0000_0000_0100;
47        const UNDERLINED        = 0b0000_0000_1000;
48        const SLOW_BLINK        = 0b0000_0001_0000;
49        const RAPID_BLINK       = 0b0000_0010_0000;
50        const REVERSED          = 0b0000_0100_0000;
51        const HIDDEN            = 0b0000_1000_0000;
52        const CROSSED_OUT       = 0b0001_0000_0000;
53    }
54}
55
56/// Style let you control the main characteristics of the displayed elements.
57///
58/// ```rust
59/// # use altui_core::style::{Color, Modifier, Style};
60/// Style::default()
61///     .fg(Color::Black)
62///     .bg(Color::Green)
63///     .add_modifier(Modifier::ITALIC | Modifier::BOLD);
64/// ```
65///
66/// It represents an incremental change. If you apply the styles S1, S2, S3 to a cell of the
67/// terminal buffer, the style of this cell will be the result of the merge of S1, S2 and S3, not
68/// just S3.
69///
70/// ```rust
71/// # use altui_core::style::{Color, Modifier, Style};
72/// # use altui_core::buffer::Buffer;
73/// # use altui_core::layout::Rect;
74/// let styles = [
75///     Style::default().fg(Color::Blue).add_modifier(Modifier::BOLD | Modifier::ITALIC),
76///     Style::default().bg(Color::Red),
77///     Style::default().fg(Color::Yellow).remove_modifier(Modifier::ITALIC),
78/// ];
79/// let mut buffer = Buffer::empty(Rect::new(0, 0, 1, 1));
80/// for style in &styles {
81///   buffer.get_mut(0, 0).set_style(*style);
82/// }
83/// assert_eq!(
84///     Style {
85///         fg: Some(Color::Yellow),
86///         bg: Some(Color::Red),
87///         add_modifier: Modifier::BOLD,
88///         sub_modifier: Modifier::empty(),
89///     },
90///     buffer.get(0, 0).style(),
91/// );
92/// ```
93///
94/// The default implementation returns a `Style` that does not modify anything. If you wish to
95/// reset all properties until that point use [`Style::reset`].
96///
97/// ```
98/// # use altui_core::style::{Color, Modifier, Style};
99/// # use altui_core::buffer::Buffer;
100/// # use altui_core::layout::Rect;
101/// let styles = [
102///     Style::default().fg(Color::Blue).add_modifier(Modifier::BOLD | Modifier::ITALIC),
103///     Style::reset().fg(Color::Yellow),
104/// ];
105/// let mut buffer = Buffer::empty(Rect::new(0, 0, 1, 1));
106/// for style in &styles {
107///   buffer.get_mut(0, 0).set_style(*style);
108/// }
109/// assert_eq!(
110///     Style {
111///         fg: Some(Color::Yellow),
112///         bg: Some(Color::Reset),
113///         add_modifier: Modifier::empty(),
114///         sub_modifier: Modifier::empty(),
115///     },
116///     buffer.get(0, 0).style(),
117/// );
118/// ```
119#[derive(Debug, Clone, Copy, PartialEq, Eq)]
120#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
121pub struct Style {
122    pub fg: Option<Color>,
123    pub bg: Option<Color>,
124    pub add_modifier: Modifier,
125    pub sub_modifier: Modifier,
126}
127
128impl Default for Style {
129    fn default() -> Style {
130        Style {
131            fg: None,
132            bg: None,
133            add_modifier: Modifier::empty(),
134            sub_modifier: Modifier::empty(),
135        }
136    }
137}
138
139impl Style {
140    /// Returns a `Style` resetting all properties.
141    pub fn reset() -> Style {
142        Style {
143            fg: Some(Color::Reset),
144            bg: Some(Color::Reset),
145            add_modifier: Modifier::empty(),
146            sub_modifier: Modifier::all(),
147        }
148    }
149
150    /// Changes the foreground color.
151    ///
152    /// ## Examples
153    ///
154    /// ```rust
155    /// # use altui_core::style::{Color, Style};
156    /// let style = Style::default().fg(Color::Blue);
157    /// let diff = Style::default().fg(Color::Red);
158    /// assert_eq!(style.patch(diff), Style::default().fg(Color::Red));
159    /// ```
160    pub fn fg(mut self, color: Color) -> Style {
161        self.fg = Some(color);
162        self
163    }
164
165    /// Changes the background color.
166    ///
167    /// ## Examples
168    ///
169    /// ```rust
170    /// # use altui_core::style::{Color, Style};
171    /// let style = Style::default().bg(Color::Blue);
172    /// let diff = Style::default().bg(Color::Red);
173    /// assert_eq!(style.patch(diff), Style::default().bg(Color::Red));
174    /// ```
175    pub fn bg(mut self, color: Color) -> Style {
176        self.bg = Some(color);
177        self
178    }
179
180    /// Changes the text emphasis.
181    ///
182    /// When applied, it adds the given modifier to the `Style` modifiers.
183    ///
184    /// ## Examples
185    ///
186    /// ```rust
187    /// # use altui_core::style::{Color, Modifier, Style};
188    /// let style = Style::default().add_modifier(Modifier::BOLD);
189    /// let diff = Style::default().add_modifier(Modifier::ITALIC);
190    /// let patched = style.patch(diff);
191    /// assert_eq!(patched.add_modifier, Modifier::BOLD | Modifier::ITALIC);
192    /// assert_eq!(patched.sub_modifier, Modifier::empty());
193    /// ```
194    pub fn add_modifier(mut self, modifier: Modifier) -> Style {
195        self.sub_modifier.remove(modifier);
196        self.add_modifier.insert(modifier);
197        self
198    }
199
200    /// Changes the text emphasis.
201    ///
202    /// When applied, it removes the given modifier from the `Style` modifiers.
203    ///
204    /// ## Examples
205    ///
206    /// ```rust
207    /// # use altui_core::style::{Color, Modifier, Style};
208    /// let style = Style::default().add_modifier(Modifier::BOLD | Modifier::ITALIC);
209    /// let diff = Style::default().remove_modifier(Modifier::ITALIC);
210    /// let patched = style.patch(diff);
211    /// assert_eq!(patched.add_modifier, Modifier::BOLD);
212    /// assert_eq!(patched.sub_modifier, Modifier::ITALIC);
213    /// ```
214    pub fn remove_modifier(mut self, modifier: Modifier) -> Style {
215        self.add_modifier.remove(modifier);
216        self.sub_modifier.insert(modifier);
217        self
218    }
219
220    /// Results in a combined style that is equivalent to applying the two individual styles to
221    /// a style one after the other.
222    ///
223    /// ## Examples
224    /// ```
225    /// # use altui_core::style::{Color, Modifier, Style};
226    /// let style_1 = Style::default().fg(Color::Yellow);
227    /// let style_2 = Style::default().bg(Color::Red);
228    /// let combined = style_1.patch(style_2);
229    /// assert_eq!(
230    ///     Style::default().patch(style_1).patch(style_2),
231    ///     Style::default().patch(combined));
232    /// ```
233    pub fn patch(mut self, other: Style) -> Style {
234        self.fg = other.fg.or(self.fg);
235        self.bg = other.bg.or(self.bg);
236
237        self.add_modifier.remove(other.sub_modifier);
238        self.add_modifier.insert(other.add_modifier);
239        self.sub_modifier.remove(other.add_modifier);
240        self.sub_modifier.insert(other.sub_modifier);
241
242        self
243    }
244}
245
246impl From<Color> for Style {
247    /// Sets a [`Style`] with chosen color as the foreground.
248    fn from(value: Color) -> Self {
249        Style::default().fg(value)
250    }
251}
252
253#[cfg(test)]
254mod tests {
255    use super::*;
256
257    fn styles() -> Vec<Style> {
258        vec![
259            Style::default(),
260            Style::default().fg(Color::Yellow),
261            Style::default().bg(Color::Yellow),
262            Style::default().add_modifier(Modifier::BOLD),
263            Style::default().remove_modifier(Modifier::BOLD),
264            Style::default().add_modifier(Modifier::ITALIC),
265            Style::default().remove_modifier(Modifier::ITALIC),
266            Style::default().add_modifier(Modifier::ITALIC | Modifier::BOLD),
267            Style::default().remove_modifier(Modifier::ITALIC | Modifier::BOLD),
268        ]
269    }
270
271    #[test]
272    fn combined_patch_gives_same_result_as_individual_patch() {
273        let styles = styles();
274        for &a in &styles {
275            for &b in &styles {
276                for &c in &styles {
277                    for &d in &styles {
278                        let combined = a.patch(b.patch(c.patch(d)));
279
280                        assert_eq!(
281                            Style::default().patch(a).patch(b).patch(c).patch(d),
282                            Style::default().patch(combined)
283                        );
284                    }
285                }
286            }
287        }
288    }
289}