tv 0.1.1

Terminal User Interface library
Documentation
/// RGB color
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)]
pub struct ColorRgb {
    pub r: u8,
    pub g: u8,
    pub b: u8,
}

/// Named ANSI colors
#[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)]
pub enum NamedColor {
    Black = 0,
    Red,
    Green,
    Yellow,
    Blue,
    Magenta,
    Cyan,
    White,
    LightBlack,
    LightRed,
    LightGreen,
    LightYellow,
    LightBlue,
    LightMagenta,
    LightCyan,
    LightWhite,
    Foreground = 256,
    Background,
    Cursor,
    DimBlack,
    DimRed,
    DimGreen,
    DimYellow,
    DimBlue,
    DimMagenta,
    DimCyan,
    DimWhite,
    LightForeground,
    DimForeground,
}

/// Terminal color
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Color {
    Named(NamedColor),
    Spec(ColorRgb),
    Indexed(u8),
}

impl Color {
    // Convenience constructors matching previous API
    pub const fn rgb(r: u8, g: u8, b: u8) -> Self {
        Color::Spec(ColorRgb { r, g, b })
    }

    pub const fn indexed(idx: u8) -> Self {
        Color::Indexed(idx)
    }

    pub const fn named(c: NamedColor) -> Self {
        Color::Named(c)
    }

    // Common color constants
    pub const RESET: Color = Color::Named(NamedColor::Foreground);
    pub const BLACK: Color = Color::Named(NamedColor::Black);
    pub const RED: Color = Color::Named(NamedColor::Red);
    pub const GREEN: Color = Color::Named(NamedColor::Green);
    pub const YELLOW: Color = Color::Named(NamedColor::Yellow);
    pub const BLUE: Color = Color::Named(NamedColor::Blue);
    pub const MAGENTA: Color = Color::Named(NamedColor::Magenta);
    pub const CYAN: Color = Color::Named(NamedColor::Cyan);
    pub const WHITE: Color = Color::Named(NamedColor::White);

    /// Write foreground ANSI code to a string buffer
    pub(crate) fn write_ansi_fg(&self, buf: &mut String) {
        use std::fmt::Write;
        match self {
            Color::Named(c) => match c {
                NamedColor::Black => buf.push_str("30"),
                NamedColor::Red => buf.push_str("31"),
                NamedColor::Green => buf.push_str("32"),
                NamedColor::Yellow => buf.push_str("33"),
                NamedColor::Blue => buf.push_str("34"),
                NamedColor::Magenta => buf.push_str("35"),
                NamedColor::Cyan => buf.push_str("36"),
                NamedColor::White => buf.push_str("37"),
                NamedColor::LightBlack => buf.push_str("90"),
                NamedColor::LightRed => buf.push_str("91"),
                NamedColor::LightGreen => buf.push_str("92"),
                NamedColor::LightYellow => buf.push_str("93"),
                NamedColor::LightBlue => buf.push_str("94"),
                NamedColor::LightMagenta => buf.push_str("95"),
                NamedColor::LightCyan => buf.push_str("96"),
                NamedColor::LightWhite => buf.push_str("97"),
                NamedColor::Foreground | NamedColor::LightForeground => {
                    buf.push_str("39")
                }
                NamedColor::Background => buf.push_str("39"),
                NamedColor::Cursor => buf.push_str("39"),
                NamedColor::DimBlack => buf.push_str("30"),
                NamedColor::DimRed => buf.push_str("31"),
                NamedColor::DimGreen => buf.push_str("32"),
                NamedColor::DimYellow => buf.push_str("33"),
                NamedColor::DimBlue => buf.push_str("34"),
                NamedColor::DimMagenta => buf.push_str("35"),
                NamedColor::DimCyan => buf.push_str("36"),
                NamedColor::DimWhite => buf.push_str("37"),
                NamedColor::DimForeground => buf.push_str("39"),
            },
            Color::Spec(rgb) => {
                write!(buf, "38;2;{};{};{}", rgb.r, rgb.g, rgb.b).unwrap()
            }
            Color::Indexed(c) => write!(buf, "38;5;{}", c).unwrap(),
        }
    }

