1use crate::overlay::OverlayManager;
11use crate::themes::{
12 mclaren_650gts_gt3, reverie, shade_sanctuary, tokyo_night, tokyo_night_light, wasp, wasp_light,
13};
14use crate::utils::*;
15use crate::visuals::*;
16use egui::{Color32, Context, Frame, Id, Margin, Style, Vec2, vec2};
17
18#[cfg(feature = "elegance")]
19use elegance::{Palette, Theme as EleganceTheme, Typography as EleganceTypography};
20
21#[cfg(feature = "elegance")]
23pub fn elegance_theme_key() -> Id {
24 Id::new("elegance::theme")
25}
26
27#[cfg(feature = "elegance")]
29#[derive(Clone, Copy, PartialEq, Eq)]
30pub struct EleganceThemeKey {
31 dark: bool,
32 bg: Color32,
33 widget_bg: Color32,
34 border: Color32,
35 text: Color32,
36 text_muted: Color32,
37 accent: Color32,
38 info: Color32,
39 success: Color32,
40 error: Color32,
41 warning: Color32,
42}
43
44#[cfg(feature = "elegance")]
45impl EleganceThemeKey {
46 fn from_theme(theme: &Theme) -> Self {
47 let c = &theme.colors;
48 Self {
49 dark: theme.dark,
50 bg: c.bg,
51 widget_bg: c.widget_bg,
52 border: c.border,
53 text: c.text,
54 text_muted: c.text_muted,
55 accent: c.accent,
56 info: c.info,
57 success: c.success,
58 error: c.error,
59 warning: c.warning,
60 }
61 }
62}
63
64#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
66#[derive(Debug, Clone, PartialEq, Eq)]
67pub enum ThemeKind {
68 TokyoNight,
70 TokyoNightLight,
72 McLaren650Gts,
74 Reverie,
76 ShadeSanctuary,
78 Wasp,
80 WaspLight,
82}
83
84impl ThemeKind {
85 pub fn to_str(&self) -> &str {
87 match self {
88 ThemeKind::TokyoNight => "Tokyo Night",
89 ThemeKind::TokyoNightLight => "Tokyo Night Light",
90 ThemeKind::McLaren650Gts => "McLaren 650GTS",
91 ThemeKind::Reverie => "Reverie",
92 ThemeKind::ShadeSanctuary => "Shade Sanctuary",
93 ThemeKind::Wasp => "Wasp",
94 ThemeKind::WaspLight => "Wasp Light",
95 }
96 }
97
98 pub fn to_vec() -> Vec<Self> {
100 vec![
101 Self::TokyoNight,
102 Self::TokyoNightLight,
103 Self::McLaren650Gts,
104 Self::Reverie,
105 Self::ShadeSanctuary,
106 Self::Wasp,
107 Self::WaspLight,
108 ]
109 }
110}
111
112#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
126#[derive(Clone)]
127pub struct Theme {
128 pub dark: bool,
130 #[cfg_attr(feature = "serde", serde(skip))]
132 pub overlay_manager: OverlayManager,
133
134 pub image_tint_recommended: bool,
139
140 pub kind: ThemeKind,
142 pub colors: ThemeColors,
144 pub visuals: ThemeVisuals,
146 pub typography: Typography,
148
149 #[cfg_attr(feature = "serde", serde(default))]
155 pub spacing: ThemeSpacing,
156
157 pub window_frame: Frame,
159 pub frame1: Frame,
161 pub frame2: Frame,
163
164 pub corner_radius: u8,
166
167 pub inner_margin: i8,
169
170 pub outer_margin: i8,
172
173 pub button_padding: Vec2,
177
178 #[cfg(feature = "elegance")]
179 #[cfg_attr(feature = "serde", serde(skip))]
180 pub elegance_key: Option<EleganceThemeKey>,
182}
183
184impl PartialEq for Theme {
185 fn eq(&self, other: &Self) -> bool {
186 self.dark == other.dark
187 && self.kind == other.kind
188 && self.colors == other.colors
189 && self.typography == other.typography
190 && self.spacing == other.spacing
191 && self.window_frame == other.window_frame
192 && self.frame1 == other.frame1
193 && self.frame2 == other.frame2
194 && self.visuals == other.visuals
195 }
196}
197
198impl Eq for Theme {}
199
200impl Theme {
201 pub fn new(kind: ThemeKind) -> Self {
203 let theme = match kind {
204 ThemeKind::TokyoNight => tokyo_night::theme(),
205 ThemeKind::TokyoNightLight => tokyo_night_light::theme(),
206 ThemeKind::McLaren650Gts => mclaren_650gts_gt3::theme(),
207 ThemeKind::Reverie => reverie::theme(),
208 ThemeKind::ShadeSanctuary => shade_sanctuary::theme(),
209 ThemeKind::Wasp => wasp::theme(),
210 ThemeKind::WaspLight => wasp_light::theme(),
211 };
212
213 theme
214 }
215
216 pub fn style(&self) -> Style {
218 match self.kind {
219 ThemeKind::TokyoNight => tokyo_night::style(),
220 ThemeKind::TokyoNightLight => tokyo_night_light::style(),
221 ThemeKind::McLaren650Gts => mclaren_650gts_gt3::style(),
222 ThemeKind::Reverie => reverie::style(),
223 ThemeKind::ShadeSanctuary => shade_sanctuary::style(),
224 ThemeKind::Wasp => wasp::style(),
225 ThemeKind::WaspLight => wasp_light::style(),
226 }
227 }
228
229 pub fn frame1_visuals(&self) -> FrameVisuals {
231 self.visuals.frame1_visuals
232 }
233
234 pub fn frame2_visuals(&self) -> FrameVisuals {
236 self.visuals.frame2_visuals
237 }
238
239 pub fn button_visuals(&self) -> ButtonVisuals {
241 self.visuals.button_visuals
242 }
243
244 pub fn label_visuals(&self) -> LabelVisuals {
246 self.visuals.label_visuals
247 }
248
249 pub fn combo_box_visuals(&self) -> ComboBoxVisuals {
251 self.visuals.combo_box_visuals
252 }
253
254 pub fn text_edit_visuals(&self) -> TextEditVisuals {
256 self.visuals.text_edit_visuals
257 }
258
259 fn storage_id() -> Id {
260 Id::new("elements::theme")
261 }
262
263 fn button_visuals_id() -> Id {
264 Id::new("elements::button_visuals")
265 }
266
267 fn label_visuals_id() -> Id {
268 Id::new("elements::label_visuals")
269 }
270
271 fn combo_box_visuals_id() -> Id {
272 Id::new("elements::combo_box_visuals")
273 }
274
275 fn text_edit_visuals_id() -> Id {
276 Id::new("elements::text_edit_visuals")
277 }
278
279 pub fn button_visuals_from_ctx(ctx: &Context) -> Option<ButtonVisuals> {
283 ctx.data(|d| d.get_temp(Self::button_visuals_id()))
284 }
285
286 pub fn label_visuals_from_ctx(ctx: &Context) -> Option<LabelVisuals> {
288 ctx.data(|d| d.get_temp(Self::label_visuals_id()))
289 }
290
291 pub fn combo_box_visuals_from_ctx(ctx: &Context) -> Option<ComboBoxVisuals> {
293 ctx.data(|d| d.get_temp(Self::combo_box_visuals_id()))
294 }
295
296 pub fn text_edit_visuals_from_ctx(ctx: &Context) -> Option<TextEditVisuals> {
298 ctx.data(|d| d.get_temp(Self::text_edit_visuals_id()))
299 }
300
301 pub fn install(&mut self, ctx: &Context) {
305 #[cfg(feature = "elegance")]
306 self.install_elegance_theme(ctx);
307
308 let button_visuals = self.button_visuals();
309 let label_visuals = self.label_visuals();
310 let combo_box_visuals = self.combo_box_visuals();
311 let text_edit_visuals = self.text_edit_visuals();
312 let mut style = self.style();
313 self.apply_spacing(&mut style);
314 let theme = self.clone();
315
316 ctx.set_global_style(style);
317 ctx.data_mut(|d| d.insert_temp(Self::storage_id(), theme));
318
319 ctx.data_mut(|d| d.insert_temp(Self::button_visuals_id(), button_visuals));
320 ctx.data_mut(|d| d.insert_temp(Self::label_visuals_id(), label_visuals));
321 ctx.data_mut(|d| d.insert_temp(Self::combo_box_visuals_id(), combo_box_visuals));
322 ctx.data_mut(|d| d.insert_temp(Self::text_edit_visuals_id(), text_edit_visuals));
323 }
324
325 #[cfg(feature = "elegance")]
326 pub fn install_elegance_theme(&mut self, ctx: &Context) {
330 let key = EleganceThemeKey::from_theme(&self);
331 if self.elegance_key == Some(key) {
332 return;
333 }
334
335 let c = &self.colors;
336 let mut pal = if key.dark {
337 Palette::charcoal()
338 } else {
339 Palette::frost()
340 };
341
342 pal.is_dark = key.dark;
343 pal.bg = c.bg;
344 pal.card = c.widget_bg;
345 pal.input_bg = c.widget_bg;
346 pal.border = c.border;
347 pal.text = c.text;
348 pal.text_muted = c.text_muted;
349 pal.text_faint = c.text_muted;
350 pal.focus = c.accent;
351 pal.blue = c.info;
352 pal.green = c.success;
353 pal.green_hover = c.success;
354 pal.red = c.error;
355 pal.red_hover = c.error;
356 pal.amber = c.warning;
357 pal.amber_hover = c.warning;
358 pal.purple = c.accent;
359 pal.purple_hover = c.accent;
360 pal.success = c.success;
361 pal.danger = c.error;
362 pal.warning = c.warning;
363
364 let elegance_typography = EleganceTypography {
365 body: self.typography.normal,
366 button: self.typography.normal,
367 label: self.typography.normal,
368 small: self.typography.small,
369 heading: self.typography.heading,
370 monospace: self.typography.small,
371 };
372
373 let elegance_theme = EleganceTheme {
374 palette: pal,
375 control_radius: self.corner_radius as f32,
376 card_radius: self.corner_radius as f32,
377 card_padding: self.frame1.inner_margin.top as f32,
378 control_padding_y: self.button_padding.y as f32,
379 control_padding_x: self.button_padding.x as f32,
380 typography: elegance_typography,
381 };
382
383 ctx.data_mut(|d| d.insert_temp(elegance_theme_key(), elegance_theme));
384 self.elegance_key = Some(key);
385 }
386
387 pub fn current(ctx: &Context) -> Theme {
390 ctx.data(|d| {
391 d.get_temp::<Theme>(Self::storage_id())
392 .unwrap_or_else(|| Theme::new(ThemeKind::TokyoNight))
393 })
394 }
395
396 fn apply_spacing(&self, style: &mut Style) {
402 let s = &self.spacing;
403 style.spacing.item_spacing = vec2(s.md, s.md);
404 style.spacing.button_padding = self.button_padding;
405 style.spacing.window_margin = Margin::same(self.inner_margin);
406 }
407
408 pub fn remap_derived_frames(&mut self, old: &ThemeColors) {
414 let new = self.colors;
415 if !frame_palette_changed(old, &new) {
416 return;
417 }
418
419 remap_frame(
420 &mut self.window_frame,
421 old.title_bar,
422 new.title_bar,
423 old.border,
424 new.border,
425 );
426 remap_frame(
427 &mut self.frame1,
428 old.widget_bg,
429 new.widget_bg,
430 old.border,
431 new.border,
432 );
433 remap_frame(
434 &mut self.frame2,
435 old.bg,
436 new.bg,
437 old.border,
438 new.border,
439 );
440 remap_frame_visuals(
441 &mut self.visuals.frame1_visuals,
442 old.hover,
443 new.hover,
444 old.widget_bg,
445 new.widget_bg,
446 old.highlight,
447 new.highlight,
448 );
449 remap_frame_visuals(
450 &mut self.visuals.frame2_visuals,
451 old.hover,
452 new.hover,
453 old.bg,
454 new.bg,
455 old.highlight,
456 new.highlight,
457 );
458 }
459}
460
461#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
463#[derive(Copy, Clone, Debug, PartialEq, Eq)]
464pub struct ThemeVisuals {
465 pub button_visuals: ButtonVisuals,
467 pub label_visuals: LabelVisuals,
469 pub combo_box_visuals: ComboBoxVisuals,
471 pub text_edit_visuals: TextEditVisuals,
473 pub frame1_visuals: FrameVisuals,
475 pub frame2_visuals: FrameVisuals,
477}
478
479#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
481#[derive(Copy, Clone, Debug, PartialEq, Eq)]
482pub struct ThemeColors {
483 pub title_bar: Color32,
485
486 pub bg: Color32,
488
489 pub widget_bg: Color32,
493
494 pub hover: Color32,
496
497 pub text: Color32,
499
500 pub text_muted: Color32,
504
505 pub highlight: Color32,
507
508 pub border: Color32,
510
511 pub accent: Color32,
513
514 pub error: Color32,
518
519 pub warning: Color32,
521
522 pub success: Color32,
526
527 pub info: Color32,
531}
532
533#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
545#[derive(Clone, Copy, Debug, PartialEq)]
546pub struct ThemeSpacing {
547 pub xs: f32,
549 pub sm: f32,
551 pub md: f32,
553 pub lg: f32,
555 pub xl: f32,
557}
558
559impl Default for ThemeSpacing {
560 fn default() -> Self {
561 Self {
562 xs: 4.0,
563 sm: 8.0,
564 md: 12.0,
565 lg: 16.0,
566 xl: 24.0,
567 }
568 }
569}
570
571impl ThemeSpacing {
572 pub fn margin(self, size: f32) -> Margin {
574 Margin::same(size as i8)
575 }
576
577 pub fn margin_xy(self, x: f32, y: f32) -> Margin {
579 Margin::symmetric(x as i8, y as i8)
580 }
581}
582
583#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
585#[derive(Clone, Default, Debug, PartialEq)]
586pub struct Typography {
587 pub very_small: f32,
589 pub small: f32,
591 pub normal: f32,
593 pub large: f32,
595 pub very_large: f32,
597 pub heading: f32,
599}