agent_core/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#[derive(Clone)]
56pub struct Theme {
57    // Base
58    pub background: Style,
59    pub text: Style,
60
61    // Borders & Chrome
62    pub border: Style,
63    pub border_focused: Style,
64
65    // Chat Title Bar
66    pub title_separator: Style,
67    pub title_indicator_connected: Style,
68    pub title_indicator_disconnected: Style,
69    pub title_text: Style,
70
71    // Message Roles
72    pub user_prefix: Style,
73    pub system_prefix: Style,
74    pub assistant_prefix: Style,
75    pub timestamp: Style,
76
77    // Streaming
78    pub cursor: Style,
79
80    // Markdown - Inline
81    pub bold: Modifier,
82    pub italic: Modifier,
83    pub strikethrough: Modifier,
84    pub inline_code: Style,
85    pub link_text: Style,
86    pub link_url: Style,
87
88    // Markdown - Headings
89    pub heading_1: Style,
90    pub heading_2: Style,
91    pub heading_3: Style,
92    pub heading_4: Style,
93
94    // Markdown - Code Blocks
95    pub code_block: Style,
96
97    // Tables
98    pub table_header: Style,
99    pub table_cell: Style,
100    pub table_border: Style,
101
102    // Tool Execution
103    pub tool_header: Style,
104    pub tool_executing: Style,
105    pub tool_completed: Style,
106    pub tool_failed: Style,
107
108    // Input Area
109    pub input_border: Style,
110    pub input_text: Style,
111    pub prompt: Style,
112
113    // Throbber/Progress
114    pub throbber_label: Style,
115    pub throbber_spinner: Style,
116
117    // Status Bar
118    pub status_help: Style,
119    pub status_model: Style,
120
121    // Slash Command Popup
122    pub popup_border: Style,
123    pub popup_header: Style,
124    pub popup_item: Style,
125    pub popup_item_selected: Style,
126    pub popup_item_desc: Style,
127    pub popup_item_desc_selected: Style,
128    pub popup_selected_bg: Style,
129    pub popup_empty: Style,
130
131    // UI Panel styles (permission panel, question panel)
132    pub help_text: Style,
133    pub muted_text: Style,
134    pub focused_text: Style,
135    pub focus_indicator: Style,
136    pub selected: Style,
137    pub unselected: Style,
138    pub button_confirm: Style,
139    pub button_confirm_focused: Style,
140    pub button_cancel: Style,
141    pub button_cancel_focused: Style,
142    pub warning: Style,
143    pub category: Style,
144    pub resource: Style,
145}
146
147impl Theme {
148    // Accessor methods for compatibility with code that used the old trait
149
150    // Markdown styles
151    pub fn bold(&self) -> Modifier { self.bold }
152    pub fn italic(&self) -> Modifier { self.italic }
153    pub fn strikethrough(&self) -> Modifier { self.strikethrough }
154    pub fn inline_code(&self) -> Style { self.inline_code }
155    pub fn link_text(&self) -> Style { self.link_text }
156    pub fn link_url(&self) -> Style { self.link_url }
157    pub fn heading_1(&self) -> Style { self.heading_1 }
158    pub fn heading_2(&self) -> Style { self.heading_2 }
159    pub fn heading_3(&self) -> Style { self.heading_3 }
160    pub fn heading_4(&self) -> Style { self.heading_4 }
161    pub fn code_block(&self) -> Style { self.code_block }
162    pub fn assistant_prefix(&self) -> Style { self.assistant_prefix }
163
164    // Table styles
165    pub fn table_header(&self) -> Style { self.table_header }
166    pub fn table_cell(&self) -> Style { self.table_cell }
167    pub fn table_border(&self) -> Style { self.table_border }
168
169    // Border styles
170    pub fn border(&self) -> Style { self.border }
171    pub fn border_focused(&self) -> Style { self.border_focused }
172
173    // Popup styles
174    pub fn popup_border(&self) -> Style { self.popup_border }
175    pub fn popup_header(&self) -> Style { self.popup_header }
176    pub fn popup_item(&self) -> Style { self.popup_item }
177    pub fn popup_item_selected(&self) -> Style { self.popup_item_selected }
178    pub fn popup_item_desc(&self) -> Style { self.popup_item_desc }
179    pub fn popup_item_desc_selected(&self) -> Style { self.popup_item_desc_selected }
180    pub fn popup_selected_bg(&self) -> Style { self.popup_selected_bg }
181    pub fn popup_empty(&self) -> Style { self.popup_empty }
182
183    // Status styles
184    pub fn status_help(&self) -> Style { self.status_help }
185    pub fn background(&self) -> Style { self.background }
186    pub fn text(&self) -> Style { self.text }
187    pub fn cursor(&self) -> Style { self.cursor }
188
189    // UI Panel styles
190    pub fn help_text(&self) -> Style { self.help_text }
191    pub fn muted_text(&self) -> Style { self.muted_text }
192    pub fn focused_text(&self) -> Style { self.focused_text }
193    pub fn focus_indicator(&self) -> Style { self.focus_indicator }
194    pub fn selected(&self) -> Style { self.selected }
195    pub fn unselected(&self) -> Style { self.unselected }
196    pub fn button_confirm(&self) -> Style { self.button_confirm }
197    pub fn button_confirm_focused(&self) -> Style { self.button_confirm_focused }
198    pub fn button_cancel(&self) -> Style { self.button_cancel }
199    pub fn button_cancel_focused(&self) -> Style { self.button_cancel_focused }
200    pub fn warning(&self) -> Style { self.warning }
201    pub fn category(&self) -> Style { self.category }
202    pub fn resource(&self) -> Style { self.resource }
203}
204
205impl Default for Theme {
206    fn default() -> Self {
207        Self {
208            // Base
209            background: Style::default(),
210            text: Style::default(),
211
212            // Borders & Chrome
213            border: Style::default().fg(Color::DarkGray),
214            border_focused: Style::default().fg(Color::Cyan),
215
216            // Chat Title Bar
217            title_separator: Style::default().fg(Color::DarkGray),
218            title_indicator_connected: Style::default().fg(Color::Green),
219            title_indicator_disconnected: Style::default().fg(Color::Red),
220            title_text: Style::default(),
221
222            // Message Roles
223            user_prefix: Style::default().fg(Color::Blue),
224            system_prefix: Style::default().fg(Color::Yellow),
225            assistant_prefix: Style::default().fg(Color::Magenta),
226            timestamp: Style::default().fg(Color::DarkGray),
227
228            // Streaming
229            cursor: Style::default().fg(Color::Green),
230
231            // Markdown - Inline (modifiers only, preserve existing fg color)
232            bold: Modifier::BOLD,
233            italic: Modifier::ITALIC,
234            strikethrough: Modifier::CROSSED_OUT,
235            inline_code: Style::default().fg(Color::Yellow),
236            link_text: Style::default().fg(Color::Cyan),
237            link_url: Style::default().fg(Color::DarkGray),
238
239            // Markdown - Headings
240            heading_1: Style::default()
241                .fg(Color::Cyan)
242                .add_modifier(Modifier::BOLD),
243            heading_2: Style::default()
244                .fg(Color::Green)
245                .add_modifier(Modifier::BOLD),
246            heading_3: Style::default()
247                .fg(Color::Yellow)
248                .add_modifier(Modifier::BOLD),
249            heading_4: Style::default()
250                .fg(Color::White)
251                .add_modifier(Modifier::BOLD),
252
253            // Markdown - Code Blocks
254            code_block: Style::default().fg(Color::Rgb(180, 180, 180)),
255
256            // Tables
257            table_header: Style::default()
258                .fg(Color::Cyan)
259                .add_modifier(Modifier::BOLD),
260            table_cell: Style::default().fg(Color::White),
261            table_border: Style::default().fg(Color::DarkGray),
262
263            // Tool Execution
264            tool_header: Style::default().fg(Color::Cyan),
265            tool_executing: Style::default()
266                .fg(Color::Gray)
267                .add_modifier(Modifier::ITALIC),
268            tool_completed: Style::default().fg(Color::Green),
269            tool_failed: Style::default().fg(Color::Red),
270
271            // Input Area
272            input_border: Style::default().fg(Color::DarkGray),
273            input_text: Style::default(),
274            prompt: Style::default(),
275
276            // Throbber/Progress
277            throbber_label: Style::default().fg(Color::DarkGray),
278            throbber_spinner: Style::default().fg(Color::Cyan),
279
280            // Status Bar
281            status_help: Style::default().fg(Color::DarkGray),
282            status_model: Style::default().fg(Color::DarkGray),
283
284            // Slash Command Popup
285            popup_border: Style::default().fg(Color::DarkGray),
286            popup_header: Style::default().fg(Color::DarkGray),
287            popup_item: Style::default().fg(Color::White),
288            popup_item_selected: Style::default()
289                .fg(Color::Cyan)
290                .add_modifier(Modifier::BOLD),
291            popup_item_desc: Style::default().fg(Color::DarkGray),
292            popup_item_desc_selected: Style::default().fg(Color::Gray),
293            popup_selected_bg: Style::default().bg(Color::DarkGray),
294            popup_empty: Style::default()
295                .fg(Color::DarkGray)
296                .add_modifier(Modifier::ITALIC),
297
298            // UI Panel styles
299            help_text: Style::default().fg(Color::DarkGray),
300            muted_text: Style::default().fg(Color::Gray),
301            focused_text: Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD),
302            focus_indicator: Style::default().fg(Color::Yellow),
303            selected: Style::default().fg(Color::Green),
304            unselected: Style::default().fg(Color::Gray),
305            button_confirm: Style::default().fg(Color::Rgb(60, 120, 60)),
306            button_confirm_focused: Style::default().fg(Color::LightGreen).add_modifier(Modifier::BOLD),
307            button_cancel: Style::default().fg(Color::Rgb(140, 70, 70)),
308            button_cancel_focused: Style::default().fg(Color::LightRed).add_modifier(Modifier::BOLD),
309            warning: Style::default().fg(Color::Yellow),
310            category: Style::default().fg(Color::Magenta),
311            resource: Style::default().fg(Color::Cyan),
312        }
313    }
314}
315