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