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, Style, 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 pub window_frame: Frame,
151 pub frame1: Frame,
153 pub frame2: Frame,
155
156 pub corner_radius: u8,
158
159 pub inner_margin: i8,
161
162 pub outer_margin: i8,
164
165 pub button_padding: Vec2,
169
170 #[cfg(feature = "elegance")]
171 #[cfg_attr(feature = "serde", serde(skip))]
172 pub elegance_key: Option<EleganceThemeKey>,
174}
175
176impl PartialEq for Theme {
177 fn eq(&self, other: &Self) -> bool {
178 self.dark == other.dark
179 && self.kind == other.kind
180 && self.colors == other.colors
181 && self.typography == other.typography
182 && self.window_frame == other.window_frame
183 && self.frame1 == other.frame1
184 && self.frame2 == other.frame2
185 && self.visuals == other.visuals
186 }
187}
188
189impl Eq for Theme {}
190
191impl Theme {
192 pub fn new(kind: ThemeKind) -> Self {
194 let theme = match kind {
195 ThemeKind::TokyoNight => tokyo_night::theme(),
196 ThemeKind::TokyoNightLight => tokyo_night_light::theme(),
197 ThemeKind::McLaren650Gts => mclaren_650gts_gt3::theme(),
198 ThemeKind::Reverie => reverie::theme(),
199 ThemeKind::ShadeSanctuary => shade_sanctuary::theme(),
200 ThemeKind::Wasp => wasp::theme(),
201 ThemeKind::WaspLight => wasp_light::theme(),
202 };
203
204 theme
205 }
206
207 pub fn style(&self) -> Style {
209 match self.kind {
210 ThemeKind::TokyoNight => tokyo_night::style(),
211 ThemeKind::TokyoNightLight => tokyo_night_light::style(),
212 ThemeKind::McLaren650Gts => mclaren_650gts_gt3::style(),
213 ThemeKind::Reverie => reverie::style(),
214 ThemeKind::ShadeSanctuary => shade_sanctuary::style(),
215 ThemeKind::Wasp => wasp::style(),
216 ThemeKind::WaspLight => wasp_light::style(),
217 }
218 }
219
220 pub fn button_visuals(&self) -> ButtonVisuals {
222 self.visuals.button_visuals
223 }
224
225 pub fn label_visuals(&self) -> LabelVisuals {
227 self.visuals.label_visuals
228 }
229
230 pub fn combo_box_visuals(&self) -> ComboBoxVisuals {
232 self.visuals.combo_box_visuals
233 }
234
235 pub fn text_edit_visuals(&self) -> TextEditVisuals {
237 self.visuals.text_edit_visuals
238 }
239
240 fn storage_id() -> Id {
241 Id::new("elements::theme")
242 }
243
244 fn button_visuals_id() -> Id {
245 Id::new("elements::button_visuals")
246 }
247
248 fn label_visuals_id() -> Id {
249 Id::new("elements::label_visuals")
250 }
251
252 fn combo_box_visuals_id() -> Id {
253 Id::new("elements::combo_box_visuals")
254 }
255
256 fn text_edit_visuals_id() -> Id {
257 Id::new("elements::text_edit_visuals")
258 }
259
260 pub fn button_visuals_from_ctx(ctx: &Context) -> Option<ButtonVisuals> {
264 ctx.data(|d| d.get_temp(Self::button_visuals_id()))
265 }
266
267 pub fn label_visuals_from_ctx(ctx: &Context) -> Option<LabelVisuals> {
269 ctx.data(|d| d.get_temp(Self::label_visuals_id()))
270 }
271
272 pub fn combo_box_visuals_from_ctx(ctx: &Context) -> Option<ComboBoxVisuals> {
274 ctx.data(|d| d.get_temp(Self::combo_box_visuals_id()))
275 }
276
277 pub fn text_edit_visuals_from_ctx(ctx: &Context) -> Option<TextEditVisuals> {
279 ctx.data(|d| d.get_temp(Self::text_edit_visuals_id()))
280 }
281
282 pub fn install(&mut self, ctx: &Context) {
286 #[cfg(feature = "elegance")]
287 self.install_elegance_theme(ctx);
288
289 let button_visuals = self.button_visuals();
290 let label_visuals = self.label_visuals();
291 let combo_box_visuals = self.combo_box_visuals();
292 let text_edit_visuals = self.text_edit_visuals();
293 let style = self.style();
294 let theme = self.clone();
295
296 ctx.set_global_style(style);
297 ctx.data_mut(|d| d.insert_temp(Self::storage_id(), theme));
298
299 ctx.data_mut(|d| d.insert_temp(Self::button_visuals_id(), button_visuals));
300 ctx.data_mut(|d| d.insert_temp(Self::label_visuals_id(), label_visuals));
301 ctx.data_mut(|d| d.insert_temp(Self::combo_box_visuals_id(), combo_box_visuals));
302 ctx.data_mut(|d| d.insert_temp(Self::text_edit_visuals_id(), text_edit_visuals));
303 }
304
305 #[cfg(feature = "elegance")]
306 pub fn install_elegance_theme(&mut self, ctx: &Context) {
310 let key = EleganceThemeKey::from_theme(&self);
311 if self.elegance_key == Some(key) {
312 return;
313 }
314
315 let c = &self.colors;
316 let mut pal = if key.dark {
317 Palette::charcoal()
318 } else {
319 Palette::frost()
320 };
321
322 pal.is_dark = key.dark;
323 pal.bg = c.bg;
324 pal.card = c.widget_bg;
325 pal.input_bg = c.widget_bg;
326 pal.border = c.border;
327 pal.text = c.text;
328 pal.text_muted = c.text_muted;
329 pal.text_faint = c.text_muted;
330 pal.focus = c.accent;
331 pal.blue = c.info;
332 pal.green = c.success;
333 pal.green_hover = c.success;
334 pal.red = c.error;
335 pal.red_hover = c.error;
336 pal.amber = c.warning;
337 pal.amber_hover = c.warning;
338 pal.purple = c.accent;
339 pal.purple_hover = c.accent;
340 pal.success = c.success;
341 pal.danger = c.error;
342 pal.warning = c.warning;
343
344 let elegance_typography = EleganceTypography {
345 body: self.typography.normal,
346 button: self.typography.normal,
347 label: self.typography.normal,
348 small: self.typography.small,
349 heading: self.typography.heading,
350 monospace: self.typography.small,
351 };
352
353 let elegance_theme = EleganceTheme {
354 palette: pal,
355 control_radius: self.corner_radius as f32,
356 card_radius: self.corner_radius as f32,
357 card_padding: self.frame1.inner_margin.top as f32,
358 control_padding_y: self.button_padding.y as f32,
359 control_padding_x: self.button_padding.x as f32,
360 typography: elegance_typography,
361 };
362
363 ctx.data_mut(|d| d.insert_temp(elegance_theme_key(), elegance_theme));
364 self.elegance_key = Some(key);
365 }
366
367 pub fn current(ctx: &Context) -> Theme {
370 ctx.data(|d| {
371 d.get_temp::<Theme>(Self::storage_id())
372 .unwrap_or_else(|| Theme::new(ThemeKind::TokyoNight))
373 })
374 }
375
376 pub fn remap_derived_frames(&mut self, old: &ThemeColors) {
382 let new = self.colors;
383 if !frame_palette_changed(old, &new) {
384 return;
385 }
386
387 remap_frame(
388 &mut self.window_frame,
389 old.title_bar,
390 new.title_bar,
391 old.border,
392 new.border,
393 );
394 remap_frame(
395 &mut self.frame1,
396 old.widget_bg,
397 new.widget_bg,
398 old.border,
399 new.border,
400 );
401 remap_frame(
402 &mut self.frame2,
403 old.bg,
404 new.bg,
405 old.border,
406 new.border,
407 );
408 remap_frame_visuals(
409 &mut self.visuals.frame1_visuals,
410 old.hover,
411 new.hover,
412 old.widget_bg,
413 new.widget_bg,
414 old.highlight,
415 new.highlight,
416 );
417 remap_frame_visuals(
418 &mut self.visuals.frame2_visuals,
419 old.hover,
420 new.hover,
421 old.bg,
422 new.bg,
423 old.highlight,
424 new.highlight,
425 );
426 }
427}
428
429#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
431#[derive(Copy, Clone, Debug, PartialEq, Eq)]
432pub struct ThemeVisuals {
433 pub button_visuals: ButtonVisuals,
435 pub label_visuals: LabelVisuals,
437 pub combo_box_visuals: ComboBoxVisuals,
439 pub text_edit_visuals: TextEditVisuals,
441 pub frame1_visuals: FrameVisuals,
443 pub frame2_visuals: FrameVisuals,
445}
446
447#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
449#[derive(Copy, Clone, Debug, PartialEq, Eq)]
450pub struct ThemeColors {
451 pub title_bar: Color32,
453
454 pub bg: Color32,
456
457 pub widget_bg: Color32,
461
462 pub hover: Color32,
464
465 pub text: Color32,
467
468 pub text_muted: Color32,
472
473 pub highlight: Color32,
475
476 pub border: Color32,
478
479 pub accent: Color32,
481
482 pub error: Color32,
486
487 pub warning: Color32,
489
490 pub success: Color32,
494
495 pub info: Color32,
499}
500
501#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
503#[derive(Clone, Default, Debug, PartialEq)]
504pub struct Typography {
505 pub very_small: f32,
507 pub small: f32,
509 pub normal: f32,
511 pub large: f32,
513 pub very_large: f32,
515 pub heading: f32,
517}