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}