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, Eq)]
19pub enum ThemeKind {
20 Dark,
21
22 TokyoNight,
23
24 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::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 pub dark_mode: bool,
51 #[cfg_attr(feature = "serde", serde(skip))]
52 pub overlay_manager: OverlayManager,
53
54 pub image_tint_recommended: bool,
59 pub kind: ThemeKind,
60 pub style: Style,
61 pub colors: ThemeColors,
62 pub text_sizes: TextSizes,
63 pub window_frame: Frame,
65 pub frame1: Frame,
67 pub frame2: Frame,
69
70 pub frame1_visuals: FrameVisuals,
71 pub frame2_visuals: FrameVisuals,
72}
73
74impl PartialEq for Theme {
75 fn eq(&self, other: &Self) -> bool {
76 self.dark_mode == other.dark_mode
77 && self.kind == other.kind
78 && self.style == other.style
79 && self.colors == other.colors
80 && self.text_sizes == other.text_sizes
81 && self.window_frame == other.window_frame
82 && self.frame1 == other.frame1
83 && self.frame2 == other.frame2
84 && self.frame1_visuals == other.frame1_visuals
85 && self.frame2_visuals == other.frame2_visuals
86 }
87}
88
89impl Eq for Theme {}
90
91impl Theme {
92 pub fn new(kind: ThemeKind) -> Self {
96 let theme = match kind {
97 ThemeKind::Dark => dark::theme(),
98 ThemeKind::TokyoNight => tokyo_night::theme(),
99 ThemeKind::Custom => panic!("{}", PANIC_MSG),
101 };
102
103 theme
104 }
105
106 pub fn set_window_frame_colors(&mut self) {
107 match self.kind {
108 ThemeKind::Dark => self.window_frame = dark::window_frame(&self.colors),
109 ThemeKind::TokyoNight => self.window_frame = tokyo_night::window_frame(&self.colors),
110 ThemeKind::Custom => panic!("{}", PANIC_MSG),
112 }
113 }
114
115 pub fn set_frame1_colors(&mut self) {
116 match self.kind {
117 ThemeKind::Dark => self.frame1 = dark::frame1(&self.colors),
118 ThemeKind::TokyoNight => self.frame1 = tokyo_night::frame1(&self.colors),
119 ThemeKind::Custom => panic!("{}", PANIC_MSG),
121 }
122 }
123
124 pub fn set_frame2_colors(&mut self) {
125 match self.kind {
126 ThemeKind::Dark => self.frame2 = dark::frame2(&self.colors),
127 ThemeKind::TokyoNight => self.frame2 = tokyo_night::frame2(&self.colors),
128 ThemeKind::Custom => panic!("{}", PANIC_MSG),
130 }
131 }
132
133 pub fn button_visuals(&self) -> ButtonVisuals {
134 match self.kind {
135 ThemeKind::Dark => self.colors.button_visuals,
136 ThemeKind::TokyoNight => self.colors.button_visuals,
137 ThemeKind::Custom => panic!("{}", PANIC_MSG),
139 }
140 }
141
142 pub fn label_visuals(&self) -> LabelVisuals {
143 match self.kind {
144 ThemeKind::Dark => self.colors.label_visuals,
145 ThemeKind::TokyoNight => self.colors.label_visuals,
146 ThemeKind::Custom => panic!("{}", PANIC_MSG),
148 }
149 }
150
151 pub fn combo_box_visuals(&self) -> ComboBoxVisuals {
152 match self.kind {
153 ThemeKind::Dark => self.colors.combo_box_visuals,
154 ThemeKind::TokyoNight => self.colors.combo_box_visuals,
155 ThemeKind::Custom => panic!("{}", PANIC_MSG),
157 }
158 }
159
160 pub fn text_edit_visuals(&self) -> TextEditVisuals {
161 match self.kind {
162 ThemeKind::Dark => self.colors.text_edit_visuals,
163 ThemeKind::TokyoNight => self.colors.text_edit_visuals,
164 ThemeKind::Custom => panic!("{}", PANIC_MSG),
166 }
167 }
168
169 pub fn install(self, ctx: &Context) {
171 let unchanged =
172 ctx.data(|d| d.get_temp::<Theme>(Self::storage_id()).is_some_and(|t| t == self));
173
174 if unchanged {
175 return;
176 }
177
178 ctx.set_global_style(self.style.clone());
179 ctx.data_mut(|d| d.insert_temp(Self::storage_id(), self));
180 }
181
182 pub fn current(ctx: &Context) -> Theme {
185 ctx.data(|d| {
186 d.get_temp::<Theme>(Self::storage_id())
187 .unwrap_or_else(|| Theme::new(ThemeKind::TokyoNight))
188 })
189 }
190
191 fn storage_id() -> Id {
192 Id::new("zeus::theme")
193 }
194}
195
196#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
198#[derive(Copy, Clone, Debug, PartialEq, Eq)]
199pub struct ThemeColors {
200 pub button_visuals: ButtonVisuals,
201
202 pub label_visuals: LabelVisuals,
203
204 pub combo_box_visuals: ComboBoxVisuals,
205
206 pub text_edit_visuals: TextEditVisuals,
207
208 pub title_bar: Color32,
210
211 pub bg: Color32,
213
214 pub widget_bg: Color32,
218
219 pub hover: Color32,
221
222 pub text: Color32,
224
225 pub text_muted: Color32,
229
230 pub highlight: Color32,
232
233 pub border: Color32,
235
236 pub accent: Color32,
238
239 pub error: Color32,
243
244 pub warning: Color32,
246
247 pub success: Color32,
251
252 pub info: Color32,
256}
257
258#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
259#[derive(Clone, Default, Debug, PartialEq)]
260pub struct TextSizes {
261 pub very_small: f32,
262 pub small: f32,
263 pub normal: f32,
264 pub large: f32,
265 pub very_large: f32,
266 pub heading: f32,
267}
268
269impl TextSizes {
270 pub fn new(
271 very_small: f32,
272 small: f32,
273 normal: f32,
274 large: f32,
275 very_large: f32,
276 heading: f32,
277 ) -> Self {
278 Self {
279 very_small,
280 small,
281 normal,
282 large,
283 very_large,
284 heading,
285 }
286 }
287}
288
289#[derive(Clone, Debug, Default)]
290pub struct OverlayManager(Arc<RwLock<OverlayCounter>>);
291
292impl OverlayManager {
293 pub fn new() -> Self {
294 Self(Arc::new(RwLock::new(OverlayCounter::new())))
295 }
296
297 pub fn tint_0(&self) -> Color32 {
298 Color32::from_black_alpha(40)
299 }
300
301 pub fn tint_1(&self) -> Color32 {
302 Color32::from_black_alpha(60)
303 }
304
305 pub fn tint_2(&self) -> Color32 {
306 Color32::from_black_alpha(80)
307 }
308
309 pub fn tint_3(&self) -> Color32 {
310 Color32::from_black_alpha(100)
311 }
312
313 pub fn counter(&self) -> u8 {
314 self.0.read().unwrap().counter()
315 }
316
317 pub fn order(&self) -> Order {
318 self.0.read().unwrap().order()
319 }
320
321 pub fn paint_background(&self) {
322 self.0.write().unwrap().paint_background()
323 }
324
325 pub fn paint_middle(&self) {
326 self.0.write().unwrap().paint_middle()
327 }
328
329 pub fn paint_foreground(&self) {
330 self.0.write().unwrap().paint_foreground()
331 }
332
333 pub fn paint_tooltip(&self) {
334 self.0.write().unwrap().paint_tooltip()
335 }
336
337 pub fn paint_debug(&self) {
338 self.0.write().unwrap().paint_debug()
339 }
340
341 pub fn window_opened(&self) {
343 self.0.write().unwrap().window_opened();
344 }
345
346 pub fn window_closed(&self) {
348 self.0.write().unwrap().window_closed();
349 }
350
351 pub fn recommended_order(&self) -> Order {
352 self.0.read().unwrap().recommended_order()
353 }
354
355 pub fn calculate_alpha(&self) -> u8 {
356 self.0.read().unwrap().calculate_alpha()
357 }
358
359 pub fn overlay_tint(&self) -> Color32 {
361 self.0.read().unwrap().overlay_tint()
362 }
363
364 pub fn paint_overlay(&self, ctx: &Context, recommend_order: bool) {
368 self.0.read().unwrap().paint_overlay(ctx, recommend_order);
369 }
370
371 pub fn paint_overlay_at(&self, ctx: &Context, rect: Rect, order: Order, id: Id, tint: Color32) {
373 self.0.read().unwrap().paint_overlay_at(ctx, rect, order, id, tint);
374 }
375}
376
377#[derive(Clone, Debug)]
378struct OverlayCounter {
379 counter: u8,
380 order: Order,
381}
382
383impl Default for OverlayCounter {
384 fn default() -> Self {
385 Self::new()
386 }
387}
388
389impl OverlayCounter {
390 pub fn new() -> Self {
391 Self {
392 counter: 0,
393 order: Order::Background,
394 }
395 }
396
397 pub fn counter(&self) -> u8 {
398 self.counter
399 }
400
401 pub fn order(&self) -> Order {
402 self.order
403 }
404
405 fn paint_background(&mut self) {
406 self.order = Order::Background;
407 }
408
409 fn paint_middle(&mut self) {
410 self.order = Order::Middle;
411 }
412
413 fn paint_foreground(&mut self) {
414 self.order = Order::Foreground;
415 }
416
417 fn paint_tooltip(&mut self) {
418 self.order = Order::Tooltip;
419 }
420
421 fn paint_debug(&mut self) {
422 self.order = Order::Debug;
423 }
424
425 fn window_opened(&mut self) {
426 self.counter += 1;
427 }
428
429 fn window_closed(&mut self) {
430 if self.counter > 0 {
431 self.counter -= 1;
432 }
433 }
434
435 fn calculate_alpha(&self) -> u8 {
436 let counter = self.counter;
437
438 if counter == 0 {
439 return 0;
440 }
441
442 let mut a = 60;
443 for _ in 1..counter {
444 a += 20;
445 }
446
447 a
448 }
449
450 fn overlay_tint(&self) -> Color32 {
451 let counter = self.counter();
452
453 if counter == 1 {
454 return Color32::from_black_alpha(60);
455 }
456
457 let alpha = self.calculate_alpha();
458 Color32::from_black_alpha(alpha)
459 }
460
461 fn recommended_order(&self) -> Order {
462 if self.counter() == 1 {
463 Order::Middle
464 } else if self.counter() == 2 {
465 Order::Foreground
466 } else {
467 Order::Tooltip
468 }
469 }
470
471 fn paint_overlay(&self, ctx: &Context, recommend_order: bool) {
472 let counter = self.counter();
473 if counter == 0 {
474 return;
475 }
476
477 let order = if recommend_order {
478 if counter == 1 {
479 Order::Middle
480 } else if counter == 2 {
481 Order::Foreground
482 } else {
483 Order::Tooltip
484 }
485 } else {
486 self.order()
487 };
488
489 let layer_id = LayerId::new(order, Id::new("darkening_overlay"));
490
491 let painter = ctx.layer_painter(layer_id);
492 painter.rect_filled(ctx.content_rect(), 0.0, self.overlay_tint());
493 }
494
495 pub fn paint_overlay_at(&self, ctx: &Context, rect: Rect, order: Order, id: Id, tint: Color32) {
496 let layer_id = LayerId::new(order, id);
497
498 let painter = ctx.layer_painter(layer_id);
499 painter.rect_filled(rect, 0.0, tint);
500 }
501}