relm4/
app.rs

1use gtk::glib;
2use gtk::prelude::{ApplicationExt, ApplicationExtManual, Cast, GtkApplicationExt, IsA, WidgetExt};
3use std::fmt::Debug;
4
5use crate::component::{AsyncComponent, AsyncComponentBuilder, AsyncComponentController};
6use crate::runtime_util::shutdown_all;
7use crate::{Component, ComponentBuilder, ComponentController, MessageBroker, RUNTIME};
8
9use std::cell::Cell;
10
11/// An app that runs the main application.
12#[derive(Debug)]
13pub struct RelmApp<M: Debug + 'static> {
14    /// The [`gtk::Application`] that's used internally to setup
15    /// and run your application.
16    app: gtk::Application,
17    broker: Option<&'static MessageBroker<M>>,
18    args: Option<Vec<String>>,
19    /// If `true`, make the window visible on
20    /// every activation.
21    visible: bool,
22}
23
24impl<M: Debug + 'static> RelmApp<M> {
25    /// Create a new Relm4 application.
26    ///
27    /// This function will create a new [`gtk::Application`] object if necessary.
28    ///
29    /// If the `libadwaita` feature is enabled, then the created [`gtk::Application`] will be an
30    /// instance of [`adw::Application`]. This can be overridden by passing your own application
31    /// object to [`RelmApp::from_app`].
32    #[must_use]
33    pub fn new(app_id: &str) -> Self {
34        crate::init();
35        let app = crate::main_application();
36        app.set_application_id(Some(app_id));
37
38        Self {
39            app,
40            broker: None,
41            args: None,
42            visible: true,
43        }
44    }
45
46    /// Create a Relm4 application with a provided [`gtk::Application`].
47    pub fn from_app(app: impl IsA<gtk::Application>) -> Self {
48        let app = app.upcast();
49        crate::set_main_application(app.clone());
50
51        Self {
52            app,
53            broker: None,
54            args: None,
55            visible: true,
56        }
57    }
58
59    /// Add [`MessageBroker`] to the top-level component.
60    #[must_use]
61    pub fn with_broker(mut self, broker: &'static MessageBroker<M>) -> Self {
62        self.broker = Some(broker);
63        self
64    }
65
66    /// Add command line arguments to run with.
67    #[must_use]
68    pub fn with_args(mut self, args: Vec<String>) -> Self {
69        self.args = Some(args);
70        self
71    }
72
73    /// If `true`, make the window visible whenever
74    /// the app is activated (e. g. every time [`RelmApp::run`] is called).
75    ///
76    /// By default, this value is `true`.
77    /// If you don't want the window to be visible immediately
78    /// (especially when using async components), you can set this
79    /// to `false` and call [`WidgetExt::set_visible()`] manually
80    /// on your window.
81    #[must_use]
82    pub fn visible_on_activate(mut self, visible: bool) -> Self {
83        self.visible = visible;
84        self
85    }
86
87    /// If `true`, allow multiple concurrent instances of the application
88    /// by setting the [`gtk::gio::ApplicationFlags::NON_UNIQUE`] flag.
89    ///
90    /// By default, this flag is not set.
91    /// When the flag is not set, the application will not be
92    /// started if another instance is already running.
93    pub fn allow_multiple_instances(&self, allow: bool) {
94        let mut flags = self.app.flags();
95        if allow {
96            flags |= gtk::gio::ApplicationFlags::NON_UNIQUE;
97        } else {
98            flags &= !gtk::gio::ApplicationFlags::NON_UNIQUE;
99        }
100        self.app.set_flags(flags);
101    }
102
103    /// Sets a custom global stylesheet, with the given priority.
104    ///
105    /// The priority can be any value, but GTK [includes some][style-providers] that you can use.
106    ///
107    /// [style-providers]: https://gtk-rs.org/gtk4-rs/stable/latest/docs/gtk4/index.html?search=const%3ASTYLE_PROVIDER&filter-crate=gtk4#constants
108    #[deprecated(note = "Use `relm4::set_global_css_with_priority` instead")]
109    pub fn set_global_css_with_priority(&self, style_data: &str, priority: u32) {
110        crate::set_global_css_with_priority(style_data, priority);
111    }
112    /// Sets a custom global stylesheet.
113    #[deprecated(note = "Use `relm4::set_global_css` instead")]
114    pub fn set_global_css(&self, style_data: &str) {
115        crate::set_global_css(style_data);
116    }
117
118    /// Sets a custom global stylesheet from a file, with the given priority.
119    ///
120    /// If the file doesn't exist a [`tracing::error`] message will be emitted and
121    /// an [`std::io::Error`] will be returned.
122    ///
123    /// The priority can be any value, but GTK [includes some][style-providers] that you can use.
124    ///
125    /// [style-providers]: https://gtk-rs.org/gtk4-rs/stable/latest/docs/gtk4/index.html?search=const%3ASTYLE_PROVIDER&filter-crate=gtk4#constants
126    #[deprecated(note = "Use `relm4::set_global_css_from_file_with_priority` instead")]
127    pub fn set_global_css_from_file_with_priority<P: AsRef<std::path::Path>>(
128        &self,
129        path: P,
130        priority: u32,
131    ) -> Result<(), std::io::Error> {
132        crate::set_global_css_from_file_with_priority(path, priority)
133    }
134
135    /// Sets a custom global stylesheet from a file.
136    ///
137    /// If the file doesn't exist a [`tracing::error`] message will be emitted and
138    /// an [`std::io::Error`] will be returned.
139    #[deprecated(note = "Use `relm4::set_global_css_from_file` instead")]
140    pub fn set_global_css_from_file<P: AsRef<std::path::Path>>(
141        &self,
142        path: P,
143    ) -> Result<(), std::io::Error> {
144        crate::set_global_css_from_file(path)
145    }
146
147    /// Runs the application, returns once the application is closed.
148    pub fn run<C>(self, payload: C::Init)
149    where
150        C: Component<Input = M>,
151        C::Root: AsRef<gtk::Window>,
152    {
153        let Self {
154            app,
155            broker,
156            args,
157            visible,
158        } = self;
159
160        let payload = Cell::new(Some(payload));
161
162        app.connect_startup(move |app| {
163            if let Some(payload) = payload.take() {
164                let builder = ComponentBuilder::<C>::default();
165
166                let connector = match broker {
167                    Some(broker) => builder.launch_with_broker(payload, broker),
168                    None => builder.launch(payload),
169                };
170
171                // Run late initialization for transient windows for example.
172                crate::late_initialization::run_late_init();
173
174                let mut controller = connector.detach();
175                let window = controller.widget();
176                app.add_window(window.as_ref());
177
178                controller.detach_runtime();
179            }
180        });
181
182        app.connect_activate(move |app| {
183            if let Some(window) = app.active_window()
184                && visible
185            {
186                window.set_visible(true);
187            }
188        });
189
190        let _guard = RUNTIME.enter();
191        if let Some(args) = args {
192            app.run_with_args(&args);
193        } else {
194            app.run();
195        }
196
197        // Make sure everything is shut down
198        shutdown_all();
199        glib::MainContext::ref_thread_default().iteration(true);
200    }
201
202    /// Runs the application, returns once the application is closed.
203    pub fn run_async<C>(self, payload: C::Init)
204    where
205        C: AsyncComponent<Input = M>,
206        C::Root: AsRef<gtk::Window>,
207    {
208        let Self {
209            app,
210            broker,
211            args,
212            visible: set_visible,
213        } = self;
214
215        let payload = Cell::new(Some(payload));
216
217        app.connect_startup(move |app| {
218            if let Some(payload) = payload.take() {
219                let builder = AsyncComponentBuilder::<C>::default();
220
221                let connector = match broker {
222                    Some(broker) => builder.launch_with_broker(payload, broker),
223                    None => builder.launch(payload),
224                };
225
226                // Run late initialization for transient windows for example.
227                crate::late_initialization::run_late_init();
228
229                let mut controller = connector.detach();
230                let window = controller.widget();
231                app.add_window(window.as_ref());
232
233                controller.detach_runtime();
234            }
235        });
236
237        app.connect_activate(move |app| {
238            if let Some(window) = app.active_window()
239                && set_visible
240            {
241                window.set_visible(true);
242            }
243        });
244
245        let _guard = RUNTIME.enter();
246        if let Some(args) = args {
247            app.run_with_args(&args);
248        } else {
249            app.run();
250        }
251
252        // Make sure everything is shut down
253        shutdown_all();
254        glib::MainContext::ref_thread_default().iteration(true);
255    }
256}