i_slint_backend_qt/
lib.rs

1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4// cSpell: ignore deinit fnbox qsize
5
6#![doc = include_str!("README.md")]
7#![doc(html_logo_url = "https://slint.dev/logo/slint-logo-square-light.svg")]
8#![recursion_limit = "2048"]
9
10extern crate alloc;
11
12use i_slint_core::platform::PlatformError;
13use std::rc::Rc;
14
15#[cfg(not(no_qt))]
16mod qt_accessible;
17#[cfg(not(no_qt))]
18mod qt_widgets;
19#[cfg(not(no_qt))]
20mod qt_window;
21
22mod accessible_generated;
23mod key_generated;
24
25#[cfg(no_qt)]
26mod ffi {
27    #[unsafe(no_mangle)]
28    pub extern "C" fn slint_qt_get_widget(
29        _: &i_slint_core::window::WindowAdapterRc,
30    ) -> *mut std::ffi::c_void {
31        std::ptr::null_mut()
32    }
33}
34
35/// NativeWidgets and NativeGlobals are "type list" containing all the native widgets and global types.
36///
37/// It is built as a tuple `(Type, Tail)`  where `Tail` is also a "type list". a `()` is the end.
38///
39/// So it can be used like this to do something for all types:
40///
41/// ```rust
42/// trait DoSomething {
43///     fn do_something(/*...*/) { /*...*/
44///     }
45/// }
46/// impl DoSomething for () {}
47/// impl<T: i_slint_core::rtti::BuiltinItem, Next: DoSomething> DoSomething for (T, Next) {
48///     fn do_something(/*...*/) {
49///          /*...*/
50///          Next::do_something(/*...*/);
51///     }
52/// }
53/// i_slint_backend_qt::NativeWidgets::do_something(/*...*/)
54/// ```
55#[cfg(not(no_qt))]
56#[rustfmt::skip]
57pub type NativeWidgets =
58    (qt_widgets::NativeButton,
59    (qt_widgets::NativeCheckBox,
60    (qt_widgets::NativeSlider,
61    (qt_widgets::NativeProgressIndicator,
62    (qt_widgets::NativeSpinBox,
63    (qt_widgets::NativeGroupBox,
64    (qt_widgets::NativeLineEdit,
65    (qt_widgets::NativeScrollView,
66    (qt_widgets::NativeStandardListViewItem,
67    (qt_widgets::NativeTableHeaderSection,
68    (qt_widgets::NativeComboBox,
69    (qt_widgets::NativeComboBoxPopup,
70    (qt_widgets::NativeTabWidget,
71    (qt_widgets::NativeTab,
72            ()))))))))))))));
73
74#[cfg(not(no_qt))]
75#[rustfmt::skip]
76pub type NativeGlobals =
77    (qt_widgets::NativeStyleMetrics,
78    (qt_widgets::NativePalette,
79        ()));
80
81#[cfg(no_qt)]
82mod native_style_metrics_stub {
83    use const_field_offset::FieldOffsets;
84    use core::pin::Pin;
85    #[cfg(feature = "rtti")]
86    use i_slint_core::rtti::*;
87    use i_slint_core_macros::*;
88
89    /// cbindgen:ignore
90    #[repr(C)]
91    #[derive(FieldOffsets, SlintElement)]
92    #[pin]
93    #[pin_drop]
94    pub struct NativeStyleMetrics {}
95
96    impl const_field_offset::PinnedDrop for NativeStyleMetrics {
97        fn drop(self: Pin<&mut Self>) {}
98    }
99
100    /// cbindgen:ignore
101    #[repr(C)]
102    #[derive(FieldOffsets, SlintElement)]
103    #[pin]
104    #[pin_drop]
105    pub struct NativePalette {}
106
107    impl const_field_offset::PinnedDrop for NativePalette {
108        fn drop(self: Pin<&mut Self>) {}
109    }
110}
111
112pub mod native_widgets {
113    #[cfg(not(no_qt))]
114    pub use super::qt_widgets::*;
115
116    #[cfg(no_qt)]
117    pub use super::native_style_metrics_stub::NativeStyleMetrics;
118
119    #[cfg(no_qt)]
120    pub use super::native_style_metrics_stub::NativePalette;
121}
122
123#[cfg(no_qt)]
124pub type NativeWidgets = ();
125#[cfg(no_qt)]
126pub type NativeGlobals = ();
127
128pub const HAS_NATIVE_STYLE: bool = cfg!(not(no_qt));
129
130pub struct Backend;
131
132impl Backend {
133    pub fn new() -> Self {
134        #[cfg(not(no_qt))]
135        {
136            use cpp::cpp;
137            // Initialize QApplication early for High-DPI support on Windows,
138            // before the first calls to QStyle.
139            cpp! {unsafe[] {
140                ensure_initialized(true);
141            }}
142        }
143        Self {}
144    }
145}
146
147impl i_slint_core::platform::Platform for Backend {
148    fn create_window_adapter(
149        &self,
150    ) -> Result<Rc<dyn i_slint_core::window::WindowAdapter>, PlatformError> {
151        #[cfg(no_qt)]
152        return Err("Qt platform requested but Slint is compiled without Qt support".into());
153        #[cfg(not(no_qt))]
154        {
155            Ok(qt_window::QtWindow::new())
156        }
157    }
158
159    fn run_event_loop(&self) -> Result<(), PlatformError> {
160        #[cfg(not(no_qt))]
161        {
162            // Schedule any timers with Qt that were set up before this event loop start.
163            crate::qt_window::timer_event();
164            use cpp::cpp;
165            cpp! {unsafe [] {
166                ensure_initialized(true);
167                qApp->exec();
168            } }
169            Ok(())
170        }
171        #[cfg(no_qt)]
172        Err("Qt platform requested but Slint is compiled without Qt support".into())
173    }
174
175    fn process_events(
176        &self,
177        _timeout: core::time::Duration,
178        _: i_slint_core::InternalToken,
179    ) -> Result<core::ops::ControlFlow<()>, PlatformError> {
180        #[cfg(not(no_qt))]
181        {
182            // Schedule any timers with Qt that were set up before this event loop start.
183            crate::qt_window::timer_event();
184            use cpp::cpp;
185            let timeout_ms: i32 = _timeout.as_millis() as _;
186            let loop_was_quit = cpp! {unsafe [timeout_ms as "int"] -> bool as "bool" {
187                ensure_initialized(true);
188                qApp->processEvents(QEventLoop::AllEvents, timeout_ms);
189                return std::exchange(g_lastWindowClosed, false);
190            } };
191            Ok(if loop_was_quit {
192                core::ops::ControlFlow::Break(())
193            } else {
194                core::ops::ControlFlow::Continue(())
195            })
196        }
197        #[cfg(no_qt)]
198        Err("Qt platform requested but Slint is compiled without Qt support".into())
199    }
200
201    #[cfg(not(no_qt))]
202    fn new_event_loop_proxy(&self) -> Option<Box<dyn i_slint_core::platform::EventLoopProxy>> {
203        struct Proxy;
204        impl i_slint_core::platform::EventLoopProxy for Proxy {
205            fn quit_event_loop(&self) -> Result<(), i_slint_core::api::EventLoopError> {
206                use cpp::cpp;
207                cpp! {unsafe [] {
208                    // Use a quit event to avoid qApp->quit() calling
209                    // [NSApp terminate:nil] and us never returning from the
210                    // event loop - slint-viewer relies on the ability to
211                    // return from run().
212                    QCoreApplication::postEvent(qApp, new QEvent(QEvent::Quit));
213                } }
214                Ok(())
215            }
216
217            fn invoke_from_event_loop(
218                &self,
219                _event: Box<dyn FnOnce() + Send>,
220            ) -> Result<(), i_slint_core::api::EventLoopError> {
221                use cpp::cpp;
222                cpp! {{
223                   struct TraitObject { void *a, *b; };
224                   struct EventHolder {
225                       TraitObject fnbox = {nullptr, nullptr};
226                       ~EventHolder() {
227                           if (fnbox.a != nullptr || fnbox.b != nullptr) {
228                               rust!(Slint_delete_event_holder [fnbox: *mut dyn FnOnce() as "TraitObject"] {
229                                   drop(Box::from_raw(fnbox))
230                               });
231                           }
232                       }
233                       EventHolder(TraitObject f) : fnbox(f)  {}
234                       EventHolder(const EventHolder&) = delete;
235                       EventHolder& operator=(const EventHolder&) = delete;
236                       EventHolder(EventHolder&& other) : fnbox(other.fnbox) {
237                            other.fnbox = {nullptr, nullptr};
238                       }
239                       void operator()() {
240                            if (fnbox.a != nullptr || fnbox.b != nullptr) {
241                                TraitObject fnbox = std::move(this->fnbox);
242                                this->fnbox = {nullptr, nullptr};
243                                rust!(Slint_call_event_holder [fnbox: *mut dyn FnOnce() as "TraitObject"] {
244                                   let b = Box::from_raw(fnbox);
245                                   b();
246                                   // in case the callback started a new timer
247                                   crate::qt_window::restart_timer();
248                                });
249                            }
250
251                       }
252                   };
253                }};
254                let fnbox = Box::into_raw(_event);
255                cpp! {unsafe [fnbox as "TraitObject"] {
256                    QTimer::singleShot(0, qApp, EventHolder{fnbox});
257                }}
258                Ok(())
259            }
260        }
261        Some(Box::new(Proxy))
262    }
263
264    #[cfg(not(no_qt))]
265    fn set_clipboard_text(&self, _text: &str, _clipboard: i_slint_core::platform::Clipboard) {
266        use cpp::cpp;
267        let is_selection: bool = match _clipboard {
268            i_slint_core::platform::Clipboard::DefaultClipboard => false,
269            i_slint_core::platform::Clipboard::SelectionClipboard => true,
270            _ => return,
271        };
272        let text: qttypes::QString = _text.into();
273        cpp! {unsafe [text as "QString", is_selection as "bool"] {
274            ensure_initialized();
275            if (is_selection && !QGuiApplication::clipboard()->supportsSelection())
276                return;
277            QGuiApplication::clipboard()->setText(text, is_selection ? QClipboard::Selection : QClipboard::Clipboard);
278        } }
279    }
280
281    #[cfg(not(no_qt))]
282    fn clipboard_text(&self, _clipboard: i_slint_core::platform::Clipboard) -> Option<String> {
283        use cpp::cpp;
284        let is_selection: bool = match _clipboard {
285            i_slint_core::platform::Clipboard::DefaultClipboard => false,
286            i_slint_core::platform::Clipboard::SelectionClipboard => true,
287            _ => return None,
288        };
289        let has_text = cpp! {unsafe [is_selection as "bool"] -> bool as "bool" {
290            ensure_initialized();
291            if (is_selection && !QGuiApplication::clipboard()->supportsSelection())
292                return false;
293            return QGuiApplication::clipboard()->mimeData(is_selection ? QClipboard::Selection : QClipboard::Clipboard)->hasText();
294        } };
295        if has_text {
296            return Some(
297                cpp! { unsafe [is_selection as "bool"] -> qttypes::QString as "QString" {
298                    return QGuiApplication::clipboard()->text(is_selection ? QClipboard::Selection : QClipboard::Clipboard);
299                }}
300                .into(),
301            );
302        }
303        None
304    }
305
306    #[cfg(not(no_qt))]
307    fn click_interval(&self) -> core::time::Duration {
308        let duration_ms = unsafe {
309            cpp::cpp! {[] -> u32 as "int" { return qApp->doubleClickInterval(); }}
310        };
311        core::time::Duration::from_millis(duration_ms as u64)
312    }
313
314    #[cfg(not(no_qt))]
315    fn cursor_flash_cycle(&self) -> core::time::Duration {
316        let duration_ms = unsafe {
317            cpp::cpp! {[] -> i32 as "int" { return qApp->cursorFlashTime(); }}
318        };
319        if duration_ms > 0 {
320            core::time::Duration::from_millis(duration_ms as u64)
321        } else {
322            core::time::Duration::ZERO
323        }
324    }
325}
326
327/// This helper trait can be used to obtain access to a pointer to a QtWidget for a given
328/// [`slint::Window`](slint:rust:slint/struct.window).")]
329#[cfg(not(no_qt))]
330pub trait QtWidgetAccessor {
331    fn qt_widget_ptr(&self) -> Option<std::ptr::NonNull<()>>;
332}
333
334#[cfg(not(no_qt))]
335impl QtWidgetAccessor for i_slint_core::api::Window {
336    fn qt_widget_ptr(&self) -> Option<std::ptr::NonNull<()>> {
337        i_slint_core::window::WindowInner::from_pub(self)
338            .window_adapter()
339            .internal(i_slint_core::InternalToken)
340            .and_then(|wa| wa.as_any().downcast_ref::<qt_window::QtWindow>())
341            .map(qt_window::QtWindow::widget_ptr)
342    }
343}