tuxtui_core/
theme.rs

1//! Theme system for consistent styling across widgets.
2
3use crate::style::{Color, Style};
4use alloc::string::String;
5
6#[cfg(feature = "serde")]
7use serde::{Deserialize, Serialize};
8
9/// A complete theme specification for the TUI.
10///
11/// Themes provide consistent styling across all widgets.
12///
13/// # Example
14///
15/// ```
16/// use tuxtui_core::theme::Theme;
17/// use tuxtui_core::style::Color;
18///
19/// let theme = Theme::dark();
20/// ```
21#[derive(Debug, Clone, PartialEq, Eq)]
22#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
23pub struct Theme {
24    /// Name of the theme
25    pub name: String,
26    /// Color palette
27    pub palette: PaletteTheme,
28    /// Widget-specific styles
29    pub widgets: WidgetTheme,
30}
31
32impl Theme {
33    /// Create a new theme.
34    #[must_use]
35    pub fn new(name: impl Into<String>) -> Self {
36        Self {
37            name: name.into(),
38            palette: PaletteTheme::default(),
39            widgets: WidgetTheme::default(),
40        }
41    }
42
43    /// Create a dark theme.
44    #[must_use]
45    pub fn dark() -> Self {
46        Self {
47            name: String::from("dark"),
48            palette: PaletteTheme {
49                background: Color::Black,
50                foreground: Color::White,
51                primary: Color::Blue,
52                secondary: Color::Cyan,
53                accent: Color::Magenta,
54                error: Color::Red,
55                warning: Color::Yellow,
56                success: Color::Green,
57                muted: Color::Gray,
58            },
59            widgets: WidgetTheme::default(),
60        }
61    }
62
63    /// Create a light theme.
64    #[must_use]
65    pub fn light() -> Self {
66        Self {
67            name: String::from("light"),
68            palette: PaletteTheme {
69                background: Color::White,
70                foreground: Color::Black,
71                primary: Color::Blue,
72                secondary: Color::Cyan,
73                accent: Color::Magenta,
74                error: Color::Red,
75                warning: Color::Yellow,
76                success: Color::Green,
77                muted: Color::Gray,
78            },
79            widgets: WidgetTheme::default(),
80        }
81    }
82
83    /// Create a high-contrast theme.
84    #[must_use]
85    pub fn high_contrast() -> Self {
86        Self {
87            name: String::from("high-contrast"),
88            palette: PaletteTheme {
89                background: Color::Black,
90                foreground: Color::White,
91                primary: Color::LightBlue,
92                secondary: Color::LightCyan,
93                accent: Color::LightMagenta,
94                error: Color::LightRed,
95                warning: Color::LightYellow,
96                success: Color::LightGreen,
97                muted: Color::LightGray,
98            },
99            widgets: WidgetTheme::default(),
100        }
101    }
102}
103
104impl Default for Theme {
105    fn default() -> Self {
106        Self::dark()
107    }
108}
109
110/// Color palette for a theme.
111#[derive(Debug, Clone, PartialEq, Eq)]
112#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
113pub struct PaletteTheme {
114    /// Background color
115    pub background: Color,
116    /// Foreground color
117    pub foreground: Color,
118    /// Primary accent color
119    pub primary: Color,
120    /// Secondary accent color
121    pub secondary: Color,
122    /// Accent color
123    pub accent: Color,
124    /// Error color
125    pub error: Color,
126    /// Warning color
127    pub warning: Color,
128    /// Success color
129    pub success: Color,
130    /// Muted/disabled color
131    pub muted: Color,
132}
133
134impl Default for PaletteTheme {
135    fn default() -> Self {
136        Self {
137            background: Color::Black,
138            foreground: Color::White,
139            primary: Color::Blue,
140            secondary: Color::Cyan,
141            accent: Color::Magenta,
142            error: Color::Red,
143            warning: Color::Yellow,
144            success: Color::Green,
145            muted: Color::Gray,
146        }
147    }
148}
149
150/// Widget-specific theme styles.
151#[derive(Debug, Clone, PartialEq, Eq)]
152#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
153pub struct WidgetTheme {
154    /// Block/border style
155    pub block: Style,
156    /// Selected item style
157    pub selected: Style,
158    /// Highlighted item style
159    pub highlighted: Style,
160    /// Active/focused style
161    pub active: Style,
162    /// Inactive style
163    pub inactive: Style,
164}
165
166impl Default for WidgetTheme {
167    fn default() -> Self {
168        Self {
169            block: Style::new().fg(Color::White),
170            selected: Style::new().fg(Color::Black).bg(Color::White),
171            highlighted: Style::new().fg(Color::Yellow),
172            active: Style::new().fg(Color::Green),
173            inactive: Style::new().fg(Color::Gray),
174        }
175    }
176}
177
178#[cfg(test)]
179mod tests {
180    use super::*;
181
182    #[test]
183    fn test_theme_creation() {
184        let theme = Theme::dark();
185        assert_eq!(theme.name, "dark");
186        assert_eq!(theme.palette.background, Color::Black);
187    }
188
189    #[test]
190    fn test_light_theme() {
191        let theme = Theme::light();
192        assert_eq!(theme.palette.background, Color::White);
193    }
194
195    #[cfg(feature = "serde")]
196    #[test]
197    fn test_theme_serialization() {
198        let theme = Theme::dark();
199        let json = serde_json::to_string(&theme).unwrap();
200        let deserialized: Theme = serde_json::from_str(&json).unwrap();
201        assert_eq!(theme, deserialized);
202    }
203}