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