1use ratatui::style::{Color, Modifier, Style};
4
5use crate::config::Theme as ThemeConfig;
6
7#[derive(Debug, Clone)]
9pub struct Theme {
10 header_bg: Color,
12 header_fg: Color,
13
14 filter_fg: Color,
16 filter_placeholder_fg: Color,
17
18 number_fg: Color,
20 script_fg: Color,
21 selected_bg: Color,
22 selected_fg: Color,
23 cursor_fg: Color,
24 multiselect_fg: Color,
25
26 description_fg: Color,
28 command_fg: Color,
29 separator_fg: Color,
30
31 footer_fg: Color,
33 key_fg: Color,
34
35 error_fg: Color,
37 success_fg: Color,
38 warning_fg: Color,
39}
40
41impl Default for Theme {
42 fn default() -> Self {
43 Self::new(&ThemeConfig::Default)
44 }
45}
46
47impl Theme {
48 pub fn new(config: &ThemeConfig) -> Self {
50 match config {
51 ThemeConfig::Default => Self::default_theme(),
52 ThemeConfig::Minimal => Self::minimal_theme(),
53 ThemeConfig::None => Self::no_color_theme(),
54 }
55 }
56
57 fn default_theme() -> Self {
59 Self {
60 header_bg: Color::Blue,
61 header_fg: Color::White,
62
63 filter_fg: Color::Yellow,
64 filter_placeholder_fg: Color::DarkGray,
65
66 number_fg: Color::Cyan,
67 script_fg: Color::White,
68 selected_bg: Color::Blue,
69 selected_fg: Color::White,
70 cursor_fg: Color::Green,
71 multiselect_fg: Color::Magenta,
72
73 description_fg: Color::Gray,
74 command_fg: Color::DarkGray,
75 separator_fg: Color::DarkGray,
76
77 footer_fg: Color::DarkGray,
78 key_fg: Color::Cyan,
79
80 error_fg: Color::Red,
81 success_fg: Color::Green,
82 warning_fg: Color::Yellow,
83 }
84 }
85
86 fn minimal_theme() -> Self {
88 Self {
89 header_bg: Color::Reset,
90 header_fg: Color::White,
91
92 filter_fg: Color::White,
93 filter_placeholder_fg: Color::DarkGray,
94
95 number_fg: Color::Gray,
96 script_fg: Color::White,
97 selected_bg: Color::Reset,
98 selected_fg: Color::Cyan,
99 cursor_fg: Color::White,
100 multiselect_fg: Color::White,
101
102 description_fg: Color::Gray,
103 command_fg: Color::DarkGray,
104 separator_fg: Color::DarkGray,
105
106 footer_fg: Color::DarkGray,
107 key_fg: Color::Gray,
108
109 error_fg: Color::Red,
110 success_fg: Color::Green,
111 warning_fg: Color::Yellow,
112 }
113 }
114
115 fn no_color_theme() -> Self {
117 Self {
118 header_bg: Color::Reset,
119 header_fg: Color::Reset,
120
121 filter_fg: Color::Reset,
122 filter_placeholder_fg: Color::Reset,
123
124 number_fg: Color::Reset,
125 script_fg: Color::Reset,
126 selected_bg: Color::Reset,
127 selected_fg: Color::Reset,
128 cursor_fg: Color::Reset,
129 multiselect_fg: Color::Reset,
130
131 description_fg: Color::Reset,
132 command_fg: Color::Reset,
133 separator_fg: Color::Reset,
134
135 footer_fg: Color::Reset,
136 key_fg: Color::Reset,
137
138 error_fg: Color::Reset,
139 success_fg: Color::Reset,
140 warning_fg: Color::Reset,
141 }
142 }
143
144 pub fn header(&self) -> Style {
148 Style::default()
149 .fg(self.header_fg)
150 .bg(self.header_bg)
151 .add_modifier(Modifier::BOLD)
152 }
153
154 pub fn header_project(&self) -> Style {
156 Style::default()
157 .fg(self.header_fg)
158 .bg(self.header_bg)
159 .add_modifier(Modifier::BOLD)
160 }
161
162 pub fn header_runner(&self) -> Style {
164 Style::default().fg(self.header_fg).bg(self.header_bg)
165 }
166
167 pub fn filter(&self) -> Style {
171 Style::default().fg(self.filter_fg)
172 }
173
174 pub fn filter_active(&self) -> Style {
176 Style::default()
177 .fg(self.filter_fg)
178 .add_modifier(Modifier::BOLD)
179 }
180
181 pub fn filter_placeholder(&self) -> Style {
183 Style::default()
184 .fg(self.filter_placeholder_fg)
185 .add_modifier(Modifier::ITALIC)
186 }
187
188 pub fn number(&self) -> Style {
192 Style::default()
193 .fg(self.number_fg)
194 .add_modifier(Modifier::DIM)
195 }
196
197 pub fn script(&self) -> Style {
199 Style::default().fg(self.script_fg)
200 }
201
202 pub fn selected(&self) -> Style {
204 if self.selected_bg == Color::Reset {
205 Style::default()
206 .fg(self.selected_fg)
207 .add_modifier(Modifier::BOLD | Modifier::UNDERLINED)
208 } else {
209 Style::default()
210 .fg(self.selected_fg)
211 .bg(self.selected_bg)
212 .add_modifier(Modifier::BOLD)
213 }
214 }
215
216 pub fn cursor(&self) -> Style {
218 Style::default()
219 .fg(self.cursor_fg)
220 .add_modifier(Modifier::BOLD)
221 }
222
223 pub fn multiselect(&self) -> Style {
225 Style::default()
226 .fg(self.multiselect_fg)
227 .add_modifier(Modifier::BOLD)
228 }
229
230 pub fn description(&self) -> Style {
234 Style::default().fg(self.description_fg)
235 }
236
237 pub fn command(&self) -> Style {
239 Style::default()
240 .fg(self.command_fg)
241 .add_modifier(Modifier::ITALIC)
242 }
243
244 pub fn separator(&self) -> Style {
246 Style::default().fg(self.separator_fg)
247 }
248
249 pub fn footer(&self) -> Style {
253 Style::default().fg(self.footer_fg)
254 }
255
256 pub fn key(&self) -> Style {
258 Style::default()
259 .fg(self.key_fg)
260 .add_modifier(Modifier::BOLD)
261 }
262
263 pub fn error(&self) -> Style {
267 Style::default()
268 .fg(self.error_fg)
269 .add_modifier(Modifier::BOLD)
270 }
271
272 pub fn success(&self) -> Style {
274 Style::default().fg(self.success_fg)
275 }
276
277 pub fn warning(&self) -> Style {
279 Style::default().fg(self.warning_fg)
280 }
281
282 pub fn dim(&self) -> Style {
286 Style::default().add_modifier(Modifier::DIM)
287 }
288
289 pub fn bold(&self) -> Style {
291 Style::default().add_modifier(Modifier::BOLD)
292 }
293}
294
295#[cfg(test)]
296mod tests {
297 use super::*;
298
299 #[test]
300 fn test_default_theme() {
301 let theme = Theme::default();
302 assert_eq!(theme.header_bg, Color::Blue);
303 assert_eq!(theme.header_fg, Color::White);
304 }
305
306 #[test]
307 fn test_theme_from_config() {
308 let theme = Theme::new(&ThemeConfig::Default);
309 assert_eq!(theme.header_bg, Color::Blue);
310
311 let theme = Theme::new(&ThemeConfig::Minimal);
312 assert_eq!(theme.header_bg, Color::Reset);
313
314 let theme = Theme::new(&ThemeConfig::None);
315 assert_eq!(theme.header_fg, Color::Reset);
316 }
317
318 #[test]
319 fn test_header_style() {
320 let theme = Theme::default();
321 let style = theme.header();
322
323 assert!(style.add_modifier.contains(Modifier::BOLD));
325 }
326
327 #[test]
328 fn test_selected_style_minimal() {
329 let theme = Theme::new(&ThemeConfig::Minimal);
330 let style = theme.selected();
331
332 assert!(style.add_modifier.contains(Modifier::UNDERLINED));
334 }
335
336 #[test]
337 fn test_no_color_theme() {
338 let theme = Theme::new(&ThemeConfig::None);
339
340 assert_eq!(theme.header_bg, Color::Reset);
342 assert_eq!(theme.filter_fg, Color::Reset);
343 assert_eq!(theme.number_fg, Color::Reset);
344 }
345}