ratatui/
style.rs

1//! `style` contains the primitives used to control how your user interface will look.
2//!
3//! There are two ways to set styles:
4//! - Creating and using the [`Style`] struct. (e.g. `Style::new().fg(Color::Red)`).
5//! - Using style shorthands. (e.g. `"hello".red()`).
6//!
7//! # Using the `Style` struct
8//!
9//! This is the original approach to styling and likely the most common. This is useful when
10//! creating style variables to reuse, however the shorthands are often more convenient and
11//! readable for most use cases.
12//!
13//! ## Example
14//!
15//! ```
16//! use ratatui::{
17//!     style::{Color, Modifier, Style},
18//!     text::Span,
19//! };
20//!
21//! let heading_style = Style::new()
22//!     .fg(Color::Black)
23//!     .bg(Color::Green)
24//!     .add_modifier(Modifier::ITALIC | Modifier::BOLD);
25//! let span = Span::styled("hello", heading_style);
26//! ```
27//!
28//! # Using style shorthands
29//!
30//! Originally Ratatui only had the ability to set styles using the `Style` struct. This is still
31//! supported, but there are now shorthands for all the styles that can be set. These save you from
32//! having to create a `Style` struct every time you want to set a style.
33//!
34//! The shorthands are implemented in the [`Stylize`] trait which is automatically implemented for
35//! many types via the [`Styled`] trait. This means that you can use the shorthands on any type
36//! that implements [`Styled`]. E.g.:
37//! - Strings and string slices when styled return a [`Span`]
38//! - [`Span`]s can be styled again, which will merge the styles.
39//! - Many widget types can be styled directly rather than calling their `style()` method.
40//!
41//! See the [`Stylize`] and [`Styled`] traits for more information.
42//!
43//! ## Example
44//!
45//! ```
46//! use ratatui::{
47//!     style::{Color, Modifier, Style, Stylize},
48//!     text::Span,
49//!     widgets::Paragraph,
50//! };
51//!
52//! assert_eq!(
53//!     "hello".red().on_blue().bold(),
54//!     Span::styled(
55//!         "hello",
56//!         Style::default()
57//!             .fg(Color::Red)
58//!             .bg(Color::Blue)
59//!             .add_modifier(Modifier::BOLD)
60//!     )
61//! );
62//!
63//! assert_eq!(
64//!     Paragraph::new("hello").red().on_blue().bold(),
65//!     Paragraph::new("hello").style(
66//!         Style::default()
67//!             .fg(Color::Red)
68//!             .bg(Color::Blue)
69//!             .add_modifier(Modifier::BOLD)
70//!     )
71//! );
72//! ```
73//!
74//! [`Span`]: crate::text::Span
75
76use std::fmt;
77
78use bitflags::bitflags;
79pub use color::{Color, ParseColorError};
80use stylize::ColorDebugKind;
81pub use stylize::{Styled, Stylize};
82
83mod color;
84pub mod palette;
85#[cfg(feature = "palette")]
86mod palette_conversion;
87mod stylize;
88
89bitflags! {
90    /// Modifier changes the way a piece of text is displayed.
91    ///
92    /// They are bitflags so they can easily be composed.
93    ///
94    /// `From<Modifier> for Style` is implemented so you can use `Modifier` anywhere that accepts
95    /// `Into<Style>`.
96    ///
97    /// ## Examples
98    ///
99    /// ```rust
100    /// use ratatui::style::Modifier;
101    ///
102    /// let m = Modifier::BOLD | Modifier::ITALIC;
103    /// ```
104    #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
105    #[derive(Default, Clone, Copy, Eq, PartialEq, Hash)]
106    pub struct Modifier: u16 {
107        const BOLD              = 0b0000_0000_0001;
108        const DIM               = 0b0000_0000_0010;
109        const ITALIC            = 0b0000_0000_0100;
110        const UNDERLINED        = 0b0000_0000_1000;
111        const SLOW_BLINK        = 0b0000_0001_0000;
112        const RAPID_BLINK       = 0b0000_0010_0000;
113        const REVERSED          = 0b0000_0100_0000;
114        const HIDDEN            = 0b0000_1000_0000;
115        const CROSSED_OUT       = 0b0001_0000_0000;
116    }
117}
118
119/// Implement the `Debug` trait for `Modifier` manually.
120///
121/// This will avoid printing the empty modifier as 'Borders(0x0)' and instead print it as 'NONE'.
122impl fmt::Debug for Modifier {
123    /// Format the modifier as `NONE` if the modifier is empty or as a list of flags separated by
124    /// `|` otherwise.
125    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
126        if self.is_empty() {
127            return write!(f, "NONE");
128        }
129        write!(f, "{}", self.0)
130    }
131}
132
133/// Style lets you control the main characteristics of the displayed elements.
134///
135/// ```rust
136/// use ratatui::style::{Color, Modifier, Style};
137///
138/// Style::default()
139///     .fg(Color::Black)
140///     .bg(Color::Green)
141///     .add_modifier(Modifier::ITALIC | Modifier::BOLD);
142/// ```
143///
144/// Styles can also be created with a [shorthand notation](crate::style#using-style-shorthands).
145///
146/// ```rust
147/// use ratatui::style::{Style, Stylize};
148///
149/// Style::new().black().on_green().italic().bold();
150/// ```
151///
152/// For more information about the style shorthands, see the [`Stylize`] trait.
153///
154/// We implement conversions from [`Color`] and [`Modifier`] to [`Style`] so you can use them
155/// anywhere that accepts `Into<Style>`.
156///
157/// ```rust
158/// use ratatui::{
159///     style::{Color, Modifier, Style},
160///     text::Line,
161/// };
162///
163/// Line::styled("hello", Style::new().fg(Color::Red));
164/// // simplifies to
165/// Line::styled("hello", Color::Red);
166///
167/// Line::styled("hello", Style::new().add_modifier(Modifier::BOLD));
168/// // simplifies to
169/// Line::styled("hello", Modifier::BOLD);
170/// ```
171///
172/// Styles represents an incremental change. If you apply the styles S1, S2, S3 to a cell of the
173/// terminal buffer, the style of this cell will be the result of the merge of S1, S2 and S3, not
174/// just S3.
175///
176/// ```rust
177/// use ratatui::{
178///     buffer::Buffer,
179///     layout::Rect,
180///     style::{Color, Modifier, Style},
181/// };
182///
183/// let styles = [
184///     Style::default()
185///         .fg(Color::Blue)
186///         .add_modifier(Modifier::BOLD | Modifier::ITALIC),
187///     Style::default()
188///         .bg(Color::Red)
189///         .add_modifier(Modifier::UNDERLINED),
190///     #[cfg(feature = "underline-color")]
191///     Style::default().underline_color(Color::Green),
192///     Style::default()
193///         .fg(Color::Yellow)
194///         .remove_modifier(Modifier::ITALIC),
195/// ];
196/// let mut buffer = Buffer::empty(Rect::new(0, 0, 1, 1));
197/// for style in &styles {
198///     buffer[(0, 0)].set_style(*style);
199/// }
200/// assert_eq!(
201///     Style {
202///         fg: Some(Color::Yellow),
203///         bg: Some(Color::Red),
204///         #[cfg(feature = "underline-color")]
205///         underline_color: Some(Color::Green),
206///         add_modifier: Modifier::BOLD | Modifier::UNDERLINED,
207///         sub_modifier: Modifier::empty(),
208///     },
209///     buffer[(0, 0)].style(),
210/// );
211/// ```
212///
213/// The default implementation returns a `Style` that does not modify anything. If you wish to
214/// reset all properties until that point use [`Style::reset`].
215///
216/// ```
217/// use ratatui::{
218///     buffer::Buffer,
219///     layout::Rect,
220///     style::{Color, Modifier, Style},
221/// };
222///
223/// let styles = [
224///     Style::default()
225///         .fg(Color::Blue)
226///         .add_modifier(Modifier::BOLD | Modifier::ITALIC),
227///     Style::reset().fg(Color::Yellow),
228/// ];
229/// let mut buffer = Buffer::empty(Rect::new(0, 0, 1, 1));
230/// for style in &styles {
231///     buffer[(0, 0)].set_style(*style);
232/// }
233/// assert_eq!(
234///     Style {
235///         fg: Some(Color::Yellow),
236///         bg: Some(Color::Reset),
237///         #[cfg(feature = "underline-color")]
238///         underline_color: Some(Color::Reset),
239///         add_modifier: Modifier::empty(),
240///         sub_modifier: Modifier::empty(),
241///     },
242///     buffer[(0, 0)].style(),
243/// );
244/// ```
245#[derive(Default, Clone, Copy, Eq, PartialEq, Hash)]
246#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
247pub struct Style {
248    pub fg: Option<Color>,
249    pub bg: Option<Color>,
250    #[cfg(feature = "underline-color")]
251    pub underline_color: Option<Color>,
252    pub add_modifier: Modifier,
253    pub sub_modifier: Modifier,
254}
255
256/// A custom debug implementation that prints only the fields that are not the default, and unwraps
257/// the `Option`s.
258impl fmt::Debug for Style {
259    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
260        f.write_str("Style::new()")?;
261        self.fmt_stylize(f)?;
262        Ok(())
263    }
264}
265
266impl Styled for Style {
267    type Item = Self;
268
269    fn style(&self) -> Style {
270        *self
271    }
272
273    fn set_style<S: Into<Self>>(self, style: S) -> Self::Item {
274        self.patch(style)
275    }
276}
277
278impl Style {
279    pub const fn new() -> Self {
280        Self {
281            fg: None,
282            bg: None,
283            #[cfg(feature = "underline-color")]
284            underline_color: None,
285            add_modifier: Modifier::empty(),
286            sub_modifier: Modifier::empty(),
287        }
288    }
289
290    /// Returns a `Style` resetting all properties.
291    pub const fn reset() -> Self {
292        Self {
293            fg: Some(Color::Reset),
294            bg: Some(Color::Reset),
295            #[cfg(feature = "underline-color")]
296            underline_color: Some(Color::Reset),
297            add_modifier: Modifier::empty(),
298            sub_modifier: Modifier::all(),
299        }
300    }
301
302    /// Changes the foreground color.
303    ///
304    /// ## Examples
305    ///
306    /// ```rust
307    /// use ratatui::style::{Color, Style};
308    ///
309    /// let style = Style::default().fg(Color::Blue);
310    /// let diff = Style::default().fg(Color::Red);
311    /// assert_eq!(style.patch(diff), Style::default().fg(Color::Red));
312    /// ```
313    #[must_use = "`fg` returns the modified style without modifying the original"]
314    pub const fn fg(mut self, color: Color) -> Self {
315        self.fg = Some(color);
316        self
317    }
318
319    /// Changes the background color.
320    ///
321    /// ## Examples
322    ///
323    /// ```rust
324    /// use ratatui::style::{Color, Style};
325    ///
326    /// let style = Style::default().bg(Color::Blue);
327    /// let diff = Style::default().bg(Color::Red);
328    /// assert_eq!(style.patch(diff), Style::default().bg(Color::Red));
329    /// ```
330    #[must_use = "`bg` returns the modified style without modifying the original"]
331    pub const fn bg(mut self, color: Color) -> Self {
332        self.bg = Some(color);
333        self
334    }
335
336    /// Changes the underline color. The text must be underlined with a modifier for this to work.
337    ///
338    /// This uses a non-standard ANSI escape sequence. It is supported by most terminal emulators,
339    /// but is only implemented in the crossterm backend and enabled by the `underline-color`
340    /// feature flag.
341    ///
342    /// See
343    /// [Wikipedia](https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters)
344    /// code `58` and `59` for more information.
345    ///
346    /// ## Examples
347    ///
348    /// ```rust
349    /// use ratatui::style::{Color, Modifier, Style};
350    ///
351    /// let style = Style::default()
352    ///     .underline_color(Color::Blue)
353    ///     .add_modifier(Modifier::UNDERLINED);
354    /// let diff = Style::default()
355    ///     .underline_color(Color::Red)
356    ///     .add_modifier(Modifier::UNDERLINED);
357    /// assert_eq!(
358    ///     style.patch(diff),
359    ///     Style::default()
360    ///         .underline_color(Color::Red)
361    ///         .add_modifier(Modifier::UNDERLINED)
362    /// );
363    /// ```
364    #[cfg(feature = "underline-color")]
365    #[must_use = "`underline_color` returns the modified style without modifying the original"]
366    pub const fn underline_color(mut self, color: Color) -> Self {
367        self.underline_color = Some(color);
368        self
369    }
370
371    /// Changes the text emphasis.
372    ///
373    /// When applied, it adds the given modifier to the `Style` modifiers.
374    ///
375    /// ## Examples
376    ///
377    /// ```rust
378    /// use ratatui::style::{Modifier, Style};
379    ///
380    /// let style = Style::default().add_modifier(Modifier::BOLD);
381    /// let diff = Style::default().add_modifier(Modifier::ITALIC);
382    /// let patched = style.patch(diff);
383    /// assert_eq!(patched.add_modifier, Modifier::BOLD | Modifier::ITALIC);
384    /// assert_eq!(patched.sub_modifier, Modifier::empty());
385    /// ```
386    #[must_use = "`add_modifier` returns the modified style without modifying the original"]
387    pub const fn add_modifier(mut self, modifier: Modifier) -> Self {
388        self.sub_modifier = self.sub_modifier.difference(modifier);
389        self.add_modifier = self.add_modifier.union(modifier);
390        self
391    }
392
393    /// Changes the text emphasis.
394    ///
395    /// When applied, it removes the given modifier from the `Style` modifiers.
396    ///
397    /// ## Examples
398    ///
399    /// ```rust
400    /// use ratatui::style::{Modifier, Style};
401    ///
402    /// let style = Style::default().add_modifier(Modifier::BOLD | Modifier::ITALIC);
403    /// let diff = Style::default().remove_modifier(Modifier::ITALIC);
404    /// let patched = style.patch(diff);
405    /// assert_eq!(patched.add_modifier, Modifier::BOLD);
406    /// assert_eq!(patched.sub_modifier, Modifier::ITALIC);
407    /// ```
408    #[must_use = "`remove_modifier` returns the modified style without modifying the original"]
409    pub const fn remove_modifier(mut self, modifier: Modifier) -> Self {
410        self.add_modifier = self.add_modifier.difference(modifier);
411        self.sub_modifier = self.sub_modifier.union(modifier);
412        self
413    }
414
415    /// Results in a combined style that is equivalent to applying the two individual styles to
416    /// a style one after the other.
417    ///
418    /// `style` accepts any type that is convertible to [`Style`] (e.g. [`Style`], [`Color`], or
419    /// your own type that implements [`Into<Style>`]).
420    ///
421    /// ## Examples
422    /// ```
423    /// use ratatui::style::{Color, Modifier, Style};
424    ///
425    /// let style_1 = Style::default().fg(Color::Yellow);
426    /// let style_2 = Style::default().bg(Color::Red);
427    /// let combined = style_1.patch(style_2);
428    /// assert_eq!(
429    ///     Style::default().patch(style_1).patch(style_2),
430    ///     Style::default().patch(combined)
431    /// );
432    /// ```
433    #[must_use = "`patch` returns the modified style without modifying the original"]
434    pub fn patch<S: Into<Self>>(mut self, other: S) -> Self {
435        let other = other.into();
436        self.fg = other.fg.or(self.fg);
437        self.bg = other.bg.or(self.bg);
438
439        #[cfg(feature = "underline-color")]
440        {
441            self.underline_color = other.underline_color.or(self.underline_color);
442        }
443
444        self.add_modifier.remove(other.sub_modifier);
445        self.add_modifier.insert(other.add_modifier);
446        self.sub_modifier.remove(other.add_modifier);
447        self.sub_modifier.insert(other.sub_modifier);
448
449        self
450    }
451
452    /// Formats the style in a way that can be copy-pasted into code using the style shorthands.
453    ///
454    /// This is useful for debugging and for generating code snippets.
455    pub(crate) fn fmt_stylize(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
456        use fmt::Debug;
457        if let Some(fg) = self.fg {
458            fg.stylize_debug(ColorDebugKind::Foreground).fmt(f)?;
459        }
460        if let Some(bg) = self.bg {
461            bg.stylize_debug(ColorDebugKind::Background).fmt(f)?;
462        }
463        #[cfg(feature = "underline-color")]
464        if let Some(underline_color) = self.underline_color {
465            underline_color
466                .stylize_debug(ColorDebugKind::Underline)
467                .fmt(f)?;
468        }
469        for modifier in self.add_modifier.iter() {
470            match modifier {
471                Modifier::BOLD => f.write_str(".bold()")?,
472                Modifier::DIM => f.write_str(".dim()")?,
473                Modifier::ITALIC => f.write_str(".italic()")?,
474                Modifier::UNDERLINED => f.write_str(".underlined()")?,
475                Modifier::SLOW_BLINK => f.write_str(".slow_blink()")?,
476                Modifier::RAPID_BLINK => f.write_str(".rapid_blink()")?,
477                Modifier::REVERSED => f.write_str(".reversed()")?,
478                Modifier::HIDDEN => f.write_str(".hidden()")?,
479                Modifier::CROSSED_OUT => f.write_str(".crossed_out()")?,
480                _ => f.write_fmt(format_args!(".add_modifier(Modifier::{modifier:?})"))?,
481            }
482        }
483        for modifier in self.sub_modifier.iter() {
484            match modifier {
485                Modifier::BOLD => f.write_str(".not_bold()")?,
486                Modifier::DIM => f.write_str(".not_dim()")?,
487                Modifier::ITALIC => f.write_str(".not_italic()")?,
488                Modifier::UNDERLINED => f.write_str(".not_underlined()")?,
489                Modifier::SLOW_BLINK => f.write_str(".not_slow_blink()")?,
490                Modifier::RAPID_BLINK => f.write_str(".not_rapid_blink()")?,
491                Modifier::REVERSED => f.write_str(".not_reversed()")?,
492                Modifier::HIDDEN => f.write_str(".not_hidden()")?,
493                Modifier::CROSSED_OUT => f.write_str(".not_crossed_out()")?,
494                _ => f.write_fmt(format_args!(".remove_modifier(Modifier::{modifier:?})"))?,
495            }
496        }
497        Ok(())
498    }
499}
500
501impl From<Color> for Style {
502    /// Creates a new `Style` with the given foreground color.
503    ///
504    /// To specify a foreground and background color, use the `from((fg, bg))` constructor.
505    ///
506    /// # Example
507    ///
508    /// ```rust
509    /// use ratatui::style::{Color, Style};
510    ///
511    /// let style = Style::from(Color::Red);
512    /// ```
513    fn from(color: Color) -> Self {
514        Self::new().fg(color)
515    }
516}
517
518impl From<(Color, Color)> for Style {
519    /// Creates a new `Style` with the given foreground and background colors.
520    ///
521    /// # Example
522    ///
523    /// ```rust
524    /// use ratatui::style::{Color, Style};
525    ///
526    /// // red foreground, blue background
527    /// let style = Style::from((Color::Red, Color::Blue));
528    /// // default foreground, blue background
529    /// let style = Style::from((Color::Reset, Color::Blue));
530    /// ```
531    fn from((fg, bg): (Color, Color)) -> Self {
532        Self::new().fg(fg).bg(bg)
533    }
534}
535
536impl From<Modifier> for Style {
537    /// Creates a new `Style` with the given modifier added.
538    ///
539    /// To specify multiple modifiers, use the `|` operator.
540    ///
541    /// To specify modifiers to add and remove, use the `from((add_modifier, sub_modifier))`
542    /// constructor.
543    ///
544    /// # Example
545    ///
546    /// ```rust
547    /// use ratatui::style::{Style, Modifier};
548    ///
549    /// // add bold and italic
550    /// let style = Style::from(Modifier::BOLD|Modifier::ITALIC);
551    fn from(modifier: Modifier) -> Self {
552        Self::new().add_modifier(modifier)
553    }
554}
555
556impl From<(Modifier, Modifier)> for Style {
557    /// Creates a new `Style` with the given modifiers added and removed.
558    ///
559    /// # Example
560    ///
561    /// ```rust
562    /// use ratatui::style::{Modifier, Style};
563    ///
564    /// // add bold and italic, remove dim
565    /// let style = Style::from((Modifier::BOLD | Modifier::ITALIC, Modifier::DIM));
566    /// ```
567    fn from((add_modifier, sub_modifier): (Modifier, Modifier)) -> Self {
568        Self::new()
569            .add_modifier(add_modifier)
570            .remove_modifier(sub_modifier)
571    }
572}
573
574impl From<(Color, Modifier)> for Style {
575    /// Creates a new `Style` with the given foreground color and modifier added.
576    ///
577    /// To specify multiple modifiers, use the `|` operator.
578    ///
579    /// # Example
580    ///
581    /// ```rust
582    /// use ratatui::style::{Color, Modifier, Style};
583    ///
584    /// // red foreground, add bold and italic
585    /// let style = Style::from((Color::Red, Modifier::BOLD | Modifier::ITALIC));
586    /// ```
587    fn from((fg, modifier): (Color, Modifier)) -> Self {
588        Self::new().fg(fg).add_modifier(modifier)
589    }
590}
591
592impl From<(Color, Color, Modifier)> for Style {
593    /// Creates a new `Style` with the given foreground and background colors and modifier added.
594    ///
595    /// To specify multiple modifiers, use the `|` operator.
596    ///
597    /// # Example
598    ///
599    /// ```rust
600    /// use ratatui::style::{Color, Modifier, Style};
601    ///
602    /// // red foreground, blue background, add bold and italic
603    /// let style = Style::from((Color::Red, Color::Blue, Modifier::BOLD | Modifier::ITALIC));
604    /// ```
605    fn from((fg, bg, modifier): (Color, Color, Modifier)) -> Self {
606        Self::new().fg(fg).bg(bg).add_modifier(modifier)
607    }
608}
609
610impl From<(Color, Color, Modifier, Modifier)> for Style {
611    /// Creates a new `Style` with the given foreground and background colors and modifiers added
612    /// and removed.
613    ///
614    /// # Example
615    ///
616    /// ```rust
617    /// use ratatui::style::{Color, Modifier, Style};
618    ///
619    /// // red foreground, blue background, add bold and italic, remove dim
620    /// let style = Style::from((
621    ///     Color::Red,
622    ///     Color::Blue,
623    ///     Modifier::BOLD | Modifier::ITALIC,
624    ///     Modifier::DIM,
625    /// ));
626    /// ```
627    fn from((fg, bg, add_modifier, sub_modifier): (Color, Color, Modifier, Modifier)) -> Self {
628        Self::new()
629            .fg(fg)
630            .bg(bg)
631            .add_modifier(add_modifier)
632            .remove_modifier(sub_modifier)
633    }
634}
635
636#[cfg(test)]
637mod tests {
638    use rstest::rstest;
639
640    use super::*;
641
642    #[rstest]
643    #[case(Style::new(), "Style::new()")]
644    #[case(Style::new().red(), "Style::new().red()")]
645    #[case(Style::new().on_blue(), "Style::new().on_blue()")]
646    #[case(Style::new().bold(), "Style::new().bold()")]
647    #[case(Style::new().not_italic(), "Style::new().not_italic()")]
648    #[case(
649        Style::new().red().on_blue().bold().italic().not_dim().not_hidden(),
650        "Style::new().red().on_blue().bold().italic().not_dim().not_hidden()"
651    )]
652    fn debug(#[case] style: Style, #[case] expected: &'static str) {
653        assert_eq!(format!("{style:?}"), expected);
654    }
655
656    #[test]
657    fn combined_patch_gives_same_result_as_individual_patch() {
658        let styles = [
659            Style::new(),
660            Style::new().fg(Color::Yellow),
661            Style::new().bg(Color::Yellow),
662            Style::new().add_modifier(Modifier::BOLD),
663            Style::new().remove_modifier(Modifier::BOLD),
664            Style::new().add_modifier(Modifier::ITALIC),
665            Style::new().remove_modifier(Modifier::ITALIC),
666            Style::new().add_modifier(Modifier::ITALIC | Modifier::BOLD),
667            Style::new().remove_modifier(Modifier::ITALIC | Modifier::BOLD),
668        ];
669        for &a in &styles {
670            for &b in &styles {
671                for &c in &styles {
672                    for &d in &styles {
673                        assert_eq!(
674                            Style::new().patch(a).patch(b).patch(c).patch(d),
675                            Style::new().patch(a.patch(b.patch(c.patch(d))))
676                        );
677                    }
678                }
679            }
680        }
681    }
682
683    #[test]
684    fn combine_individual_modifiers() {
685        use crate::{buffer::Buffer, layout::Rect};
686
687        let mods = [
688            Modifier::BOLD,
689            Modifier::DIM,
690            Modifier::ITALIC,
691            Modifier::UNDERLINED,
692            Modifier::SLOW_BLINK,
693            Modifier::RAPID_BLINK,
694            Modifier::REVERSED,
695            Modifier::HIDDEN,
696            Modifier::CROSSED_OUT,
697        ];
698
699        let mut buffer = Buffer::empty(Rect::new(0, 0, 1, 1));
700
701        for m in mods {
702            buffer[(0, 0)].set_style(Style::reset());
703            buffer[(0, 0)].set_style(Style::new().add_modifier(m));
704            let style = buffer[(0, 0)].style();
705            assert!(style.add_modifier.contains(m));
706            assert!(!style.sub_modifier.contains(m));
707        }
708    }
709
710    #[rstest]
711    #[case(Modifier::empty(), "NONE")]
712    #[case(Modifier::BOLD, "BOLD")]
713    #[case(Modifier::DIM, "DIM")]
714    #[case(Modifier::ITALIC, "ITALIC")]
715    #[case(Modifier::UNDERLINED, "UNDERLINED")]
716    #[case(Modifier::SLOW_BLINK, "SLOW_BLINK")]
717    #[case(Modifier::RAPID_BLINK, "RAPID_BLINK")]
718    #[case(Modifier::REVERSED, "REVERSED")]
719    #[case(Modifier::HIDDEN, "HIDDEN")]
720    #[case(Modifier::CROSSED_OUT, "CROSSED_OUT")]
721    #[case(Modifier::BOLD | Modifier::DIM, "BOLD | DIM")]
722    #[case(Modifier::all(), "BOLD | DIM | ITALIC | UNDERLINED | SLOW_BLINK | RAPID_BLINK | REVERSED | HIDDEN | CROSSED_OUT")]
723    fn modifier_debug(#[case] modifier: Modifier, #[case] expected: &str) {
724        assert_eq!(format!("{modifier:?}"), expected);
725    }
726
727    #[test]
728    fn style_can_be_const() {
729        const RED: Color = Color::Red;
730        const BLACK: Color = Color::Black;
731        const BOLD: Modifier = Modifier::BOLD;
732        const ITALIC: Modifier = Modifier::ITALIC;
733
734        const _RESET: Style = Style::reset();
735        const _RED_FG: Style = Style::new().fg(RED);
736        const _BLACK_BG: Style = Style::new().bg(BLACK);
737        const _ADD_BOLD: Style = Style::new().add_modifier(BOLD);
738        const _REMOVE_ITALIC: Style = Style::new().remove_modifier(ITALIC);
739        const ALL: Style = Style::new()
740            .fg(RED)
741            .bg(BLACK)
742            .add_modifier(BOLD)
743            .remove_modifier(ITALIC);
744        assert_eq!(
745            ALL,
746            Style::new()
747                .fg(Color::Red)
748                .bg(Color::Black)
749                .add_modifier(Modifier::BOLD)
750                .remove_modifier(Modifier::ITALIC)
751        );
752    }
753
754    #[rstest]
755    #[case(Style::new().black(), Color::Black)]
756    #[case(Style::new().red(), Color::Red)]
757    #[case(Style::new().green(), Color::Green)]
758    #[case(Style::new().yellow(), Color::Yellow)]
759    #[case(Style::new().blue(), Color::Blue)]
760    #[case(Style::new().magenta(), Color::Magenta)]
761    #[case(Style::new().cyan(), Color::Cyan)]
762    #[case(Style::new().white(), Color::White)]
763    #[case(Style::new().gray(), Color::Gray)]
764    #[case(Style::new().dark_gray(), Color::DarkGray)]
765    #[case(Style::new().light_red(), Color::LightRed)]
766    #[case(Style::new().light_green(), Color::LightGreen)]
767    #[case(Style::new().light_yellow(), Color::LightYellow)]
768    #[case(Style::new().light_blue(), Color::LightBlue)]
769    #[case(Style::new().light_magenta(), Color::LightMagenta)]
770    #[case(Style::new().light_cyan(), Color::LightCyan)]
771    #[case(Style::new().white(), Color::White)]
772    fn fg_can_be_stylized(#[case] stylized: Style, #[case] expected: Color) {
773        assert_eq!(stylized, Style::new().fg(expected));
774    }
775
776    #[rstest]
777    #[case(Style::new().on_black(), Color::Black)]
778    #[case(Style::new().on_red(), Color::Red)]
779    #[case(Style::new().on_green(), Color::Green)]
780    #[case(Style::new().on_yellow(), Color::Yellow)]
781    #[case(Style::new().on_blue(), Color::Blue)]
782    #[case(Style::new().on_magenta(), Color::Magenta)]
783    #[case(Style::new().on_cyan(), Color::Cyan)]
784    #[case(Style::new().on_white(), Color::White)]
785    #[case(Style::new().on_gray(), Color::Gray)]
786    #[case(Style::new().on_dark_gray(), Color::DarkGray)]
787    #[case(Style::new().on_light_red(), Color::LightRed)]
788    #[case(Style::new().on_light_green(), Color::LightGreen)]
789    #[case(Style::new().on_light_yellow(), Color::LightYellow)]
790    #[case(Style::new().on_light_blue(), Color::LightBlue)]
791    #[case(Style::new().on_light_magenta(), Color::LightMagenta)]
792    #[case(Style::new().on_light_cyan(), Color::LightCyan)]
793    #[case(Style::new().on_white(), Color::White)]
794    fn bg_can_be_stylized(#[case] stylized: Style, #[case] expected: Color) {
795        assert_eq!(stylized, Style::new().bg(expected));
796    }
797
798    #[rstest]
799    #[case(Style::new().bold(), Modifier::BOLD)]
800    #[case(Style::new().dim(), Modifier::DIM)]
801    #[case(Style::new().italic(), Modifier::ITALIC)]
802    #[case(Style::new().underlined(), Modifier::UNDERLINED)]
803    #[case(Style::new().slow_blink(), Modifier::SLOW_BLINK)]
804    #[case(Style::new().rapid_blink(), Modifier::RAPID_BLINK)]
805    #[case(Style::new().reversed(), Modifier::REVERSED)]
806    #[case(Style::new().hidden(), Modifier::HIDDEN)]
807    #[case(Style::new().crossed_out(), Modifier::CROSSED_OUT)]
808    fn add_modifier_can_be_stylized(#[case] stylized: Style, #[case] expected: Modifier) {
809        assert_eq!(stylized, Style::new().add_modifier(expected));
810    }
811
812    #[rstest]
813    #[case(Style::new().not_bold(), Modifier::BOLD)]
814    #[case(Style::new().not_dim(), Modifier::DIM)]
815    #[case(Style::new().not_italic(), Modifier::ITALIC)]
816    #[case(Style::new().not_underlined(), Modifier::UNDERLINED)]
817    #[case(Style::new().not_slow_blink(), Modifier::SLOW_BLINK)]
818    #[case(Style::new().not_rapid_blink(), Modifier::RAPID_BLINK)]
819    #[case(Style::new().not_reversed(), Modifier::REVERSED)]
820    #[case(Style::new().not_hidden(), Modifier::HIDDEN)]
821    #[case(Style::new().not_crossed_out(), Modifier::CROSSED_OUT)]
822    fn remove_modifier_can_be_stylized(#[case] stylized: Style, #[case] expected: Modifier) {
823        assert_eq!(stylized, Style::new().remove_modifier(expected));
824    }
825
826    #[test]
827    fn reset_can_be_stylized() {
828        assert_eq!(Style::new().reset(), Style::reset());
829    }
830
831    #[test]
832    fn from_color() {
833        assert_eq!(Style::from(Color::Red), Style::new().fg(Color::Red));
834    }
835
836    #[test]
837    fn from_color_color() {
838        assert_eq!(
839            Style::from((Color::Red, Color::Blue)),
840            Style::new().fg(Color::Red).bg(Color::Blue)
841        );
842    }
843
844    #[test]
845    fn from_modifier() {
846        assert_eq!(
847            Style::from(Modifier::BOLD | Modifier::ITALIC),
848            Style::new()
849                .add_modifier(Modifier::BOLD)
850                .add_modifier(Modifier::ITALIC)
851        );
852    }
853
854    #[test]
855    fn from_modifier_modifier() {
856        assert_eq!(
857            Style::from((Modifier::BOLD | Modifier::ITALIC, Modifier::DIM)),
858            Style::new()
859                .add_modifier(Modifier::BOLD)
860                .add_modifier(Modifier::ITALIC)
861                .remove_modifier(Modifier::DIM)
862        );
863    }
864
865    #[test]
866    fn from_color_modifier() {
867        assert_eq!(
868            Style::from((Color::Red, Modifier::BOLD | Modifier::ITALIC)),
869            Style::new()
870                .fg(Color::Red)
871                .add_modifier(Modifier::BOLD)
872                .add_modifier(Modifier::ITALIC)
873        );
874    }
875
876    #[test]
877    fn from_color_color_modifier() {
878        assert_eq!(
879            Style::from((Color::Red, Color::Blue, Modifier::BOLD | Modifier::ITALIC)),
880            Style::new()
881                .fg(Color::Red)
882                .bg(Color::Blue)
883                .add_modifier(Modifier::BOLD)
884                .add_modifier(Modifier::ITALIC)
885        );
886    }
887
888    #[test]
889    fn from_color_color_modifier_modifier() {
890        assert_eq!(
891            Style::from((
892                Color::Red,
893                Color::Blue,
894                Modifier::BOLD | Modifier::ITALIC,
895                Modifier::DIM
896            )),
897            Style::new()
898                .fg(Color::Red)
899                .bg(Color::Blue)
900                .add_modifier(Modifier::BOLD)
901                .add_modifier(Modifier::ITALIC)
902                .remove_modifier(Modifier::DIM)
903        );
904    }
905}