1#![doc = include_str!("../README.md")]
2#![allow(clippy::needless_doctest_main)]
3
4use fltk::{
5 app,
6 enums::{Event, Shortcut},
7 menu::MenuFlag,
8 prelude::*,
9 window::Window,
10};
11use std::{
12 any::Any,
13 sync::{Mutex, OnceLock},
14};
15
16static STATE: OnceLock<Mutex<Box<dyn Any + Send + Sync>>> = OnceLock::new();
17
18macro_rules! state_ref {
19 () => {
20 STATE
21 .get()
22 .expect("Global state not initialized.")
23 .lock()
24 .expect("Failed to lock global state.")
25 .downcast_ref()
26 .expect("State type mismatch (did you init a different type?)")
27 };
28}
29
30macro_rules! state_mut {
31 () => {
32 STATE
33 .get()
34 .expect("Global state not initialized.")
35 .lock()
36 .expect("Failed to lock global state.")
37 .downcast_mut()
38 .expect("State type mismatch (did you init a different type?)")
39 };
40}
41
42pub const STATE_CHANGED: Event = Event::from_i32(100);
44
45pub trait WidgetObserver<T, W> {
47 fn set_action<Listen: Clone + 'static + Fn(&mut T, &Self)>(&mut self, l: Listen);
50 fn set_view<Update: Clone + 'static + Fn(&T, &mut Self)>(&mut self, u: Update);
53}
54
55pub trait MenuObserver<T, W> {
57 fn add_action<Listen: Clone + 'static + Fn(&mut T, &Self)>(
59 &mut self,
60 label: &str,
61 shortcut: Shortcut,
62 flags: MenuFlag,
63 l: Listen,
64 );
65}
66
67impl<T: Send + Sync + 'static, W: WidgetExt + WidgetBase + 'static + Clone> WidgetObserver<T, W>
68 for W
69{
70 fn set_action<Listen: Clone + 'static + Fn(&mut T, &Self)>(&mut self, l: Listen) {
71 self.set_callback(move |w| {
72 let win = unsafe { Window::from_widget_ptr(w.window().unwrap().as_widget_ptr()) };
73 l(state_mut!(), w);
74 app::handle(STATE_CHANGED, &win.clone()).ok();
75 });
76 }
77
78 fn set_view<Update: Clone + 'static + Fn(&T, &mut Self)>(&mut self, u: Update) {
79 let w = self.clone();
80 let func = move || {
81 let mut w = w.clone();
82 u(state_ref!(), &mut w);
83 };
84 func();
85 self.handle(move |_w, ev| {
86 if ev == STATE_CHANGED {
87 func();
88 }
89 false
90 });
91 }
92}
93
94impl<T: Send + Sync + 'static, W: MenuExt + 'static + Clone> MenuObserver<T, W> for W {
95 fn add_action<Listen: Clone + 'static + Fn(&mut T, &Self)>(
96 &mut self,
97 label: &str,
98 shortcut: Shortcut,
99 flags: MenuFlag,
100 l: Listen,
101 ) {
102 self.add(label, shortcut, flags, move |w| {
103 let win = unsafe { Window::from_widget_ptr(w.window().unwrap().as_widget_ptr()) };
104 l(state_mut!(), w);
105 app::handle(STATE_CHANGED, &win).ok();
106 });
107 }
108}
109
110pub trait Runner<State: 'static + Send + Sync> {
112 fn use_state<F: 'static + FnOnce() -> State>(self, init: F) -> Option<Self>
115 where
116 Self: Sized;
117}
118
119impl<State: 'static + Send + Sync> Runner<State> for app::App {
120 fn use_state<F: 'static + FnOnce() -> State>(self, init: F) -> Option<Self>
121 where
122 Self: Sized,
123 {
124 STATE.set(Mutex::new(Box::new((init)()))).ok()?;
125 Some(self)
126 }
127}
128
129pub fn with_state_mut<State: 'static, F: FnOnce(&mut State) + Clone>(f: F) {
132 f(state_mut!());
133 app::handle_main(STATE_CHANGED).ok();
134}
135
136pub fn with_state_mut_on<State: 'static, F: FnOnce(&mut State) + Clone>(
139 win: &impl WindowExt,
140 f: F,
141) {
142 f(state_mut!());
143 app::handle(STATE_CHANGED, win).ok();
144}
145
146pub fn with_state<State: 'static, F: FnOnce(&State) + Clone>(f: F) {
148 f(state_ref!());
149}
150
151pub fn notify() {
153 app::handle_main(STATE_CHANGED).ok();
154 app::awake();
155}
156
157pub fn notify_win(win: &impl WindowExt) {
160 app::handle(STATE_CHANGED, win).ok();
161 app::awake();
162}