Skip to main content

fui/
app.rs

1use crate::bindings::ui;
2use crate::context_menu_manager;
3use crate::ffi::{self, HandleValue};
4use crate::frame_scheduler;
5use crate::mobile_text_selection_toolbar;
6use crate::node::{flex_box, FlexBox, Node, NodeRef, ThemeBindable};
7use crate::panic_hook;
8use crate::selection_handle_adorner;
9use crate::theme;
10use crate::timers;
11use crate::tool_tip_manager;
12use crate::Unit;
13use crate::{focus_adorner, focus_visibility};
14use std::cell::RefCell;
15use std::rc::Rc;
16
17type BuildPageFn<TPage> = Rc<dyn Fn() -> TPage>;
18type RootFn<TPage> = Rc<dyn Fn(&TPage) -> NodeRef>;
19type PageCallback<TPage> = Rc<dyn Fn(&TPage)>;
20type PostCommitCallback = Box<dyn FnOnce()>;
21
22thread_local! {
23    static MOUNTED_ROOT: RefCell<Option<NodeRef>> = const { RefCell::new(None) };
24    static MOUNTED_SHELL: RefCell<Option<FlexBox>> = const { RefCell::new(None) };
25    static POST_COMMIT_CALLBACKS: RefCell<Vec<PostCommitCallback>> = const { RefCell::new(Vec::new()) };
26}
27
28fn create_empty_page() -> FlexBox {
29    let root = flex_box();
30    root.width(100.0, Unit::Percent)
31        .height(100.0, Unit::Percent);
32    root
33}
34
35fn clear_mount_state() {
36    clear_mount_state_with_loaded_reset(true);
37}
38
39fn clear_mount_state_with_loaded_reset(clear_loaded_callbacks: bool) {
40    clear_post_commit_callbacks();
41    if clear_loaded_callbacks {
42        frame_scheduler::reset_commit_state();
43    } else {
44        frame_scheduler::reset_commit_state_preserving_loaded_callbacks();
45    }
46    crate::event::reset();
47    timers::cancel_all_timers();
48    crate::fetch::dispose_all_fetch_requests();
49    crate::file::reset_file_runtime();
50    selection_handle_adorner::reset();
51    mobile_text_selection_toolbar::reset();
52    focus_adorner::clear();
53    tool_tip_manager::ToolTipManager::clear();
54    focus_visibility::reset_keyboard_focus_visibility();
55    let disposed_shell = MOUNTED_SHELL.with(|slot| {
56        if let Some(shell) = slot.borrow_mut().take() {
57            shell.dispose();
58            true
59        } else {
60            false
61        }
62    });
63    MOUNTED_ROOT.with(|slot| {
64        if let Some(root) = slot.borrow_mut().take() {
65            if !disposed_shell {
66                root.dispose();
67            }
68        }
69    });
70}
71
72pub(crate) fn after_next_commit(callback: impl FnOnce() + 'static) {
73    POST_COMMIT_CALLBACKS.with(|slot| slot.borrow_mut().push(Box::new(callback)));
74}
75
76fn clear_post_commit_callbacks() {
77    POST_COMMIT_CALLBACKS.with(|slot| slot.borrow_mut().clear());
78}
79
80fn run_post_commit_callbacks() {
81    let callbacks = POST_COMMIT_CALLBACKS.with(|slot| std::mem::take(&mut *slot.borrow_mut()));
82    for callback in callbacks {
83        callback();
84    }
85}
86
87fn application_shell<T: Node>(root: &T) -> FlexBox {
88    let shell = flex_box();
89    shell
90        .width(100.0, Unit::Percent)
91        .height(100.0, Unit::Percent)
92        .child(root)
93        .child(&selection_handle_adorner::create_default_host())
94        .child(&mobile_text_selection_toolbar::create_default_host())
95        .child(&focus_adorner::create_default_host())
96        .child(&tool_tip_manager::ToolTipManager::create_default_host())
97        .child(&context_menu_manager::create_default_menu());
98    shell.bind_theme(|shell, theme| {
99        shell.bg_color(theme.colors.background);
100    });
101    shell
102}
103
104pub struct ApplicationRegistration {
105    build_page_fn: Rc<dyn Fn() -> FlexBox>,
106}
107
108impl Default for ApplicationRegistration {
109    fn default() -> Self {
110        Self::new()
111    }
112}
113
114impl ApplicationRegistration {
115    pub fn new() -> Self {
116        Self {
117            build_page_fn: Rc::new(create_empty_page),
118        }
119    }
120
121    pub fn page<TNode: Node + 'static>(mut self, build_page: impl Fn() -> TNode + 'static) -> Self {
122        self.build_page_fn = Rc::new(move || {
123            let node = build_page();
124            let shell = flex_box();
125            shell.child(&node);
126            shell
127        });
128        self
129    }
130
131    pub fn register(self) -> ManagedApplication<FlexBox> {
132        ManagedApplication::new(move || (self.build_page_fn)(), |page| page.clone())
133    }
134}
135
136pub struct ManagedApplication<TPage: 'static> {
137    build_page: BuildPageFn<TPage>,
138    get_root: RootFn<TPage>,
139    mount_page: Option<PageCallback<TPage>>,
140    dispose_page: Option<PageCallback<TPage>>,
141    active_page: RefCell<Option<Rc<TPage>>>,
142}
143
144impl<TPage: 'static> ManagedApplication<TPage> {
145    pub fn new<TNode: Node + 'static>(
146        build_page: impl Fn() -> TPage + 'static,
147        get_root: impl Fn(&TPage) -> TNode + 'static,
148    ) -> Self {
149        Self {
150            build_page: Rc::new(build_page),
151            get_root: Rc::new(move |page| {
152                let root = get_root(page);
153                root.node_ref()
154            }),
155            mount_page: None,
156            dispose_page: None,
157            active_page: RefCell::new(None),
158        }
159    }
160
161    pub fn mount_page(mut self, callback: impl Fn(&TPage) + 'static) -> Self {
162        self.mount_page = Some(Rc::new(callback));
163        self
164    }
165
166    pub fn dispose_page(mut self, callback: impl Fn(&TPage) + 'static) -> Self {
167        self.dispose_page = Some(Rc::new(callback));
168        self
169    }
170
171    pub fn run(&self) {
172        panic_hook::install();
173        self.dispose();
174        ui::reset();
175        ui::resize_window(ui::get_viewport_width(), ui::get_viewport_height());
176        theme::use_system_theme();
177
178        let page = Rc::new((self.build_page)());
179        let root = (self.get_root)(&page);
180        let shell = application_shell(&NodeRefMount(root.clone()));
181        shell.build();
182        ui::set_root(shell.handle().raw());
183
184        MOUNTED_ROOT.with(|slot| slot.borrow_mut().replace(root));
185        MOUNTED_SHELL.with(|slot| slot.borrow_mut().replace(shell));
186        self.active_page.borrow_mut().replace(page.clone());
187        frame_scheduler::fire_loaded_callbacks();
188        frame_scheduler::mark_needs_commit();
189        frame_scheduler::flush_commit();
190        if focus_adorner::refresh_after_commit() {
191            frame_scheduler::mark_needs_commit();
192            frame_scheduler::flush_commit();
193        }
194        run_post_commit_callbacks();
195
196        if let Some(callback) = self.mount_page.as_ref() {
197            callback(&page);
198        }
199    }
200
201    pub fn dispose(&self) {
202        if let Some(page) = self.active_page.borrow_mut().take() {
203            if let Some(callback) = self.dispose_page.as_ref() {
204                callback(&page);
205            }
206        }
207        clear_mount_state();
208        ui::set_root(HandleValue::Invalid as u64);
209    }
210
211    pub fn get_active_page(&self) -> Option<Rc<TPage>> {
212        self.active_page.borrow().as_ref().cloned()
213    }
214
215    pub fn use_system_theme(&self) -> theme::Theme {
216        theme::use_system_theme()
217    }
218
219    pub fn use_custom_theme(&self, value: theme::Theme) -> theme::Theme {
220        theme::use_custom_theme(value)
221    }
222
223    pub fn set_accent_color(&self, color: u32) -> theme::Theme {
224        theme::set_accent_color(color)
225    }
226
227    pub fn is_dark_mode(&self) -> bool {
228        theme::is_dark_mode()
229    }
230
231    pub fn is_using_system_theme(&self) -> bool {
232        theme::is_using_system_theme()
233    }
234
235    pub fn get_theme(&self) -> theme::Theme {
236        theme::current_theme()
237    }
238
239    pub fn flush_renders(&self) {
240        Application::flush_renders();
241    }
242
243    pub fn capture_persisted_ui_state(&self) {
244        Application::capture_persisted_ui_state();
245    }
246
247    pub fn restore_persisted_ui_state(&self) {
248        Application::restore_persisted_ui_state();
249    }
250}
251
252pub struct Application;
253
254impl Application {
255    pub fn caption(value: &str) {
256        let bytes = value.as_bytes();
257        unsafe {
258            ffi::fui_set_application_caption(
259                if bytes.is_empty() {
260                    0
261                } else {
262                    bytes.as_ptr() as usize
263                },
264                bytes.len() as u32,
265            );
266        }
267    }
268
269    pub fn mount<TNode: Node>(root: TNode) {
270        panic_hook::install();
271        clear_mount_state_with_loaded_reset(false);
272        ui::reset();
273        ui::resize_window(ui::get_viewport_width(), ui::get_viewport_height());
274        let mounted_root = root.node_ref();
275        let shell = application_shell(&root);
276        shell.build();
277        ui::set_root(shell.handle().raw());
278        MOUNTED_ROOT.with(|slot| {
279            *slot.borrow_mut() = Some(mounted_root);
280        });
281        MOUNTED_SHELL.with(|slot| slot.borrow_mut().replace(shell));
282        frame_scheduler::fire_loaded_callbacks();
283        frame_scheduler::mark_needs_commit();
284        frame_scheduler::flush_commit();
285        if focus_adorner::refresh_after_commit() {
286            frame_scheduler::mark_needs_commit();
287            frame_scheduler::flush_commit();
288        }
289        run_post_commit_callbacks();
290    }
291
292    pub fn unmount() {
293        clear_mount_state();
294        ui::set_root(HandleValue::Invalid as u64);
295    }
296
297    pub fn flush_renders() {
298        if !frame_scheduler::flush_commit() {
299            return;
300        }
301        if focus_adorner::refresh_after_commit() {
302            frame_scheduler::mark_needs_commit();
303            frame_scheduler::flush_commit();
304        }
305        run_post_commit_callbacks();
306    }
307
308    pub(crate) fn resolve_mounted_node(handle: crate::node::NodeHandle) -> Option<NodeRef> {
309        crate::event::resolve_node(handle)
310    }
311
312    pub fn capture_persisted_ui_state() {
313        MOUNTED_ROOT.with(|slot| {
314            if let Some(root) = slot.borrow().as_ref() {
315                root.capture_persisted_state_tree();
316            }
317        });
318    }
319
320    pub fn restore_persisted_ui_state() {
321        MOUNTED_ROOT.with(|slot| {
322            if let Some(root) = slot.borrow().as_ref() {
323                root.restore_persisted_state_tree();
324            }
325        });
326    }
327}
328
329#[derive(Clone)]
330struct NodeRefMount(NodeRef);
331
332impl Node for NodeRefMount {
333    fn node_ref(&self) -> crate::node::NodeRef {
334        self.0.clone()
335    }
336
337    fn retained_node_ref(&self) -> crate::node::NodeRef {
338        self.0.clone()
339    }
340
341    fn build_self(&self) {}
342}
343
344#[cfg(not(feature = "worker-runtime"))]
345#[no_mangle]
346pub extern "C" fn __flushRenders() {
347    Application::flush_renders();
348}