bettertui_engine 0.1.0

High-performance terminal UI framework
Documentation
use crate::tree::{BorderStyle, Color, NamedColor};

#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ThemeColors {
    pub background: Color,
    pub surface: Color,
    pub surface_high: Color,
    pub surface_low: Color,
    pub primary: Color,
    pub primary_foreground: Color,
    pub secondary: Color,
    pub secondary_foreground: Color,
    pub text: Color,
    pub text_muted: Color,
    pub text_dim: Color,
    pub border: Color,
    pub border_focused: Color,
    pub accent: Color,
    pub accent_foreground: Color,
    pub error: Color,
    pub warning: Color,
    pub success: Color,
    pub info: Color,
    pub scrollbar: Color,
    pub scrollbar_thumb: Color,
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ThemeSpacing {
    pub none: u16,
    pub xxs: u16,
    pub xs: u16,
    pub sm: u16,
    pub md: u16,
    pub lg: u16,
    pub xl: u16,
    pub xxl: u16,
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ThemeBorders {
    pub style: BorderStyle,
    pub fg: Color,
}

#[derive(Debug, Clone, PartialEq)]
pub struct Theme {
    pub name: Box<str>,
    pub colors: ThemeColors,
    pub spacing: ThemeSpacing,
    pub borders: ThemeBorders,
}

impl Default for Theme {
    fn default() -> Self {
        Self::dark()
    }
}

impl Theme {
    pub fn dark() -> Self {
        Self {
            name: "dark".into(),
            colors: ThemeColors {
                background: Color::Default,
                surface: Color::rgb(30, 30, 40),
                surface_high: Color::rgb(40, 40, 55),
                surface_low: Color::rgb(20, 20, 28),
                primary: Color::rgb(100, 140, 220),
                primary_foreground: Color::Named(NamedColor::White),
                secondary: Color::rgb(140, 100, 200),
                secondary_foreground: Color::Named(NamedColor::White),
                text: Color::rgb(220, 220, 230),
                text_muted: Color::rgb(140, 140, 160),
                text_dim: Color::rgb(90, 90, 105),
                border: Color::rgb(60, 60, 80),
                border_focused: Color::rgb(100, 140, 220),
                accent: Color::rgb(80, 200, 160),
                accent_foreground: Color::Named(NamedColor::White),
                error: Color::rgb(220, 80, 80),
                warning: Color::rgb(220, 180, 60),
                success: Color::rgb(80, 200, 120),
                info: Color::rgb(80, 160, 220),
                scrollbar: Color::rgb(50, 50, 65),
                scrollbar_thumb: Color::rgb(100, 100, 130),
            },
            spacing: ThemeSpacing { none: 0, xxs: 1, xs: 2, sm: 4, md: 8, lg: 12, xl: 16, xxl: 24 },
            borders: ThemeBorders { style: BorderStyle::Solid, fg: Color::rgb(60, 60, 80) },
        }
    }

    pub fn light() -> Self {
        Self {
            name: "light".into(),
            colors: ThemeColors {
                background: Color::Named(NamedColor::White),
                surface: Color::rgb(245, 245, 250),
                surface_high: Color::rgb(255, 255, 255),
                surface_low: Color::rgb(235, 235, 242),
                primary: Color::rgb(60, 100, 180),
                primary_foreground: Color::Named(NamedColor::White),
                secondary: Color::rgb(100, 70, 160),
                secondary_foreground: Color::Named(NamedColor::White),
                text: Color::rgb(30, 30, 40),
                text_muted: Color::rgb(100, 100, 120),
                text_dim: Color::rgb(160, 160, 175),
                border: Color::rgb(200, 200, 215),
                border_focused: Color::rgb(60, 100, 180),
                accent: Color::rgb(40, 160, 120),
                accent_foreground: Color::Named(NamedColor::White),
                error: Color::rgb(200, 50, 50),
                warning: Color::rgb(200, 150, 30),
                success: Color::rgb(40, 160, 80),
                info: Color::rgb(40, 120, 200),
                scrollbar: Color::rgb(220, 220, 230),
                scrollbar_thumb: Color::rgb(160, 160, 180),
            },
            spacing: ThemeSpacing { none: 0, xxs: 1, xs: 2, sm: 4, md: 8, lg: 12, xl: 16, xxl: 24 },
            borders: ThemeBorders { style: BorderStyle::Solid, fg: Color::rgb(200, 200, 215) },
        }
    }
}

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

    #[test]
    fn theme_dark_default() {
        let theme = Theme::dark();
        assert_eq!(theme.name.as_ref(), "dark");
        assert_eq!(theme.colors.background, Color::Default);
        assert_ne!(theme.colors.primary, Color::Default);
        assert_ne!(theme.colors.text, Color::Default);
    }

    #[test]
    fn theme_light() {
        let theme = Theme::light();
        assert_eq!(theme.name.as_ref(), "light");
        assert_eq!(theme.colors.background, Color::Named(NamedColor::White));
    }

    #[test]
    fn theme_default_is_dark() {
        let theme = Theme::default();
        assert_eq!(theme.name.as_ref(), "dark");
    }

    #[test]
    fn theme_color_values() {
        let theme = Theme::dark();
        assert_eq!(theme.colors.primary, Color::rgb(100, 140, 220));
        assert_eq!(theme.colors.accent, Color::rgb(80, 200, 160));
    }

    #[test]
    fn theme_spacing_values() {
        let theme = Theme::dark();
        assert_eq!(theme.spacing.none, 0);
        assert_eq!(theme.spacing.xxs, 1);
        assert_eq!(theme.spacing.sm, 4);
        assert_eq!(theme.spacing.md, 8);
        assert_eq!(theme.spacing.lg, 12);
        assert_eq!(theme.spacing.xl, 16);
        assert_eq!(theme.spacing.xxl, 24);
    }

    #[test]
    fn theme_borders() {
        let theme = Theme::dark();
        assert_eq!(theme.borders.style, BorderStyle::Solid);
        assert_eq!(theme.borders.fg, Color::rgb(60, 60, 80));
    }

    #[test]
    fn theme_light_colors_differ_from_dark() {
        let dark = Theme::dark();
        let light = Theme::light();
        assert_ne!(dark.colors.background, light.colors.background);
    }

    #[test]
    fn theme_colors_copy() {
        let theme = Theme::dark();
        let colors = theme.colors;
        assert_eq!(colors.primary, Color::rgb(100, 140, 220));
    }

    #[test]
    fn theme_spacing_copy() {
        let theme = Theme::dark();
        let spacing = theme.spacing;
        assert_eq!(spacing.md, 8);
    }
}