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}