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)]
148pub struct Theme {
149 pub background: Color,
150 pub surface: Color,
151 pub on_surface: Color,
152
153 pub primary: Color,
154 pub on_primary: Color,
155
156 pub outline: Color,
157 pub focus: Color,
158
159 pub button_bg: Color,
160 pub button_bg_hover: Color,
161 pub button_bg_pressed: Color,
162
163 pub scrollbar_track: Color,
164 pub scrollbar_thumb: Color,
165
166 pub error: Color,
167}
168
169impl Default for Theme {
170 fn default() -> Self {
171 Self {
172 background: Color::from_hex("#121212"),
173 surface: Color::from_hex("#1E1E1E"),
174 on_surface: Color::from_hex("#DDDDDD"),
175 primary: Color::from_hex("#34AF82"),
176 on_primary: Color::WHITE,
177 outline: Color::from_hex("#555555"),
178 focus: Color::from_hex("#88CCFF"),
179 button_bg: Color::from_hex("#34AF82"),
180 button_bg_hover: Color::from_hex("#2A8F6A"),
181 button_bg_pressed: Color::from_hex("#1F7556"),
182 scrollbar_track: Color(0xDD, 0xDD, 0xDD, 32),
183 scrollbar_thumb: Color(0xDD, 0xDD, 0xDD, 140),
184 error: Color::from_hex("#ae3636"),
185 }
186 }
187}
188
189#[derive(Clone, Copy, Debug)]
191pub struct Density {
192 pub scale: f32,
193}
194impl Default for Density {
195 fn default() -> Self {
196 Self { scale: 1.0 }
197 }
198}
199
200#[derive(Clone, Copy, Debug)]
202pub struct UiScale(pub f32);
203impl Default for UiScale {
204 fn default() -> Self {
205 Self(1.0)
206 }
207}
208
209#[derive(Clone, Copy, Debug)]
210pub struct TextScale(pub f32);
211impl Default for TextScale {
212 fn default() -> Self {
213 Self(1.0)
214 }
215}
216
217pub fn with_theme<R>(theme: Theme, f: impl FnOnce() -> R) -> R {
218 with_locals_frame(|| {
219 set_local_boxed(TypeId::of::<Theme>(), Box::new(theme));
220 f()
221 })
222}
223
224pub fn with_density<R>(density: Density, f: impl FnOnce() -> R) -> R {
225 with_locals_frame(|| {
226 set_local_boxed(TypeId::of::<Density>(), Box::new(density));
227 f()
228 })
229}
230
231pub fn with_ui_scale<R>(s: UiScale, f: impl FnOnce() -> R) -> R {
232 with_locals_frame(|| {
233 set_local_boxed(TypeId::of::<UiScale>(), Box::new(s));
234 f()
235 })
236}
237
238pub fn with_text_scale<R>(ts: TextScale, f: impl FnOnce() -> R) -> R {
239 with_locals_frame(|| {
240 set_local_boxed(TypeId::of::<TextScale>(), Box::new(ts));
241 f()
242 })
243}
244
245pub fn with_text_direction<R>(dir: TextDirection, f: impl FnOnce() -> R) -> R {
246 with_locals_frame(|| {
247 set_local_boxed(TypeId::of::<TextDirection>(), Box::new(dir));
248 f()
249 })
250}
251
252pub fn theme() -> Theme {
253 get_local::<Theme>().unwrap_or_else(|| defaults().read().theme)
254}
255
256pub fn density() -> Density {
257 get_local::<Density>().unwrap_or_else(|| defaults().read().density)
258}
259
260pub fn ui_scale() -> UiScale {
261 get_local::<UiScale>().unwrap_or_else(|| defaults().read().ui_scale)
262}
263
264pub fn text_scale() -> TextScale {
265 get_local::<TextScale>().unwrap_or_else(|| defaults().read().text_scale)
266}
267
268pub fn text_direction() -> TextDirection {
269 get_local::<TextDirection>().unwrap_or_else(|| defaults().read().text_direction)
270}