1use ratatui::style::{Color, Modifier, Style};
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4
5pub trait Theme {
7 fn name(&self) -> &str;
8
9 fn background(&self) -> Color;
11 fn background_panel(&self) -> Color;
12 fn background_element(&self) -> Color;
13
14 fn text(&self) -> Color;
16 fn text_muted(&self) -> Color;
17 fn text_secondary(&self) -> Color;
18
19 fn primary(&self) -> Color;
21 fn secondary(&self) -> Color;
22 fn accent(&self) -> Color;
23
24 fn success(&self) -> Color;
26 fn warning(&self) -> Color;
27 fn error(&self) -> Color;
28 fn info(&self) -> Color;
29
30 fn border(&self) -> Color;
32 fn border_active(&self) -> Color;
33 fn border_subtle(&self) -> Color;
34
35 fn diff_added(&self) -> Color;
37 fn diff_removed(&self) -> Color;
38 fn diff_modified(&self) -> Color;
39 fn diff_context(&self) -> Color;
40
41 fn syntax_keyword(&self) -> Color;
43 fn syntax_string(&self) -> Color;
44 fn syntax_comment(&self) -> Color;
45 fn syntax_number(&self) -> Color;
46 fn syntax_type(&self) -> Color;
47 fn syntax_function(&self) -> Color;
48 fn syntax_variable(&self) -> Color;
49 fn syntax_operator(&self) -> Color;
50}
51
52#[derive(Debug, Clone)]
54pub struct DefaultTheme;
55
56impl Theme for DefaultTheme {
57 fn name(&self) -> &str { "default" }
58
59 fn background(&self) -> Color { Color::Rgb(16, 16, 16) }
60 fn background_panel(&self) -> Color { Color::Rgb(24, 24, 24) }
61 fn background_element(&self) -> Color { Color::Rgb(32, 32, 32) }
62
63 fn text(&self) -> Color { Color::Rgb(240, 240, 240) }
64 fn text_muted(&self) -> Color { Color::Rgb(160, 160, 160) }
65 fn text_secondary(&self) -> Color { Color::Rgb(200, 200, 200) }
66
67 fn primary(&self) -> Color { Color::Rgb(99, 179, 237) }
68 fn secondary(&self) -> Color { Color::Rgb(158, 134, 200) }
69 fn accent(&self) -> Color { Color::Rgb(255, 184, 108) }
70
71 fn success(&self) -> Color { Color::Rgb(152, 195, 121) }
72 fn warning(&self) -> Color { Color::Rgb(229, 192, 123) }
73 fn error(&self) -> Color { Color::Rgb(224, 108, 117) }
74 fn info(&self) -> Color { Color::Rgb(97, 175, 239) }
75
76 fn border(&self) -> Color { Color::Rgb(64, 64, 64) }
77 fn border_active(&self) -> Color { Color::Rgb(99, 179, 237) }
78 fn border_subtle(&self) -> Color { Color::Rgb(48, 48, 48) }
79
80 fn diff_added(&self) -> Color { Color::Rgb(152, 195, 121) }
81 fn diff_removed(&self) -> Color { Color::Rgb(224, 108, 117) }
82 fn diff_modified(&self) -> Color { Color::Rgb(229, 192, 123) }
83 fn diff_context(&self) -> Color { Color::Rgb(160, 160, 160) }
84
85 fn syntax_keyword(&self) -> Color { Color::Rgb(198, 120, 221) }
86 fn syntax_string(&self) -> Color { Color::Rgb(152, 195, 121) }
87 fn syntax_comment(&self) -> Color { Color::Rgb(92, 99, 112) }
88 fn syntax_number(&self) -> Color { Color::Rgb(209, 154, 102) }
89 fn syntax_type(&self) -> Color { Color::Rgb(86, 182, 194) }
90 fn syntax_function(&self) -> Color { Color::Rgb(97, 175, 239) }
91 fn syntax_variable(&self) -> Color { Color::Rgb(224, 108, 117) }
92 fn syntax_operator(&self) -> Color { Color::Rgb(86, 182, 194) }
93}
94
95#[derive(Debug, Clone)]
97pub struct GruvboxTheme;
98
99impl Theme for GruvboxTheme {
100 fn name(&self) -> &str { "gruvbox" }
101
102 fn background(&self) -> Color { Color::Rgb(40, 40, 40) }
103 fn background_panel(&self) -> Color { Color::Rgb(50, 48, 47) }
104 fn background_element(&self) -> Color { Color::Rgb(60, 56, 54) }
105
106 fn text(&self) -> Color { Color::Rgb(235, 219, 178) }
107 fn text_muted(&self) -> Color { Color::Rgb(168, 153, 132) }
108 fn text_secondary(&self) -> Color { Color::Rgb(213, 196, 161) }
109
110 fn primary(&self) -> Color { Color::Rgb(131, 165, 152) }
111 fn secondary(&self) -> Color { Color::Rgb(211, 134, 155) }
112 fn accent(&self) -> Color { Color::Rgb(250, 189, 47) }
113
114 fn success(&self) -> Color { Color::Rgb(184, 187, 38) }
115 fn warning(&self) -> Color { Color::Rgb(250, 189, 47) }
116 fn error(&self) -> Color { Color::Rgb(204, 36, 29) }
117 fn info(&self) -> Color { Color::Rgb(69, 133, 136) }
118
119 fn border(&self) -> Color { Color::Rgb(80, 73, 69) }
120 fn border_active(&self) -> Color { Color::Rgb(131, 165, 152) }
121 fn border_subtle(&self) -> Color { Color::Rgb(60, 56, 54) }
122
123 fn diff_added(&self) -> Color { Color::Rgb(184, 187, 38) }
124 fn diff_removed(&self) -> Color { Color::Rgb(204, 36, 29) }
125 fn diff_modified(&self) -> Color { Color::Rgb(250, 189, 47) }
126 fn diff_context(&self) -> Color { Color::Rgb(168, 153, 132) }
127
128 fn syntax_keyword(&self) -> Color { Color::Rgb(251, 73, 52) }
129 fn syntax_string(&self) -> Color { Color::Rgb(184, 187, 38) }
130 fn syntax_comment(&self) -> Color { Color::Rgb(146, 131, 116) }
131 fn syntax_number(&self) -> Color { Color::Rgb(211, 134, 155) }
132 fn syntax_type(&self) -> Color { Color::Rgb(250, 189, 47) }
133 fn syntax_function(&self) -> Color { Color::Rgb(131, 165, 152) }
134 fn syntax_variable(&self) -> Color { Color::Rgb(69, 133, 136) }
135 fn syntax_operator(&self) -> Color { Color::Rgb(254, 128, 25) }
136}
137
138#[derive(Debug, Clone)]
140pub struct DraculaTheme;
141
142impl Theme for DraculaTheme {
143 fn name(&self) -> &str { "dracula" }
144
145 fn background(&self) -> Color { Color::Rgb(40, 42, 54) }
146 fn background_panel(&self) -> Color { Color::Rgb(68, 71, 90) }
147 fn background_element(&self) -> Color { Color::Rgb(98, 114, 164) }
148
149 fn text(&self) -> Color { Color::Rgb(248, 248, 242) }
150 fn text_muted(&self) -> Color { Color::Rgb(98, 114, 164) }
151 fn text_secondary(&self) -> Color { Color::Rgb(189, 147, 249) }
152
153 fn primary(&self) -> Color { Color::Rgb(139, 233, 253) }
154 fn secondary(&self) -> Color { Color::Rgb(189, 147, 249) }
155 fn accent(&self) -> Color { Color::Rgb(241, 250, 140) }
156
157 fn success(&self) -> Color { Color::Rgb(80, 250, 123) }
158 fn warning(&self) -> Color { Color::Rgb(241, 250, 140) }
159 fn error(&self) -> Color { Color::Rgb(255, 85, 85) }
160 fn info(&self) -> Color { Color::Rgb(139, 233, 253) }
161
162 fn border(&self) -> Color { Color::Rgb(68, 71, 90) }
163 fn border_active(&self) -> Color { Color::Rgb(139, 233, 253) }
164 fn border_subtle(&self) -> Color { Color::Rgb(98, 114, 164) }
165
166 fn diff_added(&self) -> Color { Color::Rgb(80, 250, 123) }
167 fn diff_removed(&self) -> Color { Color::Rgb(255, 85, 85) }
168 fn diff_modified(&self) -> Color { Color::Rgb(241, 250, 140) }
169 fn diff_context(&self) -> Color { Color::Rgb(98, 114, 164) }
170
171 fn syntax_keyword(&self) -> Color { Color::Rgb(255, 121, 198) }
172 fn syntax_string(&self) -> Color { Color::Rgb(241, 250, 140) }
173 fn syntax_comment(&self) -> Color { Color::Rgb(98, 114, 164) }
174 fn syntax_number(&self) -> Color { Color::Rgb(189, 147, 249) }
175 fn syntax_type(&self) -> Color { Color::Rgb(139, 233, 253) }
176 fn syntax_function(&self) -> Color { Color::Rgb(80, 250, 123) }
177 fn syntax_variable(&self) -> Color { Color::Rgb(255, 184, 108) }
178 fn syntax_operator(&self) -> Color { Color::Rgb(255, 121, 198) }
179}
180
181#[derive(Debug, Clone)]
183pub struct ThemeManager {
184 current_theme: Box<dyn Theme + Send + Sync>,
185 available_themes: HashMap<String, Box<dyn Theme + Send + Sync>>,
186}
187
188impl Default for ThemeManager {
189 fn default() -> Self {
190 let mut manager = Self {
191 current_theme: Box::new(DefaultTheme),
192 available_themes: HashMap::new(),
193 };
194
195 manager.register_theme(Box::new(DefaultTheme));
197 manager.register_theme(Box::new(GruvboxTheme));
198 manager.register_theme(Box::new(DraculaTheme));
199
200 manager
201 }
202}
203
204impl ThemeManager {
205 pub fn register_theme(&mut self, theme: Box<dyn Theme + Send + Sync>) {
207 let name = theme.name().to_string();
208 self.available_themes.insert(name, theme);
209 }
210
211 pub fn set_theme(&mut self, name: &str) -> anyhow::Result<()> {
213 if let Some(theme) = self.available_themes.get(name) {
214 match name {
217 "default" => self.current_theme = Box::new(DefaultTheme),
218 "gruvbox" => self.current_theme = Box::new(GruvboxTheme),
219 "dracula" => self.current_theme = Box::new(DraculaTheme),
220 _ => return Err(anyhow::anyhow!("Unknown theme: {}", name)),
221 }
222 Ok(())
223 } else {
224 Err(anyhow::anyhow!("Theme not found: {}", name))
225 }
226 }
227
228 pub fn current_theme(&self) -> &dyn Theme {
230 self.current_theme.as_ref()
231 }
232
233 pub fn available_themes(&self) -> Vec<String> {
235 self.available_themes.keys().cloned().collect()
236 }
237}
238
239pub struct Styles;
241
242impl Styles {
243 pub fn primary(theme: &dyn Theme) -> Style {
245 Style::default().fg(theme.primary())
246 }
247
248 pub fn secondary(theme: &dyn Theme) -> Style {
250 Style::default().fg(theme.secondary())
251 }
252
253 pub fn accent(theme: &dyn Theme) -> Style {
255 Style::default().fg(theme.accent())
256 }
257
258 pub fn success(theme: &dyn Theme) -> Style {
260 Style::default().fg(theme.success())
261 }
262
263 pub fn warning(theme: &dyn Theme) -> Style {
265 Style::default().fg(theme.warning())
266 }
267
268 pub fn error(theme: &dyn Theme) -> Style {
270 Style::default().fg(theme.error())
271 }
272
273 pub fn info(theme: &dyn Theme) -> Style {
275 Style::default().fg(theme.info())
276 }
277
278 pub fn muted(theme: &dyn Theme) -> Style {
280 Style::default().fg(theme.text_muted())
281 }
282
283 pub fn border(theme: &dyn Theme) -> Style {
285 Style::default().fg(theme.border())
286 }
287
288 pub fn border_active(theme: &dyn Theme) -> Style {
290 Style::default().fg(theme.border_active())
291 }
292
293 pub fn bold(theme: &dyn Theme) -> Style {
295 Style::default().fg(theme.text()).add_modifier(Modifier::BOLD)
296 }
297
298 pub fn italic(theme: &dyn Theme) -> Style {
300 Style::default().fg(theme.text()).add_modifier(Modifier::ITALIC)
301 }
302
303 pub fn underline(theme: &dyn Theme) -> Style {
305 Style::default().fg(theme.text()).add_modifier(Modifier::UNDERLINED)
306 }
307}
308
309#[derive(Debug, Clone, Serialize, Deserialize)]
311pub struct ThemeColors {
312 pub background: String,
313 pub background_panel: String,
314 pub background_element: String,
315 pub text: String,
316 pub text_muted: String,
317 pub text_secondary: String,
318 pub primary: String,
319 pub secondary: String,
320 pub accent: String,
321 pub success: String,
322 pub warning: String,
323 pub error: String,
324 pub info: String,
325 pub border: String,
326 pub border_active: String,
327 pub border_subtle: String,
328 pub diff_added: String,
329 pub diff_removed: String,
330 pub diff_modified: String,
331 pub diff_context: String,
332}
333
334impl ThemeColors {
335 pub fn parse_color(hex: &str) -> anyhow::Result<Color> {
337 let hex = hex.trim_start_matches('#');
338 if hex.len() != 6 {
339 return Err(anyhow::anyhow!("Invalid hex color: {}", hex));
340 }
341
342 let r = u8::from_str_radix(&hex[0..2], 16)?;
343 let g = u8::from_str_radix(&hex[2..4], 16)?;
344 let b = u8::from_str_radix(&hex[4..6], 16)?;
345
346 Ok(Color::Rgb(r, g, b))
347 }
348
349 pub fn color_to_hex(color: Color) -> String {
351 match color {
352 Color::Rgb(r, g, b) => format!("#{:02x}{:02x}{:02x}", r, g, b),
353 _ => "#000000".to_string(), }
355 }
356}