azuredevopstui 0.1.1

Terminal UI for your Azure DevOps pull requests, work items and sprint board
use ratatui::style::Color;
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct Theme {
    pub background: Color,
    pub foreground: Color,
    pub border: Color,
    pub border_focused: Color,
    pub selection_bg: Color,
    pub selection_fg: Color,
    pub title: Color,
    pub toolbar_bg: Color,
    pub toolbar_key: Color,
    pub pr_approved: Color,
    pub pr_waiting: Color,
    pub pr_rejected: Color,
    pub build_succeeded: Color,
    pub build_failed: Color,
    pub build_running: Color,
    pub workitem_bug: Color,
    pub workitem_story: Color,
    pub workitem_task: Color,
    pub comment_author: Color,
    pub muted: Color,
    pub error: Color,
}

mod mocha {
    use ratatui::style::Color;

    pub const ROSEWATER: Color = Color::from_u32(0xf5e0dc);
    pub const MAUVE: Color = Color::from_u32(0xcba6f7);
    pub const RED: Color = Color::from_u32(0xf38ba8);
    pub const PEACH: Color = Color::from_u32(0xfab387);
    pub const YELLOW: Color = Color::from_u32(0xf9e2af);
    pub const GREEN: Color = Color::from_u32(0xa6e3a1);
    pub const SKY: Color = Color::from_u32(0x89dceb);
    pub const BLUE: Color = Color::from_u32(0x89b4fa);
    pub const TEXT: Color = Color::from_u32(0xcdd6f4);
    pub const OVERLAY0: Color = Color::from_u32(0x6c7086);
    pub const SURFACE1: Color = Color::from_u32(0x45475a);
    pub const SURFACE0: Color = Color::from_u32(0x313244);
    pub const BASE: Color = Color::from_u32(0x1e1e2e);
    pub const MANTLE: Color = Color::from_u32(0x181825);
}

impl Default for Theme {
    fn default() -> Self {
        Self {
            background: mocha::BASE,
            foreground: mocha::TEXT,
            border: mocha::SURFACE1,
            border_focused: mocha::BLUE,
            selection_bg: mocha::SURFACE0,
            selection_fg: mocha::ROSEWATER,
            title: mocha::MAUVE,
            toolbar_bg: mocha::MANTLE,
            toolbar_key: mocha::YELLOW,
            pr_approved: mocha::GREEN,
            pr_waiting: mocha::YELLOW,
            pr_rejected: mocha::RED,
            build_succeeded: mocha::GREEN,
            build_failed: mocha::RED,
            build_running: mocha::SKY,
            workitem_bug: mocha::RED,
            workitem_story: mocha::BLUE,
            workitem_task: mocha::YELLOW,
            comment_author: mocha::PEACH,
            muted: mocha::OVERLAY0,
            error: mocha::RED,
        }
    }
}

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

    #[test]
    fn partial_colors_keep_mocha_defaults() {
        let theme: Theme = serde_json::from_str(r##"{"background":"#000000"}"##).unwrap();

        assert_eq!(theme.background, Color::Rgb(0, 0, 0));
        assert_eq!(theme.foreground, mocha::TEXT);
        assert_eq!(theme.border_focused, mocha::BLUE);
    }

    #[test]
    fn default_theme_is_mocha() {
        let theme = Theme::default();

        assert_eq!(theme.background, Color::from_u32(0x1e1e2e));
        assert_eq!(theme.error, mocha::RED);
    }

    #[test]
    fn colors_round_trip_as_hex() {
        let json = serde_json::to_value(Theme::default()).unwrap();

        assert_eq!(json["background"], "#1E1E2E");
        let theme: Theme = serde_json::from_value(json).unwrap();
        assert_eq!(theme.background, mocha::BASE);
    }
}