1use std::ops::Deref;
15
16use std::any::{Any, TypeId};
17use std::cell::RefCell;
18use std::collections::HashMap;
19use std::sync::OnceLock;
20
21use parking_lot::RwLock;
22
23use crate::Color;
24
25#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
26pub enum TextDirection {
27 #[default]
28 Ltr,
29 Rtl,
30}
31
32thread_local! {
33 static LOCALS_STACK: RefCell<Vec<HashMap<TypeId, Box<dyn Any>>>> = RefCell::new(Vec::new());
34}
35
36#[derive(Clone, Copy, Debug)]
37struct Defaults {
38 theme: Theme,
39 text_direction: TextDirection,
40 ui_scale: UiScale,
41 text_scale: TextScale,
42 density: Density,
43}
44
45impl Default for Defaults {
46 fn default() -> Self {
47 Self {
48 theme: Theme::default(),
49 text_direction: TextDirection::default(),
50 ui_scale: UiScale::default(),
51 text_scale: TextScale::default(),
52 density: Density::default(),
53 }
54 }
55}
56
57static DEFAULTS: OnceLock<RwLock<Defaults>> = OnceLock::new();
58
59fn defaults() -> &'static RwLock<Defaults> {
60 DEFAULTS.get_or_init(|| RwLock::new(Defaults::default()))
61}
62
63pub fn set_theme_default(t: Theme) {
65 defaults().write().theme = t;
66}
67
68pub fn set_text_direction_default(d: TextDirection) {
70 defaults().write().text_direction = d;
71}
72
73pub fn set_ui_scale_default(s: UiScale) {
75 defaults().write().ui_scale = UiScale(s.0.max(0.0));
76}
77
78pub fn set_text_scale_default(s: TextScale) {
80 defaults().write().text_scale = TextScale(s.0.max(0.0));
81}
82
83pub fn set_density_default(d: Density) {
86 defaults().write().density = Density {
87 scale: d.scale.max(0.0),
88 };
89}
90
91#[derive(Clone, Copy, Debug, PartialEq)]
95pub struct Dp(pub f32);
96
97impl Dp {
98 pub fn to_px(self) -> f32 {
100 self.0 * density().scale * ui_scale().0
101 }
102}
103
104pub fn dp_to_px(dp: f32) -> f32 {
106 Dp(dp).to_px()
107}
108
109fn with_locals_frame<R>(f: impl FnOnce() -> R) -> R {
110 struct Guard;
111 impl Drop for Guard {
112 fn drop(&mut self) {
113 LOCALS_STACK.with(|st| {
114 st.borrow_mut().pop();
115 });
116 }
117 }
118 LOCALS_STACK.with(|st| st.borrow_mut().push(HashMap::new()));
119 let _guard = Guard;
120 f()
121}
122
123fn set_local_boxed(t: TypeId, v: Box<dyn Any>) {
124 LOCALS_STACK.with(|st| {
125 if let Some(top) = st.borrow_mut().last_mut() {
126 top.insert(t, v);
127 } else {
128 let mut m = HashMap::new();
130 m.insert(t, v);
131 st.borrow_mut().push(m);
132 }
133 });
134}
135
136fn get_local<T: 'static + Copy>() -> Option<T> {
137 LOCALS_STACK.with(|st| {
138 for frame in st.borrow().iter().rev() {
139 if let Some(v) = frame.get(&TypeId::of::<T>())
140 && let Some(t) = v.downcast_ref::<T>()
141 {
142 return Some(*t);
143 }
144 }
145 None
146 })
147}
148
149#[derive(Clone, Copy, Debug)]
150#[must_use]
151pub struct ColorScheme {
152 pub primary: Color,
153 pub on_primary: Color,
154 pub primary_container: Color,
155 pub on_primary_container: Color,
156
157 pub secondary: Color,
158 pub on_secondary: Color,
159 pub secondary_container: Color,
160 pub on_secondary_container: Color,
161
162 pub tertiary: Color,
163 pub on_tertiary: Color,
164 pub tertiary_container: Color,
165 pub on_tertiary_container: Color,
166
167 pub error: Color,
168 pub on_error: Color,
169 pub error_container: Color,
170 pub on_error_container: Color,
171
172 pub background: Color,
173 pub on_background: Color,
174 pub surface: Color,
175 pub on_surface: Color,
176 pub surface_variant: Color,
177 pub on_surface_variant: Color,
178 pub surface_container_lowest: Color,
179 pub surface_container_low: Color,
180 pub surface_container: Color,
181 pub surface_container_high: Color,
182 pub surface_container_highest: Color,
183 pub surface_bright: Color,
184 pub surface_dim: Color,
185 pub surface_tint: Color,
186
187 pub inverse_surface: Color,
188 pub inverse_on_surface: Color,
189 pub inverse_primary: Color,
190
191 pub outline: Color,
192 pub outline_variant: Color,
193
194 pub scrim: Color,
195 pub shadow: Color,
196 pub focus: Color,
197}
198
199impl ColorScheme {
200 pub fn dark() -> Self {
201 Self {
202 primary: Color::from_hex("#69FDBE"),
203 on_primary: Color::from_hex("#003020"),
204 primary_container: Color::from_hex("#004D40"),
205 on_primary_container: Color::from_hex("#6FF7F6"),
206
207 secondary: Color::from_hex("#B3C9A7"),
208 on_secondary: Color::from_hex("#1C3519"),
209 secondary_container: Color::from_hex("#334D2E"),
210 on_secondary_container: Color::from_hex("#CCE8B3"),
211
212 tertiary: Color::from_hex("#FFC9C1"),
213 on_tertiary: Color::from_hex("#3F1619"),
214 tertiary_container: Color::from_hex("#5D1F22"),
215 on_tertiary_container: Color::from_hex("#FFDBD8"),
216
217 error: Color::from_hex("#F2B8B5"),
218 on_error: Color::from_hex("#601410"),
219 error_container: Color::from_hex("#8C1D18"),
220 on_error_container: Color::from_hex("#F9DEDC"),
221
222 background: Color::from_hex("#1A1C1E"),
223 on_background: Color::from_hex("#E6E1E5"),
224 surface: Color::from_hex("#1A1C1E"),
225 on_surface: Color::from_hex("#E6E1E5"),
226 surface_variant: Color::from_hex("#44474E"),
227 on_surface_variant: Color::from_hex("#C4C6CE"),
228 surface_container_lowest: Color::from_hex("#0A0A0C"),
229 surface_container_low: Color::from_hex("#141115"),
230 surface_container: Color::from_hex("#19131A"),
231 surface_container_high: Color::from_hex("#1F1B22"),
232 surface_container_highest: Color::from_hex("#2A2930"),
233 surface_bright: Color::from_hex("#26292F"),
234 surface_dim: Color::from_hex("#1A1C1E"),
235 surface_tint: Color::from_hex("#69FDBE"),
236
237 inverse_surface: Color::from_hex("#E6E1E5"),
238 inverse_on_surface: Color::from_hex("#2A2930"),
239 inverse_primary: Color::from_hex("#005048"),
240
241 outline: Color::from_hex("#74777F"),
242 outline_variant: Color::from_hex("#44474E"),
243
244 scrim: Color::from_hex("#000000"),
245 shadow: Color::from_hex("#000000"),
246 focus: Color::from_hex("#006A6A"),
247 }
248 }
249
250 pub fn light() -> Self {
251 Self {
252 primary: Color::from_hex("#006A6A"),
253 on_primary: Color::WHITE,
254 primary_container: Color::from_hex("#9EF0EC"),
255 on_primary_container: Color::from_hex("#002020"),
256
257 secondary: Color::from_hex("#586146"),
258 on_secondary: Color::WHITE,
259 secondary_container: Color::from_hex("#D8E3B8"),
260 on_secondary_container: Color::from_hex("#161C0A"),
261
262 tertiary: Color::from_hex("#744639"),
263 on_tertiary: Color::WHITE,
264 tertiary_container: Color::from_hex("#FFD9CD"),
265 on_tertiary_container: Color::from_hex("#2C0E07"),
266
267 error: Color::from_hex("#BA1A1A"),
268 on_error: Color::WHITE,
269 error_container: Color::from_hex("#FFDAD6"),
270 on_error_container: Color::from_hex("#410002"),
271
272 background: Color::from_hex("#FEF7FF"),
273 on_background: Color::from_hex("#1A1C1E"),
274 surface: Color::from_hex("#FEF7FF"),
275 on_surface: Color::from_hex("#1A1C1E"),
276 surface_variant: Color::from_hex("#E1E3DE"),
277 on_surface_variant: Color::from_hex("#44474E"),
278 surface_container_lowest: Color::WHITE,
279 surface_container_low: Color::from_hex("#F4F5F0"),
280 surface_container: Color::from_hex("#EEF0E9"),
281 surface_container_high: Color::from_hex("#E9EAE4"),
282 surface_container_highest: Color::from_hex("#E3E5DF"),
283 surface_bright: Color::from_hex("#FEF7FF"),
284 surface_dim: Color::from_hex("#DEDAD0"),
285 surface_tint: Color::from_hex("#006A6A"),
286
287 inverse_surface: Color::from_hex("#2F3033"),
288 inverse_on_surface: Color::from_hex("#F1F0F4"),
289 inverse_primary: Color::from_hex("#69FDBE"),
290
291 outline: Color::from_hex("#74777F"),
292 outline_variant: Color::from_hex("#C4C6CE"),
293
294 scrim: Color::from_hex("#000000"),
295 shadow: Color::from_hex("#000000"),
296 focus: Color::from_hex("#1D4ED8"),
297 }
298 }
299}
300
301impl Default for ColorScheme {
302 fn default() -> Self {
303 Self::dark()
304 }
305}
306
307#[derive(Clone, Copy, Debug)]
308#[must_use]
309pub struct Typography {
310 pub display_large: f32,
311 pub display_medium: f32,
312 pub display_small: f32,
313 pub headline_large: f32,
314 pub headline_medium: f32,
315 pub headline_small: f32,
316 pub title_large: f32,
317 pub title_medium: f32,
318 pub title_small: f32,
319 pub body_large: f32,
320 pub body_medium: f32,
321 pub body_small: f32,
322 pub label_large: f32,
323 pub label_medium: f32,
324 pub label_small: f32,
325}
326
327impl Default for Typography {
328 fn default() -> Self {
329 Self {
330 display_large: 57.0,
331 display_medium: 45.0,
332 display_small: 36.0,
333 headline_large: 32.0,
334 headline_medium: 28.0,
335 headline_small: 24.0,
336 title_large: 22.0,
337 title_medium: 16.0,
338 title_small: 14.0,
339 body_large: 16.0,
340 body_medium: 14.0,
341 body_small: 12.0,
342 label_large: 14.0,
343 label_medium: 12.0,
344 label_small: 11.0,
345 }
346 }
347}
348
349#[derive(Clone, Copy, Debug)]
350#[must_use]
351pub struct Shapes {
352 pub extra_small: f32,
353 pub small: f32,
354 pub medium: f32,
355 pub large: f32,
356 pub extra_large: f32,
357}
358
359impl Default for Shapes {
360 fn default() -> Self {
361 Self {
362 extra_small: 4.0,
363 small: 8.0,
364 medium: 12.0,
365 large: 16.0,
366 extra_large: 28.0,
367 }
368 }
369}
370
371#[derive(Clone, Copy, Debug)]
372#[must_use]
373pub struct Spacing {
374 pub xs: f32,
375 pub sm: f32,
376 pub md: f32,
377 pub lg: f32,
378 pub xl: f32,
379 pub xxl: f32,
380}
381
382impl Default for Spacing {
383 fn default() -> Self {
384 Self {
385 xs: 4.0,
386 sm: 8.0,
387 md: 12.0,
388 lg: 16.0,
389 xl: 24.0,
390 xxl: 32.0,
391 }
392 }
393}
394
395#[derive(Clone, Copy, Debug)]
396#[must_use]
397pub struct Elevation {
398 pub level0: f32,
399 pub level1: f32,
400 pub level2: f32,
401 pub level3: f32,
402 pub level4: f32,
403 pub level5: f32,
404}
405
406impl Default for Elevation {
407 fn default() -> Self {
408 Self {
409 level0: 0.0,
410 level1: 1.0,
411 level2: 3.0,
412 level3: 6.0,
413 level4: 8.0,
414 level5: 12.0,
415 }
416 }
417}
418
419#[derive(Clone, Copy, Debug)]
420#[must_use]
421pub struct Motion {
422 pub fast_ms: u32,
423 pub medium_ms: u32,
424 pub slow_ms: u32,
425}
426
427impl Default for Motion {
428 fn default() -> Self {
429 Self {
430 fast_ms: 120,
431 medium_ms: 240,
432 slow_ms: 360,
433 }
434 }
435}
436
437#[derive(Clone, Copy, Debug)]
438#[must_use]
439pub struct Theme {
440 pub colors: ColorScheme,
441 pub typography: Typography,
442 pub shapes: Shapes,
443 pub spacing: Spacing,
444 pub elevation: Elevation,
445 pub motion: Motion,
446
447 pub focus: Color,
448 pub scrollbar_track: Color,
449 pub scrollbar_thumb: Color,
450 pub button_bg: Color,
451 pub button_bg_hover: Color,
452 pub button_bg_pressed: Color,
453}
454
455impl Deref for Theme {
456 type Target = ColorScheme;
457 fn deref(&self) -> &Self::Target {
458 &self.colors
459 }
460}
461
462impl Default for Theme {
463 fn default() -> Self {
464 let colors = ColorScheme::default();
465 Self {
466 colors,
467 typography: Typography::default(),
468 shapes: Shapes::default(),
469 spacing: Spacing::default(),
470 elevation: Elevation::default(),
471 motion: Motion::default(),
472 focus: colors.focus,
473 scrollbar_track: Color(0xDD, 0xDD, 0xDD, 32),
474 scrollbar_thumb: Color(0xDD, 0xDD, 0xDD, 140),
475 button_bg: colors.primary,
476 button_bg_hover: colors.primary_container,
477 button_bg_pressed: colors.on_primary_container,
478 }
479 }
480}
481
482impl Theme {
483 pub fn with_colors(mut self, colors: ColorScheme) -> Self {
484 self.colors = colors;
485 self
486 }
487}
488
489#[derive(Clone, Copy, Debug)]
491pub struct Density {
492 pub scale: f32,
493}
494impl Default for Density {
495 fn default() -> Self {
496 Self { scale: 1.0 }
497 }
498}
499
500#[derive(Clone, Copy, Debug)]
502pub struct UiScale(pub f32);
503impl Default for UiScale {
504 fn default() -> Self {
505 Self(1.0)
506 }
507}
508
509#[derive(Clone, Copy, Debug)]
510pub struct TextScale(pub f32);
511impl Default for TextScale {
512 fn default() -> Self {
513 Self(1.0)
514 }
515}
516
517pub fn with_theme<R>(theme: Theme, f: impl FnOnce() -> R) -> R {
518 with_locals_frame(|| {
519 set_local_boxed(TypeId::of::<Theme>(), Box::new(theme));
520 f()
521 })
522}
523
524pub fn with_density<R>(density: Density, f: impl FnOnce() -> R) -> R {
525 with_locals_frame(|| {
526 set_local_boxed(TypeId::of::<Density>(), Box::new(density));
527 f()
528 })
529}
530
531pub fn with_ui_scale<R>(s: UiScale, f: impl FnOnce() -> R) -> R {
532 with_locals_frame(|| {
533 set_local_boxed(TypeId::of::<UiScale>(), Box::new(s));
534 f()
535 })
536}
537
538pub fn with_text_scale<R>(ts: TextScale, f: impl FnOnce() -> R) -> R {
539 with_locals_frame(|| {
540 set_local_boxed(TypeId::of::<TextScale>(), Box::new(ts));
541 f()
542 })
543}
544
545pub fn with_text_direction<R>(dir: TextDirection, f: impl FnOnce() -> R) -> R {
546 with_locals_frame(|| {
547 set_local_boxed(TypeId::of::<TextDirection>(), Box::new(dir));
548 f()
549 })
550}
551
552#[derive(Clone, Copy, Debug)]
553pub struct ContentColor(pub Color);
554
555pub fn with_content_color<R>(color: Color, f: impl FnOnce() -> R) -> R {
556 with_locals_frame(|| {
557 set_local_boxed(TypeId::of::<ContentColor>(), Box::new(ContentColor(color)));
558 f()
559 })
560}
561
562pub fn content_color() -> Color {
563 get_local::<ContentColor>()
564 .map(|c| c.0)
565 .unwrap_or_else(|| theme().on_surface)
566}
567
568macro_rules! def_local_getter {
569 ($fn_name:ident, $ty:ty, $default_field:ident) => {
570 pub fn $fn_name() -> $ty {
571 get_local::<$ty>().unwrap_or_else(|| defaults().read().$default_field)
572 }
573 };
574}
575
576def_local_getter!(theme, Theme, theme);
577def_local_getter!(density, Density, density);
578def_local_getter!(ui_scale, UiScale, ui_scale);
579def_local_getter!(text_scale, TextScale, text_scale);
580def_local_getter!(text_direction, TextDirection, text_direction);