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}