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 background: Color,
151 pub surface: Color,
152 pub surface_variant: Color,
153 pub on_surface: Color,
154 pub on_surface_variant: Color,
155
156 pub primary: Color,
157 pub on_primary: Color,
158 pub secondary: Color,
159 pub on_secondary: Color,
160 pub tertiary: Color,
161 pub on_tertiary: Color,
162
163 pub outline: Color,
164 pub outline_variant: Color,
165 pub error: Color,
166 pub on_error: Color,
167 pub focus: Color,
168}
169
170impl Default for ColorScheme {
171 fn default() -> Self {
172 Self {
173 background: Color::from_hex("#121212"),
174 surface: Color::from_hex("#1E1E1E"),
175 surface_variant: Color::from_hex("#2A2A2A"),
176 on_surface: Color::from_hex("#DDDDDD"),
177 on_surface_variant: Color::from_hex("#B8B8B8"),
178 primary: Color::from_hex("#34AF82"),
179 on_primary: Color::WHITE,
180 secondary: Color::from_hex("#7BB6FF"),
181 on_secondary: Color::from_hex("#0E1A2B"),
182 tertiary: Color::from_hex("#E7A3FF"),
183 on_tertiary: Color::from_hex("#2B0E2B"),
184 outline: Color::from_hex("#555555"),
185 outline_variant: Color::from_hex("#3A3A3A"),
186 error: Color::from_hex("#AE3636"),
187 on_error: Color::WHITE,
188 focus: Color::from_hex("#88CCFF"),
189 }
190 }
191}
192
193#[derive(Clone, Copy, Debug)]
194#[must_use]
195pub struct Typography {
196 pub display_large: f32,
197 pub display_medium: f32,
198 pub display_small: f32,
199 pub headline_large: f32,
200 pub headline_medium: f32,
201 pub headline_small: f32,
202 pub title_large: f32,
203 pub title_medium: f32,
204 pub title_small: f32,
205 pub body_large: f32,
206 pub body_medium: f32,
207 pub body_small: f32,
208 pub label_large: f32,
209 pub label_medium: f32,
210 pub label_small: f32,
211}
212
213impl Default for Typography {
214 fn default() -> Self {
215 Self {
216 display_large: 57.0,
217 display_medium: 45.0,
218 display_small: 36.0,
219 headline_large: 32.0,
220 headline_medium: 28.0,
221 headline_small: 24.0,
222 title_large: 22.0,
223 title_medium: 16.0,
224 title_small: 14.0,
225 body_large: 16.0,
226 body_medium: 14.0,
227 body_small: 12.0,
228 label_large: 14.0,
229 label_medium: 12.0,
230 label_small: 11.0,
231 }
232 }
233}
234
235#[derive(Clone, Copy, Debug)]
236#[must_use]
237pub struct Shapes {
238 pub extra_small: f32,
239 pub small: f32,
240 pub medium: f32,
241 pub large: f32,
242 pub extra_large: f32,
243}
244
245impl Default for Shapes {
246 fn default() -> Self {
247 Self {
248 extra_small: 4.0,
249 small: 8.0,
250 medium: 12.0,
251 large: 16.0,
252 extra_large: 28.0,
253 }
254 }
255}
256
257#[derive(Clone, Copy, Debug)]
258#[must_use]
259pub struct Spacing {
260 pub xs: f32,
261 pub sm: f32,
262 pub md: f32,
263 pub lg: f32,
264 pub xl: f32,
265 pub xxl: f32,
266}
267
268impl Default for Spacing {
269 fn default() -> Self {
270 Self {
271 xs: 4.0,
272 sm: 8.0,
273 md: 12.0,
274 lg: 16.0,
275 xl: 24.0,
276 xxl: 32.0,
277 }
278 }
279}
280
281#[derive(Clone, Copy, Debug)]
282#[must_use]
283pub struct Elevation {
284 pub level0: f32,
285 pub level1: f32,
286 pub level2: f32,
287 pub level3: f32,
288 pub level4: f32,
289 pub level5: f32,
290}
291
292impl Default for Elevation {
293 fn default() -> Self {
294 Self {
295 level0: 0.0,
296 level1: 1.0,
297 level2: 3.0,
298 level3: 6.0,
299 level4: 8.0,
300 level5: 12.0,
301 }
302 }
303}
304
305#[derive(Clone, Copy, Debug)]
306#[must_use]
307pub struct Motion {
308 pub fast_ms: u32,
309 pub medium_ms: u32,
310 pub slow_ms: u32,
311}
312
313impl Default for Motion {
314 fn default() -> Self {
315 Self {
316 fast_ms: 120,
317 medium_ms: 240,
318 slow_ms: 360,
319 }
320 }
321}
322
323#[derive(Clone, Copy, Debug)]
324#[must_use]
325pub struct Theme {
326 pub colors: ColorScheme,
327 pub typography: Typography,
328 pub shapes: Shapes,
329 pub spacing: Spacing,
330 pub elevation: Elevation,
331 pub motion: Motion,
332
333 pub focus: Color,
334 pub scrollbar_track: Color,
335 pub scrollbar_thumb: Color,
336 pub button_bg: Color,
337 pub button_bg_hover: Color,
338 pub button_bg_pressed: Color,
339
340 pub background: Color,
341 pub surface: Color,
342 pub surface_variant: Color,
343 pub on_surface: Color,
344 pub on_surface_variant: Color,
345 pub primary: Color,
346 pub on_primary: Color,
347 pub secondary: Color,
348 pub on_secondary: Color,
349 pub tertiary: Color,
350 pub on_tertiary: Color,
351 pub outline: Color,
352 pub outline_variant: Color,
353 pub error: Color,
354 pub on_error: Color,
355}
356
357impl Default for Theme {
358 fn default() -> Self {
359 let colors = ColorScheme::default();
360 Self {
361 background: colors.background,
362 surface: colors.surface,
363 surface_variant: colors.surface_variant,
364 on_surface: colors.on_surface,
365 on_surface_variant: colors.on_surface_variant,
366 primary: colors.primary,
367 on_primary: colors.on_primary,
368 secondary: colors.secondary,
369 on_secondary: colors.on_secondary,
370 tertiary: colors.tertiary,
371 on_tertiary: colors.on_tertiary,
372 outline: colors.outline,
373 outline_variant: colors.outline_variant,
374 error: colors.error,
375 on_error: colors.on_error,
376 colors,
377 typography: Typography::default(),
378 shapes: Shapes::default(),
379 spacing: Spacing::default(),
380 elevation: Elevation::default(),
381 motion: Motion::default(),
382 focus: colors.focus,
383 scrollbar_track: Color(0xDD, 0xDD, 0xDD, 32),
384 scrollbar_thumb: Color(0xDD, 0xDD, 0xDD, 140),
385 button_bg: colors.primary,
386 button_bg_hover: Color::from_hex("#2A8F6A"),
387 button_bg_pressed: Color::from_hex("#1F7556"),
388 }
389 }
390}
391
392impl Theme {
393 pub fn apply_colors(&mut self) {
394 self.background = self.colors.background;
395 self.surface = self.colors.surface;
396 self.surface_variant = self.colors.surface_variant;
397 self.on_surface = self.colors.on_surface;
398 self.on_surface_variant = self.colors.on_surface_variant;
399 self.primary = self.colors.primary;
400 self.on_primary = self.colors.on_primary;
401 self.secondary = self.colors.secondary;
402 self.on_secondary = self.colors.on_secondary;
403 self.tertiary = self.colors.tertiary;
404 self.on_tertiary = self.colors.on_tertiary;
405 self.outline = self.colors.outline;
406 self.outline_variant = self.colors.outline_variant;
407 self.error = self.colors.error;
408 self.on_error = self.colors.on_error;
409 self.focus = self.colors.focus;
410 self.button_bg = self.colors.primary;
411 }
412
413 pub fn sync_colors_from_fields(&mut self) {
414 self.colors.background = self.background;
415 self.colors.surface = self.surface;
416 self.colors.surface_variant = self.surface_variant;
417 self.colors.on_surface = self.on_surface;
418 self.colors.on_surface_variant = self.on_surface_variant;
419 self.colors.primary = self.primary;
420 self.colors.on_primary = self.on_primary;
421 self.colors.secondary = self.secondary;
422 self.colors.on_secondary = self.on_secondary;
423 self.colors.tertiary = self.tertiary;
424 self.colors.on_tertiary = self.on_tertiary;
425 self.colors.outline = self.outline;
426 self.colors.outline_variant = self.outline_variant;
427 self.colors.error = self.error;
428 self.colors.on_error = self.on_error;
429 self.colors.focus = self.focus;
430 }
431
432 pub fn with_colors(mut self, colors: ColorScheme) -> Self {
433 self.colors = colors;
434 self.apply_colors();
435 self
436 }
437}
438
439#[derive(Clone, Copy, Debug)]
441pub struct Density {
442 pub scale: f32,
443}
444impl Default for Density {
445 fn default() -> Self {
446 Self { scale: 1.0 }
447 }
448}
449
450#[derive(Clone, Copy, Debug)]
452pub struct UiScale(pub f32);
453impl Default for UiScale {
454 fn default() -> Self {
455 Self(1.0)
456 }
457}
458
459#[derive(Clone, Copy, Debug)]
460pub struct TextScale(pub f32);
461impl Default for TextScale {
462 fn default() -> Self {
463 Self(1.0)
464 }
465}
466
467pub fn with_theme<R>(theme: Theme, f: impl FnOnce() -> R) -> R {
468 with_locals_frame(|| {
469 set_local_boxed(TypeId::of::<Theme>(), Box::new(theme));
470 f()
471 })
472}
473
474pub fn with_density<R>(density: Density, f: impl FnOnce() -> R) -> R {
475 with_locals_frame(|| {
476 set_local_boxed(TypeId::of::<Density>(), Box::new(density));
477 f()
478 })
479}
480
481pub fn with_ui_scale<R>(s: UiScale, f: impl FnOnce() -> R) -> R {
482 with_locals_frame(|| {
483 set_local_boxed(TypeId::of::<UiScale>(), Box::new(s));
484 f()
485 })
486}
487
488pub fn with_text_scale<R>(ts: TextScale, f: impl FnOnce() -> R) -> R {
489 with_locals_frame(|| {
490 set_local_boxed(TypeId::of::<TextScale>(), Box::new(ts));
491 f()
492 })
493}
494
495pub fn with_text_direction<R>(dir: TextDirection, f: impl FnOnce() -> R) -> R {
496 with_locals_frame(|| {
497 set_local_boxed(TypeId::of::<TextDirection>(), Box::new(dir));
498 f()
499 })
500}
501
502pub fn theme() -> Theme {
503 get_local::<Theme>().unwrap_or_else(|| defaults().read().theme)
504}
505
506pub fn density() -> Density {
507 get_local::<Density>().unwrap_or_else(|| defaults().read().density)
508}
509
510pub fn ui_scale() -> UiScale {
511 get_local::<UiScale>().unwrap_or_else(|| defaults().read().ui_scale)
512}
513
514pub fn text_scale() -> TextScale {
515 get_local::<TextScale>().unwrap_or_else(|| defaults().read().text_scale)
516}
517
518pub fn text_direction() -> TextDirection {
519 get_local::<TextDirection>().unwrap_or_else(|| defaults().read().text_direction)
520}