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}