owo_colors/
dyn_styles.rs

1use crate::{AnsiColors, Color, DynColor, DynColors};
2use core::fmt;
3
4#[cfg(doc)]
5use crate::OwoColorize;
6
7/// A runtime-configurable text effect for use with [`Style`]
8#[allow(missing_docs)]
9#[derive(Debug, Copy, Clone)]
10pub enum Effect {
11    Bold,
12    Dimmed,
13    Italic,
14    Underline,
15    Blink,
16    BlinkFast,
17    Reversed,
18    Hidden,
19    Strikethrough,
20}
21
22macro_rules! color_methods {
23    ($(
24        #[$fg_meta:meta] #[$bg_meta:meta] $color:ident $fg_method:ident $bg_method:ident
25    ),* $(,)?) => {
26        $(
27            #[$fg_meta]
28            #[must_use]
29            pub fn $fg_method(mut self) -> Self {
30                self.fg = Some(DynColors::Ansi(AnsiColors::$color));
31                self
32            }
33
34            #[$fg_meta]
35            #[must_use]
36            pub fn $bg_method(mut self) -> Self {
37                self.bg = Some(DynColors::Ansi(AnsiColors::$color));
38                self
39            }
40         )*
41    };
42}
43
44macro_rules! style_methods {
45    ($(#[$meta:meta] ($name:ident, $set_name:ident)),* $(,)?) => {
46        $(
47            #[$meta]
48            #[must_use]
49            pub fn $name(mut self) -> Self {
50                self.style_flags.$set_name(true);
51                self
52            }
53        )*
54    };
55}
56
57const _: () = (); // workaround for syntax highlighting bug
58
59/// A wrapper type which applies a [`Style`] when displaying the inner type
60pub struct Styled<T> {
61    /// The target value to be styled
62    pub(crate) target: T,
63    /// The style to apply to target
64    pub style: Style,
65}
66
67/// A pre-computed style that can be applied to a struct using [`OwoColorize::style`]. Its
68/// interface mimicks that of [`OwoColorize`], but instead of chaining methods on your
69/// object, you instead chain them on the `Style` object before applying it.
70///
71/// ```rust
72/// use owo_colors::{OwoColorize, Style};
73///
74/// let my_style = Style::new()
75///     .red()
76///     .on_white()
77///     .strikethrough();
78///
79/// println!("{}", "red text, white background, struck through".style(my_style));
80/// ```
81#[derive(Debug, Default, Copy, Clone, PartialEq)]
82pub struct Style {
83    pub(crate) fg: Option<DynColors>,
84    pub(crate) bg: Option<DynColors>,
85    pub(crate) bold: bool,
86    pub(crate) style_flags: StyleFlags,
87}
88
89#[repr(transparent)]
90#[derive(Debug, Default, Copy, Clone, PartialEq)]
91pub(crate) struct StyleFlags(pub(crate) u8);
92
93const DIMMED_SHIFT: u8 = 0;
94const ITALIC_SHIFT: u8 = 1;
95const UNDERLINE_SHIFT: u8 = 2;
96const BLINK_SHIFT: u8 = 3;
97const BLINK_FAST_SHIFT: u8 = 4;
98const REVERSED_SHIFT: u8 = 5;
99const HIDDEN_SHIFT: u8 = 6;
100const STRIKETHROUGH_SHIFT: u8 = 7;
101
102macro_rules! style_flags_methods {
103    ($(($shift:ident, $name:ident, $set_name:ident)),* $(,)?) => {
104        $(
105            fn $name(&self) -> bool {
106                ((self.0 >> $shift) & 1) != 0
107            }
108
109            fn $set_name(&mut self, $name: bool) {
110                self.0 = (self.0 & !(1 << $shift)) | (($name as u8) << $shift);
111            }
112        )*
113    };
114}
115
116impl StyleFlags {
117    style_flags_methods! {
118        (DIMMED_SHIFT, dimmed, set_dimmed),
119        (ITALIC_SHIFT, italic, set_italic),
120        (UNDERLINE_SHIFT, underline, set_underline),
121        (BLINK_SHIFT, blink, set_blink),
122        (BLINK_FAST_SHIFT, blink_fast, set_blink_fast),
123        (REVERSED_SHIFT, reversed, set_reversed),
124        (HIDDEN_SHIFT, hidden, set_hidden),
125        (STRIKETHROUGH_SHIFT, strikethrough, set_strikethrough),
126    }
127}
128
129impl Style {
130    /// Create a new style to be applied later
131    #[must_use]
132    pub fn new() -> Self {
133        Self::default()
134    }
135
136    /// Apply the style to a given struct to output
137    pub fn style<T>(&self, target: T) -> Styled<T> {
138        Styled {
139            target,
140            style: *self,
141        }
142    }
143
144    /// Set the foreground color generically
145    ///
146    /// ```rust
147    /// use owo_colors::{OwoColorize, colors::*};
148    ///
149    /// println!("{}", "red foreground".fg::<Red>());
150    /// ```
151    #[must_use]
152    pub fn fg<C: Color>(mut self) -> Self {
153        self.fg = Some(C::into_dyncolors());
154        self
155    }
156
157    /// Set the background color generically.
158    ///
159    /// ```rust
160    /// use owo_colors::{OwoColorize, colors::*};
161    ///
162    /// println!("{}", "black background".bg::<Black>());
163    /// ```
164    #[must_use]
165    pub fn bg<C: Color>(mut self) -> Self {
166        self.bg = Some(C::into_dyncolors());
167        self
168    }
169
170    /// Removes the foreground color from the style. Note that this does not apply
171    /// the default color, but rather represents not changing the current terminal color.
172    ///
173    /// If you wish to actively change the terminal color back to the default, see
174    /// [`Style::default_color`].
175    #[must_use]
176    pub fn remove_fg(mut self) -> Self {
177        self.fg = None;
178        self
179    }
180
181    /// Removes the background color from the style. Note that this does not apply
182    /// the default color, but rather represents not changing the current terminal color.
183    ///
184    /// If you wish to actively change the terminal color back to the default, see
185    /// [`Style::on_default_color`].
186    #[must_use]
187    pub fn remove_bg(mut self) -> Self {
188        self.bg = None;
189        self
190    }
191
192    color_methods! {
193        /// Change the foreground color to black
194        /// Change the background color to black
195        Black    black    on_black,
196        /// Change the foreground color to red
197        /// Change the background color to red
198        Red      red      on_red,
199        /// Change the foreground color to green
200        /// Change the background color to green
201        Green    green    on_green,
202        /// Change the foreground color to yellow
203        /// Change the background color to yellow
204        Yellow   yellow   on_yellow,
205        /// Change the foreground color to blue
206        /// Change the background color to blue
207        Blue     blue     on_blue,
208        /// Change the foreground color to magenta
209        /// Change the background color to magenta
210        Magenta  magenta  on_magenta,
211        /// Change the foreground color to purple
212        /// Change the background color to purple
213        Magenta  purple   on_purple,
214        /// Change the foreground color to cyan
215        /// Change the background color to cyan
216        Cyan     cyan     on_cyan,
217        /// Change the foreground color to white
218        /// Change the background color to white
219        White    white    on_white,
220
221        /// Change the foreground color to the terminal default
222        /// Change the background color to the terminal default
223        Default default_color on_default_color,
224
225        /// Change the foreground color to bright black
226        /// Change the background color to bright black
227        BrightBlack    bright_black    on_bright_black,
228        /// Change the foreground color to bright red
229        /// Change the background color to bright red
230        BrightRed      bright_red      on_bright_red,
231        /// Change the foreground color to bright green
232        /// Change the background color to bright green
233        BrightGreen    bright_green    on_bright_green,
234        /// Change the foreground color to bright yellow
235        /// Change the background color to bright yellow
236        BrightYellow   bright_yellow   on_bright_yellow,
237        /// Change the foreground color to bright blue
238        /// Change the background color to bright blue
239        BrightBlue     bright_blue     on_bright_blue,
240        /// Change the foreground color to bright magenta
241        /// Change the background color to bright magenta
242        BrightMagenta  bright_magenta  on_bright_magenta,
243        /// Change the foreground color to bright purple
244        /// Change the background color to bright purple
245        BrightMagenta  bright_purple   on_bright_purple,
246        /// Change the foreground color to bright cyan
247        /// Change the background color to bright cyan
248        BrightCyan     bright_cyan     on_bright_cyan,
249        /// Change the foreground color to bright white
250        /// Change the background color to bright white
251        BrightWhite    bright_white    on_bright_white,
252    }
253
254    /// Make the text bold
255    #[must_use]
256    pub fn bold(mut self) -> Self {
257        self.bold = true;
258        self
259    }
260
261    style_methods! {
262        /// Make the text dim
263        (dimmed, set_dimmed),
264        /// Make the text italicized
265        (italic, set_italic),
266        /// Make the text italicized
267        (underline, set_underline),
268        /// Make the text blink
269        (blink, set_blink),
270        /// Make the text blink (but fast!)
271        (blink_fast, set_blink_fast),
272        /// Swap the foreground and background colors
273        (reversed, set_reversed),
274        /// Hide the text
275        (hidden, set_hidden),
276        /// Cross out the text
277        (strikethrough, set_strikethrough),
278    }
279
280    fn set_effect(&mut self, effect: Effect, to: bool) {
281        use Effect::*;
282        match effect {
283            Bold => self.bold = to,
284            Dimmed => self.style_flags.set_dimmed(to),
285            Italic => self.style_flags.set_italic(to),
286            Underline => self.style_flags.set_underline(to),
287            Blink => self.style_flags.set_blink(to),
288            BlinkFast => self.style_flags.set_blink_fast(to),
289            Reversed => self.style_flags.set_reversed(to),
290            Hidden => self.style_flags.set_hidden(to),
291            Strikethrough => self.style_flags.set_strikethrough(to),
292        }
293    }
294
295    fn set_effects(&mut self, effects: &[Effect], to: bool) {
296        for e in effects {
297            self.set_effect(*e, to)
298        }
299    }
300
301    /// Apply a given effect from the style
302    #[must_use]
303    pub fn effect(mut self, effect: Effect) -> Self {
304        self.set_effect(effect, true);
305        self
306    }
307
308    /// Remove a given effect from the style
309    #[must_use]
310    pub fn remove_effect(mut self, effect: Effect) -> Self {
311        self.set_effect(effect, false);
312        self
313    }
314
315    /// Apply a given set of effects to the style
316    #[must_use]
317    pub fn effects(mut self, effects: &[Effect]) -> Self {
318        self.set_effects(effects, true);
319        self
320    }
321
322    /// Remove a given set of effects from the style
323    #[must_use]
324    pub fn remove_effects(mut self, effects: &[Effect]) -> Self {
325        self.set_effects(effects, false);
326        self
327    }
328
329    /// Disables all the given effects from the style
330    #[must_use]
331    pub fn remove_all_effects(mut self) -> Self {
332        self.bold = false;
333        self.style_flags = StyleFlags::default();
334        self
335    }
336
337    /// Set the foreground color at runtime. Only use if you do not know which color will be used at
338    /// compile-time. If the color is constant, use either [`OwoColorize::fg`](crate::OwoColorize::fg) or
339    /// a color-specific method, such as [`OwoColorize::green`](crate::OwoColorize::green),
340    ///
341    /// ```rust
342    /// use owo_colors::{OwoColorize, AnsiColors};
343    ///
344    /// println!("{}", "green".color(AnsiColors::Green));
345    /// ```
346    #[must_use]
347    pub fn color<Color: DynColor>(mut self, color: Color) -> Self {
348        self.fg = Some(color.get_dyncolors_fg());
349        self
350    }
351
352    /// Set the background color at runtime. Only use if you do not know what color to use at
353    /// compile-time. If the color is constant, use either [`OwoColorize::bg`](crate::OwoColorize::bg) or
354    /// a color-specific method, such as [`OwoColorize::on_yellow`](crate::OwoColorize::on_yellow),
355    ///
356    /// ```rust
357    /// use owo_colors::{OwoColorize, AnsiColors};
358    ///
359    /// println!("{}", "yellow background".on_color(AnsiColors::BrightYellow));
360    /// ```
361    #[must_use]
362    pub fn on_color<Color: DynColor>(mut self, color: Color) -> Self {
363        self.bg = Some(color.get_dyncolors_bg());
364        self
365    }
366
367    /// Set the foreground color to a specific RGB value.
368    #[must_use]
369    pub fn fg_rgb<const R: u8, const G: u8, const B: u8>(mut self) -> Self {
370        self.fg = Some(DynColors::Rgb(R, G, B));
371
372        self
373    }
374
375    /// Set the background color to a specific RGB value.
376    #[must_use]
377    pub fn bg_rgb<const R: u8, const G: u8, const B: u8>(mut self) -> Self {
378        self.bg = Some(DynColors::Rgb(R, G, B));
379
380        self
381    }
382
383    /// Sets the foreground color to an RGB value.
384    #[must_use]
385    pub fn truecolor(mut self, r: u8, g: u8, b: u8) -> Self {
386        self.fg = Some(DynColors::Rgb(r, g, b));
387        self
388    }
389
390    /// Sets the background color to an RGB value.
391    #[must_use]
392    pub fn on_truecolor(mut self, r: u8, g: u8, b: u8) -> Self {
393        self.bg = Some(DynColors::Rgb(r, g, b));
394        self
395    }
396
397    /// Returns if the style does not apply any formatting
398    #[must_use]
399    #[inline]
400    pub fn is_plain(&self) -> bool {
401        let s = &self;
402        !(s.fg.is_some() || s.bg.is_some() || s.bold || s.style_flags != StyleFlags::default())
403    }
404
405    /// Applies the ANSI-prefix for this style to the given formatter
406    #[inline]
407    #[allow(unused_assignments)]
408    pub fn fmt_prefix(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
409        let s = self;
410        let format_less_important_effects = s.style_flags != StyleFlags::default();
411        let format_effect = s.bold || format_less_important_effects;
412        let format_any = !self.is_plain();
413
414        let mut semicolon = false;
415
416        if format_any {
417            f.write_str("\x1b[")?;
418        }
419
420        if let Some(fg) = s.fg {
421            <DynColors as DynColor>::fmt_raw_ansi_fg(&fg, f)?;
422            semicolon = true;
423        }
424
425        if let Some(bg) = s.bg {
426            if s.fg.is_some() {
427                f.write_str(";")?;
428            }
429            <DynColors as DynColor>::fmt_raw_ansi_bg(&bg, f)?;
430        }
431
432        if format_effect {
433            if s.bold {
434                if semicolon {
435                    f.write_str(";")?;
436                }
437
438                f.write_str("1")?;
439
440                semicolon = true;
441            }
442
443            macro_rules! text_effect_fmt {
444                ($style:ident, $formatter:ident, $semicolon:ident, $(($attr:ident, $value:literal)),* $(,)?) => {
445                    $(
446                        if $style.style_flags.$attr() {
447                            if $semicolon {
448                                $formatter.write_str(";")?;
449                            }
450                            $formatter.write_str($value)?;
451
452                            $semicolon = true;
453                        }
454                    )+
455                }
456            }
457
458            if format_less_important_effects {
459                text_effect_fmt! {
460                    s, f, semicolon,
461                    (dimmed,        "2"),
462                    (italic,        "3"),
463                    (underline,     "4"),
464                    (blink,         "5"),
465                    (blink_fast,    "6"),
466                    (reversed,      "7"),
467                    (hidden,        "8"),
468                    (strikethrough, "9"),
469                }
470            }
471        }
472
473        if format_any {
474            f.write_str("m")?;
475        }
476        Ok(())
477    }
478
479    /// Applies the ANSI-suffix for this style to the given formatter
480    #[inline]
481    pub fn fmt_suffix(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
482        if !self.is_plain() {
483            f.write_str("\x1b[0m")?;
484        }
485        Ok(())
486    }
487}
488
489/// Helper to create [`Style`]s more ergonomically
490pub fn style() -> Style {
491    Style::new()
492}
493
494impl<T> Styled<T> {
495    /// Returns a reference to the inner value to be styled
496    pub fn inner(&self) -> &T {
497        &self.target
498    }
499
500    /// Returns a mutable reference to the inner value to be styled
501    pub fn inner_mut(&mut self) -> &mut T {
502        &mut self.target
503    }
504}
505
506macro_rules! impl_fmt {
507    ($($trait:path),* $(,)?) => {
508        $(
509            impl<T: $trait> $trait for Styled<T> {
510                #[allow(unused_assignments)]
511                fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
512                    self.style.fmt_prefix(f)?;
513                    <T as $trait>::fmt(&self.target, f)?;
514                    self.style.fmt_suffix(f)
515                }
516            }
517        )*
518    };
519}
520
521impl_fmt! {
522    fmt::Display,
523    fmt::Debug,
524    fmt::UpperHex,
525    fmt::LowerHex,
526    fmt::Binary,
527    fmt::UpperExp,
528    fmt::LowerExp,
529    fmt::Octal,
530    fmt::Pointer,
531}
532
533#[cfg(test)]
534mod tests {
535    use super::*;
536    use crate::{AnsiColors, OwoColorize};
537
538    struct StylePrefixOnly(Style);
539    impl fmt::Display for StylePrefixOnly {
540        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
541            self.0.fmt_prefix(f)
542        }
543    }
544
545    struct StyleSuffixOnly(Style);
546    impl fmt::Display for StyleSuffixOnly {
547        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
548            self.0.fmt_suffix(f)
549        }
550    }
551
552    #[test]
553    fn test_it() {
554        let style = Style::new()
555            .bright_white()
556            .on_blue()
557            .bold()
558            .dimmed()
559            .italic()
560            .underline()
561            .blink()
562            //.blink_fast()
563            //.reversed()
564            //.hidden()
565            .strikethrough();
566        let s = style.style("TEST");
567        let s2 = format!("{}", &s);
568        println!("{}", &s2);
569        assert_eq!(&s2, "\u{1b}[97;44;1;2;3;4;5;9mTEST\u{1b}[0m");
570
571        let prefix = format!("{}", StylePrefixOnly(style));
572        assert_eq!(&prefix, "\u{1b}[97;44;1;2;3;4;5;9m");
573
574        let suffix = format!("{}", StyleSuffixOnly(style));
575        assert_eq!(&suffix, "\u{1b}[0m");
576    }
577
578    #[test]
579    fn test_effects() {
580        use Effect::*;
581        let style = Style::new().effects(&[Strikethrough, Underline]);
582
583        let s = style.style("TEST");
584        let s2 = format!("{}", &s);
585        println!("{}", &s2);
586        assert_eq!(&s2, "\u{1b}[4;9mTEST\u{1b}[0m");
587    }
588
589    #[test]
590    fn test_color() {
591        let style = Style::new()
592            .color(AnsiColors::White)
593            .on_color(AnsiColors::Black);
594
595        let s = style.style("TEST");
596        let s2 = format!("{}", &s);
597        println!("{}", &s2);
598        assert_eq!(&s2, "\u{1b}[37;40mTEST\u{1b}[0m");
599    }
600
601    #[test]
602    fn test_truecolor() {
603        let style = Style::new().truecolor(255, 255, 255).on_truecolor(0, 0, 0);
604
605        let s = style.style("TEST");
606        let s2 = format!("{}", &s);
607        println!("{}", &s2);
608        assert_eq!(&s2, "\u{1b}[38;2;255;255;255;48;2;0;0;0mTEST\u{1b}[0m");
609    }
610
611    #[test]
612    fn test_string_reference() {
613        let style = Style::new().truecolor(255, 255, 255).on_truecolor(0, 0, 0);
614
615        let string = String::from("TEST");
616        let s = style.style(&string);
617        let s2 = format!("{}", &s);
618        println!("{}", &s2);
619        assert_eq!(&s2, "\u{1b}[38;2;255;255;255;48;2;0;0;0mTEST\u{1b}[0m");
620    }
621
622    #[test]
623    fn test_owocolorize() {
624        let style = Style::new().bright_white().on_blue();
625
626        let s = "TEST".style(style);
627        let s2 = format!("{}", &s);
628        println!("{}", &s2);
629        assert_eq!(&s2, "\u{1b}[97;44mTEST\u{1b}[0m");
630    }
631
632    #[test]
633    fn test_is_plain() {
634        let style = Style::new().bright_white().on_blue();
635
636        assert!(!style.is_plain());
637        assert!(Style::default().is_plain());
638
639        let string = String::from("TEST");
640        let s = Style::default().style(&string);
641        let s2 = format!("{}", &s);
642
643        assert_eq!(string, s2)
644    }
645
646    #[test]
647    fn test_inner() {
648        let style = Style::default();
649
650        let mut s = "TEST".style(style);
651
652        assert_eq!(&&"TEST", s.inner());
653
654        *s.inner_mut() = &"changed";
655        assert_eq!(&&"changed", s.inner());
656        assert_eq!("changed", format!("{}", s));
657    }
658}