tuviv/terminal/
style.rs

1//! Ways of styling the terminal
2
3pub use std::borrow::Cow;
4use std::{
5    fmt::{self, Display, LowerHex},
6    mem,
7    str::FromStr,
8};
9
10#[cfg(feature = "crossterm")]
11use crossterm::style::{Attribute as CrossAttribute, Color as CrossColor};
12
13/// Creates a function to set this to a specific colour
14macro_rules! color_func {
15    ($funcname: ident, $color: path, $colorfunc: ident) => {
16        #[doc = concat!("A shorthand for `self.", stringify!($colorfunc), "(", stringify!($color), ")`")]
17        #[must_use = "its a builder pattern function"]
18        pub fn $funcname(self) -> Self {
19            self.$colorfunc($color)
20        }
21    }
22}
23
24/// Creates a function to set a modifier on this
25macro_rules! modifier_func {
26    ($funcname: ident, $modifier: path, $attrfunc: ident) => {
27        #[doc = concat!("A shorthand for `self.width_modifier(", stringify!($modifier), ")`")]
28        #[must_use = "its a builder pattern function"]
29        pub fn $funcname(self) -> Self {
30            self.$attrfunc($modifier)
31        }
32    }
33}
34
35/// Creates all the [`color_func`]s and [`modifier_func`]s
36macro_rules! funcs {
37    () => {
38        color_func!(black, Color::Black, fg);
39        color_func!(red, Color::Red, fg);
40        color_func!(green, Color::Green, fg);
41        color_func!(yellow, Color::Yellow, fg);
42        color_func!(blue, Color::Blue, fg);
43        color_func!(magenta, Color::Magenta, fg);
44        color_func!(cyan, Color::Cyan, fg);
45        color_func!(white, Color::White, fg);
46
47        color_func!(bg_black, Color::Black, bg);
48        color_func!(bg_red, Color::Red, bg);
49        color_func!(bg_green, Color::Green, bg);
50        color_func!(bg_yellow, Color::Yellow, bg);
51        color_func!(bg_blue, Color::Blue, bg);
52        color_func!(bg_magenta, Color::Magenta, bg);
53        color_func!(bg_cyan, Color::Cyan, bg);
54        color_func!(bg_white, Color::White, bg);
55
56        color_func!(underline_colored_black, Color::Black, underline_color);
57        color_func!(underline_colored_red, Color::Red, underline_color);
58        color_func!(underline_colored_green, Color::Green, underline_color);
59        color_func!(underline_colored_yellow, Color::Yellow, underline_color);
60        color_func!(underline_colored_blue, Color::Blue, underline_color);
61        color_func!(underline_colored_magenta, Color::Magenta, underline_color);
62        color_func!(underline_colored_cyan, Color::Cyan, underline_color);
63        color_func!(underline_colored_white, Color::White, underline_color);
64
65        color_func!(bright_black, Color::BrightBlack, fg);
66        color_func!(bright_red, Color::BrightRed, fg);
67        color_func!(bright_green, Color::BrightGreen, fg);
68        color_func!(bright_yellow, Color::BrightYellow, fg);
69        color_func!(bright_blue, Color::BrightBlue, fg);
70        color_func!(bright_magenta, Color::BrightMagenta, fg);
71        color_func!(bright_cyan, Color::BrightCyan, fg);
72        color_func!(bright_white, Color::BrightWhite, fg);
73
74        color_func!(bright_bg_black, Color::BrightBlack, bg);
75        color_func!(bright_bg_red, Color::BrightRed, bg);
76        color_func!(bright_bg_green, Color::BrightGreen, bg);
77        color_func!(bright_bg_yellow, Color::BrightYellow, bg);
78        color_func!(bright_bg_blue, Color::BrightBlue, bg);
79        color_func!(bright_bg_magenta, Color::BrightMagenta, bg);
80        color_func!(bright_bg_cyan, Color::BrightCyan, bg);
81        color_func!(bright_bg_white, Color::BrightWhite, bg);
82
83        color_func!(
84            underline_colored_bright_black,
85            Color::BrightBlack,
86            underline_color
87        );
88        color_func!(
89            underline_colored_bright_red,
90            Color::BrightRed,
91            underline_color
92        );
93        color_func!(
94            underline_colored_bright_green,
95            Color::BrightGreen,
96            underline_color
97        );
98        color_func!(
99            underline_colored_bright_yellow,
100            Color::BrightYellow,
101            underline_color
102        );
103        color_func!(
104            underline_colored_bright_blue,
105            Color::BrightBlue,
106            underline_color
107        );
108        color_func!(
109            underline_colored_bright_magenta,
110            Color::BrightMagenta,
111            underline_color
112        );
113        color_func!(
114            underline_colored_bright_cyan,
115            Color::BrightCyan,
116            underline_color
117        );
118        color_func!(
119            underline_colored_bright_white,
120            Color::BrightWhite,
121            underline_color
122        );
123
124        modifier_func!(not_bold, Modifier::BOLD, without_modifier);
125        modifier_func!(not_dimmed, Modifier::DIM, without_modifier);
126        modifier_func!(not_italic, Modifier::ITALIC, without_modifier);
127        modifier_func!(not_underlined, Modifier::UNDERLINED, without_modifier);
128        modifier_func!(
129            not_double_underlined,
130            Modifier::DOUBLE_UNDERLINED,
131            without_modifier
132        );
133        modifier_func!(
134            not_undercurled,
135            Modifier::UNDERCURLED,
136            without_modifier
137        );
138        modifier_func!(
139            not_underdotted,
140            Modifier::UNDERDOTTED,
141            without_modifier
142        );
143        modifier_func!(
144            not_underfashed,
145            Modifier::UNDERDASHED,
146            without_modifier
147        );
148        modifier_func!(not_slow_blink, Modifier::SLOW_BLINK, without_modifier);
149        modifier_func!(
150            not_rapid_blink,
151            Modifier::RAPID_BLINK,
152            without_modifier
153        );
154        modifier_func!(not_reversed, Modifier::REVERSED, without_modifier);
155        modifier_func!(not_hidden, Modifier::HIDDEN, without_modifier);
156        modifier_func!(
157            not_crossed_out,
158            Modifier::CROSSED_OUT,
159            without_modifier
160        );
161
162        modifier_func!(bold, Modifier::BOLD, with_modifier);
163        modifier_func!(dimmed, Modifier::DIM, with_modifier);
164        modifier_func!(italic, Modifier::ITALIC, with_modifier);
165        modifier_func!(underlined, Modifier::UNDERLINED, with_modifier);
166        modifier_func!(
167            double_underlined,
168            Modifier::DOUBLE_UNDERLINED,
169            with_modifier
170        );
171        modifier_func!(undercurled, Modifier::UNDERCURLED, with_modifier);
172        modifier_func!(underdotted, Modifier::UNDERDOTTED, with_modifier);
173        modifier_func!(underfashed, Modifier::UNDERDASHED, with_modifier);
174        modifier_func!(slow_blink, Modifier::SLOW_BLINK, with_modifier);
175        modifier_func!(rapid_blink, Modifier::RAPID_BLINK, with_modifier);
176        modifier_func!(reversed, Modifier::REVERSED, with_modifier);
177        modifier_func!(hidden, Modifier::HIDDEN, with_modifier);
178        modifier_func!(crossed_out, Modifier::CROSSED_OUT, with_modifier);
179    };
180}
181/// A style representing all the colours and modifiers a cell can have.
182#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
183#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
184pub struct Style {
185    /// The foreground (text-colour) of the cell
186    #[cfg_attr(feature = "serde", serde(default))]
187    pub fg: Option<Color>,
188    /// The background colour of the cell
189    #[cfg_attr(feature = "serde", serde(default))]
190    pub bg: Option<Color>,
191    /// The underline colour of the cell
192    #[cfg_attr(feature = "serde", serde(default))]
193    pub underline_color: Option<Color>,
194    /// The modifiers this has
195    #[cfg_attr(feature = "serde", serde(alias = "modifiers"))]
196    #[cfg_attr(feature = "serde", serde(default))]
197    pub add_modifier: Modifier,
198    /// The modifiers this specifically doesn't
199    ///
200    /// This is mainly used when overlaying [`Style`]s.
201    #[cfg_attr(feature = "serde", serde(default))]
202    pub sub_modifier: Modifier,
203}
204
205impl Style {
206    /// Overlays another style onto this.
207    #[must_use = "this returns a new modifier"]
208    pub fn overlay(self, overlay: Self) -> Self {
209        let sub = overlay.sub_modifier | self.sub_modifier;
210        Self {
211            fg: overlay.fg.or(self.fg),
212            bg: overlay.bg.or(self.bg),
213            underline_color: overlay.underline_color.or(self.underline_color),
214            add_modifier: {
215                let mut modifier = overlay.add_modifier | self.add_modifier;
216                modifier.remove(sub);
217                modifier
218            },
219            sub_modifier: sub,
220        }
221    }
222
223    /// Sets the [`Style::fg`] property on the `StyledText`
224    #[must_use = "its a builder pattern function"]
225    pub fn fg(mut self, fg: impl Into<Option<Color>>) -> Self {
226        self.fg = fg.into();
227        self
228    }
229
230    /// Sets the [`Style::bg`] property on the `StyledText`
231    #[must_use = "its a builder pattern function"]
232    pub fn bg(mut self, bg: impl Into<Option<Color>>) -> Self {
233        self.bg = bg.into();
234        self
235    }
236
237    /// Sets the [`Style::underline_color`] property on the `StyledText`
238    #[must_use = "its a builder pattern function"]
239    pub fn underline_color(
240        mut self,
241        underline_color: impl Into<Option<Color>>,
242    ) -> Self {
243        self.underline_color = underline_color.into();
244        self
245    }
246
247    /// Appends to the the [`Style::add_modifier`] property on the `StyledText`
248    #[must_use = "its a builder pattern function"]
249    pub fn with_modifier(mut self, modifier: Modifier) -> Self {
250        self.add_modifier |= modifier;
251        self.sub_modifier.remove(modifier);
252        self
253    }
254
255    /// Appends to the the [`Style::sub_modifier`] property on the
256    /// `StyledText`
257    #[must_use = "its a builder pattern function"]
258    pub fn without_modifier(mut self, modifier: Modifier) -> Self {
259        self.sub_modifier |= modifier;
260        self.add_modifier.remove(modifier);
261        self
262    }
263
264    /// Swaps the `fg` and `bg`
265    pub fn swapped(mut self) -> Self {
266        mem::swap(&mut self.fg, &mut self.bg);
267        self
268    }
269
270    /// Makes sure the colours are fully realized, and are not `None`
271    pub fn realize_color(mut self) -> Self {
272        if self.fg.is_none() {
273            self.fg = Some(Color::Reset);
274        }
275        if self.bg.is_none() {
276            self.bg = Some(Color::Reset);
277        }
278        if self.underline_color.is_none() {
279            self.underline_color = Some(Color::Reset);
280        }
281
282        self
283    }
284
285    funcs!();
286}
287
288/// Represents a terminal colour
289#[derive(Clone, Debug, Copy, PartialEq, Eq)]
290#[cfg_attr(
291    feature = "serde",
292    derive(serde_with::DeserializeFromStr, serde_with::SerializeDisplay)
293)]
294pub enum Color {
295    /// Resets the colour
296    Reset,
297
298    /// Black
299    Black,
300    /// Red
301    Red,
302    /// Green
303    Green,
304    /// Yellow
305    Yellow,
306    /// Blue
307    Blue,
308    /// Magenta
309    Magenta,
310    /// Cyan
311    Cyan,
312    /// White
313    White,
314
315    /// Bright Black
316    BrightBlack,
317    /// Bright Red
318    BrightRed,
319    /// Bright Green
320    BrightGreen,
321    /// Bright Yellow
322    BrightYellow,
323    /// Bright Blue
324    BrightBlue,
325    /// Bright Magenta
326    BrightMagenta,
327    /// Bright Cyan
328    BrightCyan,
329    /// Bright White
330    BrightWhite,
331
332    /// An RGB value
333    Rgb(u8, u8, u8),
334
335    /// An indexed ansi value -
336    /// see [Wikipedia on indexed ansi](https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit)
337    Indexed(u8),
338}
339
340/// An error for when a colour can't be parsed in the `FromStr` impl for [`Color`]
341#[derive(Debug)]
342pub struct UnrecognisedColor(String);
343
344impl Display for UnrecognisedColor {
345    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
346        f.write_str("unrecognised color: \"")?;
347        f.write_str(&self.0)?;
348        f.write_str("\"")
349    }
350}
351
352impl Display for Color {
353    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
354        match self {
355            Color::Reset => f.write_str("reset"),
356
357            Color::Black => f.write_str("black"),
358            Color::Red => f.write_str("red"),
359            Color::Green => f.write_str("green"),
360            Color::Yellow => f.write_str("yellow"),
361            Color::Blue => f.write_str("blue"),
362            Color::Magenta => f.write_str("magenta"),
363            Color::Cyan => f.write_str("cyan"),
364            Color::White => f.write_str("white"),
365
366            Color::BrightBlack => f.write_str("bright_black"),
367            Color::BrightRed => f.write_str("bright_red"),
368            Color::BrightGreen => f.write_str("bright_green"),
369            Color::BrightYellow => f.write_str("bright_yellow"),
370            Color::BrightBlue => f.write_str("bright_blue"),
371            Color::BrightMagenta => f.write_str("bright_magenta"),
372            Color::BrightCyan => f.write_str("bright_cyan"),
373            Color::BrightWhite => f.write_str("bright_white"),
374
375            Color::Rgb(r, g, b) => f.write_str(&format!("#{r:x}{g:x}{b:x})")),
376            Color::Indexed(i) => {
377                f.write_str("#")?;
378                <u8 as LowerHex>::fmt(i, f)
379            }
380        }
381    }
382}
383
384impl FromStr for Color {
385    type Err = UnrecognisedColor;
386    fn from_str(s: &str) -> Result<Self, Self::Err> {
387        match s {
388            "reset" => Ok(Color::Reset),
389
390            "black" => Ok(Color::Black),
391            "red" => Ok(Color::Red),
392            "green" => Ok(Color::Green),
393            "yellow" => Ok(Color::Yellow),
394            "blue" => Ok(Color::Blue),
395            "magenta" => Ok(Color::Magenta),
396            "cyan" => Ok(Color::Cyan),
397            "white" => Ok(Color::White),
398
399            "bright_black" => Ok(Color::BrightBlack),
400            "bright_red" => Ok(Color::BrightRed),
401            "bright_green" => Ok(Color::BrightGreen),
402            "bright_yellow" => Ok(Color::BrightYellow),
403            "bright_blue" => Ok(Color::BrightBlue),
404            "bright_magenta" => Ok(Color::BrightMagenta),
405            "bright_cyan" => Ok(Color::BrightCyan),
406            "bright_white" => Ok(Color::BrightWhite),
407
408            s => {
409                // Parse rgb or indexed colours
410                if s.starts_with('#') {
411                    if s.len() == 7 {
412                        // Get str
413                        let r = &s[1..=2];
414                        let g = &s[3..=4];
415                        let b = &s[5..=6];
416
417                        // Convert from hex
418                        let r = u8::from_str_radix(r, 16).ok();
419                        let g = u8::from_str_radix(g, 16).ok();
420                        let b = u8::from_str_radix(b, 16).ok();
421
422                        if let Some(((r, g), b)) = r.zip(g).zip(b) {
423                            Ok(Color::Rgb(r, g, b))
424                        } else {
425                            Err(UnrecognisedColor(s.to_string()))
426                        }
427                    } else if s.len() == 3 {
428                        let i = &s[1..=2];
429                        match u8::from_str_radix(i, 16) {
430                            Ok(i) => Ok(Color::Indexed(i)),
431                            Err(_) => Err(UnrecognisedColor(s.to_owned())),
432                        }
433                    } else {
434                        Err(UnrecognisedColor(s.to_string()))
435                    }
436                } else {
437                    Err(UnrecognisedColor(s.to_string()))
438                }
439            }
440        }
441    }
442}
443
444#[cfg(feature = "image")]
445impl<P> From<P> for Color
446where
447    P: image::Pixel,
448    u8: From<<P as image::Pixel>::Subpixel>,
449{
450    fn from(them: P) -> Self {
451        let them = image::Pixel::to_rgb(&them);
452        Color::Rgb(them[0].into(), them[1].into(), them[2].into())
453    }
454}
455
456impl Color {
457    /// Creates a colour from an ansi escape
458    pub fn from_ansi(ansi: u8) -> Option<Self> {
459        match ansi {
460            0 | 39 | 49 => Some(Self::Reset),
461
462            30 | 40 => Some(Self::Black),
463            31 | 41 => Some(Self::Red),
464            32 | 42 => Some(Self::Green),
465            33 | 43 => Some(Self::Yellow),
466            34 | 44 => Some(Self::Blue),
467            35 | 45 => Some(Self::Magenta),
468            36 | 46 => Some(Self::Cyan),
469            37 | 47 => Some(Self::White),
470
471            90 | 100 => Some(Self::BrightBlack),
472            91 | 101 => Some(Self::BrightRed),
473            92 | 102 => Some(Self::BrightGreen),
474            93 | 103 => Some(Self::BrightYellow),
475            94 | 104 => Some(Self::BrightBlue),
476            95 | 105 => Some(Self::BrightMagenta),
477            96 | 106 => Some(Self::BrightCyan),
478            97 | 107 => Some(Self::BrightWhite),
479
480            _ => None,
481        }
482    }
483}
484
485#[cfg(feature = "crossterm")]
486impl From<Color> for CrossColor {
487    #[cfg(feature = "crossterm")]
488    fn from(them: Color) -> CrossColor {
489        match them {
490            Color::Reset => CrossColor::Reset,
491
492            Color::Black => CrossColor::Black,
493            Color::Red => CrossColor::DarkRed,
494            Color::Green => CrossColor::DarkGreen,
495            Color::Yellow => CrossColor::DarkYellow,
496            Color::Blue => CrossColor::DarkBlue,
497            Color::Magenta => CrossColor::DarkMagenta,
498            Color::Cyan => CrossColor::DarkCyan,
499            Color::White => CrossColor::Grey,
500
501            Color::BrightBlack => CrossColor::DarkGrey,
502            Color::BrightRed => CrossColor::Red,
503            Color::BrightGreen => CrossColor::Green,
504            Color::BrightYellow => CrossColor::Yellow,
505            Color::BrightBlue => CrossColor::Blue,
506            Color::BrightMagenta => CrossColor::Magenta,
507            Color::BrightCyan => CrossColor::Cyan,
508            Color::BrightWhite => CrossColor::White,
509
510            Color::Rgb(r, g, b) => CrossColor::Rgb { r, g, b },
511            Color::Indexed(n) => CrossColor::AnsiValue(n),
512        }
513    }
514}
515
516bitflags::bitflags! {
517    /// A bitflags of modifiers a cell can have
518    #[derive(Default, Clone, Copy,PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
519    #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(transparent))]
520    pub struct Modifier: u32 {
521        /// No style
522        const NONE              = 0;
523        /// Bold (Strong) text
524        const BOLD              = 2_u32.pow(1);
525        /// Dim text
526        const DIM               = 2_u32.pow(2);
527        /// Italics text
528        const ITALIC            = 2_u32.pow(3);
529        /// Underlines text
530        const UNDERLINED        = 2_u32.pow(4);
531        /// Sets the text to blink slowly
532        const SLOW_BLINK        = 2_u32.pow(5);
533        /// Sets the text to blink rapidly
534        const RAPID_BLINK       = 2_u32.pow(6);
535        /// Sets the background to be the forground and visa-versa
536        const REVERSED          = 2_u32.pow(7);
537        /// Makes the text invisible
538        const HIDDEN            = 2_u32.pow(8);
539        /// Crosses the text.
540        const CROSSED_OUT       = 2_u32.pow(9);
541        /// Doubly nderlines text
542        const DOUBLE_UNDERLINED = 2_u32.pow(10);
543        /// Undercurled text
544        const UNDERCURLED       = 2_u32.pow(11);
545        /// Underdotted text
546        const UNDERDOTTED       = 2_u32.pow(12);
547        /// Underdashed text
548        const UNDERDASHED       = 2_u32.pow(12);
549    }
550}
551
552#[cfg(feature = "crossterm")]
553impl From<Modifier> for Vec<CrossAttribute> {
554    /// Gives the crossterm version of a modifier.
555    fn from(them: Modifier) -> Self {
556        let mut vec = Vec::new();
557        if them.contains(Modifier::BOLD) {
558            vec.push(CrossAttribute::Bold);
559        }
560        if them.contains(Modifier::DIM) {
561            vec.push(CrossAttribute::Dim);
562        }
563        if them.contains(Modifier::ITALIC) {
564            vec.push(CrossAttribute::Italic);
565        }
566        if them.contains(Modifier::UNDERLINED) {
567            vec.push(CrossAttribute::Underlined);
568        }
569        if them.contains(Modifier::SLOW_BLINK) {
570            vec.push(CrossAttribute::SlowBlink);
571        }
572        if them.contains(Modifier::RAPID_BLINK) {
573            vec.push(CrossAttribute::RapidBlink);
574        }
575        if them.contains(Modifier::REVERSED) {
576            vec.push(CrossAttribute::Reverse);
577        }
578        if them.contains(Modifier::HIDDEN) {
579            vec.push(CrossAttribute::Hidden);
580        }
581        if them.contains(Modifier::CROSSED_OUT) {
582            vec.push(CrossAttribute::CrossedOut);
583        }
584        if them.contains(Modifier::DOUBLE_UNDERLINED) {
585            vec.push(CrossAttribute::DoubleUnderlined);
586        }
587        if them.contains(Modifier::UNDERCURLED) {
588            vec.push(CrossAttribute::Undercurled);
589        }
590        if them.contains(Modifier::UNDERDOTTED) {
591            vec.push(CrossAttribute::Underdotted);
592        }
593        if them.contains(Modifier::UNDERDASHED) {
594            vec.push(CrossAttribute::Underdashed);
595        }
596        vec
597    }
598}
599
600/// Text with a style attached to it.
601#[derive(Clone, Default, PartialEq, Eq, Debug)]
602pub struct StyledText<'a> {
603    /// The style of the text
604    pub style: Style,
605    /// The text itself
606    pub text: Cow<'a, str>,
607}
608
609impl<'a, T> From<T> for StyledText<'a>
610where
611    Cow<'a, str>: From<T>,
612{
613    fn from(text: T) -> Self {
614        Self {
615            style: Style::default(),
616            text: text.into(),
617        }
618    }
619}
620
621impl StyledText<'_> {
622    /// Sets the style
623    #[must_use = "its a builder pattern function"]
624    pub fn style(mut self, style: Style) -> Self {
625        self.style = style;
626        self
627    }
628
629    /// Sets the [`Style::fg`] property on the `StyledText`
630    #[must_use = "its a builder pattern function"]
631    pub fn fg(mut self, fg: impl Into<Option<Color>>) -> Self {
632        self.style.fg = fg.into();
633        self
634    }
635
636    /// Sets the [`Style::bg`] property on the `StyledText`
637    #[must_use = "its a builder pattern function"]
638    pub fn bg(mut self, bg: impl Into<Option<Color>>) -> Self {
639        self.style.bg = bg.into();
640        self
641    }
642
643    /// Sets the [`Style::underline_color`] property on the `StyledText`
644    #[must_use = "its a builder pattern function"]
645    pub fn underline_color(
646        mut self,
647        underline_color: impl Into<Option<Color>>,
648    ) -> Self {
649        self.style.underline_color = underline_color.into();
650        self
651    }
652
653    /// Appends to the the [`Style::add_modifier`] property on the `StyledText`
654    #[must_use = "its a builder pattern function"]
655    pub fn with_modifier(mut self, modifier: Modifier) -> Self {
656        self.style.add_modifier |= modifier;
657        self.style.sub_modifier.remove(modifier);
658        self
659    }
660
661    /// Appends to the the [`Style::sub_modifier`] property on the
662    /// `StyledText`
663    #[must_use = "its a builder pattern function"]
664    pub fn without_modifier(mut self, modifier: Modifier) -> Self {
665        self.style.sub_modifier |= modifier;
666        self.style.add_modifier.remove(modifier);
667        self
668    }
669
670    funcs!();
671}