1#[cfg(feature = "alloc")]
6use alloc::collections::BTreeMap;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
10pub enum ThemePreset {
11 Light,
13 #[default]
15 Dark,
16 SolarizedDark,
18 SolarizedLight,
20 Nord,
22 Dracula,
24 Custom,
26}
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub struct ThemeColor(pub u32);
31
32impl ThemeColor {
33 pub const fn from_argb(a: u8, r: u8, g: u8, b: u8) -> Self {
35 Self(((a as u32) << 24) | ((r as u32) << 16) | ((g as u32) << 8) | (b as u32))
36 }
37
38 pub const fn from_rgb(r: u8, g: u8, b: u8) -> Self {
40 Self::from_argb(0xFF, r, g, b)
41 }
42
43 pub const fn alpha(self) -> u8 {
45 (self.0 >> 24) as u8
46 }
47
48 pub const fn red(self) -> u8 {
50 (self.0 >> 16) as u8
51 }
52
53 pub const fn green(self) -> u8 {
55 (self.0 >> 8) as u8
56 }
57
58 pub const fn blue(self) -> u8 {
60 self.0 as u8
61 }
62
63 pub fn blend(self, other: Self, alpha_256: u32) -> Self {
66 let inv = 256 - alpha_256;
67 let r = ((self.red() as u32 * inv) + (other.red() as u32 * alpha_256)) >> 8;
68 let g = ((self.green() as u32 * inv) + (other.green() as u32 * alpha_256)) >> 8;
69 let b = ((self.blue() as u32 * inv) + (other.blue() as u32 * alpha_256)) >> 8;
70 Self::from_rgb(r as u8, g as u8, b as u8)
71 }
72
73 pub fn darken(self, percent: u32) -> Self {
75 let factor = 100u32.saturating_sub(percent);
76 let r = (self.red() as u32 * factor) / 100;
77 let g = (self.green() as u32 * factor) / 100;
78 let b = (self.blue() as u32 * factor) / 100;
79 Self::from_argb(self.alpha(), r as u8, g as u8, b as u8)
80 }
81
82 pub fn lighten(self, percent: u32) -> Self {
84 let factor = percent;
85 let r = self.red() as u32 + ((255 - self.red() as u32) * factor) / 100;
86 let g = self.green() as u32 + ((255 - self.green() as u32) * factor) / 100;
87 let b = self.blue() as u32 + ((255 - self.blue() as u32) * factor) / 100;
88 Self::from_argb(
89 self.alpha(),
90 r.min(255) as u8,
91 g.min(255) as u8,
92 b.min(255) as u8,
93 )
94 }
95}
96
97#[derive(Debug, Clone, Copy, PartialEq, Eq)]
99pub struct ThemeColors {
100 pub window_background: ThemeColor,
102 pub window_foreground: ThemeColor,
103 pub window_border: ThemeColor,
104 pub window_border_focused: ThemeColor,
105
106 pub titlebar_background: ThemeColor,
108 pub titlebar_foreground: ThemeColor,
109 pub titlebar_background_inactive: ThemeColor,
110 pub titlebar_foreground_inactive: ThemeColor,
111
112 pub button_background: ThemeColor,
114 pub button_foreground: ThemeColor,
115 pub button_hover: ThemeColor,
116 pub button_pressed: ThemeColor,
117
118 pub accent: ThemeColor,
120 pub selection_background: ThemeColor,
121 pub selection_foreground: ThemeColor,
122
123 pub desktop_background: ThemeColor,
125 pub panel_background: ThemeColor,
126 pub panel_foreground: ThemeColor,
127
128 pub text_primary: ThemeColor,
130 pub text_secondary: ThemeColor,
131 pub text_disabled: ThemeColor,
132
133 pub error: ThemeColor,
135 pub warning: ThemeColor,
136 pub success: ThemeColor,
137 pub info: ThemeColor,
138
139 pub scrollbar_track: ThemeColor,
141 pub scrollbar_thumb: ThemeColor,
142
143 pub tooltip_background: ThemeColor,
145 pub tooltip_foreground: ThemeColor,
146}
147
148impl ThemeColors {
149 pub const fn dark() -> Self {
151 Self {
152 window_background: ThemeColor::from_rgb(0x2D, 0x2D, 0x2D),
153 window_foreground: ThemeColor::from_rgb(0xE0, 0xE0, 0xE0),
154 window_border: ThemeColor::from_rgb(0x44, 0x44, 0x44),
155 window_border_focused: ThemeColor::from_rgb(0x5A, 0x9F, 0xD4),
156 titlebar_background: ThemeColor::from_rgb(0x38, 0x38, 0x38),
157 titlebar_foreground: ThemeColor::from_rgb(0xE0, 0xE0, 0xE0),
158 titlebar_background_inactive: ThemeColor::from_rgb(0x30, 0x30, 0x30),
159 titlebar_foreground_inactive: ThemeColor::from_rgb(0x80, 0x80, 0x80),
160 button_background: ThemeColor::from_rgb(0x45, 0x45, 0x45),
161 button_foreground: ThemeColor::from_rgb(0xE0, 0xE0, 0xE0),
162 button_hover: ThemeColor::from_rgb(0x55, 0x55, 0x55),
163 button_pressed: ThemeColor::from_rgb(0x35, 0x35, 0x35),
164 accent: ThemeColor::from_rgb(0x5A, 0x9F, 0xD4),
165 selection_background: ThemeColor::from_rgb(0x26, 0x4F, 0x78),
166 selection_foreground: ThemeColor::from_rgb(0xFF, 0xFF, 0xFF),
167 desktop_background: ThemeColor::from_rgb(0x1A, 0x1A, 0x2E),
168 panel_background: ThemeColor::from_rgb(0x20, 0x20, 0x20),
169 panel_foreground: ThemeColor::from_rgb(0xD0, 0xD0, 0xD0),
170 text_primary: ThemeColor::from_rgb(0xE0, 0xE0, 0xE0),
171 text_secondary: ThemeColor::from_rgb(0xA0, 0xA0, 0xA0),
172 text_disabled: ThemeColor::from_rgb(0x60, 0x60, 0x60),
173 error: ThemeColor::from_rgb(0xE0, 0x50, 0x50),
174 warning: ThemeColor::from_rgb(0xE0, 0xA0, 0x30),
175 success: ThemeColor::from_rgb(0x50, 0xC8, 0x78),
176 info: ThemeColor::from_rgb(0x5A, 0x9F, 0xD4),
177 scrollbar_track: ThemeColor::from_rgb(0x30, 0x30, 0x30),
178 scrollbar_thumb: ThemeColor::from_rgb(0x55, 0x55, 0x55),
179 tooltip_background: ThemeColor::from_rgb(0x40, 0x40, 0x40),
180 tooltip_foreground: ThemeColor::from_rgb(0xE0, 0xE0, 0xE0),
181 }
182 }
183
184 pub const fn light() -> Self {
186 Self {
187 window_background: ThemeColor::from_rgb(0xF5, 0xF5, 0xF5),
188 window_foreground: ThemeColor::from_rgb(0x20, 0x20, 0x20),
189 window_border: ThemeColor::from_rgb(0xCC, 0xCC, 0xCC),
190 window_border_focused: ThemeColor::from_rgb(0x33, 0x7A, 0xB7),
191 titlebar_background: ThemeColor::from_rgb(0xE8, 0xE8, 0xE8),
192 titlebar_foreground: ThemeColor::from_rgb(0x20, 0x20, 0x20),
193 titlebar_background_inactive: ThemeColor::from_rgb(0xF0, 0xF0, 0xF0),
194 titlebar_foreground_inactive: ThemeColor::from_rgb(0x80, 0x80, 0x80),
195 button_background: ThemeColor::from_rgb(0xE0, 0xE0, 0xE0),
196 button_foreground: ThemeColor::from_rgb(0x20, 0x20, 0x20),
197 button_hover: ThemeColor::from_rgb(0xD0, 0xD0, 0xD0),
198 button_pressed: ThemeColor::from_rgb(0xC0, 0xC0, 0xC0),
199 accent: ThemeColor::from_rgb(0x33, 0x7A, 0xB7),
200 selection_background: ThemeColor::from_rgb(0xB3, 0xD4, 0xFC),
201 selection_foreground: ThemeColor::from_rgb(0x00, 0x00, 0x00),
202 desktop_background: ThemeColor::from_rgb(0xDE, 0xDE, 0xE8),
203 panel_background: ThemeColor::from_rgb(0xF0, 0xF0, 0xF0),
204 panel_foreground: ThemeColor::from_rgb(0x30, 0x30, 0x30),
205 text_primary: ThemeColor::from_rgb(0x20, 0x20, 0x20),
206 text_secondary: ThemeColor::from_rgb(0x60, 0x60, 0x60),
207 text_disabled: ThemeColor::from_rgb(0xA0, 0xA0, 0xA0),
208 error: ThemeColor::from_rgb(0xD3, 0x2F, 0x2F),
209 warning: ThemeColor::from_rgb(0xF5, 0x7C, 0x00),
210 success: ThemeColor::from_rgb(0x38, 0x8E, 0x3C),
211 info: ThemeColor::from_rgb(0x19, 0x76, 0xD2),
212 scrollbar_track: ThemeColor::from_rgb(0xE8, 0xE8, 0xE8),
213 scrollbar_thumb: ThemeColor::from_rgb(0xB0, 0xB0, 0xB0),
214 tooltip_background: ThemeColor::from_rgb(0x30, 0x30, 0x30),
215 tooltip_foreground: ThemeColor::from_rgb(0xF0, 0xF0, 0xF0),
216 }
217 }
218
219 pub const fn solarized_dark() -> Self {
221 Self {
222 window_background: ThemeColor::from_rgb(0x00, 0x2B, 0x36),
223 window_foreground: ThemeColor::from_rgb(0x83, 0x94, 0x96),
224 window_border: ThemeColor::from_rgb(0x07, 0x36, 0x42),
225 window_border_focused: ThemeColor::from_rgb(0x26, 0x8B, 0xD2),
226 titlebar_background: ThemeColor::from_rgb(0x07, 0x36, 0x42),
227 titlebar_foreground: ThemeColor::from_rgb(0x93, 0xA1, 0xA1),
228 titlebar_background_inactive: ThemeColor::from_rgb(0x00, 0x2B, 0x36),
229 titlebar_foreground_inactive: ThemeColor::from_rgb(0x58, 0x6E, 0x75),
230 button_background: ThemeColor::from_rgb(0x07, 0x36, 0x42),
231 button_foreground: ThemeColor::from_rgb(0x93, 0xA1, 0xA1),
232 button_hover: ThemeColor::from_rgb(0x0A, 0x43, 0x50),
233 button_pressed: ThemeColor::from_rgb(0x05, 0x2A, 0x33),
234 accent: ThemeColor::from_rgb(0x26, 0x8B, 0xD2),
235 selection_background: ThemeColor::from_rgb(0x07, 0x36, 0x42),
236 selection_foreground: ThemeColor::from_rgb(0xFD, 0xF6, 0xE3),
237 desktop_background: ThemeColor::from_rgb(0x00, 0x2B, 0x36),
238 panel_background: ThemeColor::from_rgb(0x07, 0x36, 0x42),
239 panel_foreground: ThemeColor::from_rgb(0x83, 0x94, 0x96),
240 text_primary: ThemeColor::from_rgb(0x83, 0x94, 0x96),
241 text_secondary: ThemeColor::from_rgb(0x58, 0x6E, 0x75),
242 text_disabled: ThemeColor::from_rgb(0x3B, 0x51, 0x50),
243 error: ThemeColor::from_rgb(0xDC, 0x32, 0x2F),
244 warning: ThemeColor::from_rgb(0xCB, 0x4B, 0x16),
245 success: ThemeColor::from_rgb(0x85, 0x99, 0x00),
246 info: ThemeColor::from_rgb(0x26, 0x8B, 0xD2),
247 scrollbar_track: ThemeColor::from_rgb(0x00, 0x2B, 0x36),
248 scrollbar_thumb: ThemeColor::from_rgb(0x07, 0x36, 0x42),
249 tooltip_background: ThemeColor::from_rgb(0x07, 0x36, 0x42),
250 tooltip_foreground: ThemeColor::from_rgb(0xFD, 0xF6, 0xE3),
251 }
252 }
253
254 pub const fn solarized_light() -> Self {
256 Self {
257 window_background: ThemeColor::from_rgb(0xFD, 0xF6, 0xE3),
258 window_foreground: ThemeColor::from_rgb(0x65, 0x7B, 0x83),
259 window_border: ThemeColor::from_rgb(0xEE, 0xE8, 0xD5),
260 window_border_focused: ThemeColor::from_rgb(0x26, 0x8B, 0xD2),
261 titlebar_background: ThemeColor::from_rgb(0xEE, 0xE8, 0xD5),
262 titlebar_foreground: ThemeColor::from_rgb(0x58, 0x6E, 0x75),
263 titlebar_background_inactive: ThemeColor::from_rgb(0xFD, 0xF6, 0xE3),
264 titlebar_foreground_inactive: ThemeColor::from_rgb(0x93, 0xA1, 0xA1),
265 button_background: ThemeColor::from_rgb(0xEE, 0xE8, 0xD5),
266 button_foreground: ThemeColor::from_rgb(0x58, 0x6E, 0x75),
267 button_hover: ThemeColor::from_rgb(0xE0, 0xDA, 0xC7),
268 button_pressed: ThemeColor::from_rgb(0xD3, 0xCD, 0xBB),
269 accent: ThemeColor::from_rgb(0x26, 0x8B, 0xD2),
270 selection_background: ThemeColor::from_rgb(0xEE, 0xE8, 0xD5),
271 selection_foreground: ThemeColor::from_rgb(0x00, 0x2B, 0x36),
272 desktop_background: ThemeColor::from_rgb(0xFD, 0xF6, 0xE3),
273 panel_background: ThemeColor::from_rgb(0xEE, 0xE8, 0xD5),
274 panel_foreground: ThemeColor::from_rgb(0x65, 0x7B, 0x83),
275 text_primary: ThemeColor::from_rgb(0x65, 0x7B, 0x83),
276 text_secondary: ThemeColor::from_rgb(0x93, 0xA1, 0xA1),
277 text_disabled: ThemeColor::from_rgb(0xC0, 0xBB, 0xAA),
278 error: ThemeColor::from_rgb(0xDC, 0x32, 0x2F),
279 warning: ThemeColor::from_rgb(0xCB, 0x4B, 0x16),
280 success: ThemeColor::from_rgb(0x85, 0x99, 0x00),
281 info: ThemeColor::from_rgb(0x26, 0x8B, 0xD2),
282 scrollbar_track: ThemeColor::from_rgb(0xFD, 0xF6, 0xE3),
283 scrollbar_thumb: ThemeColor::from_rgb(0xEE, 0xE8, 0xD5),
284 tooltip_background: ThemeColor::from_rgb(0x07, 0x36, 0x42),
285 tooltip_foreground: ThemeColor::from_rgb(0xFD, 0xF6, 0xE3),
286 }
287 }
288
289 pub const fn nord() -> Self {
291 Self {
292 window_background: ThemeColor::from_rgb(0x2E, 0x34, 0x40),
293 window_foreground: ThemeColor::from_rgb(0xD8, 0xDE, 0xE9),
294 window_border: ThemeColor::from_rgb(0x3B, 0x42, 0x52),
295 window_border_focused: ThemeColor::from_rgb(0x88, 0xC0, 0xD0),
296 titlebar_background: ThemeColor::from_rgb(0x3B, 0x42, 0x52),
297 titlebar_foreground: ThemeColor::from_rgb(0xEC, 0xEF, 0xF4),
298 titlebar_background_inactive: ThemeColor::from_rgb(0x2E, 0x34, 0x40),
299 titlebar_foreground_inactive: ThemeColor::from_rgb(0x4C, 0x56, 0x6A),
300 button_background: ThemeColor::from_rgb(0x43, 0x4C, 0x5E),
301 button_foreground: ThemeColor::from_rgb(0xEC, 0xEF, 0xF4),
302 button_hover: ThemeColor::from_rgb(0x4C, 0x56, 0x6A),
303 button_pressed: ThemeColor::from_rgb(0x3B, 0x42, 0x52),
304 accent: ThemeColor::from_rgb(0x88, 0xC0, 0xD0),
305 selection_background: ThemeColor::from_rgb(0x43, 0x4C, 0x5E),
306 selection_foreground: ThemeColor::from_rgb(0xEC, 0xEF, 0xF4),
307 desktop_background: ThemeColor::from_rgb(0x2E, 0x34, 0x40),
308 panel_background: ThemeColor::from_rgb(0x3B, 0x42, 0x52),
309 panel_foreground: ThemeColor::from_rgb(0xD8, 0xDE, 0xE9),
310 text_primary: ThemeColor::from_rgb(0xD8, 0xDE, 0xE9),
311 text_secondary: ThemeColor::from_rgb(0x81, 0xA1, 0xC1),
312 text_disabled: ThemeColor::from_rgb(0x4C, 0x56, 0x6A),
313 error: ThemeColor::from_rgb(0xBF, 0x61, 0x6A),
314 warning: ThemeColor::from_rgb(0xEB, 0xCB, 0x8B),
315 success: ThemeColor::from_rgb(0xA3, 0xBE, 0x8C),
316 info: ThemeColor::from_rgb(0x88, 0xC0, 0xD0),
317 scrollbar_track: ThemeColor::from_rgb(0x2E, 0x34, 0x40),
318 scrollbar_thumb: ThemeColor::from_rgb(0x4C, 0x56, 0x6A),
319 tooltip_background: ThemeColor::from_rgb(0x3B, 0x42, 0x52),
320 tooltip_foreground: ThemeColor::from_rgb(0xEC, 0xEF, 0xF4),
321 }
322 }
323
324 pub const fn dracula() -> Self {
326 Self {
327 window_background: ThemeColor::from_rgb(0x28, 0x2A, 0x36),
328 window_foreground: ThemeColor::from_rgb(0xF8, 0xF8, 0xF2),
329 window_border: ThemeColor::from_rgb(0x44, 0x47, 0x5A),
330 window_border_focused: ThemeColor::from_rgb(0xBD, 0x93, 0xF9),
331 titlebar_background: ThemeColor::from_rgb(0x44, 0x47, 0x5A),
332 titlebar_foreground: ThemeColor::from_rgb(0xF8, 0xF8, 0xF2),
333 titlebar_background_inactive: ThemeColor::from_rgb(0x28, 0x2A, 0x36),
334 titlebar_foreground_inactive: ThemeColor::from_rgb(0x62, 0x72, 0xA4),
335 button_background: ThemeColor::from_rgb(0x44, 0x47, 0x5A),
336 button_foreground: ThemeColor::from_rgb(0xF8, 0xF8, 0xF2),
337 button_hover: ThemeColor::from_rgb(0x55, 0x58, 0x6E),
338 button_pressed: ThemeColor::from_rgb(0x38, 0x3A, 0x4A),
339 accent: ThemeColor::from_rgb(0xBD, 0x93, 0xF9),
340 selection_background: ThemeColor::from_rgb(0x44, 0x47, 0x5A),
341 selection_foreground: ThemeColor::from_rgb(0xF8, 0xF8, 0xF2),
342 desktop_background: ThemeColor::from_rgb(0x28, 0x2A, 0x36),
343 panel_background: ThemeColor::from_rgb(0x21, 0x22, 0x2C),
344 panel_foreground: ThemeColor::from_rgb(0xF8, 0xF8, 0xF2),
345 text_primary: ThemeColor::from_rgb(0xF8, 0xF8, 0xF2),
346 text_secondary: ThemeColor::from_rgb(0x62, 0x72, 0xA4),
347 text_disabled: ThemeColor::from_rgb(0x44, 0x47, 0x5A),
348 error: ThemeColor::from_rgb(0xFF, 0x55, 0x55),
349 warning: ThemeColor::from_rgb(0xFF, 0xB8, 0x6C),
350 success: ThemeColor::from_rgb(0x50, 0xFA, 0x7B),
351 info: ThemeColor::from_rgb(0x8B, 0xE9, 0xFD),
352 scrollbar_track: ThemeColor::from_rgb(0x28, 0x2A, 0x36),
353 scrollbar_thumb: ThemeColor::from_rgb(0x44, 0x47, 0x5A),
354 tooltip_background: ThemeColor::from_rgb(0x28, 0x2A, 0x36),
355 tooltip_foreground: ThemeColor::from_rgb(0xF8, 0xF8, 0xF2),
356 }
357 }
358}
359
360#[derive(Debug, Clone, Copy, PartialEq, Eq)]
362pub enum StyleProperty {
363 BackgroundColor,
364 ForegroundColor,
365 BorderColor,
366 BorderWidth,
367 BorderRadius,
368 FontSize,
369 FontWeight,
370 Padding,
371 Margin,
372 Opacity,
373}
374
375#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
377pub enum IconTheme {
378 #[default]
379 Adwaita,
380 Breeze,
381 Papirus,
382 Custom,
383}
384
385#[derive(Debug)]
387pub struct ThemeManager {
388 current_preset: ThemePreset,
390 colors: ThemeColors,
392 icon_theme: IconTheme,
394 animate_transitions: bool,
396 #[cfg(feature = "alloc")]
398 custom_overrides: BTreeMap<u8, ThemeColor>,
399}
400
401impl Default for ThemeManager {
402 fn default() -> Self {
403 Self::new()
404 }
405}
406
407impl ThemeManager {
408 pub fn new() -> Self {
410 Self {
411 current_preset: ThemePreset::Dark,
412 colors: ThemeColors::dark(),
413 icon_theme: IconTheme::Adwaita,
414 animate_transitions: true,
415 #[cfg(feature = "alloc")]
416 custom_overrides: BTreeMap::new(),
417 }
418 }
419
420 pub fn set_theme(&mut self, preset: ThemePreset) {
422 self.current_preset = preset;
423 self.colors = match preset {
424 ThemePreset::Light => ThemeColors::light(),
425 ThemePreset::Dark => ThemeColors::dark(),
426 ThemePreset::SolarizedDark => ThemeColors::solarized_dark(),
427 ThemePreset::SolarizedLight => ThemeColors::solarized_light(),
428 ThemePreset::Nord => ThemeColors::nord(),
429 ThemePreset::Dracula => ThemeColors::dracula(),
430 ThemePreset::Custom => self.colors, };
432 }
433
434 pub fn colors(&self) -> &ThemeColors {
436 &self.colors
437 }
438
439 pub fn current_preset(&self) -> ThemePreset {
441 self.current_preset
442 }
443
444 pub fn set_colors(&mut self, colors: ThemeColors) {
446 self.current_preset = ThemePreset::Custom;
447 self.colors = colors;
448 }
449
450 pub fn set_icon_theme(&mut self, theme: IconTheme) {
452 self.icon_theme = theme;
453 }
454
455 pub fn icon_theme(&self) -> IconTheme {
457 self.icon_theme
458 }
459
460 pub fn set_animate_transitions(&mut self, animate: bool) {
462 self.animate_transitions = animate;
463 }
464
465 pub fn animate_transitions(&self) -> bool {
467 self.animate_transitions
468 }
469
470 pub fn map_style_property(&self, property: StyleProperty) -> u32 {
473 match property {
474 StyleProperty::BackgroundColor => self.colors.window_background.0,
475 StyleProperty::ForegroundColor => self.colors.window_foreground.0,
476 StyleProperty::BorderColor => self.colors.window_border.0,
477 StyleProperty::BorderWidth => 1,
478 StyleProperty::BorderRadius => 4,
479 StyleProperty::FontSize => 14,
480 StyleProperty::FontWeight => 400,
481 StyleProperty::Padding => 8,
482 StyleProperty::Margin => 4,
483 StyleProperty::Opacity => 255,
484 }
485 }
486
487 pub fn gtk_theme_name(&self) -> &'static str {
490 match self.current_preset {
491 ThemePreset::Light => "Adwaita",
492 ThemePreset::Dark => "Adwaita-dark",
493 ThemePreset::SolarizedDark | ThemePreset::SolarizedLight => "Solarized",
494 ThemePreset::Nord => "Nordic",
495 ThemePreset::Dracula => "Dracula",
496 ThemePreset::Custom => "Custom",
497 }
498 }
499
500 pub fn qt_style_hint(&self) -> u32 {
502 match self.current_preset {
503 ThemePreset::Light | ThemePreset::SolarizedLight => 0, _ => 1, }
506 }
507}