ratatui_core/style/
anstyle.rs

1//! This module contains conversion functions for styles from the `anstyle` crate.
2use anstyle::{Ansi256Color, AnsiColor, Effects, RgbColor};
3use thiserror::Error;
4
5use super::{Color, Modifier, Style};
6
7/// Error type for converting between `anstyle` colors and `Color`
8#[derive(Debug, Error, PartialEq, Eq)]
9pub enum TryFromColorError {
10    #[error("cannot convert Ratatui Color to an Ansi256Color as it is not an indexed color")]
11    Ansi256,
12    #[error("cannot convert Ratatui Color to AnsiColor as it is not a 4-bit color")]
13    Ansi,
14    #[error("cannot convert Ratatui Color to RgbColor as it is not an RGB color")]
15    RgbColor,
16}
17
18impl From<Ansi256Color> for Color {
19    fn from(color: Ansi256Color) -> Self {
20        Self::Indexed(color.index())
21    }
22}
23
24impl TryFrom<Color> for Ansi256Color {
25    type Error = TryFromColorError;
26
27    fn try_from(color: Color) -> Result<Self, Self::Error> {
28        match color {
29            Color::Indexed(index) => Ok(Self(index)),
30            _ => Err(TryFromColorError::Ansi256),
31        }
32    }
33}
34
35impl From<AnsiColor> for Color {
36    fn from(value: AnsiColor) -> Self {
37        match value {
38            AnsiColor::Black => Self::Black,
39            AnsiColor::Red => Self::Red,
40            AnsiColor::Green => Self::Green,
41            AnsiColor::Yellow => Self::Yellow,
42            AnsiColor::Blue => Self::Blue,
43            AnsiColor::Magenta => Self::Magenta,
44            AnsiColor::Cyan => Self::Cyan,
45            AnsiColor::White => Self::Gray,
46            AnsiColor::BrightBlack => Self::DarkGray,
47            AnsiColor::BrightRed => Self::LightRed,
48            AnsiColor::BrightGreen => Self::LightGreen,
49            AnsiColor::BrightYellow => Self::LightYellow,
50            AnsiColor::BrightBlue => Self::LightBlue,
51            AnsiColor::BrightMagenta => Self::LightMagenta,
52            AnsiColor::BrightCyan => Self::LightCyan,
53            AnsiColor::BrightWhite => Self::White,
54        }
55    }
56}
57
58impl TryFrom<Color> for AnsiColor {
59    type Error = TryFromColorError;
60
61    fn try_from(color: Color) -> Result<Self, Self::Error> {
62        match color {
63            Color::Black => Ok(Self::Black),
64            Color::Red => Ok(Self::Red),
65            Color::Green => Ok(Self::Green),
66            Color::Yellow => Ok(Self::Yellow),
67            Color::Blue => Ok(Self::Blue),
68            Color::Magenta => Ok(Self::Magenta),
69            Color::Cyan => Ok(Self::Cyan),
70            Color::Gray => Ok(Self::White),
71            Color::DarkGray => Ok(Self::BrightBlack),
72            Color::LightRed => Ok(Self::BrightRed),
73            Color::LightGreen => Ok(Self::BrightGreen),
74            Color::LightYellow => Ok(Self::BrightYellow),
75            Color::LightBlue => Ok(Self::BrightBlue),
76            Color::LightMagenta => Ok(Self::BrightMagenta),
77            Color::LightCyan => Ok(Self::BrightCyan),
78            Color::White => Ok(Self::BrightWhite),
79            _ => Err(TryFromColorError::Ansi),
80        }
81    }
82}
83
84impl From<RgbColor> for Color {
85    fn from(color: RgbColor) -> Self {
86        Self::Rgb(color.r(), color.g(), color.b())
87    }
88}
89
90impl TryFrom<Color> for RgbColor {
91    type Error = TryFromColorError;
92
93    fn try_from(color: Color) -> Result<Self, Self::Error> {
94        match color {
95            Color::Rgb(red, green, blue) => Ok(Self(red, green, blue)),
96            _ => Err(TryFromColorError::RgbColor),
97        }
98    }
99}
100
101impl From<anstyle::Color> for Color {
102    fn from(color: anstyle::Color) -> Self {
103        match color {
104            anstyle::Color::Ansi(ansi_color) => Self::from(ansi_color),
105            anstyle::Color::Ansi256(ansi256_color) => Self::from(ansi256_color),
106            anstyle::Color::Rgb(rgb_color) => Self::from(rgb_color),
107        }
108    }
109}
110
111impl From<Color> for anstyle::Color {
112    fn from(color: Color) -> Self {
113        match color {
114            Color::Rgb(_, _, _) => Self::Rgb(RgbColor::try_from(color).unwrap()),
115            Color::Indexed(_) => Self::Ansi256(Ansi256Color::try_from(color).unwrap()),
116            _ => Self::Ansi(AnsiColor::try_from(color).unwrap()),
117        }
118    }
119}
120
121impl From<Effects> for Modifier {
122    fn from(effect: Effects) -> Self {
123        let mut modifier = Self::empty();
124        if effect.contains(Effects::BOLD) {
125            modifier |= Self::BOLD;
126        }
127        if effect.contains(Effects::DIMMED) {
128            modifier |= Self::DIM;
129        }
130        if effect.contains(Effects::ITALIC) {
131            modifier |= Self::ITALIC;
132        }
133        if effect.contains(Effects::UNDERLINE)
134            || effect.contains(Effects::DOUBLE_UNDERLINE)
135            || effect.contains(Effects::CURLY_UNDERLINE)
136            || effect.contains(Effects::DOTTED_UNDERLINE)
137            || effect.contains(Effects::DASHED_UNDERLINE)
138        {
139            modifier |= Self::UNDERLINED;
140        }
141        if effect.contains(Effects::BLINK) {
142            modifier |= Self::SLOW_BLINK;
143        }
144        if effect.contains(Effects::INVERT) {
145            modifier |= Self::REVERSED;
146        }
147        if effect.contains(Effects::HIDDEN) {
148            modifier |= Self::HIDDEN;
149        }
150        if effect.contains(Effects::STRIKETHROUGH) {
151            modifier |= Self::CROSSED_OUT;
152        }
153        modifier
154    }
155}
156
157impl From<Modifier> for Effects {
158    fn from(modifier: Modifier) -> Self {
159        let mut effects = Self::new();
160        if modifier.contains(Modifier::BOLD) {
161            effects |= Self::BOLD;
162        }
163        if modifier.contains(Modifier::DIM) {
164            effects |= Self::DIMMED;
165        }
166        if modifier.contains(Modifier::ITALIC) {
167            effects |= Self::ITALIC;
168        }
169        if modifier.contains(Modifier::UNDERLINED) {
170            effects |= Self::UNDERLINE;
171        }
172        if modifier.contains(Modifier::SLOW_BLINK) || modifier.contains(Modifier::RAPID_BLINK) {
173            effects |= Self::BLINK;
174        }
175        if modifier.contains(Modifier::REVERSED) {
176            effects |= Self::INVERT;
177        }
178        if modifier.contains(Modifier::HIDDEN) {
179            effects |= Self::HIDDEN;
180        }
181        if modifier.contains(Modifier::CROSSED_OUT) {
182            effects |= Self::STRIKETHROUGH;
183        }
184        effects
185    }
186}
187
188impl From<anstyle::Style> for Style {
189    fn from(style: anstyle::Style) -> Self {
190        Self {
191            fg: style.get_fg_color().map(Color::from),
192            bg: style.get_bg_color().map(Color::from),
193            #[cfg(feature = "underline-color")]
194            underline_color: style.get_underline_color().map(Color::from),
195            add_modifier: style.get_effects().into(),
196            ..Default::default()
197        }
198    }
199}
200
201impl From<Style> for anstyle::Style {
202    fn from(style: Style) -> Self {
203        let mut anstyle_style = Self::new();
204        if let Some(fg) = style.fg {
205            let fg = anstyle::Color::from(fg);
206            anstyle_style = anstyle_style.fg_color(Some(fg));
207        }
208        if let Some(bg) = style.bg {
209            let bg = anstyle::Color::from(bg);
210            anstyle_style = anstyle_style.bg_color(Some(bg));
211        }
212        #[cfg(feature = "underline-color")]
213        if let Some(underline) = style.underline_color {
214            let underline = anstyle::Color::from(underline);
215            anstyle_style = anstyle_style.underline_color(Some(underline));
216        }
217        anstyle_style = anstyle_style.effects(style.add_modifier.into());
218        anstyle_style
219    }
220}
221
222#[cfg(test)]
223mod tests {
224    use super::*;
225
226    #[test]
227    fn anstyle_to_color() {
228        let anstyle_color = Ansi256Color(42);
229        let color = Color::from(anstyle_color);
230        assert_eq!(color, Color::Indexed(42));
231    }
232
233    #[test]
234    fn color_to_ansi256color() {
235        let color = Color::Indexed(42);
236        let anstyle_color = Ansi256Color::try_from(color);
237        assert_eq!(anstyle_color, Ok(Ansi256Color(42)));
238    }
239
240    #[test]
241    fn color_to_ansi256color_error() {
242        let color = Color::Rgb(0, 0, 0);
243        let anstyle_color = Ansi256Color::try_from(color);
244        assert_eq!(anstyle_color, Err(TryFromColorError::Ansi256));
245    }
246
247    #[test]
248    fn ansi_color_to_color() {
249        let ansi_color = AnsiColor::Red;
250        let color = Color::from(ansi_color);
251        assert_eq!(color, Color::Red);
252    }
253
254    #[test]
255    fn color_to_ansicolor() {
256        let color = Color::Red;
257        let ansi_color = AnsiColor::try_from(color);
258        assert_eq!(ansi_color, Ok(AnsiColor::Red));
259    }
260
261    #[test]
262    fn color_to_ansicolor_error() {
263        let color = Color::Rgb(0, 0, 0);
264        let ansi_color = AnsiColor::try_from(color);
265        assert_eq!(ansi_color, Err(TryFromColorError::Ansi));
266    }
267
268    #[test]
269    fn rgb_color_to_color() {
270        let rgb_color = RgbColor(255, 0, 0);
271        let color = Color::from(rgb_color);
272        assert_eq!(color, Color::Rgb(255, 0, 0));
273    }
274
275    #[test]
276    fn color_to_rgbcolor() {
277        let color = Color::Rgb(255, 0, 0);
278        let rgb_color = RgbColor::try_from(color);
279        assert_eq!(rgb_color, Ok(RgbColor(255, 0, 0)));
280    }
281
282    #[test]
283    fn color_to_rgbcolor_error() {
284        let color = Color::Indexed(42);
285        let rgb_color = RgbColor::try_from(color);
286        assert_eq!(rgb_color, Err(TryFromColorError::RgbColor));
287    }
288
289    #[test]
290    fn effects_to_modifier() {
291        let effects = Effects::BOLD | Effects::ITALIC;
292        let modifier = Modifier::from(effects);
293        assert!(modifier.contains(Modifier::BOLD));
294        assert!(modifier.contains(Modifier::ITALIC));
295    }
296
297    #[test]
298    fn modifier_to_effects() {
299        let modifier = Modifier::BOLD | Modifier::ITALIC;
300        let effects = Effects::from(modifier);
301        assert!(effects.contains(Effects::BOLD));
302        assert!(effects.contains(Effects::ITALIC));
303    }
304
305    #[test]
306    fn anstyle_style_to_style() {
307        let anstyle_style = anstyle::Style::new()
308            .fg_color(Some(anstyle::Color::Ansi(AnsiColor::Red)))
309            .bg_color(Some(anstyle::Color::Ansi(AnsiColor::Blue)))
310            .underline_color(Some(anstyle::Color::Ansi(AnsiColor::Green)))
311            .effects(Effects::BOLD | Effects::ITALIC);
312        let style = Style::from(anstyle_style);
313        assert_eq!(style.fg, Some(Color::Red));
314        assert_eq!(style.bg, Some(Color::Blue));
315        #[cfg(feature = "underline-color")]
316        assert_eq!(style.underline_color, Some(Color::Green));
317        assert!(style.add_modifier.contains(Modifier::BOLD));
318        assert!(style.add_modifier.contains(Modifier::ITALIC));
319    }
320
321    #[test]
322    fn style_to_anstyle_style() {
323        let style = Style {
324            fg: Some(Color::Red),
325            bg: Some(Color::Blue),
326            #[cfg(feature = "underline-color")]
327            underline_color: Some(Color::Green),
328            add_modifier: Modifier::BOLD | Modifier::ITALIC,
329            ..Default::default()
330        };
331        let anstyle_style = anstyle::Style::from(style);
332        assert_eq!(
333            anstyle_style.get_fg_color(),
334            Some(anstyle::Color::Ansi(AnsiColor::Red))
335        );
336        assert_eq!(
337            anstyle_style.get_bg_color(),
338            Some(anstyle::Color::Ansi(AnsiColor::Blue))
339        );
340        #[cfg(feature = "underline-color")]
341        assert_eq!(
342            anstyle_style.get_underline_color(),
343            Some(anstyle::Color::Ansi(AnsiColor::Green))
344        );
345        assert!(anstyle_style.get_effects().contains(Effects::BOLD));
346        assert!(anstyle_style.get_effects().contains(Effects::ITALIC));
347    }
348}