fltk/app/
state.rs

1use crate::prelude::WidgetExt;
2use std::any::Any;
3use std::collections::HashMap;
4use std::sync::Mutex;
5use std::sync::OnceLock;
6
7#[cfg(not(feature = "single-threaded"))]
8use std::sync::LazyLock;
9
10#[cfg(feature = "single-threaded")]
11use std::cell::RefCell;
12
13static STATE: OnceLock<Mutex<Box<dyn Any + Send + Sync + 'static>>> = OnceLock::new();
14
15/// Represents global state
16#[derive(Debug, Copy)]
17pub struct GlobalState<T> {
18    marker: std::marker::PhantomData<T>,
19}
20
21impl<T> Clone for GlobalState<T> {
22    fn clone(&self) -> Self {
23        Self {
24            marker: std::marker::PhantomData,
25        }
26    }
27}
28
29impl<T: Sync + Send + 'static> GlobalState<T> {
30    /// Creates a new global state
31    pub fn new(val: T) -> Self {
32        STATE.set(Mutex::new(Box::new(val))).unwrap();
33        GlobalState {
34            marker: std::marker::PhantomData,
35        }
36    }
37
38    /// Modifies the global state by acquiring a mutable reference
39    /// # Panics
40    /// Panics on state downcast errors
41    pub fn with<V: Clone, F: 'static + FnMut(&mut T) -> V>(&self, mut cb: F) -> V {
42        if let Some(state) = STATE.get() {
43            if let Ok(mut state) = state.try_lock() {
44                if let Some(state) = state.downcast_mut::<T>() {
45                    cb(state)
46                } else {
47                    panic!("failed to downcast state");
48                }
49            } else {
50                panic!("failed to lock state");
51            }
52        } else {
53            panic!("failed to get state");
54        }
55    }
56
57    /// Gets the already initialized global state
58    pub fn get() -> Self {
59        GlobalState {
60            marker: std::marker::PhantomData,
61        }
62    }
63}
64
65#[cfg(not(feature = "single-threaded"))]
66static WIDGET_MAP: LazyLock<Mutex<HashMap<String, Box<dyn Any + Send + Sync + 'static>>>> =
67    LazyLock::new(|| Mutex::new(HashMap::default()));
68
69#[cfg(feature = "single-threaded")]
70thread_local! {
71    static WIDGET_MAP: RefCell<HashMap<String, Box<dyn Any>>> = RefCell::new(HashMap::new());
72}
73
74/// Allows setting a an id to a widget.
75pub trait WidgetId<W>
76where
77    W: WidgetExt,
78{
79    /// Set the widget's Id
80    fn set_id(&mut self, id: &str);
81
82    #[allow(clippy::return_self_not_must_use)]
83    /// Construct a widget with an Id
84    fn with_id(self, id: &str) -> Self
85    where
86        Self: Sized;
87}
88
89#[cfg(not(feature = "single-threaded"))]
90impl<W> WidgetId<W> for W
91where
92    W: WidgetExt + Clone + Send + Sync + 'static,
93{
94    fn set_id(&mut self, id: &str) {
95        WIDGET_MAP
96            .lock()
97            .unwrap()
98            .insert(id.to_string(), Box::new(self.clone()));
99    }
100    fn with_id(mut self, id: &str) -> Self {
101        self.set_id(id);
102        self
103    }
104}
105
106#[cfg(feature = "single-threaded")]
107impl<W> WidgetId<W> for W
108where
109    W: WidgetExt + Clone + 'static,
110{
111    fn set_id(&mut self, id: &str) {
112        WIDGET_MAP.with(|w| {
113            w.borrow_mut()
114                .insert(id.to_string(), Box::new(self.clone()))
115        });
116    }
117    fn with_id(mut self, id: &str) -> Self {
118        self.set_id(id);
119        self
120    }
121}
122
123/// Get back the widget thru its id
124pub fn widget_from_id<T: 'static + Clone>(id: &str) -> Option<T> {
125    #[cfg(not(feature = "single-threaded"))]
126    if let Some(w) = WIDGET_MAP.lock().unwrap().get(id) {
127        w.downcast_ref::<T>().map(|w| (*w).clone())
128    } else {
129        None
130    }
131    #[cfg(feature = "single-threaded")]
132    WIDGET_MAP.with(|w| {
133        if let Some(w) = w.borrow().get(id) {
134            w.downcast_ref::<T>().map(|w| (*w).clone())
135        } else {
136            None
137        }
138    })
139}