agentic_core/
theme.rs

1// theme.rs
2//! Everforest Theme System for Agentic
3//!
4//! Implements a comprehensive theming architecture with Everforest Dark/Light variants.
5//! Provides clean separation of concerns and runtime theme switching capability.
6
7use ratatui::style::{Color, Modifier, Style};
8use serde::{Deserialize, Serialize};
9
10/// Theme variants supported by Agentic
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
12pub enum ThemeVariant {
13    /// Everforest Dark theme (default)
14    EverforestDark,
15    /// Everforest Light theme
16    EverforestLight,
17}
18
19impl Default for ThemeVariant {
20    fn default() -> Self {
21        Self::EverforestDark
22    }
23}
24
25/// Color palette for a theme variant
26#[derive(Debug, Clone)]
27pub struct ColorPalette {
28    pub background: Color,
29    pub foreground: Color,
30    pub accent: Color,
31    pub secondary: Color,
32    pub info: Color,
33    pub border: Color,
34    pub selection: Color,
35    pub cursor: Color,
36    pub warning: Color, // Yellow/orange for settings
37}
38
39/// UI element types for styling
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41pub enum Element {
42    /// Normal text content
43    Text,
44    /// Titles and headers
45    Title,
46    /// Borders and frames
47    Border,
48    /// Highlighted/selected items
49    Highlight,
50    /// Accent elements (buttons, links)
51    Accent,
52    /// Secondary elements
53    Secondary,
54    /// Information/status elements
55    Info,
56    /// Background elements
57    Background,
58    /// Active/focused elements
59    Active,
60    /// Inactive/disabled elements
61    Inactive,
62    /// Warning/settings elements (yellow/orange)
63    Warning,
64}
65
66/// Main theme structure managing all UI styling
67#[derive(Debug, Clone)]
68pub struct Theme {
69    variant: ThemeVariant,
70    colors: ColorPalette,
71}
72
73impl Default for Theme {
74    fn default() -> Self {
75        Self::new(ThemeVariant::default())
76    }
77}
78
79impl Theme {
80    /// Create a new theme with the specified variant
81    pub fn new(variant: ThemeVariant) -> Self {
82        // Check if we're in Terminal.app (which has poor RGB support)
83        let use_basic_colors = std::env::var("TERM_PROGRAM")
84            .map(|term| term == "Apple_Terminal")
85            .unwrap_or(false);
86
87        let colors = match (variant, use_basic_colors) {
88            (ThemeVariant::EverforestDark, false) => ColorPalette {
89                background: Color::Rgb(45, 53, 59),    // #2d353b
90                foreground: Color::Rgb(211, 198, 170), // #d3c6aa
91                accent: Color::Rgb(167, 192, 128),     // #a7c080 (green)
92                secondary: Color::Rgb(230, 126, 128),  // #e67e80 (red)
93                info: Color::Rgb(127, 187, 179),       // #7fbbb3 (aqua)
94                border: Color::Rgb(130, 140, 150),     // #828c96
95                selection: Color::Rgb(64, 72, 78),     // #40484e
96                cursor: Color::Rgb(211, 198, 170),     // #d3c6aa
97                warning: Color::Rgb(219, 188, 127),    // #dbbc7f
98            },
99            (ThemeVariant::EverforestLight, false) => ColorPalette {
100                background: Color::Rgb(253, 246, 227), // #fdf6e3
101                foreground: Color::Rgb(76, 86, 94),    // #4c565e
102                accent: Color::Rgb(141, 161, 1),       // #8da101
103                secondary: Color::Rgb(248, 85, 82),    // #f85552
104                info: Color::Rgb(53, 167, 124),        // #35a77c
105                border: Color::Rgb(150, 160, 170),     // #96a0aa
106                selection: Color::Rgb(243, 236, 217),  // #f3ecd9
107                cursor: Color::Rgb(76, 86, 94),        // #4c565e
108                warning: Color::Rgb(207, 131, 44),     // #cf832c
109            },
110            // Terminal.app fallback colors
111            (ThemeVariant::EverforestDark, true) => ColorPalette {
112                background: Color::Reset, // Use terminal's default background
113                foreground: Color::White,
114                accent: Color::Green,
115                secondary: Color::Red,
116                info: Color::Cyan,
117                border: Color::Gray,    // Gray instead of DarkGray for visibility
118                selection: Color::Blue, // Blue selection for better visibility
119                cursor: Color::White,
120                warning: Color::Yellow,
121            },
122            (ThemeVariant::EverforestLight, true) => ColorPalette {
123                background: Color::Reset, // Use terminal's default background
124                foreground: Color::Black,
125                accent: Color::Green,
126                secondary: Color::Red,
127                info: Color::Blue,       // Blue instead of Cyan for better contrast
128                border: Color::DarkGray, // DarkGray for better contrast
129                selection: Color::Yellow, // Yellow instead of LightYellow
130                cursor: Color::Black,
131                warning: Color::Magenta, // Magenta for warnings to stand out
132            },
133        };
134
135        Self { variant, colors }
136    }
137
138    /// Get the current theme variant
139    pub fn variant(&self) -> ThemeVariant {
140        self.variant
141    }
142
143    /// Get the color palette
144    pub fn colors(&self) -> &ColorPalette {
145        &self.colors
146    }
147
148    /// Toggle between dark and light variants
149    pub fn toggle(&mut self) {
150        self.variant = match self.variant {
151            ThemeVariant::EverforestDark => ThemeVariant::EverforestLight,
152            ThemeVariant::EverforestLight => ThemeVariant::EverforestDark,
153        };
154        *self = Self::new(self.variant);
155    }
156
157    /// Set specific theme variant
158    pub fn set_variant(&mut self, variant: ThemeVariant) {
159        if self.variant != variant {
160            self.variant = variant;
161            *self = Self::new(self.variant);
162        }
163    }
164
165    /// Get a ratatui Style for the specified UI element
166    pub fn ratatui_style(&self, element: Element) -> Style {
167        match element {
168            Element::Text => Style::default()
169                .fg(self.colors.foreground)
170                .bg(self.colors.background),
171
172            Element::Title => Style::default()
173                .fg(self.colors.accent)
174                .bg(self.colors.background)
175                .add_modifier(Modifier::BOLD),
176
177            Element::Border => Style::default()
178                .fg(self.colors.border)
179                .bg(self.colors.background),
180
181            Element::Highlight => Style::default()
182                .fg(self.colors.foreground)
183                .bg(self.colors.selection)
184                .add_modifier(Modifier::BOLD),
185
186            Element::Accent => Style::default()
187                .fg(self.colors.accent)
188                .bg(self.colors.background)
189                .add_modifier(Modifier::BOLD),
190
191            Element::Secondary => Style::default()
192                .fg(self.colors.secondary)
193                .bg(self.colors.background),
194
195            Element::Info => Style::default()
196                .fg(self.colors.info)
197                .bg(self.colors.background),
198
199            Element::Background => Style::default()
200                .fg(self.colors.foreground)
201                .bg(self.colors.background),
202
203            Element::Active => Style::default()
204                .fg(self.colors.accent)
205                .bg(self.colors.selection)
206                .add_modifier(Modifier::BOLD),
207
208            Element::Inactive => Style::default()
209                .fg(self.colors.border)
210                .bg(self.colors.background),
211
212            Element::Warning => Style::default()
213                .fg(self.colors.warning)
214                .bg(self.colors.background),
215        }
216    }
217
218    /// Get foreground color for an element
219    pub fn fg_color(&self, element: Element) -> Color {
220        match element {
221            Element::Text | Element::Background => self.colors.foreground,
222            Element::Title | Element::Accent | Element::Active => self.colors.accent,
223            Element::Border | Element::Inactive => self.colors.border,
224            Element::Highlight => self.colors.foreground,
225            Element::Secondary => self.colors.secondary,
226            Element::Info => self.colors.info,
227            Element::Warning => self.colors.warning,
228        }
229    }
230
231    /// Get background color for an element
232    pub fn bg_color(&self, element: Element) -> Color {
233        match element {
234            Element::Highlight | Element::Active => self.colors.selection,
235            _ => self.colors.background,
236        }
237    }
238
239    /// Get style for block titles
240    pub fn title_style(&self) -> Style {
241        self.ratatui_style(Element::Title)
242    }
243
244    /// Get style for block borders
245    pub fn border_style(&self) -> Style {
246        self.ratatui_style(Element::Border)
247    }
248
249    /// Get style for normal text
250    pub fn text_style(&self) -> Style {
251        self.ratatui_style(Element::Text)
252    }
253
254    /// Get style for highlighted/selected items
255    pub fn highlight_style(&self) -> Style {
256        self.ratatui_style(Element::Highlight)
257    }
258
259    /// Get style for accent elements
260    pub fn accent_style(&self) -> Style {
261        self.ratatui_style(Element::Accent)
262    }
263
264    /// Get style for secondary elements
265    pub fn secondary_style(&self) -> Style {
266        self.ratatui_style(Element::Secondary)
267    }
268
269    /// Get style for info elements
270    pub fn info_style(&self) -> Style {
271        self.ratatui_style(Element::Info)
272    }
273
274    /// Get style for warning/settings elements
275    pub fn warning_style(&self) -> Style {
276        self.ratatui_style(Element::Warning)
277    }
278}