Skip to main content

mermaid_cli/render/
theme.rs

1use ratatui::style::Color;
2use serde::{Deserialize, Serialize};
3
4/// Theme configuration for the TUI
5#[derive(Debug, Clone, Serialize, Deserialize)]
6pub struct Theme {
7    pub name: String,
8    pub colors: ThemeColors,
9}
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct ThemeColors {
13    // Primary colors
14    pub background: ColorValue,
15    pub foreground: ColorValue,
16
17    // UI elements
18    pub border: ColorValue,
19    pub border_focused: ColorValue,
20    pub header: ColorValue,
21    pub status_bar: ColorValue,
22
23    // Text colors
24    pub text_primary: ColorValue,
25    pub text_secondary: ColorValue,
26    pub text_disabled: ColorValue,
27    pub text_highlight: ColorValue,
28
29    // Message colors
30    pub user_message: ColorValue,
31    pub assistant_message: ColorValue,
32    pub system_message: ColorValue,
33
34    // Code highlighting
35    pub code_background: ColorValue,
36    pub code_foreground: ColorValue,
37    pub code_keyword: ColorValue,
38    pub code_string: ColorValue,
39    pub code_comment: ColorValue,
40
41    // Mode colors
42    pub mode_normal: ColorValue,
43    pub mode_accept_edits: ColorValue,
44    pub mode_plan: ColorValue,
45    pub mode_bypass_all: ColorValue,
46
47    // Status colors
48    pub success: ColorValue,
49    pub warning: ColorValue,
50    pub error: ColorValue,
51    pub info: ColorValue,
52}
53
54#[derive(Debug, Clone, Serialize, Deserialize)]
55#[serde(untagged)]
56pub enum ColorValue {
57    Rgb { r: u8, g: u8, b: u8 },
58    Named(String),
59}
60
61impl ColorValue {
62    pub fn to_color(&self) -> Color {
63        match self {
64            ColorValue::Rgb { r, g, b } => Color::Rgb(*r, *g, *b),
65            ColorValue::Named(name) => match name.as_str() {
66                "black" => Color::Black,
67                "red" => Color::Red,
68                "green" => Color::Green,
69                "yellow" => Color::Yellow,
70                "blue" => Color::Blue,
71                "magenta" => Color::Magenta,
72                "cyan" => Color::Cyan,
73                "white" => Color::White,
74                "gray" | "grey" => Color::Gray,
75                "dark_gray" | "dark_grey" => Color::DarkGray,
76                _ => Color::White,
77            },
78        }
79    }
80}
81
82impl Theme {
83    /// Create a light theme.
84    ///
85    /// Not wired into any config reader yet — the `Deserialize` impl on
86    /// `Theme` is retained so a future patch can load
87    /// `config.ui.theme = "light" | "dark"` from config.toml and select
88    /// the constructor. Until then, call this explicitly to test.
89    pub fn light() -> Self {
90        Self {
91            name: "Light".to_string(),
92            colors: ThemeColors {
93                background: ColorValue::Rgb {
94                    r: 250,
95                    g: 250,
96                    b: 250,
97                },
98                foreground: ColorValue::Rgb {
99                    r: 30,
100                    g: 30,
101                    b: 30,
102                },
103
104                border: ColorValue::Named("gray".to_string()),
105                border_focused: ColorValue::Named("blue".to_string()),
106                header: ColorValue::Named("blue".to_string()),
107                status_bar: ColorValue::Named("white".to_string()),
108
109                text_primary: ColorValue::Named("black".to_string()),
110                text_secondary: ColorValue::Named("dark_gray".to_string()),
111                text_disabled: ColorValue::Named("gray".to_string()),
112                text_highlight: ColorValue::Named("magenta".to_string()),
113
114                user_message: ColorValue::Named("blue".to_string()),
115                assistant_message: ColorValue::Named("green".to_string()),
116                system_message: ColorValue::Named("yellow".to_string()),
117
118                code_background: ColorValue::Rgb {
119                    r: 240,
120                    g: 240,
121                    b: 240,
122                },
123                code_foreground: ColorValue::Named("dark_gray".to_string()),
124                code_keyword: ColorValue::Named("magenta".to_string()),
125                code_string: ColorValue::Named("green".to_string()),
126                code_comment: ColorValue::Named("gray".to_string()),
127
128                mode_normal: ColorValue::Named("green".to_string()),
129                mode_accept_edits: ColorValue::Named("yellow".to_string()),
130                mode_plan: ColorValue::Named("blue".to_string()),
131                mode_bypass_all: ColorValue::Named("red".to_string()),
132
133                success: ColorValue::Named("green".to_string()),
134                warning: ColorValue::Named("yellow".to_string()),
135                error: ColorValue::Named("red".to_string()),
136                info: ColorValue::Named("blue".to_string()),
137            },
138        }
139    }
140
141    /// Create the default dark theme
142    pub fn dark() -> Self {
143        Self {
144            name: "Dark".to_string(),
145            colors: ThemeColors {
146                background: ColorValue::Rgb {
147                    r: 20,
148                    g: 20,
149                    b: 20,
150                },
151                foreground: ColorValue::Rgb {
152                    r: 230,
153                    g: 230,
154                    b: 230,
155                },
156
157                border: ColorValue::Named("dark_gray".to_string()),
158                border_focused: ColorValue::Named("cyan".to_string()),
159                header: ColorValue::Named("cyan".to_string()),
160                status_bar: ColorValue::Named("black".to_string()),
161
162                text_primary: ColorValue::Named("white".to_string()),
163                text_secondary: ColorValue::Named("gray".to_string()),
164                text_disabled: ColorValue::Named("dark_gray".to_string()),
165                text_highlight: ColorValue::Named("yellow".to_string()),
166
167                user_message: ColorValue::Named("blue".to_string()),
168                assistant_message: ColorValue::Named("green".to_string()),
169                system_message: ColorValue::Named("yellow".to_string()),
170
171                code_background: ColorValue::Rgb {
172                    r: 40,
173                    g: 40,
174                    b: 40,
175                },
176                code_foreground: ColorValue::Named("gray".to_string()),
177                code_keyword: ColorValue::Named("magenta".to_string()),
178                code_string: ColorValue::Named("green".to_string()),
179                code_comment: ColorValue::Named("dark_gray".to_string()),
180
181                mode_normal: ColorValue::Named("green".to_string()),
182                mode_accept_edits: ColorValue::Named("yellow".to_string()),
183                mode_plan: ColorValue::Named("blue".to_string()),
184                mode_bypass_all: ColorValue::Named("red".to_string()),
185
186                success: ColorValue::Named("green".to_string()),
187                warning: ColorValue::Named("yellow".to_string()),
188                error: ColorValue::Named("red".to_string()),
189                info: ColorValue::Named("cyan".to_string()),
190            },
191        }
192    }
193}