1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
use gtk::glib;
use gtk::prelude::{ApplicationExt, ApplicationExtManual, Cast, GtkApplicationExt, IsA, WidgetExt};

use crate::component::{AsyncComponent, AsyncComponentBuilder, AsyncComponentController};
use crate::runtime_util::shutdown_all;
use crate::{Component, ComponentBuilder, ComponentController, RUNTIME};

use std::cell::Cell;

/// An app that runs the main application.
#[derive(Debug)]
pub struct RelmApp {
    /// The [`gtk::Application`] that's used internally to setup
    /// and run your application.
    app: gtk::Application,
}

impl RelmApp {
    /// Create a new Relm4 application.
    ///
    /// This function will create a new [`gtk::Application`] object if necessary.
    ///
    /// If the `libadwaita` feature is enabled, then the created [`gtk::Application`] will be an
    /// instance of [`adw::Application`]. This can be overridden by passing your own application
    /// object to [`RelmApp::with_app`].
    #[must_use]
    pub fn new(app_id: &str) -> Self {
        crate::init();
        let app = crate::main_application();
        app.set_application_id(Some(app_id));

        Self { app }
    }

    /// Create a Relm4 application with a provided [`gtk::Application`].
    pub fn with_app(app: impl IsA<gtk::Application>) -> Self {
        let app = app.upcast();
        crate::set_main_application(app.clone());

        Self { app }
    }

    /// Runs the application, returns once the application is closed.
    ///
    /// Unlike [`gtk::prelude::ApplicationExtManual::run`], this function
    /// does not handle command-line arguments. To pass arguments to GTK, use
    /// [`RelmApp::run_with_args`].
    pub fn run<C>(self, payload: C::Init)
    where
        C: Component,
        C::Root: IsA<gtk::Window> + WidgetExt,
    {
        self.run_with_args::<C, &str>(payload, &[]);
    }

    /// Runs the application with the provided command-line arguments, returns once the application
    /// is closed.
    pub fn run_with_args<C, S>(self, payload: C::Init, args: &[S])
    where
        C: Component,
        C::Root: IsA<gtk::Window> + WidgetExt,
        S: AsRef<str>,
    {
        let Self { app } = self;

        let payload = Cell::new(Some(payload));

        app.connect_activate(move |app| {
            if let Some(payload) = payload.take() {
                assert!(
                    app.is_registered(),
                    "App should be already registered when activated"
                );

                let builder = ComponentBuilder::<C>::default();
                let connector = builder.launch(payload);

                // Run late initialization for transient windows for example.
                crate::late_initialization::run_late_init();

                let mut controller = connector.detach();
                let window = controller.widget().clone();

                controller.detach_runtime();

                app.add_window(window.as_ref());
                window.show();
            }
        });

        let _guard = RUNTIME.enter();
        app.run_with_args(args);

        // Make sure everything is shut down
        shutdown_all();
        glib::MainContext::ref_thread_default().iteration(true);
    }

    /// Runs the application, returns once the application is closed.
    ///
    /// Unlike [`gtk::prelude::ApplicationExtManual::run`], this function
    /// does not handle command-line arguments. To pass arguments to GTK, use
    /// [`RelmApp::run_with_args`].
    pub fn run_async<C>(self, payload: C::Init)
    where
        C: AsyncComponent,
        C::Root: IsA<gtk::Window> + WidgetExt,
    {
        self.run_async_with_args::<C, &str>(payload, &[]);
    }

    /// Runs the application with the provided command-line arguments, returns once the application
    /// is closed.
    pub fn run_async_with_args<C, S>(self, payload: C::Init, args: &[S])
    where
        C: AsyncComponent,
        C::Root: IsA<gtk::Window> + WidgetExt,
        S: AsRef<str>,
    {
        let Self { app } = self;

        let payload = Cell::new(Some(payload));

        app.connect_activate(move |app| {
            if let Some(payload) = payload.take() {
                assert!(
                    app.is_registered(),
                    "App should be already registered when activated"
                );

                let builder = AsyncComponentBuilder::<C>::default();
                let connector = builder.launch(payload);

                // Run late initialization for transient windows for example.
                crate::late_initialization::run_late_init();

                let mut controller = connector.detach();
                let window = controller.widget().clone();

                controller.detach_runtime();

                app.add_window(window.as_ref());
                window.show();
            }
        });

        let _guard = RUNTIME.enter();
        app.run_with_args(args);

        // Make sure everything is shut down
        shutdown_all();
        glib::MainContext::ref_thread_default().iteration(true);
    }
}