fltk/app/
state.rs

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