zeus_theme/
lib.rs

1use egui::{Color32, Context, Frame, Id, LayerId, Order, Rect, Style};
2use std::sync::{Arc, RwLock};
3
4const PANIC_MSG: &str = "Custom theme not supported, use Theme::from_custom() instead";
5
6pub mod editor;
7pub mod hsla;
8pub mod themes;
9pub mod utils;
10pub mod visuals;
11pub mod window;
12
13pub use editor::ThemeEditor;
14use themes::*;
15pub use visuals::*;
16
17#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
18#[derive(Debug, Clone, PartialEq)]
19pub enum ThemeKind {
20   Dark,
21
22   TokyoNight,
23
24   /// WIP
25  // Light,
26
27   /// A custom theme
28   Custom,
29}
30
31impl ThemeKind {
32   pub fn to_str(&self) -> &str {
33      match self {
34         ThemeKind::Dark => "Dark",
35         ThemeKind::TokyoNight => "Tokyo Night",
36        // ThemeKind::Light => "Light",
37         ThemeKind::Custom => "Custom",
38      }
39   }
40
41   pub fn to_vec() -> Vec<Self> {
42      vec![Self::Dark, Self::TokyoNight]
43   }
44}
45
46#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
47#[derive(Debug, Clone)]
48pub struct Theme {
49   /// True if the theme is dark
50   pub dark_mode: bool,
51   #[cfg_attr(feature = "serde", serde(skip))]
52   pub overlay_manager: OverlayManager,
53
54   /// True if a tint is recomended to be applied to images
55   /// to soften the contrast between the image and the background
56   ///
57   /// This is usually true for themes with very dark background
58   pub image_tint_recommended: bool,
59   pub kind: ThemeKind,
60   pub style: Style,
61   pub colors: ThemeColors,
62   pub text_sizes: TextSizes,
63   /// Used for [window::window_frame]
64   pub window_frame: Frame,
65   /// Base container frame for major UI sections.
66   pub frame1: Frame,
67   /// Frame for nested elements, like individual list items.
68   pub frame2: Frame,
69
70   pub frame1_visuals: FrameVisuals,
71   pub frame2_visuals: FrameVisuals,
72}
73
74impl Theme {
75   /// Panics if the kind is [ThemeKind::Custom]
76   ///
77   /// Use [Theme::from_custom()] instead
78   pub fn new(kind: ThemeKind) -> Self {
79      let theme = match kind {
80         ThemeKind::Dark => dark::theme(),
81         ThemeKind::TokyoNight => tokyo_night::theme(),
82        // ThemeKind::Light => light::theme(),
83         ThemeKind::Custom => panic!("{}", PANIC_MSG),
84      };
85
86      theme
87   }
88
89   pub fn set_window_frame_colors(&mut self) {
90      match self.kind {
91         ThemeKind::Dark => self.window_frame = dark::window_frame(&self.colors),
92         ThemeKind::TokyoNight => self.window_frame = tokyo_night::window_frame(&self.colors),
93        // ThemeKind::Light => self.window_frame = light::window_frame(&self.colors),
94         ThemeKind::Custom => panic!("{}", PANIC_MSG),
95      }
96   }
97
98   pub fn set_frame1_colors(&mut self) {
99      match self.kind {
100         ThemeKind::Dark => self.frame1 = dark::frame1(&self.colors),
101         ThemeKind::TokyoNight => self.frame1 = tokyo_night::frame1(&self.colors),
102        // ThemeKind::Light => self.frame1 = light::frame1(&self.colors),
103         ThemeKind::Custom => panic!("{}", PANIC_MSG),
104      }
105   }
106
107   pub fn set_frame2_colors(&mut self) {
108      match self.kind {
109         ThemeKind::Dark => self.frame2 = dark::frame2(&self.colors),
110         ThemeKind::TokyoNight => self.frame2 = tokyo_night::frame2(&self.colors),
111        // ThemeKind::Light => self.frame2 = light::frame2(&self.colors),
112         ThemeKind::Custom => panic!("{}", PANIC_MSG),
113      }
114   }
115
116   pub fn button_visuals(&self) -> ButtonVisuals {
117      match self.kind {
118         ThemeKind::Dark => self.colors.button_visuals,
119         ThemeKind::TokyoNight => self.colors.button_visuals,
120        // ThemeKind::Light => self.colors.button_visuals,
121         ThemeKind::Custom => panic!("{}", PANIC_MSG),
122      }
123   }
124
125   pub fn label_visuals(&self) -> LabelVisuals {
126      match self.kind {
127         ThemeKind::Dark => self.colors.label_visuals,
128         ThemeKind::TokyoNight => self.colors.label_visuals,
129        // ThemeKind::Light => self.colors.label_visuals,
130         ThemeKind::Custom => panic!("{}", PANIC_MSG),
131      }
132   }
133
134   pub fn combo_box_visuals(&self) -> ComboBoxVisuals {
135      match self.kind {
136         ThemeKind::Dark => self.colors.combo_box_visuals,
137         ThemeKind::TokyoNight => self.colors.combo_box_visuals,
138        // ThemeKind::Light => self.colors.combo_box_visuals,
139         ThemeKind::Custom => panic!("{}", PANIC_MSG),
140      }
141   }
142
143   pub fn text_edit_visuals(&self) -> TextEditVisuals {
144      match self.kind {
145         ThemeKind::Dark => self.colors.text_edit_visuals,
146         ThemeKind::TokyoNight => self.colors.text_edit_visuals,
147        // ThemeKind::Light => self.colors.text_edit_visuals,
148         ThemeKind::Custom => panic!("{}", PANIC_MSG),
149      }
150   }
151}
152
153/// This is the color palette of the theme
154#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
155#[derive(Copy, Clone, Debug)]
156pub struct ThemeColors {
157   pub button_visuals: ButtonVisuals,
158
159   pub label_visuals: LabelVisuals,
160
161   pub combo_box_visuals: ComboBoxVisuals,
162
163   pub text_edit_visuals: TextEditVisuals,
164
165   /// The color for the title bar of the app (if using custom window frame)
166   pub title_bar: Color32,
167
168   /// Main BG color of the theme
169   pub bg: Color32,
170
171   /// Widget BG color
172   /// 
173   /// This is the color of the widget backgrounds
174   pub widget_bg: Color32,
175
176   /// The color to use when hovering over a widget
177   pub hover: Color32,
178
179   /// Main text color
180   pub text: Color32,
181
182   /// Muted text color
183   ///
184   /// For example a hint inside a text field
185   pub text_muted: Color32,
186
187   /// Highlight color
188   pub highlight: Color32,
189
190   /// Border color
191   pub border: Color32,
192
193   /// Accent color
194   pub accent: Color32,
195
196   /// Error color
197   ///
198   /// Can be used to indicate something bad or to highlight a dangerous action
199   pub error: Color32,
200
201   /// Warning color
202   pub warning: Color32,
203
204   /// Success color
205   ///
206   /// Can be used to indicate something good or to highlight a successful action
207   pub success: Color32,
208
209   /// Info color
210   ///
211   /// Can be used for hyperlinks or to highlight something important
212   pub info: Color32,
213}
214
215#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
216#[derive(Clone, Default, Debug)]
217pub struct TextSizes {
218   pub very_small: f32,
219   pub small: f32,
220   pub normal: f32,
221   pub large: f32,
222   pub very_large: f32,
223   pub heading: f32,
224}
225
226impl TextSizes {
227   pub fn new(
228      very_small: f32,
229      small: f32,
230      normal: f32,
231      large: f32,
232      very_large: f32,
233      heading: f32,
234   ) -> Self {
235      Self {
236         very_small,
237         small,
238         normal,
239         large,
240         very_large,
241         heading,
242      }
243   }
244}
245
246#[derive(Clone, Debug, Default)]
247pub struct OverlayManager(Arc<RwLock<OverlayCounter>>);
248
249impl OverlayManager {
250   pub fn new() -> Self {
251      Self(Arc::new(RwLock::new(OverlayCounter::new())))
252   }
253
254   pub fn tint_0(&self) -> Color32 {
255      Color32::from_black_alpha(40)
256   }
257
258   pub fn tint_1(&self) -> Color32 {
259      Color32::from_black_alpha(60)
260   }
261
262   pub fn tint_2(&self) -> Color32 {
263      Color32::from_black_alpha(80)
264   }
265
266   pub fn tint_3(&self) -> Color32 {
267      Color32::from_black_alpha(100)
268   }
269
270   pub fn counter(&self) -> u8 {
271      self.0.read().unwrap().counter()
272   }
273
274   pub fn order(&self) -> Order {
275      self.0.read().unwrap().order()
276   }
277
278   pub fn paint_background(&self) {
279      self.0.write().unwrap().paint_background()
280   }
281
282   pub fn paint_middle(&self) {
283      self.0.write().unwrap().paint_middle()
284   }
285
286   pub fn paint_foreground(&self) {
287      self.0.write().unwrap().paint_foreground()
288   }
289
290   pub fn paint_tooltip(&self) {
291      self.0.write().unwrap().paint_tooltip()
292   }
293
294   pub fn paint_debug(&self) {
295      self.0.write().unwrap().paint_debug()
296   }
297
298   /// Call this when you open a window
299   pub fn window_opened(&self) {
300      self.0.write().unwrap().window_opened();
301   }
302
303   /// Call this when you close a window
304   pub fn window_closed(&self) {
305      self.0.write().unwrap().window_closed();
306   }
307
308   pub fn recommended_order(&self) -> Order {
309      self.0.read().unwrap().recommended_order()
310   }
311
312   pub fn calculate_alpha(&self) -> u8 {
313      self.0.read().unwrap().calculate_alpha()
314   }
315
316   /// Returns the tint color based on the counter
317   pub fn overlay_tint(&self) -> Color32 {
318      self.0.read().unwrap().overlay_tint()
319   }
320
321   /// Paints a full-screen darkening overlay up to Foreground layer if needed
322   ///
323   /// If `recommend_order` is true, it will choose an order based on the counter
324   pub fn paint_overlay(&self, ctx: &Context, recommend_order: bool) {
325      self.0.read().unwrap().paint_overlay(ctx, recommend_order);
326   }
327
328   /// Paints an overlay at a specific screen position
329   pub fn paint_overlay_at(&self, ctx: &Context, rect: Rect, order: Order, id: Id, tint: Color32) {
330      self.0.read().unwrap().paint_overlay_at(ctx, rect, order, id, tint);
331   }
332}
333
334#[derive(Clone, Debug)]
335struct OverlayCounter {
336   counter: u8,
337   order: Order,
338}
339
340impl Default for OverlayCounter {
341   fn default() -> Self {
342      Self::new()
343   }
344}
345
346impl OverlayCounter {
347   pub fn new() -> Self {
348      Self {
349         counter: 0,
350         order: Order::Background,
351      }
352   }
353
354   pub fn counter(&self) -> u8 {
355      self.counter
356   }
357
358   pub fn order(&self) -> Order {
359      self.order
360   }
361
362   fn paint_background(&mut self) {
363      self.order = Order::Background;
364   }
365
366   fn paint_middle(&mut self) {
367      self.order = Order::Middle;
368   }
369
370   fn paint_foreground(&mut self) {
371      self.order = Order::Foreground;
372   }
373
374   fn paint_tooltip(&mut self) {
375      self.order = Order::Tooltip;
376   }
377
378   fn paint_debug(&mut self) {
379      self.order = Order::Debug;
380   }
381
382   fn window_opened(&mut self) {
383      self.counter += 1;
384   }
385
386   fn window_closed(&mut self) {
387      if self.counter > 0 {
388         self.counter -= 1;
389      }
390   }
391
392   fn calculate_alpha(&self) -> u8 {
393      let counter = self.counter;
394
395      if counter == 0 {
396         return 0;
397      }
398
399      let mut a = 60;
400      for _ in 1..counter {
401         a += 20;
402      }
403
404      a
405   }
406
407   fn overlay_tint(&self) -> Color32 {
408      let counter = self.counter();
409
410      if counter == 1 {
411         return Color32::from_black_alpha(60);
412      }
413
414      let alpha = self.calculate_alpha();
415      Color32::from_black_alpha(alpha)
416   }
417
418   fn recommended_order(&self) -> Order {
419      if self.counter() == 1 {
420         Order::Middle
421      } else if self.counter() == 2 {
422         Order::Foreground
423      } else {
424         Order::Tooltip
425      }
426   }
427
428   fn paint_overlay(&self, ctx: &Context, recommend_order: bool) {
429      let counter = self.counter();
430      if counter == 0 {
431         return;
432      }
433
434      let order = if recommend_order {
435         if counter == 1 {
436            Order::Middle
437         } else if counter == 2 {
438            Order::Foreground
439         } else {
440            Order::Tooltip
441         }
442      } else {
443         self.order()
444      };
445
446      let layer_id = LayerId::new(order, Id::new("darkening_overlay"));
447
448      let painter = ctx.layer_painter(layer_id);
449      painter.rect_filled(ctx.content_rect(), 0.0, self.overlay_tint());
450   }
451
452   pub fn paint_overlay_at(&self, ctx: &Context, rect: Rect, order: Order, id: Id, tint: Color32) {
453      let layer_id = LayerId::new(order, id);
454
455      let painter = ctx.layer_painter(layer_id);
456      painter.rect_filled(rect, 0.0, tint);
457   }
458}