repose_core/
locals.rs

1use std::any::{Any, TypeId};
2use std::cell::RefCell;
3use std::collections::HashMap;
4
5#[derive(Clone, Copy, Debug, PartialEq, Eq)]
6pub enum TextDirection {
7    Ltr,
8    Rtl,
9}
10impl Default for TextDirection {
11    fn default() -> Self {
12        TextDirection::Ltr
13    }
14}
15
16thread_local! {
17    static LOCALS_STACK: RefCell<Vec<HashMap<TypeId, Box<dyn Any>>>> = RefCell::new(Vec::new());
18}
19
20pub fn with_text_direction<R>(dir: TextDirection, f: impl FnOnce() -> R) -> R {
21    with_locals_frame(|| {
22        set_local_boxed(std::any::TypeId::of::<TextDirection>(), Box::new(dir));
23        f()
24    })
25}
26
27pub fn text_direction() -> TextDirection {
28    LOCALS_STACK.with(|st| {
29        for frame in st.borrow().iter().rev() {
30            if let Some(v) = frame.get(&std::any::TypeId::of::<TextDirection>()) {
31                if let Some(d) = v.downcast_ref::<TextDirection>() {
32                    return *d;
33                }
34            }
35        }
36        TextDirection::default()
37    })
38}
39
40fn with_locals_frame<R>(f: impl FnOnce() -> R) -> R {
41    LOCALS_STACK.with(|st| st.borrow_mut().push(HashMap::new()));
42    let out = f();
43    LOCALS_STACK.with(|st| st.borrow_mut().pop());
44    out
45}
46
47fn set_local_boxed(t: TypeId, v: Box<dyn Any>) {
48    LOCALS_STACK.with(|st| {
49        if let Some(top) = st.borrow_mut().last_mut() {
50            top.insert(t, v);
51        } else {
52            // no frame: create a temporary one
53            let mut m = HashMap::new();
54            m.insert(t, v);
55            st.borrow_mut().push(m);
56        }
57    });
58}
59
60// Typed API
61
62#[derive(Clone, Copy, Debug)]
63pub struct Theme {
64    pub background: crate::Color,
65    pub surface: crate::Color,
66    pub on_surface: crate::Color,
67    pub primary: crate::Color,
68    pub on_primary: crate::Color,
69}
70impl Default for Theme {
71    fn default() -> Self {
72        Self {
73            background: crate::Color::from_hex("#121212"),
74            surface: crate::Color::from_hex("#1E1E1E"),
75            on_surface: crate::Color::from_hex("#DDDDDD"),
76            primary: crate::Color::from_hex("#34AF82"),
77            on_primary: crate::Color::WHITE,
78        }
79    }
80}
81
82#[derive(Clone, Copy, Debug)]
83pub struct Density {
84    pub scale: f32, // dp→px multiplier
85}
86impl Default for Density {
87    fn default() -> Self {
88        Self { scale: 1.0 }
89    }
90}
91
92#[derive(Clone, Copy, Debug)]
93pub struct TextScale(pub f32);
94impl Default for TextScale {
95    fn default() -> Self {
96        Self(1.0)
97    }
98}
99
100// Provide helpers (push a new frame, set the local, run closure, pop frame)
101
102pub fn with_theme<R>(theme: Theme, f: impl FnOnce() -> R) -> R {
103    with_locals_frame(|| {
104        set_local_boxed(TypeId::of::<Theme>(), Box::new(theme));
105        f()
106    })
107}
108
109pub fn with_density<R>(density: Density, f: impl FnOnce() -> R) -> R {
110    with_locals_frame(|| {
111        set_local_boxed(TypeId::of::<Density>(), Box::new(density));
112        f()
113    })
114}
115
116pub fn with_text_scale<R>(ts: TextScale, f: impl FnOnce() -> R) -> R {
117    with_locals_frame(|| {
118        set_local_boxed(TypeId::of::<TextScale>(), Box::new(ts));
119        f()
120    })
121}
122
123// Getters with defaults if not set
124
125pub fn theme() -> Theme {
126    LOCALS_STACK.with(|st| {
127        for frame in st.borrow().iter().rev() {
128            if let Some(v) = frame.get(&TypeId::of::<Theme>()) {
129                if let Some(t) = v.downcast_ref::<Theme>() {
130                    return *t;
131                }
132            }
133        }
134        Theme::default()
135    })
136}
137
138pub fn density() -> Density {
139    LOCALS_STACK.with(|st| {
140        for frame in st.borrow().iter().rev() {
141            if let Some(v) = frame.get(&TypeId::of::<Density>()) {
142                if let Some(d) = v.downcast_ref::<Density>() {
143                    return *d;
144                }
145            }
146        }
147        Density::default()
148    })
149}
150
151pub fn text_scale() -> TextScale {
152    LOCALS_STACK.with(|st| {
153        for frame in st.borrow().iter().rev() {
154            if let Some(v) = frame.get(&TypeId::of::<TextScale>()) {
155                if let Some(ts) = v.downcast_ref::<TextScale>() {
156                    return *ts;
157                }
158            }
159        }
160        TextScale::default()
161    })
162}
163
164// Convenience
165
166pub fn dp(px: f32) -> f32 {
167    px * density().scale
168}