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