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