Skip to main content

void/app/
theme.rs

1use crate::canvas_timer::SceneStyle;
2use crate::model::ThemeVariant;
3use ratatui::style::Color;
4
5pub struct Theme {
6    pub bg: Color,
7    pub text: Color,
8    pub dim: Color,
9    pub accent: Color,
10    pub on_accent: Color,
11    pub success: Color,
12    pub warning: Color,
13    pub error: Color,
14    pub info: Color,
15    pub progress_dim: Color,
16    pub task_track: Color,
17    pub panel: Color,
18    pub panel_border: Color,
19    pub select_bg: Color,
20    pub select_fg: Color,
21    pub active_bg: Color,
22    pub active_fg: Color,
23}
24
25impl Theme {
26    pub fn dark() -> Self {
27        Self {
28            bg: Color::Rgb(15, 15, 20),
29            text: Color::Rgb(225, 225, 230),
30            dim: Color::Rgb(90, 90, 100),
31            accent: Color::Rgb(100, 180, 255),
32            on_accent: Color::Rgb(10, 10, 15),
33            success: Color::Rgb(80, 210, 130),
34            warning: Color::Rgb(245, 185, 70),
35            error: Color::Rgb(245, 85, 85),
36            info: Color::Rgb(170, 140, 250),
37            progress_dim: Color::Rgb(45, 45, 55),
38            task_track: Color::Rgb(35, 35, 42),
39            panel: Color::Rgb(20, 22, 30),
40            panel_border: Color::Rgb(55, 60, 75),
41            select_bg: Color::Rgb(38, 52, 78),
42            select_fg: Color::Rgb(230, 235, 245),
43            active_bg: Color::Rgb(32, 48, 72),
44            active_fg: Color::Rgb(170, 210, 255),
45        }
46    }
47
48    pub fn light() -> Self {
49        Self {
50            bg: Color::Rgb(250, 250, 252),
51            text: Color::Rgb(30, 30, 35),
52            dim: Color::Rgb(140, 140, 150),
53            accent: Color::Rgb(25, 110, 200),
54            on_accent: Color::Rgb(255, 255, 255),
55            success: Color::Rgb(30, 150, 80),
56            warning: Color::Rgb(200, 130, 20),
57            error: Color::Rgb(200, 50, 50),
58            info: Color::Rgb(110, 70, 190),
59            progress_dim: Color::Rgb(200, 205, 215),
60            task_track: Color::Rgb(220, 225, 232),
61            panel: Color::Rgb(242, 245, 250),
62            panel_border: Color::Rgb(190, 198, 210),
63            select_bg: Color::Rgb(210, 225, 245),
64            select_fg: Color::Rgb(20, 40, 70),
65            active_bg: Color::Rgb(195, 218, 245),
66            active_fg: Color::Rgb(15, 60, 120),
67        }
68    }
69
70    pub fn polaris() -> Self {
71        Self {
72            bg: Color::Rgb(10, 14, 30),
73            text: Color::Rgb(215, 225, 250),
74            dim: Color::Rgb(100, 120, 160),
75            accent: Color::Rgb(90, 200, 255),
76            on_accent: Color::Rgb(10, 14, 30),
77            success: Color::Rgb(80, 235, 180),
78            warning: Color::Rgb(255, 160, 75),
79            error: Color::Rgb(255, 95, 120),
80            info: Color::Rgb(180, 135, 255),
81            progress_dim: Color::Rgb(40, 50, 80),
82            task_track: Color::Rgb(25, 35, 60),
83            panel: Color::Rgb(14, 20, 38),
84            panel_border: Color::Rgb(55, 70, 110),
85            select_bg: Color::Rgb(28, 42, 78),
86            select_fg: Color::Rgb(210, 225, 255),
87            active_bg: Color::Rgb(22, 38, 68),
88            active_fg: Color::Rgb(140, 210, 255),
89        }
90    }
91
92    pub fn matrix() -> Self {
93        Self {
94            bg: Color::Rgb(3, 12, 5),
95            text: Color::Rgb(150, 240, 140),
96            dim: Color::Rgb(60, 110, 65),
97            accent: Color::Rgb(80, 230, 90),
98            on_accent: Color::Rgb(0, 0, 0),
99            success: Color::Rgb(70, 220, 110),
100            warning: Color::Rgb(220, 200, 60),
101            error: Color::Rgb(255, 80, 90),
102            info: Color::Rgb(100, 190, 240),
103            progress_dim: Color::Rgb(15, 40, 18),
104            task_track: Color::Rgb(8, 28, 10),
105            panel: Color::Rgb(5, 16, 7),
106            panel_border: Color::Rgb(35, 85, 40),
107            select_bg: Color::Rgb(10, 32, 14),
108            select_fg: Color::Rgb(160, 255, 165),
109            active_bg: Color::Rgb(14, 42, 18),
110            active_fg: Color::Rgb(110, 255, 120),
111        }
112    }
113
114    pub fn from_variant(variant: ThemeVariant) -> Self {
115        match variant {
116            ThemeVariant::Dark => Theme::dark(),
117            ThemeVariant::Light => Theme::light(),
118            ThemeVariant::Polaris => Theme::polaris(),
119            ThemeVariant::Matrix => Theme::matrix(),
120        }
121    }
122
123    /// Colors for the dashboard / zen timer canvas, derived from core theme tokens.
124    pub fn scene_style(&self, mode: Color) -> SceneStyle {
125        SceneStyle {
126            mode,
127            track: self.progress_dim,
128            task: self.success,
129            task_dim: self.task_track,
130            bg: self.bg,
131            bg_mid: mix(self.bg, self.panel, 160),
132            bg_light: self.panel,
133            wave: self.accent,
134            core: mode,
135            glow: self.accent,
136            particle: self.dim,
137            text: self.text,
138            session_on: self.accent,
139            session_off: self.dim,
140        }
141    }
142}
143
144fn mix(a: Color, b: Color, t: u8) -> Color {
145    let (ar, ag, ab) = rgb(a);
146    let (br, bg, bb) = rgb(b);
147    let t = t as u16;
148    let inv = 255 - t;
149    Color::Rgb(
150        ((ar as u16 * inv + br as u16 * t) / 255) as u8,
151        ((ag as u16 * inv + bg as u16 * t) / 255) as u8,
152        ((ab as u16 * inv + bb as u16 * t) / 255) as u8,
153    )
154}
155
156fn rgb(c: Color) -> (u8, u8, u8) {
157    match c {
158        Color::Rgb(r, g, b) => (r, g, b),
159        Color::Black => (0, 0, 0),
160        Color::White => (255, 255, 255),
161        _ => (128, 128, 128),
162    }
163}