Skip to main content

agent_air_tui/themes/
theme.rs

1// Theme system for the TUI
2//
3// Centralizes all colors, modifiers, and styles for consistent theming.
4// Supports runtime theme switching via RwLock.
5
6use std::sync::RwLock;
7
8use ratatui::style::{Color, Modifier, Style};
9
10/// Global theme instance with runtime switching support
11static THEME: RwLock<Option<Theme>> = RwLock::new(None);
12
13/// Current theme name
14static THEME_NAME: RwLock<Option<String>> = RwLock::new(None);
15
16/// Initialize the global theme (call once at startup)
17pub fn init_theme(name: &str, theme: Theme) {
18    if let Ok(mut guard) = THEME.write() {
19        *guard = Some(theme);
20    }
21    if let Ok(mut guard) = THEME_NAME.write() {
22        *guard = Some(name.to_string());
23    }
24}
25
26/// Set a new theme at runtime
27pub fn set_theme(name: &str, theme: Theme) {
28    if let Ok(mut guard) = THEME.write() {
29        *guard = Some(theme);
30    }
31    if let Ok(mut guard) = THEME_NAME.write() {
32        *guard = Some(name.to_string());
33    }
34}
35
36/// Get the current theme name
37pub fn current_theme_name() -> String {
38    THEME_NAME
39        .read()
40        .ok()
41        .and_then(|guard| guard.clone())
42        .unwrap_or_else(|| "default".to_string())
43}
44
45/// Get the current theme (cloned for safe access)
46pub fn theme() -> Theme {
47    THEME
48        .read()
49        .ok()
50        .and_then(|guard| guard.clone())
51        .unwrap_or_default()
52}
53
54/// Complete theme definition for the TUI.
55///
56/// Contains styles for all UI elements including chat messages, markdown formatting,
57/// tool execution, input areas, popups, and interactive panels.
58#[derive(Clone)]
59pub struct Theme {
60    // Base
61    /// Background style for the entire UI.
62    pub background: Style,
63    /// Default text style.
64    pub text: Style,
65
66    // Borders & Chrome
67    pub border: Style,
68    pub border_focused: Style,
69
70    // Chat Title Bar
71    pub title_separator: Style,
72    pub title_indicator_connected: Style,
73    pub title_indicator_disconnected: Style,
74    pub title_text: Style,
75
76    // Message Roles
77    pub user_prefix: Style,
78    pub system_prefix: Style,
79    pub assistant_prefix: Style,
80    pub timestamp: Style,
81
82    // Streaming
83    pub cursor: Style,
84
85    // Markdown - Inline
86    pub bold: Modifier,
87    pub italic: Modifier,
88    pub strikethrough: Modifier,
89    pub inline_code: Style,
90    pub link_text: Style,
91    pub link_url: Style,
92
93    // Markdown - Headings
94    pub heading_1: Style,
95    pub heading_2: Style,
96    pub heading_3: Style,
97    pub heading_4: Style,
98
99    // Markdown - Code Blocks
100    pub code_block: Style,
101
102    // Tables
103    pub table_header: Style,
104    pub table_cell: Style,
105    pub table_border: Style,
106
107    // Tool Execution
108    pub tool_header: Style,
109    pub tool_executing: Style,
110    pub tool_completed: Style,
111    pub tool_failed: Style,
112
113    // Input Area
114    pub input_border: Style,
115    pub input_text: Style,
116    pub prompt: Style,
117
118    // Throbber/Progress
119    pub throbber_label: Style,
120    pub throbber_spinner: Style,
121
122    // Status Bar
123    pub status_help: Style,
124    pub status_model: Style,
125
126    // Slash Command Popup
127    pub popup_border: Style,
128    pub popup_header: Style,
129    pub popup_item: Style,
130    pub popup_item_selected: Style,
131    pub popup_item_desc: Style,
132    pub popup_item_desc_selected: Style,
133    pub popup_selected_bg: Style,
134    pub popup_empty: Style,
135
136    // UI Panel styles (permission panel, question panel)
137    pub help_text: Style,
138    pub muted_text: Style,
139    pub focused_text: Style,
140    pub focus_indicator: Style,
141    pub selected: Style,
142    pub unselected: Style,
143    pub button_confirm: Style,
144    pub button_confirm_focused: Style,
145    pub button_cancel: Style,
146    pub button_cancel_focused: Style,
147    pub warning: Style,
148    pub category: Style,
149    pub resource: Style,
150}
151
152impl Theme {
153    // Accessor methods for compatibility with code that used the old trait
154
155    // Markdown styles
156    pub fn bold(&self) -> Modifier {
157        self.bold
158    }
159    pub fn italic(&self) -> Modifier {
160        self.italic
161    }
162    pub fn strikethrough(&self) -> Modifier {
163        self.strikethrough
164    }
165    pub fn inline_code(&self) -> Style {
166        self.inline_code
167    }
168    pub fn link_text(&self) -> Style {
169        self.link_text
170    }
171    pub fn link_url(&self) -> Style {
172        self.link_url
173    }
174    pub fn heading_1(&self) -> Style {
175        self.heading_1
176    }
177    pub fn heading_2(&self) -> Style {
178        self.heading_2
179    }
180    pub fn heading_3(&self) -> Style {
181        self.heading_3
182    }
183    pub fn heading_4(&self) -> Style {
184        self.heading_4
185    }
186    pub fn code_block(&self) -> Style {
187        self.code_block
188    }
189    pub fn assistant_prefix(&self) -> Style {
190        self.assistant_prefix
191    }
192
193    // Table styles
194    pub fn table_header(&self) -> Style {
195        self.table_header
196    }
197    pub fn table_cell(&self) -> Style {
198        self.table_cell
199    }
200    pub fn table_border(&self) -> Style {
201        self.table_border
202    }
203
204    // Border styles
205    pub fn border(&self) -> Style {
206        self.border
207    }
208    pub fn border_focused(&self) -> Style {
209        self.border_focused
210    }
211
212    // Popup styles
213    pub fn popup_border(&self) -> Style {
214        self.popup_border
215    }
216    pub fn popup_header(&self) -> Style {
217        self.popup_header
218    }
219    pub fn popup_item(&self) -> Style {
220        self.popup_item
221    }
222    pub fn popup_item_selected(&self) -> Style {
223        self.popup_item_selected
224    }
225    pub fn popup_item_desc(&self) -> Style {
226        self.popup_item_desc
227    }
228    pub fn popup_item_desc_selected(&self) -> Style {
229        self.popup_item_desc_selected
230    }
231    pub fn popup_selected_bg(&self) -> Style {
232        self.popup_selected_bg
233    }
234    pub fn popup_empty(&self) -> Style {
235        self.popup_empty
236    }
237
238    // Status styles
239    pub fn status_help(&self) -> Style {
240        self.status_help
241    }
242    pub fn background(&self) -> Style {
243        self.background
244    }
245    pub fn text(&self) -> Style {
246        self.text
247    }
248    pub fn cursor(&self) -> Style {
249        self.cursor
250    }
251
252    // UI Panel styles
253    pub fn help_text(&self) -> Style {
254        self.help_text
255    }
256    pub fn muted_text(&self) -> Style {
257        self.muted_text
258    }
259    pub fn focused_text(&self) -> Style {
260        self.focused_text
261    }
262    pub fn focus_indicator(&self) -> Style {
263        self.focus_indicator
264    }
265    pub fn selected(&self) -> Style {
266        self.selected
267    }
268    pub fn unselected(&self) -> Style {
269        self.unselected
270    }
271    pub fn button_confirm(&self) -> Style {
272        self.button_confirm
273    }
274    pub fn button_confirm_focused(&self) -> Style {
275        self.button_confirm_focused
276    }
277    pub fn button_cancel(&self) -> Style {
278        self.button_cancel
279    }
280    pub fn button_cancel_focused(&self) -> Style {
281        self.button_cancel_focused
282    }
283    pub fn warning(&self) -> Style {
284        self.warning
285    }
286    pub fn category(&self) -> Style {
287        self.category
288    }
289    pub fn resource(&self) -> Style {
290        self.resource
291    }
292}
293
294impl Default for Theme {
295    fn default() -> Self {
296        Self {
297            // Base
298            background: Style::default(),
299            text: Style::default(),
300
301            // Borders & Chrome
302            border: Style::default().fg(Color::DarkGray),
303            border_focused: Style::default().fg(Color::Cyan),
304
305            // Chat Title Bar
306            title_separator: Style::default().fg(Color::DarkGray),
307            title_indicator_connected: Style::default().fg(Color::Green),
308            title_indicator_disconnected: Style::default().fg(Color::Red),
309            title_text: Style::default(),
310
311            // Message Roles
312            user_prefix: Style::default().fg(Color::Blue),
313            system_prefix: Style::default().fg(Color::Yellow),
314            assistant_prefix: Style::default().fg(Color::Magenta),
315            timestamp: Style::default().fg(Color::DarkGray),
316
317            // Streaming
318            cursor: Style::default().fg(Color::Green),
319
320            // Markdown - Inline (modifiers only, preserve existing fg color)
321            bold: Modifier::BOLD,
322            italic: Modifier::ITALIC,
323            strikethrough: Modifier::CROSSED_OUT,
324            inline_code: Style::default().fg(Color::Yellow),
325            link_text: Style::default().fg(Color::Cyan),
326            link_url: Style::default().fg(Color::DarkGray),
327
328            // Markdown - Headings
329            heading_1: Style::default()
330                .fg(Color::Cyan)
331                .add_modifier(Modifier::BOLD),
332            heading_2: Style::default()
333                .fg(Color::Green)
334                .add_modifier(Modifier::BOLD),
335            heading_3: Style::default()
336                .fg(Color::Yellow)
337                .add_modifier(Modifier::BOLD),
338            heading_4: Style::default()
339                .fg(Color::White)
340                .add_modifier(Modifier::BOLD),
341
342            // Markdown - Code Blocks
343            code_block: Style::default().fg(Color::Rgb(180, 180, 180)),
344
345            // Tables
346            table_header: Style::default()
347                .fg(Color::Cyan)
348                .add_modifier(Modifier::BOLD),
349            table_cell: Style::default().fg(Color::White),
350            table_border: Style::default().fg(Color::DarkGray),
351
352            // Tool Execution
353            tool_header: Style::default().fg(Color::Cyan),
354            tool_executing: Style::default()
355                .fg(Color::Gray)
356                .add_modifier(Modifier::ITALIC),
357            tool_completed: Style::default().fg(Color::Green),
358            tool_failed: Style::default().fg(Color::Red),
359
360            // Input Area
361            input_border: Style::default().fg(Color::DarkGray),
362            input_text: Style::default(),
363            prompt: Style::default(),
364
365            // Throbber/Progress
366            throbber_label: Style::default().fg(Color::DarkGray),
367            throbber_spinner: Style::default().fg(Color::Cyan),
368
369            // Status Bar
370            status_help: Style::default().fg(Color::DarkGray),
371            status_model: Style::default().fg(Color::DarkGray),
372
373            // Slash Command Popup
374            popup_border: Style::default().fg(Color::DarkGray),
375            popup_header: Style::default().fg(Color::DarkGray),
376            popup_item: Style::default().fg(Color::White),
377            popup_item_selected: Style::default()
378                .fg(Color::Cyan)
379                .add_modifier(Modifier::BOLD),
380            popup_item_desc: Style::default().fg(Color::DarkGray),
381            popup_item_desc_selected: Style::default().fg(Color::Gray),
382            popup_selected_bg: Style::default().bg(Color::DarkGray),
383            popup_empty: Style::default()
384                .fg(Color::DarkGray)
385                .add_modifier(Modifier::ITALIC),
386
387            // UI Panel styles
388            help_text: Style::default().fg(Color::DarkGray),
389            muted_text: Style::default().fg(Color::Gray),
390            focused_text: Style::default()
391                .fg(Color::Cyan)
392                .add_modifier(Modifier::BOLD),
393            focus_indicator: Style::default().fg(Color::Yellow),
394            selected: Style::default().fg(Color::Green),
395            unselected: Style::default().fg(Color::Gray),
396            button_confirm: Style::default().fg(Color::Rgb(60, 120, 60)),
397            button_confirm_focused: Style::default()
398                .fg(Color::LightGreen)
399                .add_modifier(Modifier::BOLD),
400            button_cancel: Style::default().fg(Color::Rgb(140, 70, 70)),
401            button_cancel_focused: Style::default()
402                .fg(Color::LightRed)
403                .add_modifier(Modifier::BOLD),
404            warning: Style::default().fg(Color::Yellow),
405            category: Style::default().fg(Color::Magenta),
406            resource: Style::default().fg(Color::Cyan),
407        }
408    }
409}