1use std::any::{Any, TypeId};
15use std::cell::RefCell;
16use std::collections::HashMap;
17use std::sync::OnceLock;
18
19use parking_lot::RwLock;
20
21use crate::Color;
22
23#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
24pub enum TextDirection {
25 #[default]
26 Ltr,
27 Rtl,
28}
29
30thread_local! {
31 static LOCALS_STACK: RefCell<Vec<HashMap<TypeId, Box<dyn Any>>>> = RefCell::new(Vec::new());
32}
33
34#[derive(Clone, Copy, Debug)]
35struct Defaults {
36 theme: Theme,
37 text_direction: TextDirection,
38 ui_scale: UiScale,
39 text_scale: TextScale,
40 density: Density,
41}
42
43impl Default for Defaults {
44 fn default() -> Self {
45 Self {
46 theme: Theme::default(),
47 text_direction: TextDirection::default(),
48 ui_scale: UiScale::default(),
49 text_scale: TextScale::default(),
50 density: Density::default(),
51 }
52 }
53}
54
55static DEFAULTS: OnceLock<RwLock<Defaults>> = OnceLock::new();
56
57fn defaults() -> &'static RwLock<Defaults> {
58 DEFAULTS.get_or_init(|| RwLock::new(Defaults::default()))
59}
60
61pub fn set_theme_default(t: Theme) {
63 defaults().write().theme = t;
64}
65
66pub fn set_text_direction_default(d: TextDirection) {
68 defaults().write().text_direction = d;
69}
70
71pub fn set_ui_scale_default(s: UiScale) {
73 defaults().write().ui_scale = UiScale(s.0.max(0.0));
74}
75
76pub fn set_text_scale_default(s: TextScale) {
78 defaults().write().text_scale = TextScale(s.0.max(0.0));
79}
80
81pub fn set_density_default(d: Density) {
84 defaults().write().density = Density {
85 scale: d.scale.max(0.0),
86 };
87}
88
89#[derive(Clone, Copy, Debug, PartialEq)]
93pub struct Dp(pub f32);
94
95impl Dp {
96 pub fn to_px(self) -> f32 {
98 self.0 * density().scale * ui_scale().0
99 }
100}
101
102pub fn dp_to_px(dp: f32) -> f32 {
104 Dp(dp).to_px()
105}
106
107fn with_locals_frame<R>(f: impl FnOnce() -> R) -> R {
108 struct Guard;
109 impl Drop for Guard {
110 fn drop(&mut self) {
111 LOCALS_STACK.with(|st| {
112 st.borrow_mut().pop();
113 });
114 }
115 }
116 LOCALS_STACK.with(|st| st.borrow_mut().push(HashMap::new()));
117 let _guard = Guard;
118 f()
119}
120
121fn set_local_boxed(t: TypeId, v: Box<dyn Any>) {
122 LOCALS_STACK.with(|st| {
123 if let Some(top) = st.borrow_mut().last_mut() {
124 top.insert(t, v);
125 } else {
126 let mut m = HashMap::new();
128 m.insert(t, v);
129 st.borrow_mut().push(m);
130 }
131 });
132}
133
134fn get_local<T: 'static + Copy>() -> Option<T> {
135 LOCALS_STACK.with(|st| {
136 for frame in st.borrow().iter().rev() {
137 if let Some(v) = frame.get(&TypeId::of::<T>())
138 && let Some(t) = v.downcast_ref::<T>()
139 {
140 return Some(*t);
141 }
142 }
143 None
144 })
145}
146
147#[derive(Clone, Copy, Debug)]
148#[must_use]
149pub struct ColorScheme {
150 pub primary: Color,
151 pub on_primary: Color,
152 pub primary_container: Color,
153 pub on_primary_container: Color,
154
155 pub secondary: Color,
156 pub on_secondary: Color,
157 pub secondary_container: Color,
158 pub on_secondary_container: Color,
159
160 pub tertiary: Color,
161 pub on_tertiary: Color,
162 pub tertiary_container: Color,
163 pub on_tertiary_container: Color,
164
165 pub error: Color,
166 pub on_error: Color,
167 pub error_container: Color,
168 pub on_error_container: Color,
169
170 pub background: Color,
171 pub on_background: Color,
172 pub surface: Color,
173 pub on_surface: Color,
174 pub surface_variant: Color,
175 pub on_surface_variant: Color,
176 pub surface_container_lowest: Color,
177 pub surface_container_low: Color,
178 pub surface_container: Color,
179 pub surface_container_high: Color,
180 pub surface_container_highest: Color,
181 pub surface_bright: Color,
182 pub surface_dim: Color,
183 pub surface_tint: Color,
184
185 pub inverse_surface: Color,
186 pub inverse_on_surface: Color,
187 pub inverse_primary: Color,
188
189 pub outline: Color,
190 pub outline_variant: Color,
191
192 pub scrim: Color,
193 pub shadow: Color,
194 pub focus: Color,
195}
196
197impl ColorScheme {
198 pub fn dark() -> Self {
199 Self {
200 primary: Color::from_hex("#D0BCFF"),
201 on_primary: Color::from_hex("#381E72"),
202 primary_container: Color::from_hex("#4F378B"),
203 on_primary_container: Color::from_hex("#EADDFF"),
204
205 secondary: Color::from_hex("#CCC2DC"),
206 on_secondary: Color::from_hex("#332D41"),
207 secondary_container: Color::from_hex("#4A4458"),
208 on_secondary_container: Color::from_hex("#E8DEF8"),
209
210 tertiary: Color::from_hex("#EFB8C8"),
211 on_tertiary: Color::from_hex("#492532"),
212 tertiary_container: Color::from_hex("#633B48"),
213 on_tertiary_container: Color::from_hex("#FFD8E4"),
214
215 error: Color::from_hex("#F2B8B5"),
216 on_error: Color::from_hex("#601410"),
217 error_container: Color::from_hex("#8C1D18"),
218 on_error_container: Color::from_hex("#F9DEDC"),
219
220 background: Color::from_hex("#141218"),
221 on_background: Color::from_hex("#E6E0E9"),
222 surface: Color::from_hex("#141218"),
223 on_surface: Color::from_hex("#E6E0E9"),
224 surface_variant: Color::from_hex("#49454F"),
225 on_surface_variant: Color::from_hex("#CAC4D0"),
226 surface_container_lowest: Color::from_hex("#0F0D13"),
227 surface_container_low: Color::from_hex("#1D1B20"),
228 surface_container: Color::from_hex("#211F26"),
229 surface_container_high: Color::from_hex("#2B2930"),
230 surface_container_highest: Color::from_hex("#36343B"),
231 surface_bright: Color::from_hex("#3B383E"),
232 surface_dim: Color::from_hex("#141218"),
233 surface_tint: Color::from_hex("#D0BCFF"),
234
235 inverse_surface: Color::from_hex("#E6E0E9"),
236 inverse_on_surface: Color::from_hex("#322F35"),
237 inverse_primary: Color::from_hex("#6750A4"),
238
239 outline: Color::from_hex("#938F99"),
240 outline_variant: Color::from_hex("#49454F"),
241
242 scrim: Color::from_hex("#000000"),
243 shadow: Color::from_hex("#000000"),
244 focus: Color::from_hex("#88CCFF"),
245 }
246 }
247
248 pub fn light() -> Self {
249 Self {
250 primary: Color::from_hex("#6750A4"),
251 on_primary: Color::WHITE,
252 primary_container: Color::from_hex("#EADDFF"),
253 on_primary_container: Color::from_hex("#21005D"),
254
255 secondary: Color::from_hex("#625B71"),
256 on_secondary: Color::WHITE,
257 secondary_container: Color::from_hex("#E8DEF8"),
258 on_secondary_container: Color::from_hex("#1D192B"),
259
260 tertiary: Color::from_hex("#7D5260"),
261 on_tertiary: Color::WHITE,
262 tertiary_container: Color::from_hex("#FFD8E4"),
263 on_tertiary_container: Color::from_hex("#31111D"),
264
265 error: Color::from_hex("#B3261E"),
266 on_error: Color::WHITE,
267 error_container: Color::from_hex("#F9DEDC"),
268 on_error_container: Color::from_hex("#410E0B"),
269
270 background: Color::from_hex("#FEF7FF"),
271 on_background: Color::from_hex("#1D1B20"),
272 surface: Color::from_hex("#FEF7FF"),
273 on_surface: Color::from_hex("#1D1B20"),
274 surface_variant: Color::from_hex("#E7E0EC"),
275 on_surface_variant: Color::from_hex("#49454F"),
276 surface_container_lowest: Color::WHITE,
277 surface_container_low: Color::from_hex("#F7F2FA"),
278 surface_container: Color::from_hex("#F3EDF7"),
279 surface_container_high: Color::from_hex("#ECE6F0"),
280 surface_container_highest: Color::from_hex("#E6E0E9"),
281 surface_bright: Color::from_hex("#FEF7FF"),
282 surface_dim: Color::from_hex("#DED8E1"),
283 surface_tint: Color::from_hex("#6750A4"),
284
285 inverse_surface: Color::from_hex("#322F35"),
286 inverse_on_surface: Color::from_hex("#F5EFF7"),
287 inverse_primary: Color::from_hex("#D0BCFF"),
288
289 outline: Color::from_hex("#79747E"),
290 outline_variant: Color::from_hex("#CAC4D0"),
291
292 scrim: Color::from_hex("#000000"),
293 shadow: Color::from_hex("#000000"),
294 focus: Color::from_hex("#1D4ED8"),
295 }
296 }
297}
298
299impl Default for ColorScheme {
300 fn default() -> Self {
301 Self::dark()
302 }
303}
304
305#[derive(Clone, Copy, Debug)]
306#[must_use]
307pub struct Typography {
308 pub display_large: f32,
309 pub display_medium: f32,
310 pub display_small: f32,
311 pub headline_large: f32,
312 pub headline_medium: f32,
313 pub headline_small: f32,
314 pub title_large: f32,
315 pub title_medium: f32,
316 pub title_small: f32,
317 pub body_large: f32,
318 pub body_medium: f32,
319 pub body_small: f32,
320 pub label_large: f32,
321 pub label_medium: f32,
322 pub label_small: f32,
323}
324
325impl Default for Typography {
326 fn default() -> Self {
327 Self {
328 display_large: 57.0,
329 display_medium: 45.0,
330 display_small: 36.0,
331 headline_large: 32.0,
332 headline_medium: 28.0,
333 headline_small: 24.0,
334 title_large: 22.0,
335 title_medium: 16.0,
336 title_small: 14.0,
337 body_large: 16.0,
338 body_medium: 14.0,
339 body_small: 12.0,
340 label_large: 14.0,
341 label_medium: 12.0,
342 label_small: 11.0,
343 }
344 }
345}
346
347#[derive(Clone, Copy, Debug)]
348#[must_use]
349pub struct Shapes {
350 pub extra_small: f32,
351 pub small: f32,
352 pub medium: f32,
353 pub large: f32,
354 pub extra_large: f32,
355}
356
357impl Default for Shapes {
358 fn default() -> Self {
359 Self {
360 extra_small: 4.0,
361 small: 8.0,
362 medium: 12.0,
363 large: 16.0,
364 extra_large: 28.0,
365 }
366 }
367}
368
369#[derive(Clone, Copy, Debug)]
370#[must_use]
371pub struct Spacing {
372 pub xs: f32,
373 pub sm: f32,
374 pub md: f32,
375 pub lg: f32,
376 pub xl: f32,
377 pub xxl: f32,
378}
379
380impl Default for Spacing {
381 fn default() -> Self {
382 Self {
383 xs: 4.0,
384 sm: 8.0,
385 md: 12.0,
386 lg: 16.0,
387 xl: 24.0,
388 xxl: 32.0,
389 }
390 }
391}
392
393#[derive(Clone, Copy, Debug)]
394#[must_use]
395pub struct Elevation {
396 pub level0: f32,
397 pub level1: f32,
398 pub level2: f32,
399 pub level3: f32,
400 pub level4: f32,
401 pub level5: f32,
402}
403
404impl Default for Elevation {
405 fn default() -> Self {
406 Self {
407 level0: 0.0,
408 level1: 1.0,
409 level2: 3.0,
410 level3: 6.0,
411 level4: 8.0,
412 level5: 12.0,
413 }
414 }
415}
416
417#[derive(Clone, Copy, Debug)]
418#[must_use]
419pub struct Motion {
420 pub fast_ms: u32,
421 pub medium_ms: u32,
422 pub slow_ms: u32,
423}
424
425impl Default for Motion {
426 fn default() -> Self {
427 Self {
428 fast_ms: 120,
429 medium_ms: 240,
430 slow_ms: 360,
431 }
432 }
433}
434
435#[derive(Clone, Copy, Debug)]
436#[must_use]
437pub struct Theme {
438 pub colors: ColorScheme,
439 pub typography: Typography,
440 pub shapes: Shapes,
441 pub spacing: Spacing,
442 pub elevation: Elevation,
443 pub motion: Motion,
444
445 pub focus: Color,
446 pub scrollbar_track: Color,
447 pub scrollbar_thumb: Color,
448 pub button_bg: Color,
449 pub button_bg_hover: Color,
450 pub button_bg_pressed: Color,
451
452 pub background: Color,
453 pub on_background: Color,
454 pub surface: Color,
455 pub surface_variant: Color,
456 pub on_surface: Color,
457 pub on_surface_variant: Color,
458 pub surface_container_lowest: Color,
459 pub surface_container_low: Color,
460 pub surface_container: Color,
461 pub surface_container_high: Color,
462 pub surface_container_highest: Color,
463 pub surface_bright: Color,
464 pub surface_dim: Color,
465 pub surface_tint: Color,
466 pub primary: Color,
467 pub on_primary: Color,
468 pub primary_container: Color,
469 pub on_primary_container: Color,
470 pub secondary: Color,
471 pub on_secondary: Color,
472 pub secondary_container: Color,
473 pub on_secondary_container: Color,
474 pub tertiary: Color,
475 pub on_tertiary: Color,
476 pub tertiary_container: Color,
477 pub on_tertiary_container: Color,
478 pub error: Color,
479 pub on_error: Color,
480 pub error_container: Color,
481 pub on_error_container: Color,
482 pub inverse_surface: Color,
483 pub inverse_on_surface: Color,
484 pub inverse_primary: Color,
485 pub outline: Color,
486 pub outline_variant: Color,
487 pub scrim: Color,
488 pub shadow: Color,
489}
490
491impl Default for Theme {
492 fn default() -> Self {
493 let colors = ColorScheme::default();
494 Self {
495 background: colors.background,
496 on_background: colors.on_background,
497 surface: colors.surface,
498 surface_variant: colors.surface_variant,
499 on_surface: colors.on_surface,
500 on_surface_variant: colors.on_surface_variant,
501 surface_container_lowest: colors.surface_container_lowest,
502 surface_container_low: colors.surface_container_low,
503 surface_container: colors.surface_container,
504 surface_container_high: colors.surface_container_high,
505 surface_container_highest: colors.surface_container_highest,
506 surface_bright: colors.surface_bright,
507 surface_dim: colors.surface_dim,
508 surface_tint: colors.surface_tint,
509 primary: colors.primary,
510 on_primary: colors.on_primary,
511 primary_container: colors.primary_container,
512 on_primary_container: colors.on_primary_container,
513 secondary: colors.secondary,
514 on_secondary: colors.on_secondary,
515 secondary_container: colors.secondary_container,
516 on_secondary_container: colors.on_secondary_container,
517 tertiary: colors.tertiary,
518 on_tertiary: colors.on_tertiary,
519 tertiary_container: colors.tertiary_container,
520 on_tertiary_container: colors.on_tertiary_container,
521 error: colors.error,
522 on_error: colors.on_error,
523 error_container: colors.error_container,
524 on_error_container: colors.on_error_container,
525 inverse_surface: colors.inverse_surface,
526 inverse_on_surface: colors.inverse_on_surface,
527 inverse_primary: colors.inverse_primary,
528 outline: colors.outline,
529 outline_variant: colors.outline_variant,
530 scrim: colors.scrim,
531 shadow: colors.shadow,
532 colors,
533 typography: Typography::default(),
534 shapes: Shapes::default(),
535 spacing: Spacing::default(),
536 elevation: Elevation::default(),
537 motion: Motion::default(),
538 focus: colors.focus,
539 scrollbar_track: Color(0xDD, 0xDD, 0xDD, 32),
540 scrollbar_thumb: Color(0xDD, 0xDD, 0xDD, 140),
541 button_bg: colors.primary,
542 button_bg_hover: colors.primary_container,
543 button_bg_pressed: colors.on_primary_container,
544 }
545 }
546}
547
548impl Theme {
549 pub fn apply_colors(&mut self) {
550 self.background = self.colors.background;
551 self.on_background = self.colors.on_background;
552 self.surface = self.colors.surface;
553 self.surface_variant = self.colors.surface_variant;
554 self.on_surface = self.colors.on_surface;
555 self.on_surface_variant = self.colors.on_surface_variant;
556 self.surface_container_lowest = self.colors.surface_container_lowest;
557 self.surface_container_low = self.colors.surface_container_low;
558 self.surface_container = self.colors.surface_container;
559 self.surface_container_high = self.colors.surface_container_high;
560 self.surface_container_highest = self.colors.surface_container_highest;
561 self.surface_bright = self.colors.surface_bright;
562 self.surface_dim = self.colors.surface_dim;
563 self.surface_tint = self.colors.surface_tint;
564 self.primary = self.colors.primary;
565 self.on_primary = self.colors.on_primary;
566 self.primary_container = self.colors.primary_container;
567 self.on_primary_container = self.colors.on_primary_container;
568 self.secondary = self.colors.secondary;
569 self.on_secondary = self.colors.on_secondary;
570 self.secondary_container = self.colors.secondary_container;
571 self.on_secondary_container = self.colors.on_secondary_container;
572 self.tertiary = self.colors.tertiary;
573 self.on_tertiary = self.colors.on_tertiary;
574 self.tertiary_container = self.colors.tertiary_container;
575 self.on_tertiary_container = self.colors.on_tertiary_container;
576 self.error = self.colors.error;
577 self.on_error = self.colors.on_error;
578 self.error_container = self.colors.error_container;
579 self.on_error_container = self.colors.on_error_container;
580 self.inverse_surface = self.colors.inverse_surface;
581 self.inverse_on_surface = self.colors.inverse_on_surface;
582 self.inverse_primary = self.colors.inverse_primary;
583 self.outline = self.colors.outline;
584 self.outline_variant = self.colors.outline_variant;
585 self.scrim = self.colors.scrim;
586 self.shadow = self.colors.shadow;
587 self.focus = self.colors.focus;
588 self.button_bg = self.colors.primary;
589 self.button_bg_hover = self.colors.primary_container;
590 self.button_bg_pressed = self.colors.on_primary_container;
591 }
592
593 pub fn sync_colors_from_fields(&mut self) {
594 self.colors.background = self.background;
595 self.colors.on_background = self.on_background;
596 self.colors.surface = self.surface;
597 self.colors.surface_variant = self.surface_variant;
598 self.colors.on_surface = self.on_surface;
599 self.colors.on_surface_variant = self.on_surface_variant;
600 self.colors.surface_container_lowest = self.surface_container_lowest;
601 self.colors.surface_container_low = self.surface_container_low;
602 self.colors.surface_container = self.surface_container;
603 self.colors.surface_container_high = self.surface_container_high;
604 self.colors.surface_container_highest = self.surface_container_highest;
605 self.colors.surface_bright = self.surface_bright;
606 self.colors.surface_dim = self.surface_dim;
607 self.colors.surface_tint = self.surface_tint;
608 self.colors.primary = self.primary;
609 self.colors.on_primary = self.on_primary;
610 self.colors.primary_container = self.primary_container;
611 self.colors.on_primary_container = self.on_primary_container;
612 self.colors.secondary = self.secondary;
613 self.colors.on_secondary = self.on_secondary;
614 self.colors.secondary_container = self.secondary_container;
615 self.colors.on_secondary_container = self.on_secondary_container;
616 self.colors.tertiary = self.tertiary;
617 self.colors.on_tertiary = self.on_tertiary;
618 self.colors.tertiary_container = self.tertiary_container;
619 self.colors.on_tertiary_container = self.on_tertiary_container;
620 self.colors.error = self.error;
621 self.colors.on_error = self.on_error;
622 self.colors.error_container = self.error_container;
623 self.colors.on_error_container = self.on_error_container;
624 self.colors.inverse_surface = self.inverse_surface;
625 self.colors.inverse_on_surface = self.inverse_on_surface;
626 self.colors.inverse_primary = self.inverse_primary;
627 self.colors.outline = self.outline;
628 self.colors.outline_variant = self.outline_variant;
629 self.colors.scrim = self.scrim;
630 self.colors.shadow = self.shadow;
631 self.colors.focus = self.focus;
632 }
633
634 pub fn with_colors(mut self, colors: ColorScheme) -> Self {
635 self.colors = colors;
636 self.apply_colors();
637 self
638 }
639}
640
641#[derive(Clone, Copy, Debug)]
643pub struct Density {
644 pub scale: f32,
645}
646impl Default for Density {
647 fn default() -> Self {
648 Self { scale: 1.0 }
649 }
650}
651
652#[derive(Clone, Copy, Debug)]
654pub struct UiScale(pub f32);
655impl Default for UiScale {
656 fn default() -> Self {
657 Self(1.0)
658 }
659}
660
661#[derive(Clone, Copy, Debug)]
662pub struct TextScale(pub f32);
663impl Default for TextScale {
664 fn default() -> Self {
665 Self(1.0)
666 }
667}
668
669pub fn with_theme<R>(theme: Theme, f: impl FnOnce() -> R) -> R {
670 with_locals_frame(|| {
671 set_local_boxed(TypeId::of::<Theme>(), Box::new(theme));
672 f()
673 })
674}
675
676pub fn with_density<R>(density: Density, f: impl FnOnce() -> R) -> R {
677 with_locals_frame(|| {
678 set_local_boxed(TypeId::of::<Density>(), Box::new(density));
679 f()
680 })
681}
682
683pub fn with_ui_scale<R>(s: UiScale, f: impl FnOnce() -> R) -> R {
684 with_locals_frame(|| {
685 set_local_boxed(TypeId::of::<UiScale>(), Box::new(s));
686 f()
687 })
688}
689
690pub fn with_text_scale<R>(ts: TextScale, f: impl FnOnce() -> R) -> R {
691 with_locals_frame(|| {
692 set_local_boxed(TypeId::of::<TextScale>(), Box::new(ts));
693 f()
694 })
695}
696
697pub fn with_text_direction<R>(dir: TextDirection, f: impl FnOnce() -> R) -> R {
698 with_locals_frame(|| {
699 set_local_boxed(TypeId::of::<TextDirection>(), Box::new(dir));
700 f()
701 })
702}
703
704pub fn theme() -> Theme {
705 get_local::<Theme>().unwrap_or_else(|| defaults().read().theme)
706}
707
708pub fn density() -> Density {
709 get_local::<Density>().unwrap_or_else(|| defaults().read().density)
710}
711
712pub fn ui_scale() -> UiScale {
713 get_local::<UiScale>().unwrap_or_else(|| defaults().read().ui_scale)
714}
715
716pub fn text_scale() -> TextScale {
717 get_local::<TextScale>().unwrap_or_else(|| defaults().read().text_scale)
718}
719
720pub fn text_direction() -> TextDirection {
721 get_local::<TextDirection>().unwrap_or_else(|| defaults().read().text_direction)
722}