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
20/// density‑independent pixels (dp)
21#[derive(Clone, Copy, Debug, PartialEq)]
22pub struct Dp(pub f32);
23
24impl Dp {
25    /// Converts this dp value into physical pixels using the current Density.
26    pub fn to_px(self) -> f32 {
27        self.0 * density().scale
28    }
29}
30
31/// Convenience: convert a raw dp scalar into px using current Density.
32pub fn dp_to_px(dp: f32) -> f32 {
33    Dp(dp).to_px()
34}
35
36pub fn with_text_direction<R>(dir: TextDirection, f: impl FnOnce() -> R) -> R {
37    with_locals_frame(|| {
38        set_local_boxed(std::any::TypeId::of::<TextDirection>(), Box::new(dir));
39        f()
40    })
41}
42
43pub fn text_direction() -> TextDirection {
44    LOCALS_STACK.with(|st| {
45        for frame in st.borrow().iter().rev() {
46            if let Some(v) = frame.get(&std::any::TypeId::of::<TextDirection>()) {
47                if let Some(d) = v.downcast_ref::<TextDirection>() {
48                    return *d;
49                }
50            }
51        }
52        TextDirection::default()
53    })
54}
55
56fn with_locals_frame<R>(f: impl FnOnce() -> R) -> R {
57    // Non-panicking frame guard (ensures pop on unwind)
58    struct Guard;
59    impl Drop for Guard {
60        fn drop(&mut self) {
61            LOCALS_STACK.with(|st| {
62                st.borrow_mut().pop();
63            });
64        }
65    }
66    LOCALS_STACK.with(|st| st.borrow_mut().push(HashMap::new()));
67    let _guard = Guard;
68    f()
69}
70
71fn set_local_boxed(t: TypeId, v: Box<dyn Any>) {
72    LOCALS_STACK.with(|st| {
73        if let Some(top) = st.borrow_mut().last_mut() {
74            top.insert(t, v);
75        } else {
76            // no frame: create a temporary one
77            let mut m = HashMap::new();
78            m.insert(t, v);
79            st.borrow_mut().push(m);
80        }
81    });
82}
83
84// Typed API
85
86#[derive(Clone, Copy, Debug)]
87pub struct Theme {
88    pub background: crate::Color,
89    pub surface: crate::Color,
90    pub on_surface: crate::Color,
91    pub primary: crate::Color,
92    pub on_primary: crate::Color,
93    pub outline: crate::Color,           // Extra: borders, dividers
94    pub focus: crate::Color,             // focus rings / a11y highlights
95    pub button_bg: crate::Color,         // default button background
96    pub button_bg_hover: crate::Color,   // hover state
97    pub button_bg_pressed: crate::Color, // pressed state
98    pub scrollbar_track: crate::Color,   // low-emphasis track
99    pub scrollbar_thumb: crate::Color,   // higher-emphasis thumb
100}
101impl Default for Theme {
102    fn default() -> Self {
103        Self {
104            background: crate::Color::from_hex("#121212"),
105            surface: crate::Color::from_hex("#1E1E1E"),
106            on_surface: crate::Color::from_hex("#DDDDDD"),
107            primary: crate::Color::from_hex("#34AF82"),
108            on_primary: crate::Color::WHITE,
109            outline: crate::Color::from_hex("#555555"),
110            focus: crate::Color::from_hex("#88CCFF"),
111            button_bg: crate::Color::from_hex("#34AF82"),
112            button_bg_hover: crate::Color::from_hex("#2A8F6A"),
113            button_bg_pressed: crate::Color::from_hex("#1F7556"),
114            scrollbar_track: crate::Color(0xDD, 0xDD, 0xDD, 32),
115            scrollbar_thumb: crate::Color(0xDD, 0xDD, 0xDD, 140),
116        }
117    }
118}
119
120#[derive(Clone, Copy, Debug)]
121pub struct Density {
122    pub scale: f32, // dp→px multiplier
123}
124impl Default for Density {
125    fn default() -> Self {
126        Self { scale: 1.0 }
127    }
128}
129
130#[derive(Clone, Copy, Debug)]
131pub struct TextScale(pub f32);
132impl Default for TextScale {
133    fn default() -> Self {
134        Self(1.0)
135    }
136}
137
138// Provide helpers (push a new frame, set the local, run closure, pop frame)
139
140pub fn with_theme<R>(theme: Theme, f: impl FnOnce() -> R) -> R {
141    with_locals_frame(|| {
142        set_local_boxed(TypeId::of::<Theme>(), Box::new(theme));
143        f()
144    })
145}
146
147pub fn with_density<R>(density: Density, f: impl FnOnce() -> R) -> R {
148    with_locals_frame(|| {
149        set_local_boxed(TypeId::of::<Density>(), Box::new(density));
150        f()
151    })
152}
153
154pub fn with_text_scale<R>(ts: TextScale, f: impl FnOnce() -> R) -> R {
155    with_locals_frame(|| {
156        set_local_boxed(TypeId::of::<TextScale>(), Box::new(ts));
157        f()
158    })
159}
160
161// Getters with defaults if not set
162
163pub fn theme() -> Theme {
164    LOCALS_STACK.with(|st| {
165        for frame in st.borrow().iter().rev() {
166            if let Some(v) = frame.get(&TypeId::of::<Theme>()) {
167                if let Some(t) = v.downcast_ref::<Theme>() {
168                    return *t;
169                }
170            }
171        }
172        Theme::default()
173    })
174}
175
176pub fn density() -> Density {
177    LOCALS_STACK.with(|st| {
178        for frame in st.borrow().iter().rev() {
179            if let Some(v) = frame.get(&TypeId::of::<Density>()) {
180                if let Some(d) = v.downcast_ref::<Density>() {
181                    return *d;
182                }
183            }
184        }
185        Density::default()
186    })
187}
188
189pub fn text_scale() -> TextScale {
190    LOCALS_STACK.with(|st| {
191        for frame in st.borrow().iter().rev() {
192            if let Some(v) = frame.get(&TypeId::of::<TextScale>()) {
193                if let Some(ts) = v.downcast_ref::<TextScale>() {
194                    return *ts;
195                }
196            }
197        }
198        TextScale::default()
199    })
200}