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}