1use crate::themes::{
11 mclaren_650gts_gt3, reverie, shade_sanctuary, tokyo_night, tokyo_night_light, wasp, wasp_light,
12};
13use crate::utils::*;
14use crate::visuals::*;
15use egui::{Color32, Context, Frame, Id, Margin, Style, Vec2, vec2};
16
17#[cfg(feature = "elegance")]
18use elegance::{Palette, Theme as EleganceTheme, Typography as EleganceTypography};
19
20#[cfg(feature = "elegance")]
22pub fn elegance_theme_key() -> Id {
23 Id::new("elegance::theme")
24}
25
26#[cfg(feature = "elegance")]
28#[derive(Clone, Copy, PartialEq, Eq)]
29pub struct EleganceThemeKey {
30 dark: bool,
31 bg: Color32,
32 widget_bg: Color32,
33 border: Color32,
34 text: Color32,
35 text_muted: Color32,
36 accent: Color32,
37 info: Color32,
38 success: Color32,
39 error: Color32,
40 warning: Color32,
41}
42
43#[cfg(feature = "elegance")]
44impl EleganceThemeKey {
45 fn from_theme(theme: &Theme) -> Self {
46 let c = &theme.colors;
47 Self {
48 dark: theme.dark,
49 bg: c.bg,
50 widget_bg: c.widget_bg,
51 border: c.border,
52 text: c.text,
53 text_muted: c.text_muted,
54 accent: c.accent,
55 info: c.info,
56 success: c.success,
57 error: c.error,
58 warning: c.warning,
59 }
60 }
61}
62
63#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
65#[derive(Debug, Clone, PartialEq, Eq)]
66pub enum ThemeKind {
67 TokyoNight,
69 TokyoNightLight,
71 McLaren650Gts,
73 Reverie,
75 ShadeSanctuary,
77 Wasp,
79 WaspLight,
81}
82
83impl ThemeKind {
84 pub fn to_str(&self) -> &str {
86 match self {
87 ThemeKind::TokyoNight => "Tokyo Night",
88 ThemeKind::TokyoNightLight => "Tokyo Night Light",
89 ThemeKind::McLaren650Gts => "McLaren 650GTS",
90 ThemeKind::Reverie => "Reverie",
91 ThemeKind::ShadeSanctuary => "Shade Sanctuary",
92 ThemeKind::Wasp => "Wasp",
93 ThemeKind::WaspLight => "Wasp Light",
94 }
95 }
96
97 pub fn to_vec() -> Vec<Self> {
99 vec![
100 Self::TokyoNight,
101 Self::TokyoNightLight,
102 Self::McLaren650Gts,
103 Self::Reverie,
104 Self::ShadeSanctuary,
105 Self::Wasp,
106 Self::WaspLight,
107 ]
108 }
109}
110
111#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
125#[derive(Clone)]
126pub struct Theme {
127 pub dark: bool,
129
130 pub image_tint_recommended: bool,
135
136 pub kind: ThemeKind,
138 pub colors: ThemeColors,
140 pub visuals: ThemeVisuals,
142 pub typography: Typography,
144
145 #[cfg_attr(feature = "serde", serde(default))]
151 pub spacing: ThemeSpacing,
152
153 pub window_frame: Frame,
155 pub frame1: Frame,
157 pub frame2: Frame,
159
160 pub corner_radius: u8,
162
163 pub inner_margin: i8,
165
166 pub outer_margin: i8,
168
169 pub button_padding: Vec2,
173
174 #[cfg(feature = "elegance")]
175 #[cfg_attr(feature = "serde", serde(skip))]
176 pub elegance_key: Option<EleganceThemeKey>,
178}
179
180impl PartialEq for Theme {
181 fn eq(&self, other: &Self) -> bool {
182 self.dark == other.dark
183 && self.kind == other.kind
184 && self.colors == other.colors
185 && self.typography == other.typography
186 && self.spacing == other.spacing
187 && self.window_frame == other.window_frame
188 && self.frame1 == other.frame1
189 && self.frame2 == other.frame2
190 && self.visuals == other.visuals
191 }
192}
193
194impl Eq for Theme {}
195
196impl Theme {
197 pub fn new(kind: ThemeKind) -> Self {
199 let theme = match kind {
200 ThemeKind::TokyoNight => tokyo_night::theme(),
201 ThemeKind::TokyoNightLight => tokyo_night_light::theme(),
202 ThemeKind::McLaren650Gts => mclaren_650gts_gt3::theme(),
203 ThemeKind::Reverie => reverie::theme(),
204 ThemeKind::ShadeSanctuary => shade_sanctuary::theme(),
205 ThemeKind::Wasp => wasp::theme(),
206 ThemeKind::WaspLight => wasp_light::theme(),
207 };
208
209 theme
210 }
211
212 pub fn style(&self) -> Style {
214 match self.kind {
215 ThemeKind::TokyoNight => tokyo_night::style(),
216 ThemeKind::TokyoNightLight => tokyo_night_light::style(),
217 ThemeKind::McLaren650Gts => mclaren_650gts_gt3::style(),
218 ThemeKind::Reverie => reverie::style(),
219 ThemeKind::ShadeSanctuary => shade_sanctuary::style(),
220 ThemeKind::Wasp => wasp::style(),
221 ThemeKind::WaspLight => wasp_light::style(),
222 }
223 }
224
225 pub fn frame1_visuals(&self) -> FrameVisuals {
227 self.visuals.frame1_visuals
228 }
229
230 pub fn frame2_visuals(&self) -> FrameVisuals {
232 self.visuals.frame2_visuals
233 }
234
235 pub fn button_visuals(&self) -> ButtonVisuals {
237 self.visuals.button_visuals
238 }
239
240 pub fn label_visuals(&self) -> LabelVisuals {
242 self.visuals.label_visuals
243 }
244
245 pub fn combo_box_visuals(&self) -> ComboBoxVisuals {
247 self.visuals.combo_box_visuals
248 }
249
250 pub fn text_edit_visuals(&self) -> TextEditVisuals {
252 self.visuals.text_edit_visuals
253 }
254
255 fn storage_id() -> Id {
256 Id::new("elements::theme")
257 }
258
259 fn button_visuals_id() -> Id {
260 Id::new("elements::button_visuals")
261 }
262
263 fn label_visuals_id() -> Id {
264 Id::new("elements::label_visuals")
265 }
266
267 fn combo_box_visuals_id() -> Id {
268 Id::new("elements::combo_box_visuals")
269 }
270
271 fn text_edit_visuals_id() -> Id {
272 Id::new("elements::text_edit_visuals")
273 }
274
275 pub fn button_visuals_from_ctx(ctx: &Context) -> Option<ButtonVisuals> {
279 ctx.data(|d| d.get_temp(Self::button_visuals_id()))
280 }
281
282 pub fn label_visuals_from_ctx(ctx: &Context) -> Option<LabelVisuals> {
284 ctx.data(|d| d.get_temp(Self::label_visuals_id()))
285 }
286
287 pub fn combo_box_visuals_from_ctx(ctx: &Context) -> Option<ComboBoxVisuals> {
289 ctx.data(|d| d.get_temp(Self::combo_box_visuals_id()))
290 }
291
292 pub fn text_edit_visuals_from_ctx(ctx: &Context) -> Option<TextEditVisuals> {
294 ctx.data(|d| d.get_temp(Self::text_edit_visuals_id()))
295 }
296
297 pub fn install(&mut self, ctx: &Context) {
301 #[cfg(feature = "elegance")]
302 self.install_elegance_theme(ctx);
303
304 let button_visuals = self.button_visuals();
305 let label_visuals = self.label_visuals();
306 let combo_box_visuals = self.combo_box_visuals();
307 let text_edit_visuals = self.text_edit_visuals();
308 let mut style = self.style();
309 self.apply_spacing(&mut style);
310 let theme = self.clone();
311
312 ctx.set_global_style(style);
313 ctx.data_mut(|d| d.insert_temp(Self::storage_id(), theme));
314
315 ctx.data_mut(|d| d.insert_temp(Self::button_visuals_id(), button_visuals));
316 ctx.data_mut(|d| d.insert_temp(Self::label_visuals_id(), label_visuals));
317 ctx.data_mut(|d| d.insert_temp(Self::combo_box_visuals_id(), combo_box_visuals));
318 ctx.data_mut(|d| d.insert_temp(Self::text_edit_visuals_id(), text_edit_visuals));
319 }
320
321 #[cfg(feature = "elegance")]
322 pub fn install_elegance_theme(&mut self, ctx: &Context) {
326 let key = EleganceThemeKey::from_theme(&self);
327 if self.elegance_key == Some(key) {
328 return;
329 }
330
331 let c = &self.colors;
332 let mut pal = if key.dark {
333 Palette::charcoal()
334 } else {
335 Palette::frost()
336 };
337
338 pal.is_dark = key.dark;
339 pal.bg = c.bg;
340 pal.card = c.widget_bg;
341 pal.input_bg = c.widget_bg;
342 pal.border = c.border;
343 pal.text = c.text;
344 pal.text_muted = c.text_muted;
345 pal.text_faint = c.text_muted;
346 pal.focus = c.accent;
347 pal.blue = c.info;
348 pal.green = c.success;
349 pal.green_hover = c.success;
350 pal.red = c.error;
351 pal.red_hover = c.error;
352 pal.amber = c.warning;
353 pal.amber_hover = c.warning;
354 pal.purple = c.accent;
355 pal.purple_hover = c.accent;
356 pal.success = c.success;
357 pal.danger = c.error;
358 pal.warning = c.warning;
359
360 let elegance_typography = EleganceTypography {
361 body: self.typography.normal,
362 button: self.typography.normal,
363 label: self.typography.normal,
364 small: self.typography.small,
365 heading: self.typography.heading,
366 monospace: self.typography.small,
367 };
368
369 let elegance_theme = EleganceTheme {
370 palette: pal,
371 control_radius: self.corner_radius as f32,
372 card_radius: self.corner_radius as f32,
373 card_padding: self.frame1.inner_margin.top as f32,
374 control_padding_y: self.button_padding.y as f32,
375 control_padding_x: self.button_padding.x as f32,
376 typography: elegance_typography,
377 };
378
379 ctx.data_mut(|d| d.insert_temp(elegance_theme_key(), elegance_theme));
380 self.elegance_key = Some(key);
381 }
382
383 pub fn current(ctx: &Context) -> Theme {
386 ctx.data(|d| {
387 d.get_temp::<Theme>(Self::storage_id())
388 .unwrap_or_else(|| Theme::new(ThemeKind::TokyoNight))
389 })
390 }
391
392 fn apply_spacing(&self, style: &mut Style) {
398 let s = &self.spacing;
399 style.spacing.item_spacing = vec2(s.md, s.md);
400 style.spacing.button_padding = self.button_padding;
401 style.spacing.window_margin = Margin::same(self.inner_margin);
402 }
403
404 pub fn remap_derived_frames(&mut self, old: &ThemeColors) {
410 let new = self.colors;
411 if !frame_palette_changed(old, &new) {
412 return;
413 }
414
415 remap_frame(
416 &mut self.window_frame,
417 old.title_bar,
418 new.title_bar,
419 old.border,
420 new.border,
421 );
422 remap_frame(
423 &mut self.frame1,
424 old.widget_bg,
425 new.widget_bg,
426 old.border,
427 new.border,
428 );
429 remap_frame(
430 &mut self.frame2,
431 old.bg,
432 new.bg,
433 old.border,
434 new.border,
435 );
436 remap_frame_visuals(
437 &mut self.visuals.frame1_visuals,
438 old.hover,
439 new.hover,
440 old.widget_bg,
441 new.widget_bg,
442 old.highlight,
443 new.highlight,
444 );
445 remap_frame_visuals(
446 &mut self.visuals.frame2_visuals,
447 old.hover,
448 new.hover,
449 old.bg,
450 new.bg,
451 old.highlight,
452 new.highlight,
453 );
454 }
455}
456
457#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
459#[derive(Copy, Clone, Debug, PartialEq, Eq)]
460pub struct ThemeVisuals {
461 pub button_visuals: ButtonVisuals,
463 pub label_visuals: LabelVisuals,
465 pub combo_box_visuals: ComboBoxVisuals,
467 pub text_edit_visuals: TextEditVisuals,
469 pub frame1_visuals: FrameVisuals,
471 pub frame2_visuals: FrameVisuals,
473}
474
475#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
477#[derive(Copy, Clone, Debug, PartialEq, Eq)]
478pub struct ThemeColors {
479 pub title_bar: Color32,
481
482 pub bg: Color32,
484
485 pub widget_bg: Color32,
489
490 pub hover: Color32,
492
493 pub text: Color32,
495
496 pub text_muted: Color32,
500
501 pub highlight: Color32,
503
504 pub border: Color32,
506
507 pub accent: Color32,
509
510 pub error: Color32,
514
515 pub warning: Color32,
517
518 pub success: Color32,
522
523 pub info: Color32,
527}
528
529#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
541#[derive(Clone, Copy, Debug, PartialEq)]
542pub struct ThemeSpacing {
543 pub xs: f32,
545 pub sm: f32,
547 pub md: f32,
549 pub lg: f32,
551 pub xl: f32,
553}
554
555impl Default for ThemeSpacing {
556 fn default() -> Self {
557 Self {
558 xs: 4.0,
559 sm: 8.0,
560 md: 12.0,
561 lg: 16.0,
562 xl: 24.0,
563 }
564 }
565}
566
567impl ThemeSpacing {
568 pub fn margin(self, size: f32) -> Margin {
570 Margin::same(size as i8)
571 }
572
573 pub fn margin_xy(self, x: f32, y: f32) -> Margin {
575 Margin::symmetric(x as i8, y as i8)
576 }
577}
578
579#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
581#[derive(Clone, Default, Debug, PartialEq)]
582pub struct Typography {
583 pub very_small: f32,
585 pub small: f32,
587 pub normal: f32,
589 pub large: f32,
591 pub very_large: f32,
593 pub heading: f32,
595}