Skip to main content

qtbridge_runtime/
qapp.rs

1// Copyright (C) 2025 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only
3
4use std::cell::RefCell;
5use std::rc::Rc;
6use std::pin::Pin;
7
8use cxx::UniquePtr;
9use cxx_qt::casting::Upcast;
10use qtbridge_type_lib::{
11    QGuiApplication, QObjectMutPtr, QQmlApplicationEngine, QQmlEngine, QUrl, QString, QVariant, QVariantMap,
12};
13use crate::qmlelement::QmlElement;
14use crate::qobjectholder::QObjectHolder;
15
16/// Entry point for a QML application.
17///
18/// Wraps the Qt application and QML engine. Configure it with the builder
19/// methods and call [`run`](QApp::run) to start the event loop.
20///
21/// # Example
22///
23/// A minimal “Hello World” application without a Rust backend:
24///
25/// ```rust
26///# use qtbridge_runtime::QApp;
27/// QApp::new()
28///     .load_qml(br#"
29///         import QtQuick
30///         import QtQuick.Controls
31///         Text {
32///             text: "Hello Rust!"
33///#            Component.onCompleted: closeTimer.start()
34///#            Timer {
35///#                id: closeTimer
36///#                interval: 1
37///#                onTriggered: Qt.quit()
38///#            }
39///         }"#)
40///     .run();
41/// ```
42pub struct QApp {
43    engine: UniquePtr<QQmlApplicationEngine>,
44    #[allow(dead_code)]
45    app: UniquePtr<QGuiApplication>,
46    #[allow(dead_code)]
47    quit_connection: cxx_qt::QMetaObjectConnectionGuard,
48    // create QVariant at load time to avoid storing raw QObject pointers.
49    initial_properties: Vec<(QString, Box<dyn FnOnce() -> QVariant>)>,
50}
51
52impl Drop for QApp {
53    fn drop(&mut self) {
54        // Drop the engine first, releasing every JS wrapper: the final
55        // collect then frees all objects Rust no longer holds, so their
56        // Drop runs here instead of leaking at thread exit.
57        self.engine = UniquePtr::null();
58        crate::registry::collect_garbage();
59    }
60}
61
62// Not Default: New needs to be in main thread. Once per process.
63#[allow(clippy::new_without_default)]
64impl QApp {
65    /// Creates the Qt application and QML engine.
66    ///
67    /// Must be called before any QML or GUI functionality is used.
68    pub fn new() -> Self {
69        let app = QGuiApplication::new();
70        crate::qmlprivate::call_qml_register_callbacks();
71        let mut engine = QQmlApplicationEngine::new();
72        // Clean up the registry in sync with the QML
73        // garbage collection (see `registry::install_gc_sentinel`).
74        crate::registry::install_gc_sentinel(engine.pin_mut());
75
76        let quit_connection = engine.as_mut().unwrap().on_object_creation_failed(
77            |engine: Pin::<&mut QQmlApplicationEngine>, _: &QUrl| {
78                let engine: Pin<&mut QQmlEngine> = engine.upcast_pin();
79                engine.quit();
80            });
81
82        Self {
83            engine,
84            app,
85            quit_connection,
86            initial_properties: Vec::new(),
87        }
88    }
89
90    /// Enters the Qt main event loop.
91    ///
92    /// Blocks until the application exits and returns the exit code.
93    /// Usually the last call in `main`.
94    pub fn run(&mut self) -> i32 {
95        self.app.pin_mut().exec()
96    }
97
98    /// Queues an initial property to be set on the root QML object.
99    ///
100    /// Properties are applied when [`load_qml`](QApp::load_qml) or
101    /// [`load_qml_from_file`](QApp::load_qml_from_file) is called.
102    /// Call multiple times to set several properties.
103    ///
104    /// # Example
105    ///
106    /// ```rust
107    ///# use qtbridge_runtime::QApp;
108    /// let prop = 42;
109    ///
110    /// QApp::new()
111    /// .set_initial_property("answer", &prop)
112    /// .load_qml(br#"
113    ///     import QtQuick
114    ///     import QtQuick.Controls
115    ///     ApplicationWindow {
116    ///         required property var answer
117    ///#        Component.onCompleted: closeTimer.start()
118    ///#        Timer {
119    ///#            id: closeTimer
120    ///#            interval: 1
121    ///#            onTriggered: Qt.quit()
122    ///#        }
123    ///     }"#)
124    /// .run();
125    /// ```
126    pub fn set_initial_property(&mut self, id: &str, value: impl Into<QVariant>) -> &mut Self {
127        let variant = value.into();
128        self.initial_properties.push((QString::from(id), Box::new( move || {
129            variant
130        })));
131        self
132    }
133
134    /// Sets a `#[qobject]` instance as initial property on the root QML
135    /// object, attaching a `QObject` to it first if none exists.
136    ///
137    /// Must be called before [`load_qml`](QApp::load_qml) or
138    /// [`load_qml_from_file`](QApp::load_qml_from_file).
139    /// Call multiple times to set several objects.
140    ///
141    /// # Example
142    ///
143    /// ```rust
144    ///# use std::cell::RefCell;
145    ///# use std::rc::Rc;
146    ///# use qtbridge::{QApp, qobject};
147    /// #[derive(Default)]
148    /// pub struct Backend {
149    /// }
150    /// #[qobject]
151    /// impl Backend {
152    /// }
153    ///
154    /// let backend = Rc::new(RefCell::new(Backend::default()));
155    ///
156    /// QApp::new()
157    ///     .set_initial_object("backend", backend)
158    ///     .load_qml(br#"
159    ///         import QtQuick
160    ///         Item {
161    ///             required property var backend
162    ///#            Component.onCompleted: closeTimer.start()
163    ///#            Timer {
164    ///#                id: closeTimer
165    ///#                interval: 1
166    ///#                onTriggered: Qt.quit()
167    ///#            }
168    ///         }"#)
169    ///     .run();
170    /// ```
171    pub fn set_initial_object<T: QObjectHolder>(
172        &mut self, id: &str, object: Rc<RefCell<T>>,
173    ) -> &mut Self {
174        self.initial_properties.push((QString::from(id), Box::new( move || {
175            let ptr = T::rc_ref_cell_to_qobject(&object).cast_mut();
176            let ptr_wrap = unsafe { QObjectMutPtr::from_raw(ptr.cast()) };
177            (&ptr_wrap).into()
178        })));
179        self
180    }
181
182    /// Loads QML source from an in-memory byte slice.
183    ///
184    /// Applies any properties queued with [`set_initial_property`](QApp::set_initial_property)
185    /// before loading.
186    pub fn load_qml(&mut self, code: &[u8]) -> &mut Self {
187        if !self.initial_properties.is_empty() {
188           let mut initial_properties_resolved = QVariantMap::default();
189            for (id, resolve) in std::mem::take(&mut self.initial_properties) {
190                initial_properties_resolved.insert(id, resolve());
191            }
192            self.engine.pin_mut().set_initial_properties(&initial_properties_resolved);
193        }
194        self.engine.pin_mut().load_data(&code.into(), &Default::default());
195        self
196    }
197
198    /// Loads the entry-point QML file by URL.
199    ///
200    /// Use this instead of [`load_qml`](QApp::load_qml) when the QML is
201    /// embedded in a Qt resource (`qrc:`) or accessible as a file path.
202    /// Accepts URLs such as `"qrc:/qt/qml/MyApp/Main.qml"` or
203    /// `"file:///path/to/main.qml"`.
204    ///
205    /// Import paths for any modules the file uses must be registered with
206    /// [`add_import_path`](QApp::add_import_path) before this call.
207    pub fn load_qml_from_file(&mut self, url: &str) -> &mut Self {
208        if !self.initial_properties.is_empty() {
209           let mut initial_properties_resolved = QVariantMap::default();
210            for (id, resolve) in std::mem::take(&mut self.initial_properties) {
211                initial_properties_resolved.insert(id, resolve());
212            }
213            self.engine.pin_mut().set_initial_properties(&initial_properties_resolved);
214        }
215        self.engine.pin_mut().load(&url.into());
216        self
217    }
218
219    /// Adds a directory to the QML engine's module import search path.
220    ///
221    /// Call before [`load_qml_from_file`](QApp::load_qml_from_file) when the
222    /// loaded QML imports modules from a directory the engine would not
223    /// otherwise find. Accepts both URLs and file-system paths.
224    pub fn add_import_path(&mut self, path: &str) -> &mut Self {
225        self.engine.pin_mut().add_import_path(&path.into());
226        self
227    }
228
229    /// Registers `T` with the QML type system, making it instantiable from QML.
230    ///
231    /// ```rust
232    ///# use qtbridge::{QApp, qobject};
233    /// #[derive(Default)]
234    /// pub struct Backend {
235    /// }
236    /// #[qobject]
237    /// impl Backend {
238    /// }
239    ///
240    /// QApp::new()
241    ///     .register::<Backend>()
242    ///     .load_qml(br#"
243    ///         import QtQuick
244    ///         import QtQuick.Controls
245    ///#        import qtbridge_runtime
246    ///         ApplicationWindow {
247    ///             Backend {}
248    ///#            Component.onCompleted: closeTimer.start()
249    ///#            Timer {
250    ///#                id: closeTimer
251    ///#                interval: 1
252    ///#                onTriggered: Qt.quit()
253    ///#            }
254    ///      }"#)
255    ///     .run();
256    /// ```
257    pub fn register<T: QmlElement>(&mut self) -> &mut Self {
258        T::register();
259        self
260    }
261
262    /// Sets the application name reported to the OS.
263    pub fn application_name(&mut self, name: &str) -> &mut Self {
264        self.app.pin_mut().set_application_name(&name.into());
265        self
266    }
267
268    #[doc(hidden)]
269    pub fn qml_engine(&mut self) -> std::pin::Pin<&mut QQmlEngine> {
270        self.engine.pin_mut().upcast_pin()
271    }
272}