ferroui_controls/desktop_application_extensions.rs
1//! The desktop-style ways of running an [`Application`] without a lifetime.
2//!
3//! # Run loops
4//!
5//! All of these end in [`Application::run_with_token`], which runs the main
6//! loop of the UI thread dispatcher only where the platform supports run
7//! loops ([`Dispatcher::supports_run_loops`]). On the other platforms (the
8//! event loop belongs to the host, such as a browser) they do everything
9//! else - show the window, hook its closing - and return immediately; the
10//! application keeps running, driven by the host, and nothing blocks.
11
12use crate::{Application, Window};
13use ferroui_base::input::ICloseable;
14use ferroui_base::threading::{CancellationToken, CancellationTokenSource, Dispatcher};
15use ferroui_base::{Nullable, ObjectType, Ref, Upcast};
16use std::rc::Rc;
17
18impl Application {
19 /// On desktop-style platforms runs the application's main loop until
20 /// `closable` is closed.
21 ///
22 /// Consider using a classic desktop lifetime instead. Without run loops
23 /// this returns right after subscribing to the closed event.
24 pub fn run(&self, closable: &dyn ICloseable) {
25 let cts = CancellationTokenSource::new();
26 let source = cts.clone();
27 closable.closed(Rc::new(move || source.cancel()));
28
29 self.run_with_token(&cts.token());
30 }
31
32 /// On desktop-style platforms runs the application's main loop until
33 /// the main window is closed. The window is shown if it is not visible.
34 ///
35 /// Consider using a classic desktop lifetime instead. Without run loops
36 /// this returns right after showing the window, which stays alive until
37 /// it is closed.
38 ///
39 /// # Panics
40 /// Panics when `main_window` is null.
41 pub fn run_window(&self, main_window: impl Into<Nullable<Window>>) {
42 let Some(main_window) = main_window.into().0 else {
43 panic!("Value cannot be null. (Parameter 'main_window')");
44 };
45 let cts = CancellationTokenSource::new();
46 let source = cts.clone();
47 main_window.closed(move || source.cancel());
48 if !main_window.is_visible() {
49 main_window.show();
50 }
51 self.run_with_token(&cts.token());
52 }
53
54 /// On desktop-style platforms runs the application's main loop with a
55 /// custom cancellation token, without setting a lifetime: the loop runs
56 /// until `token` is canceled.
57 ///
58 /// Without run loops this does nothing and returns.
59 pub fn run_with_token(&self, token: &CancellationToken) {
60 let dispatcher = Dispatcher::ui_thread();
61 if dispatcher.supports_run_loops() {
62 dispatcher.main_loop(token);
63 }
64 }
65
66 /// Creates a window with `new_window`, shows it and runs the
67 /// application's main loop until it is closed; see
68 /// [`run_window`](Self::run_window).
69 pub fn run_with_main_window<TWindow: ObjectType + Upcast<Window>>(&self, new_window: impl FnOnce() -> Ref<TWindow>) {
70 let window: Ref<Window> = new_window().upcast();
71 window.show();
72 self.run_window(window);
73 }
74}