Skip to main content

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}