    /// Write background ANSI code to a string buffer
    pub(crate) fn write_ansi_bg(&self, buf: &mut String) {
        use std::fmt::Write;
        match self {
            Color::Named(c) => match c {
                NamedColor::Black => buf.push_str("40"),
                NamedColor::Red => buf.push_str("41"),
                NamedColor::Green => buf.push_str("42"),
                NamedColor::Yellow => buf.push_str("43"),
                NamedColor::Blue => buf.push_str("44"),
                NamedColor::Magenta => buf.push_str("45"),
                NamedColor::Cyan => buf.push_str("46"),
                NamedColor::White => buf.push_str("47"),
                NamedColor::LightBlack => buf.push_str("100"),
                NamedColor::LightRed => buf.push_str("101"),
                NamedColor::LightGreen => buf.push_str("102"),
                NamedColor::LightYellow => buf.push_str("103"),
                NamedColor::LightBlue => buf.push_str("104"),
                NamedColor::LightMagenta => buf.push_str("105"),
                NamedColor::LightCyan => buf.push_str("106"),
                NamedColor::LightWhite => buf.push_str("107"),
                NamedColor::Background
                | NamedColor::Foreground
                | NamedColor::LightForeground
                | NamedColor::Cursor
                | NamedColor::DimForeground => buf.push_str("49"),
                NamedColor::DimBlack => buf.push_str("40"),
                NamedColor::DimRed => buf.push_str("41"),
                NamedColor::DimGreen => buf.push_str("42"),
                NamedColor::DimYellow => buf.push_str("43"),
                NamedColor::DimBlue => buf.push_str("44"),
                NamedColor::DimMagenta => buf.push_str("45"),
                NamedColor::DimCyan => buf.push_str("46"),
                NamedColor::DimWhite => buf.push_str("47"),
            },
            Color::Spec(rgb) => {
                write!(buf, "48;2;{};{};{}", rgb.r, rgb.g, rgb.b).unwrap()
            }
            Color::Indexed(c) => write!(buf, "48;5;{}", c).unwrap(),
        }
    }
}

/// A color pair consisting of foreground and background colors
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ColorPair {
    pub fg: Color,
    pub bg: Color,
}

impl ColorPair {
    pub fn new(fg: Color, bg: Color) -> Self {
        Self { fg, bg }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_rgb_constructor() {
        let c = Color::rgb(255, 128, 0);
        assert_eq!(
            c,
            Color::Spec(ColorRgb {
                r: 255,
                g: 128,
                b: 0
            })
        );
    }

    #[test]
    fn test_named_color() {
        let c = Color::Named(NamedColor::Red);
        assert_eq!(c, Color::RED);
    }

    #[test]
    fn test_legacy_rgb_alias() {
        let c = Color::rgb(10, 20, 30);
        assert_eq!(c, Color::rgb(10, 20, 30));
    }

    #[test]
    fn test_color_equality() {
        assert_eq!(Color::RED, Color::RED);
        assert_ne!(Color::RED, Color::BLUE);
        assert_eq!(Color::rgb(255, 0, 0), Color::rgb(255, 0, 0));
        assert_ne!(Color::rgb(255, 0, 0), Color::rgb(255, 0, 1));
    }

    #[test]
    fn test_ansi_fg() {
        let mut buf = String::new();
        Color::RED.write_ansi_fg(&mut buf);
        assert_eq!(buf, "31");

        buf.clear();
        Color::rgb(255, 128, 0).write_ansi_fg(&mut buf);
        assert_eq!(buf, "38;2;255;128;0");

        buf.clear();
        Color::Indexed(42).write_ansi_fg(&mut buf);
        assert_eq!(buf, "38;5;42");
    }

    #[test]
    fn test_ansi_bg() {
        let mut buf = String::new();
        Color::GREEN.write_ansi_bg(&mut buf);
        assert_eq!(buf, "42");

        buf.clear();
        Color::rgb(0, 128, 255).write_ansi_bg(&mut buf);
        assert_eq!(buf, "48;2;0;128;255");
    }
}