Skip to main content

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