i_slint_backend_qt/
qt_window.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 frameless qbrush qpointf qreal qwidgetsize svgz
5
6use cpp::*;
7use i_slint_common::sharedfontique;
8use i_slint_core::graphics::rendering_metrics_collector::{
9    RenderingMetrics, RenderingMetricsCollector,
10};
11use i_slint_core::graphics::{
12    euclid, Brush, Color, FontRequest, IntRect, Point, Rgba8Pixel, SharedImageBuffer,
13    SharedPixelBuffer,
14};
15use i_slint_core::input::{KeyEvent, KeyEventType, MouseEvent};
16use i_slint_core::item_rendering::{
17    CachedRenderingData, ItemCache, ItemRenderer, RenderBorderRectangle, RenderImage,
18    RenderRectangle, RenderText,
19};
20use i_slint_core::item_tree::ParentItemTraversalMode;
21use i_slint_core::item_tree::{ItemTreeRc, ItemTreeRef, ItemTreeWeak};
22use i_slint_core::items::{
23    self, ColorScheme, FillRule, ImageRendering, ItemRc, ItemRef, Layer, LineCap, MouseCursor,
24    Opacity, PointerEventButton, RenderingResult, TextWrap,
25};
26use i_slint_core::layout::Orientation;
27use i_slint_core::lengths::{
28    LogicalBorderRadius, LogicalLength, LogicalPoint, LogicalRect, LogicalSize, LogicalVector,
29    PhysicalPx, ScaleFactor,
30};
31use i_slint_core::platform::{PlatformError, WindowEvent};
32use i_slint_core::textlayout::sharedparley::{self, parley, GlyphRenderer};
33use i_slint_core::window::{WindowAdapter, WindowAdapterInternal, WindowInner};
34use i_slint_core::{ImageInner, Property, SharedString};
35
36use std::cell::RefCell;
37use std::collections::HashMap;
38use std::pin::Pin;
39use std::ptr::NonNull;
40use std::rc::{Rc, Weak};
41
42use crate::key_generated;
43use i_slint_core::renderer::Renderer;
44use std::cell::OnceCell;
45
46cpp! {{
47    #include <QtWidgets/QtWidgets>
48    #include <QtWidgets/QGraphicsScene>
49    #include <QtWidgets/QGraphicsBlurEffect>
50    #include <QtWidgets/QGraphicsPixmapItem>
51    #include <QtGui/QAccessible>
52    #include <QtGui/QPainter>
53    #include <QtGui/QPaintEngine>
54    #include <QtGui/QPainterPath>
55    #include <QtGui/QWindow>
56    #include <QtGui/QResizeEvent>
57    #include <QtGui/QTextLayout>
58    #include <QtGui/QImageReader>
59    #include <QtGui/QCursor>
60    #include <QtCore/QBasicTimer>
61    #include <QtCore/QTimer>
62    #include <QtCore/QPointer>
63    #include <QtCore/QBuffer>
64    #include <QtCore/QEvent>
65    #include <QtCore/QFileInfo>
66
67    #include <memory>
68
69    void ensure_initialized(bool from_qt_backend);
70
71    using QPainterPtr = std::unique_ptr<QPainter>;
72
73    struct TimerHandler : QObject {
74        QBasicTimer timer;
75        static TimerHandler& instance() {
76            static TimerHandler instance;
77            return instance;
78        }
79
80        void timerEvent(QTimerEvent *event) override {
81            if (event->timerId() != timer.timerId()) {
82                QObject::timerEvent(event);
83                return;
84            }
85            timer.stop();
86            rust!(Slint_timerEvent [] { timer_event() });
87        }
88
89    };
90
91    struct SlintWidget : QWidget {
92        void *rust_window = nullptr;
93        bool isMouseButtonDown = false;
94        QRect ime_position;
95        QString ime_text;
96        int ime_cursor = 0;
97        int ime_anchor = 0;
98
99        SlintWidget() {
100            setMouseTracking(true);
101            setFocusPolicy(Qt::StrongFocus);
102            setAttribute(Qt::WA_TranslucentBackground);
103            // WA_TranslucentBackground sets WA_NoSystemBackground, but we actually need WA_NoSystemBackground
104            // to draw the window background which is set on the palette.
105            // (But the window background might not be opaque)
106            setAttribute(Qt::WA_NoSystemBackground, false);
107        }
108
109        void paintEvent(QPaintEvent *) override {
110            if (!rust_window)
111                return;
112            auto painter = std::unique_ptr<QPainter>(new QPainter(this));
113            painter->setClipRect(rect());
114            painter->setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
115            QPainterPtr *painter_ptr = &painter;
116            rust!(Slint_paintEvent [rust_window: &QtWindow as "void*", painter_ptr: &mut QPainterPtr as "QPainterPtr*"] {
117                rust_window.paint_event(std::mem::take(painter_ptr))
118            });
119        }
120
121        void resizeEvent(QResizeEvent *) override {
122            if (!rust_window)
123                return;
124
125            // On windows, the size in the event is not reliable during
126            // fullscreen changes. Querying the widget itself seems to work
127            // better, see: https://stackoverflow.com/questions/52157587/why-qresizeevent-qwidgetsize-gives-different-when-fullscreen
128            QSize size = this->size();
129            rust!(Slint_resizeEvent [rust_window: &QtWindow as "void*", size: qttypes::QSize as "QSize"] {
130                rust_window.resize_event(size)
131            });
132        }
133
134        /// If this window is a PopupWindow and the mouse event is outside of the popup, then adjust the event to map to the parent window
135        /// Returns the position and the rust_window to which we need to deliver the event
136        std::tuple<QPoint, void*> adjust_mouse_event_to_popup_parent(QMouseEvent *event) {
137            auto pos = event->pos();
138            if (auto p = dynamic_cast<const SlintWidget*>(parent()); p && !rect().contains(pos)) {
139    #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
140                QPoint eventPos = event->globalPosition().toPoint();
141    #else
142                QPoint eventPos = event->globalPos();
143    #endif
144                while (auto pp = dynamic_cast<const SlintWidget*>(p->parent())) {
145                    if (p->rect().contains(p->mapFromGlobal(eventPos)))
146                        break;
147                    p = pp;
148                }
149                return { p->mapFromGlobal(eventPos), p->rust_window };
150            } else {
151                return { pos, rust_window };
152            }
153        }
154
155        void mousePressEvent(QMouseEvent *event) override {
156            isMouseButtonDown = true;
157            auto [pos, rust_window] = adjust_mouse_event_to_popup_parent(event);
158            if (!rust_window)
159                return;
160            int button = event->button();
161            rust!(Slint_mousePressEvent [rust_window: &QtWindow as "void*", pos: qttypes::QPoint as "QPoint", button: u32 as "int" ] {
162                let position = LogicalPoint::new(pos.x as _, pos.y as _);
163                let button = from_qt_button(button);
164                rust_window.mouse_event(MouseEvent::Pressed{ position, button, click_count: 0 })
165            });
166        }
167        void mouseReleaseEvent(QMouseEvent *event) override {
168            auto [pos, rust_window] = adjust_mouse_event_to_popup_parent(event);
169            if (!rust_window)
170                return;
171
172            // HACK: Qt on windows is a bit special when clicking on the window
173            //       close button and when the resulting close event is ignored.
174            //       In that case a release event that was not preceded by
175            //       a press event is sent on Windows.
176            //       This confuses Slint, so eat this event.
177            //
178            //       One example is a popup is shown in the close event that
179            //       then ignores the close request to ask the user what to
180            //       do. The stray release event will then close the popup
181            //       straight away
182            //
183            //       However, we must still forward the event to the right popup menu
184            //       that's why we compare rust_window with this->rust_window
185            if (!isMouseButtonDown && rust_window == this->rust_window) {
186                return;
187            }
188            isMouseButtonDown = event->button() != Qt::NoButton;
189
190            int button = event->button();
191            rust!(Slint_mouseReleaseEvent [rust_window: &QtWindow as "void*", pos: qttypes::QPoint as "QPoint", button: u32 as "int" ] {
192                let position = LogicalPoint::new(pos.x as _, pos.y as _);
193                let button = from_qt_button(button);
194                rust_window.mouse_event(MouseEvent::Released{ position, button, click_count: 0 })
195            });
196        }
197        void mouseMoveEvent(QMouseEvent *event) override {
198            auto [pos, rust_window] = adjust_mouse_event_to_popup_parent(event);
199            if (!rust_window)
200                return;
201            rust!(Slint_mouseMoveEvent [rust_window: &QtWindow as "void*", pos: qttypes::QPoint as "QPoint"] {
202                let position = LogicalPoint::new(pos.x as _, pos.y as _);
203                rust_window.mouse_event(MouseEvent::Moved{position})
204            });
205        }
206        void wheelEvent(QWheelEvent *event) override {
207            if (!rust_window)
208                return;
209            QPointF pos = event->position();
210            QPoint delta = event->pixelDelta();
211            if (delta.isNull()) {
212                delta = event->angleDelta();
213            }
214            rust!(Slint_mouseWheelEvent [rust_window: &QtWindow as "void*", pos: qttypes::QPointF as "QPointF", delta: qttypes::QPoint as "QPoint"] {
215                let position = LogicalPoint::new(pos.x as _, pos.y as _);
216                rust_window.mouse_event(MouseEvent::Wheel{position, delta_x: delta.x as _, delta_y: delta.y as _})
217            });
218        }
219        void leaveEvent(QEvent *) override {
220            if (!rust_window)
221                return;
222            rust!(Slint_mouseLeaveEvent [rust_window: &QtWindow as "void*"] {
223                rust_window.mouse_event(MouseEvent::Exit)
224            });
225        }
226
227        void keyPressEvent(QKeyEvent *event) override {
228            if (!rust_window)
229                return;
230            QString text =  event->text();
231            int key = event->key();
232            bool repeat = event->isAutoRepeat();
233            rust!(Slint_keyPress [rust_window: &QtWindow as "void*", key: i32 as "int", text: qttypes::QString as "QString", repeat: bool as "bool"] {
234                rust_window.key_event(key, text.clone(), false, repeat);
235            });
236        }
237        void keyReleaseEvent(QKeyEvent *event) override {
238            if (!rust_window)
239                return;
240            // Qt sends repeated releases together with presses for auto-repeat events, but Slint only sends presses in that case.
241            // This matches the behavior of at least winit, Web and Android.
242            if (event->isAutoRepeat())
243                return;
244
245            QString text =  event->text();
246            int key = event->key();
247            rust!(Slint_keyRelease [rust_window: &QtWindow as "void*", key: i32 as "int", text: qttypes::QString as "QString"] {
248                rust_window.key_event(key, text.clone(), true, false);
249            });
250        }
251
252        void changeEvent(QEvent *event) override {
253            if (!rust_window)
254                return QWidget::changeEvent(event);
255
256            if (event->type() == QEvent::ActivationChange) {
257                bool active = isActiveWindow();
258                rust!(Slint_updateWindowActivation [rust_window: &QtWindow as "void*", active: bool as "bool"] {
259                    rust_window.window.dispatch_event(WindowEvent::WindowActiveChanged(active));
260                });
261            } else if (event->type() == QEvent::PaletteChange || event->type() == QEvent::StyleChange) {
262                bool dark_color_scheme = qApp->palette().color(QPalette::Window).valueF() < 0.5;
263                rust!(Slint_updateWindowDarkColorScheme [rust_window: &QtWindow as "void*", dark_color_scheme: bool as "bool"] {
264                    if let Some(ds) = rust_window.color_scheme.get() {
265                        ds.as_ref().set(if dark_color_scheme {
266                            ColorScheme::Dark
267                        } else {
268                            ColorScheme::Light
269                        });
270                    }
271                });
272            }
273
274            // Entering fullscreen, maximizing or minimizing the window will
275            // trigger a change event. We need to update the internal window
276            // state to match the actual window state.
277            if (event->type() == QEvent::WindowStateChange)
278            {
279                rust!(Slint_syncWindowState [rust_window: &QtWindow as "void*"]{
280                    rust_window.window_state_event();
281                });
282            }
283
284
285            QWidget::changeEvent(event);
286        }
287
288        void closeEvent(QCloseEvent *event) override {
289            if (!rust_window)
290                return;
291            rust!(Slint_requestClose [rust_window: &QtWindow as "void*"] {
292                rust_window.window.dispatch_event(WindowEvent::CloseRequested);
293            });
294            event->ignore();
295        }
296
297        QSize sizeHint() const override {
298            if (!rust_window)
299                return {};
300            auto preferred_size = rust!(Slint_sizeHint [rust_window: &QtWindow as "void*"] -> qttypes::QSize as "QSize" {
301                let component_rc = WindowInner::from_pub(&rust_window.window).component();
302                let component = ItemTreeRc::borrow_pin(&component_rc);
303                let layout_info_h = component.as_ref().layout_info(Orientation::Horizontal);
304                let layout_info_v = component.as_ref().layout_info(Orientation::Vertical);
305                qttypes::QSize {
306                    width: layout_info_h.preferred_bounded() as _,
307                    height: layout_info_v.preferred_bounded() as _,
308                }
309            });
310            if (!preferred_size.isEmpty()) {
311                return preferred_size;
312            } else {
313                return QWidget::sizeHint();
314            }
315        }
316
317        QVariant inputMethodQuery(Qt::InputMethodQuery query) const override {
318            switch (query) {
319            case Qt::ImCursorRectangle: return ime_position;
320            case Qt::ImCursorPosition: return ime_cursor;
321            case Qt::ImSurroundingText: return ime_text;
322            case Qt::ImCurrentSelection: return ime_text.mid(qMin(ime_cursor, ime_anchor), qAbs(ime_cursor - ime_anchor));
323            case Qt::ImAnchorPosition: return ime_anchor;
324            case Qt::ImTextBeforeCursor: return ime_text.left(ime_cursor);
325            case Qt::ImTextAfterCursor: return ime_text.right(ime_cursor);
326            default: break;
327            }
328            return QWidget::inputMethodQuery(query);
329        }
330
331        void inputMethodEvent(QInputMethodEvent *event) override {
332            if (!rust_window)
333                return;
334            QString commit_string = event->commitString();
335            QString preedit_string = event->preeditString();
336            int replacement_start = event->replacementStart();
337            QStringView ime_text(this->ime_text);
338            replacement_start = replacement_start < 0 ?
339                -ime_text.mid(ime_cursor,-replacement_start).toUtf8().size() :
340                ime_text.mid(ime_cursor,replacement_start).toUtf8().size();
341            int replacement_length = qMax(0, event->replacementLength());
342            ime_text.mid(ime_cursor + replacement_start, replacement_length).toUtf8().size();
343            int preedit_cursor = -1;
344            for (const QInputMethodEvent::Attribute &attribute: event->attributes()) {
345                if (attribute.type == QInputMethodEvent::Cursor) {
346                    if (attribute.length > 0) {
347                        preedit_cursor = QStringView(preedit_string).left(attribute.start).toUtf8().size();
348                    }
349                }
350            }
351            event->accept();
352            rust!(Slint_inputMethodEvent [rust_window: &QtWindow as "void*", commit_string: qttypes::QString as "QString",
353                preedit_string: qttypes::QString as "QString", replacement_start: i32 as "int", replacement_length: i32 as "int",
354                preedit_cursor: i32 as "int"] {
355                    let runtime_window = WindowInner::from_pub(&rust_window.window);
356
357                    let event = KeyEvent {
358                        event_type: KeyEventType::UpdateComposition,
359                        text: i_slint_core::format!("{}", commit_string),
360                        preedit_text: i_slint_core::format!("{}", preedit_string),
361                        preedit_selection: (preedit_cursor >= 0).then_some(preedit_cursor..preedit_cursor),
362                        replacement_range: (!commit_string.is_empty() || !preedit_string.is_empty() || preedit_cursor >= 0)
363                            .then_some(replacement_start..replacement_start+replacement_length),
364                        ..Default::default()
365                    };
366                    runtime_window.process_key_input(event);
367                });
368        }
369    };
370
371    QPainterPath to_painter_path(const QRectF &rect, qreal top_left_radius, qreal top_right_radius, qreal bottom_right_radius, qreal bottom_left_radius) {
372        QPainterPath path;
373        if (qFuzzyCompare(top_left_radius, top_right_radius) && qFuzzyCompare(top_left_radius, bottom_right_radius) && qFuzzyCompare(top_left_radius, bottom_left_radius)) {
374            path.addRoundedRect(rect, top_left_radius, top_left_radius);
375        } else {
376            QSizeF half = rect.size() / 2.0;
377
378            qreal tl_rx = qMin(top_left_radius, half.width());
379            qreal tl_ry = qMin(top_left_radius, half.height());
380            QRectF top_left(rect.left(), rect.top(), 2 * tl_rx, 2 * tl_ry);
381
382            qreal tr_rx = qMin(top_right_radius, half.width());
383            qreal tr_ry = qMin(top_right_radius, half.height());
384            QRectF top_right(rect.right() - 2 * tr_rx, rect.top(), 2 * tr_rx, 2 * tr_ry);
385
386            qreal br_rx = qMin(bottom_right_radius, half.width());
387            qreal br_ry = qMin(bottom_right_radius, half.height());
388            QRectF bottom_right(rect.right() - 2 * br_rx, rect.bottom() - 2 * br_ry, 2 * br_rx, 2 * br_ry);
389
390            qreal bl_rx = qMin(bottom_left_radius, half.width());
391            qreal bl_ry = qMin(bottom_left_radius, half.height());
392            QRectF bottom_left(rect.left(), rect.bottom() - 2 * bl_ry, 2 * bl_rx, 2 * bl_ry);
393
394            if (top_left.isNull()) {
395                path.moveTo(rect.topLeft());
396            } else {
397                path.arcMoveTo(top_left, 180);
398                path.arcTo(top_left, 180, -90);
399            }
400            if (top_right.isNull()) {
401                path.lineTo(rect.topRight());
402            } else {
403                path.arcTo(top_right, 90, -90);
404            }
405            if (bottom_right.isNull()) {
406                path.lineTo(rect.bottomRight());
407            } else {
408                path.arcTo(bottom_right, 0, -90);
409            }
410            if (bottom_left.isNull()) {
411                path.lineTo(rect.bottomLeft());
412            } else {
413                path.arcTo(bottom_left, -90, -90);
414            }
415            path.closeSubpath();
416        }
417        return path;
418    };
419}}
420
421cpp_class!(
422    /// Wrapper around a pointer to a QPainter.
423    // We can't use [`qttypes::QPainter`] because it is not sound <https://github.com/woboq/qmetaobject-rs/issues/267>
424    pub unsafe struct QPainterPtr as "QPainterPtr"
425);
426impl QPainterPtr {
427    pub fn restore(&mut self) {
428        cpp!(unsafe [self as "QPainterPtr*"] {
429            (*self)->restore();
430        });
431    }
432
433    pub fn save(&mut self) {
434        cpp!(unsafe [self as "QPainterPtr*"] {
435            (*self)->save();
436        });
437    }
438}
439
440cpp_class! {pub unsafe struct QPainterPath as "QPainterPath"}
441
442impl QPainterPath {
443    /*
444    pub fn reserve(&mut self, size: usize) {
445        cpp! { unsafe [self as "QPainterPath*", size as "long long"] {
446            self->reserve(size);
447        }}
448    }*/
449
450    pub fn move_to(&mut self, to: qttypes::QPointF) {
451        cpp! { unsafe [self as "QPainterPath*", to as "QPointF"] {
452            self->moveTo(to);
453        }}
454    }
455    pub fn line_to(&mut self, to: qttypes::QPointF) {
456        cpp! { unsafe [self as "QPainterPath*", to as "QPointF"] {
457            self->lineTo(to);
458        }}
459    }
460    pub fn quad_to(&mut self, ctrl: qttypes::QPointF, to: qttypes::QPointF) {
461        cpp! { unsafe [self as "QPainterPath*", ctrl as "QPointF", to as "QPointF"] {
462            self->quadTo(ctrl, to);
463        }}
464    }
465    pub fn cubic_to(
466        &mut self,
467        ctrl1: qttypes::QPointF,
468        ctrl2: qttypes::QPointF,
469        to: qttypes::QPointF,
470    ) {
471        cpp! { unsafe [self as "QPainterPath*", ctrl1 as "QPointF", ctrl2 as "QPointF", to as "QPointF"] {
472            self->cubicTo(ctrl1, ctrl2, to);
473        }}
474    }
475
476    pub fn close(&mut self) {
477        cpp! { unsafe [self as "QPainterPath*"] {
478            self->closeSubpath();
479        }}
480    }
481
482    pub fn set_fill_rule(&mut self, rule: key_generated::Qt_FillRule) {
483        cpp! { unsafe [self as "QPainterPath*", rule as "Qt::FillRule" ] {
484            self->setFillRule(rule);
485        }}
486    }
487}
488
489fn into_qbrush(
490    brush: i_slint_core::Brush,
491    width: qttypes::qreal,
492    height: qttypes::qreal,
493) -> qttypes::QBrush {
494    /// Mangle the position to work around the fact that Qt merge stop at equal position
495    fn mangle_position(position: f32, idx: usize, count: usize) -> f32 {
496        // Add or subtract a small amount to make sure each stop is different but still in [0..1].
497        // It is possible that we swap stops that are both really really close to 0.54321+ε,
498        // but that is really unlikely
499        if position < 0.54321 + 67.8 * f32::EPSILON {
500            position + f32::EPSILON * idx as f32
501        } else {
502            position - f32::EPSILON * (count - idx - 1) as f32
503        }
504    }
505    match brush {
506        i_slint_core::Brush::SolidColor(color) => {
507            let color: u32 = color.as_argb_encoded();
508            cpp!(unsafe [color as "QRgb"] -> qttypes::QBrush as "QBrush" {
509                return QBrush(QColor::fromRgba(color));
510            })
511        }
512        i_slint_core::Brush::LinearGradient(g) => {
513            let (start, end) = i_slint_core::graphics::line_for_angle(
514                g.angle(),
515                [width as f32, height as f32].into(),
516            );
517            let p1 = qttypes::QPointF { x: start.x as _, y: start.y as _ };
518            let p2 = qttypes::QPointF { x: end.x as _, y: end.y as _ };
519            cpp_class!(unsafe struct QLinearGradient as "QLinearGradient");
520            let mut qlg = cpp! {
521                unsafe [p1 as "QPointF", p2 as "QPointF"] -> QLinearGradient as "QLinearGradient" {
522                    QLinearGradient qlg(p1, p2);
523                    return qlg;
524                }
525            };
526            let count = g.stops().count();
527            for (idx, s) in g.stops().enumerate() {
528                let pos: f32 = mangle_position(s.position, idx, count);
529                let color: u32 = s.color.as_argb_encoded();
530                cpp! {unsafe [mut qlg as "QLinearGradient", pos as "float", color as "QRgb"] {
531                    qlg.setColorAt(pos, QColor::fromRgba(color));
532                }};
533            }
534            cpp! {unsafe [qlg as "QLinearGradient"] -> qttypes::QBrush as "QBrush" {
535                return QBrush(qlg);
536            }}
537        }
538        i_slint_core::Brush::RadialGradient(g) => {
539            cpp_class!(unsafe struct QRadialGradient as "QRadialGradient");
540            let mut qrg = cpp! {
541                unsafe [width as "qreal", height as "qreal"] -> QRadialGradient as "QRadialGradient" {
542                    QRadialGradient qrg(width / 2, height / 2, sqrt(width * width + height * height) / 2);
543                    return qrg;
544                }
545            };
546            let count = g.stops().count();
547            for (idx, s) in g.stops().enumerate() {
548                let pos: f32 = mangle_position(s.position, idx, count);
549                let color: u32 = s.color.as_argb_encoded();
550                cpp! {unsafe [mut qrg as "QRadialGradient", pos as "float", color as "QRgb"] {
551                    qrg.setColorAt(pos, QColor::fromRgba(color));
552                }};
553            }
554            cpp! {unsafe [qrg as "QRadialGradient"] -> qttypes::QBrush as "QBrush" {
555                return QBrush(qrg);
556            }}
557        }
558        i_slint_core::Brush::ConicGradient(g) => {
559            cpp_class!(unsafe struct QConicalGradient as "QConicalGradient");
560            // QConicalGradient uses angles where 0 degrees is at 3 o'clock (east)
561            // We want gradient position 0 at 12 o'clock (north), so start at -90°
562            let mut qcg = cpp! {
563                unsafe [width as "qreal", height as "qreal"] -> QConicalGradient as "QConicalGradient" {
564                    QConicalGradient qcg(width / 2, height / 2, 90);
565                    return qcg;
566                }
567            };
568            let count = g.stops().count();
569            for (idx, s) in g.stops().enumerate() {
570                // Qt's conical gradient goes counter-clockwise, but Slint expects clockwise
571                // So we need to invert the positions: Qt position = 1.0 - Slint position
572                let pos: f32 = 1.0 - mangle_position(s.position, idx, count);
573                let color: u32 = s.color.as_argb_encoded();
574                cpp! {unsafe [mut qcg as "QConicalGradient", pos as "float", color as "QRgb"] {
575                    qcg.setColorAt(pos, QColor::fromRgba(color));
576                }};
577            }
578            cpp! {unsafe [qcg as "QConicalGradient"] -> qttypes::QBrush as "QBrush" {
579                return QBrush(qcg);
580            }}
581        }
582        _ => qttypes::QBrush::default(),
583    }
584}
585
586fn from_qt_button(qt_button: u32) -> PointerEventButton {
587    match qt_button {
588        // https://doc.qt.io/qt-6/qt.html#MouseButton-enum
589        1 => PointerEventButton::Left,
590        2 => PointerEventButton::Right,
591        4 => PointerEventButton::Middle,
592        8 => PointerEventButton::Back,
593        16 => PointerEventButton::Forward,
594        _ => PointerEventButton::Other,
595    }
596}
597
598/// Given a position offset and an object of a given type that has x,y,width,height properties,
599/// create a QRectF that fits it.
600macro_rules! check_geometry {
601    ($size:expr) => {{
602        let size = $size;
603        if size.width < 1. || size.height < 1. {
604            return Default::default();
605        };
606        qttypes::QRectF { x: 0., y: 0., width: size.width as _, height: size.height as _ }
607    }};
608}
609
610fn adjust_rect_and_border_for_inner_drawing(rect: &mut qttypes::QRectF, border_width: &mut f32) {
611    // If the border width exceeds the width, just fill the rectangle.
612    *border_width = border_width.min((rect.width as f32) / 2.);
613    // adjust the size so that the border is drawn within the geometry
614    rect.x += *border_width as f64 / 2.;
615    rect.y += *border_width as f64 / 2.;
616    rect.width -= *border_width as f64;
617    rect.height -= *border_width as f64;
618}
619
620struct QtItemRenderer<'a> {
621    painter: QPainterPtr,
622    cache: &'a ItemCache<qttypes::QPixmap>,
623    window: &'a i_slint_core::api::Window,
624    metrics: RenderingMetrics,
625}
626
627impl ItemRenderer for QtItemRenderer<'_> {
628    fn draw_rectangle(
629        &mut self,
630        rect_: Pin<&dyn RenderRectangle>,
631        _: &ItemRc,
632        size: LogicalSize,
633        _cache: &CachedRenderingData,
634    ) {
635        let rect: qttypes::QRectF = check_geometry!(size);
636        let brush: qttypes::QBrush = into_qbrush(rect_.background(), rect.width, rect.height);
637        let painter: &mut QPainterPtr = &mut self.painter;
638        cpp! { unsafe [painter as "QPainterPtr*", brush as "QBrush", rect as "QRectF"] {
639            (*painter)->fillRect(rect, brush);
640        }}
641    }
642
643    fn draw_border_rectangle(
644        &mut self,
645        rect: Pin<&dyn RenderBorderRectangle>,
646        _: &ItemRc,
647        size: LogicalSize,
648        _: &CachedRenderingData,
649    ) {
650        Self::draw_rectangle_impl(
651            &mut self.painter,
652            check_geometry!(size),
653            rect.background(),
654            rect.border_color(),
655            rect.border_width().get(),
656            rect.border_radius(),
657        );
658    }
659
660    fn draw_window_background(
661        &mut self,
662        _rect: Pin<&dyn RenderRectangle>,
663        _self_rc: &ItemRc,
664        _size: LogicalSize,
665        _cache: &CachedRenderingData,
666    ) {
667        // Background is applied via WindowProperties::background()
668    }
669
670    fn draw_image(
671        &mut self,
672        image: Pin<&dyn RenderImage>,
673        item_rc: &ItemRc,
674        size: LogicalSize,
675        _: &CachedRenderingData,
676    ) {
677        self.draw_image_impl(item_rc, size, image);
678    }
679
680    fn draw_text(
681        &mut self,
682        text: Pin<&dyn RenderText>,
683        self_rc: &ItemRc,
684        size: LogicalSize,
685        _: &CachedRenderingData,
686    ) {
687        sharedparley::draw_text(self, text, Some(text.font_request(self_rc)), size);
688    }
689
690    fn draw_text_input(
691        &mut self,
692        text_input: Pin<&items::TextInput>,
693        self_rc: &ItemRc,
694        size: LogicalSize,
695    ) {
696        sharedparley::draw_text_input(
697            self,
698            text_input,
699            Some(text_input.font_request(self_rc)),
700            size,
701            Some(qt_password_character),
702        );
703    }
704
705    fn draw_path(&mut self, path: Pin<&items::Path>, item_rc: &ItemRc, size: LogicalSize) {
706        let (offset, path_events) = match path.fitted_path_events(item_rc) {
707            Some(offset_and_events) => offset_and_events,
708            None => return,
709        };
710        let rect: qttypes::QRectF = check_geometry!(size);
711        let fill_brush: qttypes::QBrush = into_qbrush(path.fill(), rect.width, rect.height);
712        let stroke_brush: qttypes::QBrush = into_qbrush(path.stroke(), rect.width, rect.height);
713        let stroke_width: f32 = path.stroke_width().get();
714        let stroke_pen_cap_style: i32 = match path.stroke_line_cap() {
715            LineCap::Butt => 0x00,
716            LineCap::Round => 0x20,
717            LineCap::Square => 0x10,
718        };
719        let pos = qttypes::QPoint { x: offset.x as _, y: offset.y as _ };
720        let mut painter_path = QPainterPath::default();
721
722        painter_path.set_fill_rule(match path.fill_rule() {
723            FillRule::Nonzero => key_generated::Qt_FillRule_WindingFill,
724            FillRule::Evenodd => key_generated::Qt_FillRule_OddEvenFill,
725        });
726
727        for x in path_events.iter() {
728            fn to_qpointf(p: Point) -> qttypes::QPointF {
729                qttypes::QPointF { x: p.x as _, y: p.y as _ }
730            }
731            match x {
732                lyon_path::Event::Begin { at } => {
733                    painter_path.move_to(to_qpointf(at));
734                }
735                lyon_path::Event::Line { from: _, to } => {
736                    painter_path.line_to(to_qpointf(to));
737                }
738                lyon_path::Event::Quadratic { from: _, ctrl, to } => {
739                    painter_path.quad_to(to_qpointf(ctrl), to_qpointf(to));
740                }
741
742                lyon_path::Event::Cubic { from: _, ctrl1, ctrl2, to } => {
743                    painter_path.cubic_to(to_qpointf(ctrl1), to_qpointf(ctrl2), to_qpointf(to));
744                }
745                lyon_path::Event::End { last: _, first: _, close } => {
746                    // FIXME: are we supposed to do something with last and first?
747                    if close {
748                        painter_path.close()
749                    }
750                }
751            }
752        }
753
754        let anti_alias: bool = path.anti_alias();
755
756        let painter: &mut QPainterPtr = &mut self.painter;
757        cpp! { unsafe [
758                painter as "QPainterPtr*",
759                pos as "QPoint",
760                mut painter_path as "QPainterPath",
761                fill_brush as "QBrush",
762                stroke_brush as "QBrush",
763                stroke_width as "float",
764                stroke_pen_cap_style as "int",
765                anti_alias as "bool"] {
766            (*painter)->save();
767            auto cleanup = qScopeGuard([&] { (*painter)->restore(); });
768            (*painter)->translate(pos);
769            (*painter)->setPen(stroke_width > 0 ? QPen(stroke_brush, stroke_width, Qt::SolidLine, Qt::PenCapStyle(stroke_pen_cap_style)) : Qt::NoPen);
770            (*painter)->setBrush(fill_brush);
771            (*painter)->setRenderHint(QPainter::Antialiasing, anti_alias);
772            (*painter)->drawPath(painter_path);
773        }}
774    }
775
776    fn draw_box_shadow(
777        &mut self,
778        box_shadow: Pin<&items::BoxShadow>,
779        item_rc: &ItemRc,
780        _size: LogicalSize,
781    ) {
782        let pixmap : qttypes::QPixmap = self.cache.get_or_update_cache_entry( item_rc, || {
783                let shadow_rect = check_geometry!(item_rc.geometry().size);
784
785                let source_size = qttypes::QSize {
786                    width: shadow_rect.width.ceil() as _,
787                    height: shadow_rect.height.ceil() as _,
788                };
789
790                let mut source_image =
791                    qttypes::QImage::new(source_size, qttypes::ImageFormat::ARGB32_Premultiplied);
792                source_image.fill(qttypes::QColor::from_rgba_f(0., 0., 0., 0.));
793
794                let img = &mut source_image;
795                let mut painter_ = cpp!(unsafe [img as "QImage*"] -> QPainterPtr as "QPainterPtr" {
796                    return std::make_unique<QPainter>(img);
797                });
798
799                Self::draw_rectangle_impl(
800                    &mut painter_,
801                    qttypes::QRectF { x: 0., y: 0., width: shadow_rect.width, height: shadow_rect.height },
802                    Brush::SolidColor(box_shadow.color()),
803                    Brush::default(),
804                    0.,
805                    LogicalBorderRadius::new_uniform(box_shadow.border_radius().get()),
806                );
807
808                drop(painter_);
809
810                let blur_radius = box_shadow.blur().get();
811
812                if blur_radius > 0. {
813                    cpp! {
814                    unsafe[img as "QImage*", blur_radius as "float"] -> qttypes::QPixmap as "QPixmap" {
815                        QGraphicsScene scene;
816                        auto pixmap_item = scene.addPixmap(QPixmap::fromImage(*img));
817
818                        auto blur_effect = new QGraphicsBlurEffect;
819                        blur_effect->setBlurRadius(blur_radius);
820                        blur_effect->setBlurHints(QGraphicsBlurEffect::QualityHint);
821
822                        // takes ownership of the effect and registers the item with
823                        // the effect as source.
824                        pixmap_item->setGraphicsEffect(blur_effect);
825
826                        QImage blurred_scene(img->width() + 2 * blur_radius, img->height() + 2 * blur_radius, QImage::Format_ARGB32_Premultiplied);
827                        blurred_scene.fill(Qt::transparent);
828
829                        QPainter p(&blurred_scene);
830                        scene.render(&p,
831                            QRectF(0, 0, blurred_scene.width(), blurred_scene.height()),
832                            QRectF(-blur_radius, -blur_radius, blurred_scene.width(), blurred_scene.height()));
833                        p.end();
834
835                        return QPixmap::fromImage(blurred_scene);
836                    }}
837                } else {
838                    cpp! { unsafe[img as "QImage*"] -> qttypes::QPixmap as "QPixmap" {
839                        return QPixmap::fromImage(*img);
840                    }}
841                }
842            });
843
844        let blur_radius = box_shadow.blur();
845
846        let shadow_offset = qttypes::QPointF {
847            x: (box_shadow.offset_x() - blur_radius).get() as f64,
848            y: (box_shadow.offset_y() - blur_radius).get() as f64,
849        };
850
851        let painter: &mut QPainterPtr = &mut self.painter;
852        cpp! { unsafe [
853                painter as "QPainterPtr*",
854                shadow_offset as "QPointF",
855                pixmap as "QPixmap"
856            ] {
857            (*painter)->drawPixmap(shadow_offset, pixmap);
858        }}
859    }
860
861    fn visit_opacity(
862        &mut self,
863        opacity_item: Pin<&Opacity>,
864        item_rc: &ItemRc,
865        _size: LogicalSize,
866    ) -> RenderingResult {
867        let opacity = opacity_item.opacity();
868        if Opacity::need_layer(item_rc, opacity) {
869            self.render_and_blend_layer(opacity, item_rc)
870        } else {
871            self.apply_opacity(opacity);
872            self.cache.release(item_rc);
873            RenderingResult::ContinueRenderingChildren
874        }
875    }
876
877    fn visit_layer(
878        &mut self,
879        layer_item: Pin<&Layer>,
880        self_rc: &ItemRc,
881        _size: LogicalSize,
882    ) -> RenderingResult {
883        if layer_item.cache_rendering_hint() {
884            self.render_and_blend_layer(1.0, self_rc)
885        } else {
886            RenderingResult::ContinueRenderingChildren
887        }
888    }
889
890    fn combine_clip(
891        &mut self,
892        rect: LogicalRect,
893        radius: LogicalBorderRadius,
894        border_width: LogicalLength,
895    ) -> bool {
896        let mut border_width: f32 = border_width.get();
897        let mut clip_rect = qttypes::QRectF {
898            x: rect.min_x() as _,
899            y: rect.min_y() as _,
900            width: rect.width() as _,
901            height: rect.height() as _,
902        };
903        adjust_rect_and_border_for_inner_drawing(&mut clip_rect, &mut border_width);
904        let painter: &mut QPainterPtr = &mut self.painter;
905        let top_left_radius = radius.top_left;
906        let top_right_radius = radius.top_right;
907        let bottom_left_radius = radius.bottom_left;
908        let bottom_right_radius = radius.bottom_right;
909        cpp! { unsafe [
910                painter as "QPainterPtr*",
911                clip_rect as "QRectF",
912                top_left_radius as "float",
913                top_right_radius as "float",
914                bottom_right_radius as "float",
915                bottom_left_radius as "float"] -> bool as "bool" {
916            if (top_left_radius <= 0 && top_right_radius <= 0 && bottom_right_radius <= 0 && bottom_left_radius <= 0) {
917                (*painter)->setClipRect(clip_rect, Qt::IntersectClip);
918            } else {
919                QPainterPath path = to_painter_path(clip_rect, top_left_radius, top_right_radius, bottom_right_radius, bottom_left_radius);
920                (*painter)->setClipPath(path, Qt::IntersectClip);
921            }
922            return !(*painter)->clipBoundingRect().isEmpty();
923        }}
924    }
925
926    fn get_current_clip(&self) -> LogicalRect {
927        let painter: &QPainterPtr = &self.painter;
928        let res = cpp! { unsafe [painter as "const QPainterPtr*" ] -> qttypes::QRectF as "QRectF" {
929            return (*painter)->clipBoundingRect();
930        }};
931        LogicalRect::new(
932            LogicalPoint::new(res.x as _, res.y as _),
933            LogicalSize::new(res.width as _, res.height as _),
934        )
935    }
936
937    fn save_state(&mut self) {
938        self.painter.save()
939    }
940
941    fn restore_state(&mut self) {
942        self.painter.restore()
943    }
944
945    fn scale_factor(&self) -> f32 {
946        1.
947        /* cpp! { unsafe [painter as "QPainterPtr*"] -> f32 as "float" {
948            return (*painter)->paintEngine()->paintDevice()->devicePixelRatioF();
949        }} */
950    }
951
952    fn draw_cached_pixmap(
953        &mut self,
954        _item_rc: &ItemRc,
955        update_fn: &dyn Fn(&mut dyn FnMut(u32, u32, &[u8])),
956    ) {
957        update_fn(&mut |width: u32, height: u32, data: &[u8]| {
958            let data = data.as_ptr();
959            let painter: &mut QPainterPtr = &mut self.painter;
960            cpp! { unsafe [painter as "QPainterPtr*",  width as "int", height as "int", data as "const unsigned char *"] {
961                QImage img(data, width, height, width * 4, QImage::Format_RGBA8888_Premultiplied);
962                (*painter)->drawImage(QPoint(), img);
963            }}
964        })
965    }
966
967    fn draw_string(&mut self, string: &str, color: Color) {
968        sharedparley::draw_text(
969            self,
970            std::pin::pin!((SharedString::from(string), Brush::from(color))),
971            None,
972            LogicalSize::new(1., 1.), // Non-zero size to avoid an early return
973        );
974    }
975
976    fn draw_image_direct(&mut self, _image: i_slint_core::graphics::Image) {
977        todo!()
978    }
979
980    fn window(&self) -> &i_slint_core::window::WindowInner {
981        i_slint_core::window::WindowInner::from_pub(self.window)
982    }
983
984    fn as_any(&mut self) -> Option<&mut dyn std::any::Any> {
985        Some(&mut self.painter)
986    }
987
988    fn translate(&mut self, distance: LogicalVector) {
989        let x: f32 = distance.x;
990        let y: f32 = distance.y;
991        let painter: &mut QPainterPtr = &mut self.painter;
992        cpp! { unsafe [painter as "QPainterPtr*", x as "float", y as "float"] {
993            (*painter)->translate(x, y);
994        }}
995    }
996
997    fn rotate(&mut self, angle_in_degrees: f32) {
998        let painter: &mut QPainterPtr = &mut self.painter;
999        cpp! { unsafe [painter as "QPainterPtr*", angle_in_degrees as "float"] {
1000            (*painter)->rotate(angle_in_degrees);
1001        }}
1002    }
1003
1004    fn scale(&mut self, x_factor: f32, y_factor: f32) {
1005        let painter: &mut QPainterPtr = &mut self.painter;
1006        cpp! { unsafe [painter as "QPainterPtr*", x_factor as "float", y_factor as "float"] {
1007            (*painter)->scale(x_factor, y_factor);
1008        }}
1009    }
1010
1011    fn apply_opacity(&mut self, opacity: f32) {
1012        let painter: &mut QPainterPtr = &mut self.painter;
1013        cpp! { unsafe [painter as "QPainterPtr*", opacity as "float"] {
1014            (*painter)->setOpacity((*painter)->opacity() * opacity);
1015        }}
1016    }
1017}
1018
1019#[derive(Clone)]
1020pub enum GlyphBrush {
1021    Fill(qttypes::QBrush),
1022    Stroke(qttypes::QPen),
1023}
1024
1025impl GlyphRenderer for QtItemRenderer<'_> {
1026    type PlatformBrush = GlyphBrush;
1027
1028    fn platform_text_fill_brush(
1029        &mut self,
1030        brush: i_slint_core::Brush,
1031        size: LogicalSize,
1032    ) -> Option<Self::PlatformBrush> {
1033        Some(GlyphBrush::Fill(into_qbrush(brush, size.width as _, size.height as _)))
1034    }
1035
1036    fn platform_brush_for_color(
1037        &mut self,
1038        color: &i_slint_core::Color,
1039    ) -> Option<Self::PlatformBrush> {
1040        let color: u32 = color.as_argb_encoded();
1041        Some(GlyphBrush::Fill(cpp!(unsafe [color as "QRgb"] -> qttypes::QBrush as "QBrush" {
1042            return QBrush(QColor::fromRgba(color));
1043        })))
1044    }
1045
1046    fn platform_text_stroke_brush(
1047        &mut self,
1048        brush: i_slint_core::Brush,
1049        physical_stroke_width: f32,
1050        size: LogicalSize,
1051    ) -> Option<Self::PlatformBrush> {
1052        let brush = into_qbrush(brush, size.width as _, size.height as _);
1053        Some(GlyphBrush::Stroke(
1054            cpp!(unsafe [brush as "QBrush", physical_stroke_width as "float"] -> qttypes::QPen as "QPen" {
1055                QPen pen(brush, physical_stroke_width);
1056                pen.setJoinStyle(Qt::MiterJoin);
1057                return pen;
1058            }),
1059        ))
1060    }
1061
1062    fn draw_glyph_run(
1063        &mut self,
1064        font: &sharedparley::parley::FontData,
1065        font_size: sharedparley::PhysicalLength,
1066        brush: Self::PlatformBrush,
1067        y_offset: sharedparley::PhysicalLength,
1068        glyphs_it: &mut dyn Iterator<Item = sharedparley::parley::layout::Glyph>,
1069    ) {
1070        let Some(mut raw_font) = FONT_CACHE.with(|cache| cache.borrow_mut().font(font)) else {
1071            return;
1072        };
1073
1074        raw_font.set_pixel_size(font_size.get());
1075
1076        let (glyph_indices, positions): (Vec<u32>, Vec<qttypes::QPointF>) = glyphs_it
1077            .into_iter()
1078            .map(|g| {
1079                (g.id, qttypes::QPointF { x: g.x as f64, y: g.y as f64 + y_offset.get() as f64 })
1080            })
1081            .unzip();
1082
1083        let glyph_indices_ptr = glyph_indices.as_ptr();
1084        let glyph_positions_ptr = positions.as_ptr();
1085        let size: u32 = glyph_indices.len() as u32;
1086        if size == 0 {
1087            return;
1088        }
1089
1090        let painter: &mut QPainterPtr = &mut self.painter;
1091
1092        match brush {
1093            GlyphBrush::Fill(qt_brush) => {
1094                cpp! { unsafe [painter as "QPainterPtr*", glyph_indices_ptr as "const quint32 *", glyph_positions_ptr as "const QPointF *", size as "int", raw_font as "QRawFont", qt_brush as "QBrush"] {
1095                    // drawGlyphRun uses QPen to fill glyphs
1096                    (*painter)->setPen(QPen(qt_brush, 1));
1097                    (*painter)->setBrush(Qt::NoBrush);
1098
1099                    QGlyphRun glyphRun;
1100                    glyphRun.setRawFont(raw_font);
1101                    glyphRun.setRawData(glyph_indices_ptr, glyph_positions_ptr, size);
1102                    (*painter)->drawGlyphRun(QPointF(0, 0), glyphRun);
1103                }}
1104            }
1105            GlyphBrush::Stroke(qt_pen) => {
1106                cpp! { unsafe [painter as "QPainterPtr*", glyph_indices_ptr as "const quint32 *", glyph_positions_ptr as "const QPointF *", size as "int", raw_font as "QRawFont", qt_pen as "QPen"] {
1107                    (*painter)->setPen(qt_pen);
1108                    (*painter)->setBrush(Qt::NoBrush);
1109
1110                    QPainterPath path;
1111                    for (int i = 0; i < size; i++) {
1112                        QPainterPath glyphPath = raw_font.pathForGlyph(glyph_indices_ptr[i]);
1113                        glyphPath.translate(glyph_positions_ptr[i]);
1114                        path.addPath(glyphPath);
1115                    }
1116                    (*painter)->drawPath(path);
1117                }}
1118            }
1119        }
1120    }
1121
1122    fn fill_rectangle(
1123        &mut self,
1124        physical_rect: sharedparley::PhysicalRect,
1125        color: i_slint_core::Color,
1126    ) {
1127        let rect = qttypes::QRectF {
1128            x: physical_rect.min_x() as _,
1129            y: physical_rect.min_y() as _,
1130            width: physical_rect.width() as _,
1131            height: physical_rect.height() as _,
1132        };
1133        let color: u32 = color.as_argb_encoded();
1134        let painter: &mut QPainterPtr = &mut self.painter;
1135        cpp! { unsafe [painter as "QPainterPtr*", color as "QRgb", rect as "QRectF"] {
1136            (*painter)->fillRect(rect, QBrush(QColor::fromRgba(color)));
1137        }}
1138    }
1139}
1140
1141cpp_class! {pub unsafe struct QRawFont as "QRawFont"}
1142
1143impl QRawFont {
1144    pub fn load_from_data(&mut self, data: &[u8], pixel_size: f32) {
1145        let font_data = qttypes::QByteArray::from(data);
1146        cpp! { unsafe [ self as "QRawFont*", font_data as "QByteArray", pixel_size as "float"] {
1147            self->loadFromData(font_data, pixel_size, QFont::PreferDefaultHinting);
1148        }}
1149    }
1150
1151    pub fn set_pixel_size(&mut self, pixel_size: f32) {
1152        cpp! { unsafe [ self as "QRawFont*", pixel_size as "float"] {
1153            self->setPixelSize(pixel_size);
1154        }}
1155    }
1156
1157    pub fn is_valid(&self) -> bool {
1158        cpp! { unsafe [ self as "const QRawFont*"] -> bool as "bool" {
1159            return self->isValid();
1160        }}
1161    }
1162}
1163
1164pub struct FontCache {
1165    /// Fonts are indexed by unique blob id (atomically incremented in fontique) and the font collection index.
1166    fonts: HashMap<(u64, u32), Option<QRawFont>>,
1167}
1168
1169impl Default for FontCache {
1170    fn default() -> Self {
1171        Self { fonts: Default::default() }
1172    }
1173}
1174
1175impl FontCache {
1176    pub fn font(&mut self, font: &parley::FontData) -> Option<QRawFont> {
1177        self.fonts
1178            .entry((font.data.id(), font.index))
1179            .or_insert_with(move || {
1180                let mut raw_font = QRawFont::default();
1181                raw_font.load_from_data(font.data.as_ref(), 12.0);
1182                if raw_font.is_valid() {
1183                    Some(raw_font)
1184                } else {
1185                    None
1186                }
1187            })
1188            .clone()
1189    }
1190}
1191
1192thread_local! {
1193    pub static FONT_CACHE: RefCell<FontCache> = RefCell::new(Default::default())
1194}
1195
1196fn shared_image_buffer_to_pixmap(buffer: &SharedImageBuffer) -> Option<qttypes::QPixmap> {
1197    let (format, bytes_per_line, buffer_ptr) = match buffer {
1198        SharedImageBuffer::RGBA8(img) => {
1199            (qttypes::ImageFormat::RGBA8888, img.width() * 4, img.as_bytes().as_ptr())
1200        }
1201        SharedImageBuffer::RGBA8Premultiplied(img) => {
1202            (qttypes::ImageFormat::RGBA8888_Premultiplied, img.width() * 4, img.as_bytes().as_ptr())
1203        }
1204        SharedImageBuffer::RGB8(img) => {
1205            (qttypes::ImageFormat::RGB888, img.width() * 3, img.as_bytes().as_ptr())
1206        }
1207    };
1208    let width: i32 = buffer.width() as _;
1209    let height: i32 = buffer.height() as _;
1210    let pixmap = cpp! { unsafe [format as "QImage::Format", width as "int", height as "int", bytes_per_line as "uint32_t", buffer_ptr as "const uchar *"] -> qttypes::QPixmap as "QPixmap" {
1211        QImage img(buffer_ptr, width, height, bytes_per_line, format);
1212        return QPixmap::fromImage(img);
1213    } };
1214    Some(pixmap)
1215}
1216
1217pub(crate) fn image_to_pixmap(
1218    image: &ImageInner,
1219    source_size: Option<euclid::Size2D<u32, PhysicalPx>>,
1220) -> Option<qttypes::QPixmap> {
1221    shared_image_buffer_to_pixmap(&image.render_to_buffer(source_size)?)
1222}
1223
1224impl QtItemRenderer<'_> {
1225    fn draw_image_impl(
1226        &mut self,
1227        item_rc: &ItemRc,
1228        size: LogicalSize,
1229        image: Pin<&dyn i_slint_core::item_rendering::RenderImage>,
1230    ) {
1231        let source_rect = image.source_clip();
1232
1233        let pixmap: qttypes::QPixmap = self.cache.get_or_update_cache_entry(item_rc, || {
1234            let source = image.source();
1235            let origin = source.size();
1236            let source: &ImageInner = (&source).into();
1237
1238            // Query target_width/height here again to ensure that changes will invalidate the item rendering cache.
1239            let scale_factor = ScaleFactor::new(self.scale_factor());
1240            let t = (image.target_size() * scale_factor).cast();
1241
1242            let source_size = if source.is_svg() {
1243                let has_source_clipping = source_rect.map_or(false, |rect| {
1244                    rect.origin.x != 0
1245                        || rect.origin.y != 0
1246                        || !rect.size.width != t.width
1247                        || !rect.size.height != t.height
1248                });
1249                if has_source_clipping {
1250                    // Source size & clipping is not implemented yet
1251                    None
1252                } else {
1253                    Some(
1254                        i_slint_core::graphics::fit(
1255                            image.image_fit(),
1256                            t.cast(),
1257                            IntRect::from_size(origin.cast()),
1258                            scale_factor,
1259                            Default::default(), // We only care about the size, so alignments don't matter
1260                            image.tiling(),
1261                        )
1262                        .size
1263                        .cast(),
1264                    )
1265                }
1266            } else {
1267                None
1268            };
1269
1270            image_to_pixmap(source, source_size).map_or_else(
1271                Default::default,
1272                |mut pixmap: qttypes::QPixmap| {
1273                    let colorize = image.colorize();
1274                    if !colorize.is_transparent() {
1275                        let pixmap_size = pixmap.size();
1276                        let brush: qttypes::QBrush = into_qbrush(
1277                            colorize,
1278                            pixmap_size.width.into(),
1279                            pixmap_size.height.into(),
1280                        );
1281                        cpp!(unsafe [mut pixmap as "QPixmap", brush as "QBrush"] {
1282                            QPainter p(&pixmap);
1283                            p.setCompositionMode(QPainter::CompositionMode_SourceIn);
1284                            p.fillRect(QRect(QPoint(), pixmap.size()), brush);
1285                        });
1286                    }
1287                    pixmap
1288                },
1289            )
1290        });
1291
1292        let image_size = pixmap.size();
1293        let source_rect = source_rect
1294            .unwrap_or_else(|| euclid::rect(0, 0, image_size.width as _, image_size.height as _));
1295        let scale_factor = ScaleFactor::new(self.scale_factor());
1296
1297        let fit = if let &i_slint_core::ImageInner::NineSlice(ref nine) = (&image.source()).into() {
1298            i_slint_core::graphics::fit9slice(
1299                nine.0.size(),
1300                nine.1,
1301                size * scale_factor,
1302                scale_factor,
1303                image.alignment(),
1304                image.tiling(),
1305            )
1306            .collect::<Vec<_>>()
1307        } else {
1308            vec![i_slint_core::graphics::fit(
1309                image.image_fit(),
1310                size * scale_factor,
1311                source_rect,
1312                scale_factor,
1313                image.alignment(),
1314                image.tiling(),
1315            )]
1316        };
1317
1318        for fit in fit {
1319            let dest_rect = qttypes::QRectF {
1320                x: fit.offset.x as _,
1321                y: fit.offset.y as _,
1322                width: fit.size.width as _,
1323                height: fit.size.height as _,
1324            };
1325            let source_rect = qttypes::QRectF {
1326                x: fit.clip_rect.origin.x as _,
1327                y: fit.clip_rect.origin.y as _,
1328                width: fit.clip_rect.size.width as _,
1329                height: fit.clip_rect.size.height as _,
1330            };
1331
1332            let painter: &mut QPainterPtr = &mut self.painter;
1333            let smooth: bool = image.rendering() == ImageRendering::Smooth;
1334            if let Some(offset) = fit.tiled {
1335                let scale_x: f32 = fit.source_to_target_x;
1336                let scale_y: f32 = fit.source_to_target_y;
1337                let offset = qttypes::QPoint { x: offset.x as _, y: offset.y as _ };
1338                cpp! { unsafe [
1339                    painter as "QPainterPtr*", pixmap as "QPixmap", source_rect as "QRectF",
1340                    dest_rect as "QRectF", smooth as "bool", scale_x as "float", scale_y as "float",
1341                    offset as "QPoint"
1342                    ] {
1343                        (*painter)->save();
1344                        (*painter)->setRenderHint(QPainter::SmoothPixmapTransform, smooth);
1345                        auto transform = QTransform::fromScale(1 / scale_x, 1 / scale_y);
1346                        auto scaled_destination = (dest_rect * transform).boundingRect();
1347                        QPixmap source_pixmap = pixmap.copy(source_rect.toRect());
1348                        (*painter)->scale(scale_x, scale_y);
1349                        (*painter)->drawTiledPixmap(scaled_destination, source_pixmap, offset);
1350                        (*painter)->restore();
1351                    }
1352                };
1353            } else {
1354                cpp! { unsafe [
1355                        painter as "QPainterPtr*",
1356                        pixmap as "QPixmap",
1357                        source_rect as "QRectF",
1358                        dest_rect as "QRectF",
1359                        smooth as "bool"] {
1360                    (*painter)->save();
1361                    (*painter)->setRenderHint(QPainter::SmoothPixmapTransform, smooth);
1362                    (*painter)->drawPixmap(dest_rect, pixmap, source_rect);
1363                    (*painter)->restore();
1364                }};
1365            }
1366        }
1367    }
1368
1369    fn draw_rectangle_impl(
1370        painter: &mut QPainterPtr,
1371        mut rect: qttypes::QRectF,
1372        brush: Brush,
1373        border_color: Brush,
1374        mut border_width: f32,
1375        border_radius: LogicalBorderRadius,
1376    ) {
1377        if border_color.is_transparent() {
1378            border_width = 0.;
1379        };
1380        let brush: qttypes::QBrush = into_qbrush(brush, rect.width, rect.height);
1381        let border_color: qttypes::QBrush = into_qbrush(border_color, rect.width, rect.height);
1382        let top_left_radius = border_radius.top_left;
1383        let top_right_radius = border_radius.top_right;
1384        let bottom_left_radius = border_radius.bottom_left;
1385        let bottom_right_radius = border_radius.bottom_right;
1386        border_width = border_width.min(rect.height.min(rect.width) as f32 / 2.);
1387        cpp! { unsafe [
1388                painter as "QPainterPtr*",
1389                brush as "QBrush",
1390                border_color as "QBrush",
1391                border_width as "float",
1392                top_left_radius as "float",
1393                top_right_radius as "float",
1394                bottom_left_radius as "float",
1395                bottom_right_radius as "float",
1396                mut rect as "QRectF"] {
1397            (*painter)->save();
1398            auto cleanup = qScopeGuard([&] { (*painter)->restore(); });
1399            (*painter)->setBrush(brush);
1400            QPen pen = border_width > 0 ? QPen(border_color, border_width, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin) : Qt::NoPen;
1401            if (top_left_radius <= 0 && top_right_radius <= 0 && bottom_left_radius <= 0 && bottom_right_radius <= 0) {
1402                if (!border_color.isOpaque() && border_width > 1) {
1403                    // In case of transparent pen, we want the background to cover the whole rectangle, which Qt doesn't do.
1404                    // So first draw the background, then draw the pen over it
1405                    (*painter)->setPen(Qt::NoPen);
1406                    (*painter)->drawRect(rect);
1407                    (*painter)->setBrush(QBrush());
1408                }
1409                rect.adjust(border_width / 2, border_width / 2, -border_width / 2, -border_width / 2);
1410                (*painter)->setPen(pen);
1411                (*painter)->drawRect(rect);
1412            } else {
1413                if (!border_color.isOpaque() && border_width > 1) {
1414                    // See adjustment below
1415                    float tl_r = qFuzzyIsNull(top_left_radius) ? top_left_radius : qMax(border_width/2, top_left_radius);
1416                    float tr_r = qFuzzyIsNull(top_right_radius) ? top_right_radius : qMax(border_width/2, top_right_radius);
1417                    float br_r = qFuzzyIsNull(bottom_right_radius) ? bottom_right_radius : qMax(border_width/2, bottom_right_radius);
1418                    float bl_r = qFuzzyIsNull(bottom_left_radius) ? bottom_left_radius : qMax(border_width/2, bottom_left_radius);
1419                    // In case of transparent pen, we want the background to cover the whole rectangle, which Qt doesn't do.
1420                    // So first draw the background, then draw the pen over it
1421                    (*painter)->setPen(Qt::NoPen);
1422                    (*painter)->drawPath(to_painter_path(rect, tl_r, tr_r, br_r, bl_r));
1423                    (*painter)->setBrush(QBrush());
1424                }
1425                // Qt's border radius is in the middle of the border. But we want it to be the radius of the rectangle itself.
1426                // This is incorrect if border_radius < border_width/2,  but this can't be fixed. Better to have a radius a bit too big than no radius at all
1427                float tl_r = qMax(0.0f, top_left_radius - border_width / 2);
1428                float tr_r = qMax(0.0f, top_right_radius - border_width / 2);
1429                float br_r = qMax(0.0f, bottom_right_radius - border_width / 2);
1430                float bl_r = qMax(0.0f, bottom_left_radius - border_width / 2);
1431                rect.adjust(border_width / 2, border_width / 2, -border_width / 2, -border_width / 2);
1432                (*painter)->setPen(pen);
1433                (*painter)->drawPath(to_painter_path(rect, tl_r, tr_r, br_r, bl_r));
1434            }
1435        }}
1436    }
1437
1438    fn render_layer(
1439        &mut self,
1440        item_rc: &ItemRc,
1441        layer_size_fn: &dyn Fn() -> LogicalSize,
1442    ) -> qttypes::QPixmap {
1443        self.cache.get_or_update_cache_entry(item_rc,  || {
1444            let painter: &mut QPainterPtr = &mut self.painter;
1445            let dpr = cpp! { unsafe [painter as "QPainterPtr*"] -> f32 as "float" {
1446                return (*painter)->paintEngine()->paintDevice()->devicePixelRatioF();
1447            }};
1448
1449            let layer_size = layer_size_fn();
1450            let layer_size = qttypes::QSize {
1451                width: (layer_size.width * dpr) as _,
1452                height: (layer_size.height * dpr) as _,
1453            };
1454
1455            let mut layer_image = qttypes::QImage::new(layer_size, qttypes::ImageFormat::ARGB32_Premultiplied);
1456            layer_image.fill(qttypes::QColor::from_rgba_f(0., 0., 0., 0.));
1457
1458            *self.metrics.layers_created.as_mut().unwrap() += 1;
1459
1460            let img_ref: &mut qttypes::QImage = &mut layer_image;
1461            let mut layer_painter = cpp!(unsafe [img_ref as "QImage*", dpr as "float"] -> QPainterPtr as "QPainterPtr" {
1462                img_ref->setDevicePixelRatio(dpr);
1463                auto painter = std::make_unique<QPainter>(img_ref);
1464                painter->setClipRect(0, 0, img_ref->width(), img_ref->height());
1465                painter->setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
1466                return painter;
1467            });
1468
1469            std::mem::swap(&mut self.painter, &mut layer_painter);
1470
1471            let window_adapter = self.window().window_adapter();
1472
1473            i_slint_core::item_rendering::render_item_children(
1474                self,
1475                &item_rc.item_tree(),
1476                item_rc.index() as isize, &window_adapter
1477            );
1478
1479            std::mem::swap(&mut self.painter, &mut layer_painter);
1480            drop(layer_painter);
1481
1482            qttypes::QPixmap::from(layer_image)
1483        })
1484    }
1485
1486    fn render_and_blend_layer(&mut self, alpha_tint: f32, self_rc: &ItemRc) -> RenderingResult {
1487        let current_clip = self.get_current_clip();
1488        let mut layer_image = self.render_layer(self_rc, &|| {
1489            // We don't need to include the size of the opacity item itself, since it has no content.
1490            let children_rect = i_slint_core::properties::evaluate_no_tracking(|| {
1491                self_rc.geometry().union(
1492                    &i_slint_core::item_rendering::item_children_bounding_rect(
1493                        &self_rc.item_tree(),
1494                        self_rc.index() as isize,
1495                        &current_clip,
1496                    ),
1497                )
1498            });
1499            children_rect.size
1500        });
1501        self.save_state();
1502        self.apply_opacity(alpha_tint);
1503        {
1504            let painter: &mut QPainterPtr = &mut self.painter;
1505            let layer_image_ref: &mut qttypes::QPixmap = &mut layer_image;
1506            cpp! { unsafe [
1507                    painter as "QPainterPtr*",
1508                    layer_image_ref as "QPixmap*"
1509                ] {
1510                (*painter)->drawPixmap(0, 0, *layer_image_ref);
1511            }}
1512        }
1513        self.restore_state();
1514        RenderingResult::ContinueRenderingWithoutChildren
1515    }
1516}
1517
1518cpp! {{
1519    struct QWidgetDeleteLater
1520    {
1521        void operator()(QWidget *widget_ptr)
1522        {
1523            if (widget_ptr->parent()) {
1524                // if the widget is a popup, use deleteLater (#4129)
1525                widget_ptr->hide();
1526                widget_ptr->deleteLater();
1527            } else {
1528                // Otherwise, use normal delete as it would otherwise cause crash at exit (#7570)
1529                delete widget_ptr;
1530            }
1531        }
1532    };
1533}}
1534
1535cpp_class!(pub(crate) unsafe struct QWidgetPtr as "std::unique_ptr<QWidget, QWidgetDeleteLater>");
1536
1537pub struct QtWindow {
1538    widget_ptr: QWidgetPtr,
1539    pub(crate) window: i_slint_core::api::Window,
1540    self_weak: Weak<Self>,
1541
1542    rendering_metrics_collector: RefCell<Option<Rc<RenderingMetricsCollector>>>,
1543
1544    cache: ItemCache<qttypes::QPixmap>,
1545
1546    tree_structure_changed: RefCell<bool>,
1547
1548    color_scheme: OnceCell<Pin<Box<Property<ColorScheme>>>>,
1549}
1550
1551impl Drop for QtWindow {
1552    fn drop(&mut self) {
1553        let widget_ptr = self.widget_ptr();
1554        cpp! {unsafe [widget_ptr as "SlintWidget*"]  {
1555            // widget_ptr uses deleteLater to destroy the SlintWidget, we must prevent events to still call us
1556            widget_ptr->rust_window = nullptr;
1557        }};
1558    }
1559}
1560
1561impl QtWindow {
1562    pub fn new() -> Rc<Self> {
1563        let rc = Rc::new_cyclic(|self_weak| {
1564            let window_ptr = self_weak.clone().into_raw();
1565            let widget_ptr = cpp! {unsafe [window_ptr as "void*"] -> QWidgetPtr as "std::unique_ptr<QWidget, QWidgetDeleteLater>" {
1566                ensure_initialized(true);
1567                auto widget = std::unique_ptr<SlintWidget, QWidgetDeleteLater>(new SlintWidget, QWidgetDeleteLater());
1568
1569                auto accessibility = new Slint_accessible_window(widget.get(), window_ptr);
1570                QAccessible::registerAccessibleInterface(accessibility);
1571
1572                return widget;
1573            }};
1574
1575            QtWindow {
1576                widget_ptr,
1577                window: i_slint_core::api::Window::new(self_weak.clone() as _),
1578                self_weak: self_weak.clone(),
1579                rendering_metrics_collector: Default::default(),
1580                cache: Default::default(),
1581                tree_structure_changed: RefCell::new(false),
1582                color_scheme: Default::default(),
1583            }
1584        });
1585        let widget_ptr = rc.widget_ptr();
1586        let rust_window = Rc::as_ptr(&rc);
1587        cpp! {unsafe [widget_ptr as "SlintWidget*", rust_window as "void*"]  {
1588            widget_ptr->rust_window = rust_window;
1589        }};
1590        ALL_WINDOWS.with(|aw| aw.borrow_mut().push(rc.self_weak.clone()));
1591        rc
1592    }
1593
1594    /// Return the QWidget*
1595    pub fn widget_ptr(&self) -> NonNull<()> {
1596        unsafe { std::mem::transmute_copy::<QWidgetPtr, NonNull<_>>(&self.widget_ptr) }
1597    }
1598
1599    fn paint_event(&self, painter: QPainterPtr) {
1600        let runtime_window = WindowInner::from_pub(&self.window);
1601        let window_adapter = runtime_window.window_adapter();
1602        runtime_window.draw_contents(|components| {
1603            i_slint_core::animations::update_animations();
1604            let mut renderer = QtItemRenderer {
1605                painter,
1606                cache: &self.cache,
1607                window: &self.window,
1608                metrics: RenderingMetrics { layers_created: Some(0), ..Default::default() },
1609            };
1610
1611            for (component, origin) in components {
1612                if let Some(component) = ItemTreeWeak::upgrade(&component) {
1613                    i_slint_core::item_rendering::render_component_items(
1614                        &component,
1615                        &mut renderer,
1616                        *origin,
1617                        &window_adapter,
1618                    );
1619                }
1620            }
1621
1622            if let Some(collector) = &*self.rendering_metrics_collector.borrow() {
1623                let metrics = renderer.metrics.clone();
1624                collector.measure_frame_rendered(&mut renderer, metrics);
1625            }
1626
1627            if self.window.has_active_animations() {
1628                self.request_redraw();
1629            }
1630        });
1631
1632        // Update the accessibility tree (if the component tree has changed)
1633        if self.tree_structure_changed.replace(false) {
1634            let widget_ptr = self.widget_ptr();
1635            cpp! { unsafe [widget_ptr as "QWidget*"] {
1636                auto accessible = dynamic_cast<Slint_accessible_window*>(QAccessible::queryAccessibleInterface(widget_ptr));
1637                if (accessible->isUsed()) { accessible->updateAccessibilityTree(); }
1638            }};
1639        }
1640
1641        timer_event();
1642    }
1643
1644    fn resize_event(&self, size: qttypes::QSize) {
1645        self.window().dispatch_event(WindowEvent::Resized {
1646            size: i_slint_core::api::LogicalSize::new(size.width as _, size.height as _),
1647        });
1648    }
1649
1650    fn mouse_event(&self, event: MouseEvent) {
1651        WindowInner::from_pub(&self.window).process_mouse_input(event);
1652        timer_event();
1653    }
1654
1655    fn key_event(&self, key: i32, text: qttypes::QString, released: bool, repeat: bool) {
1656        i_slint_core::animations::update_animations();
1657        let text: String = text.into();
1658
1659        let text = qt_key_to_string(key as key_generated::Qt_Key, text);
1660
1661        let event = if released {
1662            WindowEvent::KeyReleased { text }
1663        } else if repeat {
1664            WindowEvent::KeyPressRepeated { text }
1665        } else {
1666            WindowEvent::KeyPressed { text }
1667        };
1668        self.window.dispatch_event(event);
1669
1670        timer_event();
1671    }
1672
1673    fn window_state_event(&self) {
1674        let widget_ptr = self.widget_ptr();
1675
1676        // This function is called from the changeEvent slot which triggers whenever
1677        // one of these properties changes. To prevent recursive call issues (e.g.,
1678        // set_fullscreen -> update_window_properties -> changeEvent ->
1679        // window_state_event -> set_fullscreen), we avoid resetting the internal state
1680        // when it already matches the Qt state.
1681
1682        let minimized = cpp! { unsafe [widget_ptr as "QWidget*"] -> bool as "bool" {
1683            return widget_ptr->isMinimized();
1684        }};
1685
1686        if minimized != self.window().is_minimized() {
1687            self.window().set_minimized(minimized);
1688        }
1689
1690        let maximized = cpp! { unsafe [widget_ptr as "QWidget*"] -> bool as "bool" {
1691            return widget_ptr->isMaximized();
1692        }};
1693
1694        if maximized != self.window().is_maximized() {
1695            self.window().set_maximized(maximized);
1696        }
1697
1698        let fullscreen = cpp! { unsafe [widget_ptr as "QWidget*"] -> bool as "bool" {
1699            return widget_ptr->isFullScreen();
1700        }};
1701
1702        if fullscreen != self.window().is_fullscreen() {
1703            self.window().set_fullscreen(fullscreen);
1704        }
1705    }
1706}
1707
1708impl WindowAdapter for QtWindow {
1709    fn window(&self) -> &i_slint_core::api::Window {
1710        &self.window
1711    }
1712
1713    fn renderer(&self) -> &dyn Renderer {
1714        self
1715    }
1716
1717    fn set_visible(&self, visible: bool) -> Result<(), PlatformError> {
1718        if let Some(xdg_app_id) = WindowInner::from_pub(&self.window)
1719            .xdg_app_id()
1720            .map(|s| qttypes::QString::from(s.as_str()))
1721        {
1722            cpp! {unsafe [xdg_app_id as "QString"] {
1723                QGuiApplication::setDesktopFileName(xdg_app_id);
1724            }};
1725        }
1726
1727        if visible {
1728            let widget_ptr = self.widget_ptr();
1729            cpp! {unsafe [widget_ptr as "QWidget*"] {
1730                widget_ptr->show();
1731            }};
1732            let qt_platform_name = cpp! {unsafe [] -> qttypes::QString as "QString" {
1733                return QGuiApplication::platformName();
1734            }};
1735            *self.rendering_metrics_collector.borrow_mut() = RenderingMetricsCollector::new(
1736                &format!("Qt backend (platform {})", qt_platform_name),
1737            );
1738            Ok(())
1739        } else {
1740            self.rendering_metrics_collector.take();
1741            let widget_ptr = self.widget_ptr();
1742            cpp! {unsafe [widget_ptr as "QWidget*"] {
1743
1744                bool wasVisible = widget_ptr->isVisible();
1745
1746                widget_ptr->hide();
1747                if (wasVisible) {
1748                    // Since we don't call close(), try to compute whether this was the last window and that
1749                    // we must end the application
1750                    auto windows = QGuiApplication::topLevelWindows();
1751                    bool visible_windows_left = std::any_of(windows.begin(), windows.end(), [](auto window) {
1752                        return window->isVisible() || window->transientParent();
1753                    });
1754                    g_lastWindowClosed = !visible_windows_left;
1755                }
1756            }};
1757
1758            Ok(())
1759        }
1760    }
1761
1762    fn position(&self) -> Option<i_slint_core::api::PhysicalPosition> {
1763        let widget_ptr = self.widget_ptr();
1764        let qp = cpp! {unsafe [widget_ptr as "QWidget*"] -> qttypes::QPoint as "QPoint" {
1765            return widget_ptr->pos();
1766        }};
1767        // Qt returns logical coordinates, so scale those!
1768        i_slint_core::api::LogicalPosition::new(qp.x as _, qp.y as _)
1769            .to_physical(self.window().scale_factor())
1770            .into()
1771    }
1772
1773    fn set_position(&self, position: i_slint_core::api::WindowPosition) {
1774        let physical_position = position.to_physical(self.window().scale_factor());
1775        let widget_ptr = self.widget_ptr();
1776        let pos = qttypes::QPoint { x: physical_position.x as _, y: physical_position.y as _ };
1777        cpp! {unsafe [widget_ptr as "QWidget*", pos as "QPoint"] {
1778            widget_ptr->move(pos);
1779        }};
1780    }
1781
1782    fn set_size(&self, size: i_slint_core::api::WindowSize) {
1783        let logical_size = size.to_logical(self.window().scale_factor());
1784        let widget_ptr = self.widget_ptr();
1785        let sz: qttypes::QSize = into_qsize(logical_size);
1786
1787        // Qt uses logical units!
1788        cpp! {unsafe [widget_ptr as "QWidget*", sz as "QSize"] {
1789            widget_ptr->resize(sz);
1790        }};
1791
1792        self.resize_event(sz);
1793    }
1794
1795    fn size(&self) -> i_slint_core::api::PhysicalSize {
1796        let widget_ptr = self.widget_ptr();
1797        let s = cpp! {unsafe [widget_ptr as "QWidget*"] -> qttypes::QSize as "QSize" {
1798            return widget_ptr->size();
1799        }};
1800        i_slint_core::api::PhysicalSize::new(s.width as _, s.height as _)
1801    }
1802
1803    fn request_redraw(&self) {
1804        let widget_ptr = self.widget_ptr();
1805        cpp! {unsafe [widget_ptr as "QWidget*"] {
1806            // If embedded as a QWidget, just use regular QWidget::update(), but if we're a top-level,
1807            // then use requestUpdate() to achieve frame-throttling.
1808            if (widget_ptr->parentWidget()) {
1809                widget_ptr->update();
1810            } else if (auto w = widget_ptr->window()->windowHandle()) {
1811                w->requestUpdate();
1812            }
1813        }}
1814    }
1815
1816    /// Apply windows property such as title to the QWidget*
1817    fn update_window_properties(&self, properties: i_slint_core::window::WindowProperties<'_>) {
1818        let widget_ptr = self.widget_ptr();
1819        let title: qttypes::QString = properties.title().as_str().into();
1820        let Some(window_item) = WindowInner::from_pub(&self.window).window_item() else { return };
1821        let window_item = window_item.as_pin_ref();
1822        let no_frame = window_item.no_frame();
1823        let always_on_top = window_item.always_on_top();
1824        let mut size = qttypes::QSize {
1825            width: window_item.width().get().ceil() as _,
1826            height: window_item.height().get().ceil() as _,
1827        };
1828
1829        if size.width == 0 || size.height == 0 {
1830            let existing_size = cpp!(unsafe [widget_ptr as "QWidget*"] -> qttypes::QSize as "QSize" {
1831                return widget_ptr->size();
1832            });
1833            if size.width == 0 {
1834                window_item.width.set(LogicalLength::new(existing_size.width as _));
1835                size.width = existing_size.width;
1836            }
1837            if size.height == 0 {
1838                window_item.height.set(LogicalLength::new(existing_size.height as _));
1839                size.height = existing_size.height;
1840            }
1841        }
1842
1843        let background =
1844            into_qbrush(properties.background(), size.width.into(), size.height.into());
1845
1846        match (&window_item.icon()).into() {
1847            &ImageInner::None => (),
1848            r => {
1849                if let Some(pixmap) = image_to_pixmap(r, None) {
1850                    cpp! {unsafe [widget_ptr as "QWidget*", pixmap as "QPixmap"] {
1851                        widget_ptr->setWindowIcon(QIcon(pixmap));
1852                    }};
1853                }
1854            }
1855        };
1856
1857        let fullscreen: bool = properties.is_fullscreen();
1858        let minimized: bool = properties.is_minimized();
1859        let maximized: bool = properties.is_maximized();
1860
1861        cpp! {unsafe [widget_ptr as "QWidget*",  title as "QString", size as "QSize", background as "QBrush", no_frame as "bool", always_on_top as "bool",
1862                      fullscreen as "bool", minimized as "bool", maximized as "bool"] {
1863
1864            if (size != widget_ptr->size()) {
1865                widget_ptr->resize(size.expandedTo({1, 1}));
1866            }
1867
1868            widget_ptr->setWindowFlag(Qt::FramelessWindowHint, no_frame);
1869            widget_ptr->setWindowFlag(Qt::WindowStaysOnTopHint, always_on_top);
1870
1871                        {
1872                // Depending on the request, we either set or clear the bits.
1873                // See also: https://doc.qt.io/qt-6/qt.html#WindowState-enum
1874                auto state = widget_ptr->windowState();
1875
1876                if (fullscreen != widget_ptr->isFullScreen()) {
1877                    state = state ^ Qt::WindowFullScreen;
1878                }
1879                if (minimized != widget_ptr->isMinimized()) {
1880                    state = state ^ Qt::WindowMinimized;
1881                }
1882                if (maximized != widget_ptr->isMaximized()) {
1883                    state = state ^ Qt::WindowMaximized;
1884                }
1885
1886                widget_ptr->setWindowState(state);
1887            }
1888
1889            widget_ptr->setWindowTitle(title);
1890            auto pal = widget_ptr->palette();
1891
1892            #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
1893            // If the background color is the same as what NativeStyleMetrics supplied from QGuiApplication::palette().color(QPalette::Window),
1894            // then the setColor (implicitly setBrush) call will not detach the palette. However it will set the resolveMask, which due to the
1895            // lack of a detach changes QGuiApplicationPrivate::app_pal's resolve mask and thus breaks future theme based palette changes.
1896            // Therefore we force a detach.
1897            // https://bugreports.qt.io/browse/QTBUG-98762
1898            {
1899                pal.setResolveMask(~pal.resolveMask());
1900                pal.setResolveMask(~pal.resolveMask());
1901            }
1902            #endif
1903            pal.setBrush(QPalette::Window, background);
1904            widget_ptr->setPalette(pal);
1905        }};
1906
1907        let constraints = properties.layout_constraints();
1908
1909        let min_size: qttypes::QSize = constraints.min.map_or_else(
1910            || qttypes::QSize { width: 0, height: 0 }, // (0x0) means unset min size for QWidget
1911            into_qsize,
1912        );
1913
1914        const WIDGET_SIZE_MAX: u32 = 16_777_215;
1915
1916        let max_size: qttypes::QSize = constraints.max.map_or_else(
1917            || qttypes::QSize { width: WIDGET_SIZE_MAX, height: WIDGET_SIZE_MAX },
1918            into_qsize,
1919        );
1920
1921        cpp! {unsafe [widget_ptr as "QWidget*",  min_size as "QSize", max_size as "QSize"] {
1922            widget_ptr->setMinimumSize(min_size);
1923            widget_ptr->setMaximumSize(max_size);
1924        }};
1925    }
1926
1927    fn internal(&self, _: i_slint_core::InternalToken) -> Option<&dyn WindowAdapterInternal> {
1928        Some(self)
1929    }
1930}
1931
1932fn into_qsize(logical_size: i_slint_core::api::LogicalSize) -> qttypes::QSize {
1933    qttypes::QSize {
1934        width: logical_size.width.round() as _,
1935        height: logical_size.height.round() as _,
1936    }
1937}
1938
1939impl WindowAdapterInternal for QtWindow {
1940    fn register_item_tree(&self) {
1941        self.tree_structure_changed.replace(true);
1942    }
1943
1944    fn unregister_item_tree(
1945        &self,
1946        _component: ItemTreeRef,
1947        _: &mut dyn Iterator<Item = Pin<ItemRef<'_>>>,
1948    ) {
1949        self.tree_structure_changed.replace(true);
1950    }
1951
1952    fn create_popup(&self, geometry: LogicalRect) -> Option<Rc<dyn WindowAdapter>> {
1953        let popup_window = QtWindow::new();
1954
1955        let size = qttypes::QSize { width: geometry.width() as _, height: geometry.height() as _ };
1956
1957        let popup_ptr = popup_window.widget_ptr();
1958        let pos = qttypes::QPoint { x: geometry.origin.x as _, y: geometry.origin.y as _ };
1959        let widget_ptr = self.widget_ptr();
1960        cpp! {unsafe [widget_ptr as "QWidget*", popup_ptr as "QWidget*", pos as "QPoint", size as "QSize"] {
1961            popup_ptr->setParent(widget_ptr, Qt::Popup);
1962            popup_ptr->setGeometry(QRect(pos + widget_ptr->mapToGlobal(QPoint(0,0)), size));
1963            popup_ptr->show();
1964        }};
1965        Some(popup_window as _)
1966    }
1967
1968    fn set_mouse_cursor(&self, cursor: MouseCursor) {
1969        let widget_ptr = self.widget_ptr();
1970        //unidirectional resize cursors are replaced with bidirectional ones
1971        let cursor_shape = match cursor {
1972            MouseCursor::Default => key_generated::Qt_CursorShape_ArrowCursor,
1973            MouseCursor::None => key_generated::Qt_CursorShape_BlankCursor,
1974            MouseCursor::Help => key_generated::Qt_CursorShape_WhatsThisCursor,
1975            MouseCursor::Pointer => key_generated::Qt_CursorShape_PointingHandCursor,
1976            MouseCursor::Progress => key_generated::Qt_CursorShape_BusyCursor,
1977            MouseCursor::Wait => key_generated::Qt_CursorShape_WaitCursor,
1978            MouseCursor::Crosshair => key_generated::Qt_CursorShape_CrossCursor,
1979            MouseCursor::Text => key_generated::Qt_CursorShape_IBeamCursor,
1980            MouseCursor::Alias => key_generated::Qt_CursorShape_DragLinkCursor,
1981            MouseCursor::Copy => key_generated::Qt_CursorShape_DragCopyCursor,
1982            MouseCursor::Move => key_generated::Qt_CursorShape_DragMoveCursor,
1983            MouseCursor::NoDrop => key_generated::Qt_CursorShape_ForbiddenCursor,
1984            MouseCursor::NotAllowed => key_generated::Qt_CursorShape_ForbiddenCursor,
1985            MouseCursor::Grab => key_generated::Qt_CursorShape_OpenHandCursor,
1986            MouseCursor::Grabbing => key_generated::Qt_CursorShape_ClosedHandCursor,
1987            MouseCursor::ColResize => key_generated::Qt_CursorShape_SplitHCursor,
1988            MouseCursor::RowResize => key_generated::Qt_CursorShape_SplitVCursor,
1989            MouseCursor::NResize => key_generated::Qt_CursorShape_SizeVerCursor,
1990            MouseCursor::EResize => key_generated::Qt_CursorShape_SizeHorCursor,
1991            MouseCursor::SResize => key_generated::Qt_CursorShape_SizeVerCursor,
1992            MouseCursor::WResize => key_generated::Qt_CursorShape_SizeHorCursor,
1993            MouseCursor::NeResize => key_generated::Qt_CursorShape_SizeBDiagCursor,
1994            MouseCursor::NwResize => key_generated::Qt_CursorShape_SizeFDiagCursor,
1995            MouseCursor::SeResize => key_generated::Qt_CursorShape_SizeFDiagCursor,
1996            MouseCursor::SwResize => key_generated::Qt_CursorShape_SizeBDiagCursor,
1997            MouseCursor::EwResize => key_generated::Qt_CursorShape_SizeHorCursor,
1998            MouseCursor::NsResize => key_generated::Qt_CursorShape_SizeVerCursor,
1999            MouseCursor::NeswResize => key_generated::Qt_CursorShape_SizeBDiagCursor,
2000            MouseCursor::NwseResize => key_generated::Qt_CursorShape_SizeFDiagCursor,
2001        };
2002        cpp! {unsafe [widget_ptr as "QWidget*", cursor_shape as "Qt::CursorShape"] {
2003            widget_ptr->setCursor(QCursor{cursor_shape});
2004        }};
2005    }
2006
2007    fn input_method_request(&self, request: i_slint_core::window::InputMethodRequest) {
2008        let widget_ptr = self.widget_ptr();
2009        let props = match request {
2010            i_slint_core::window::InputMethodRequest::Enable(props) => {
2011                cpp! {unsafe [widget_ptr as "QWidget*"] {
2012                    widget_ptr->setAttribute(Qt::WA_InputMethodEnabled, true);
2013                }};
2014                props
2015            }
2016            i_slint_core::window::InputMethodRequest::Disable => {
2017                cpp! {unsafe [widget_ptr as "SlintWidget*"] {
2018                    widget_ptr->ime_text = "";
2019                    widget_ptr->ime_cursor = 0;
2020                    widget_ptr->ime_anchor = 0;
2021                    widget_ptr->setAttribute(Qt::WA_InputMethodEnabled, false);
2022                }};
2023                return;
2024            }
2025            i_slint_core::window::InputMethodRequest::Update(props) => props,
2026            _ => return,
2027        };
2028
2029        let rect = qttypes::QRectF {
2030            x: props.cursor_rect_origin.x as _,
2031            y: props.cursor_rect_origin.y as _,
2032            width: props.cursor_rect_size.width as _,
2033            height: props.cursor_rect_size.height as _,
2034        };
2035        let cursor: i32 = props.text[..props.cursor_position].encode_utf16().count() as _;
2036        let anchor: i32 =
2037            props.anchor_position.map_or(cursor, |a| props.text[..a].encode_utf16().count() as _);
2038        let text: qttypes::QString = props.text.as_str().into();
2039        cpp! {unsafe [widget_ptr as "SlintWidget*", rect as "QRectF", cursor as "int", anchor as "int", text as "QString"]  {
2040            widget_ptr->ime_position = rect.toRect();
2041            widget_ptr->ime_text = text;
2042            widget_ptr->ime_cursor = cursor;
2043            widget_ptr->ime_anchor = anchor;
2044            QGuiApplication::inputMethod()->update(Qt::ImQueryInput);
2045        }};
2046    }
2047
2048    fn as_any(&self) -> &dyn std::any::Any {
2049        self
2050    }
2051
2052    fn handle_focus_change(&self, _old: Option<ItemRc>, new: Option<ItemRc>) {
2053        let widget_ptr = self.widget_ptr();
2054        if let Some(ai) = accessible_item(new) {
2055            let item = &ai;
2056            cpp! {unsafe [widget_ptr as "QWidget*", item as "void*"] {
2057                auto accessible = QAccessible::queryAccessibleInterface(widget_ptr);
2058                if (auto slint_accessible = dynamic_cast<Slint_accessible*>(accessible)) {
2059                    slint_accessible->clearFocus();
2060                    slint_accessible->focusItem(item);
2061                }
2062            }};
2063        }
2064    }
2065
2066    fn color_scheme(&self) -> ColorScheme {
2067        let ds = self.color_scheme.get_or_init(|| {
2068            Box::pin(Property::new(
2069                if cpp! {unsafe [] -> bool as "bool" {
2070                    return qApp->palette().color(QPalette::Window).valueF() < 0.5;
2071                }} {
2072                    ColorScheme::Dark
2073                } else {
2074                    ColorScheme::Light
2075                },
2076            ))
2077        });
2078        ds.as_ref().get()
2079    }
2080
2081    fn bring_to_front(&self) -> Result<(), i_slint_core::platform::PlatformError> {
2082        let widget_ptr = self.widget_ptr();
2083        cpp! {unsafe [widget_ptr as "QWidget*"] {
2084            widget_ptr->raise();
2085            widget_ptr->activateWindow();
2086        }};
2087        Ok(())
2088    }
2089}
2090
2091impl i_slint_core::renderer::RendererSealed for QtWindow {
2092    fn text_size(
2093        &self,
2094        font_request: FontRequest,
2095        text: &str,
2096        max_width: Option<LogicalLength>,
2097        scale_factor: ScaleFactor,
2098        text_wrap: TextWrap,
2099    ) -> LogicalSize {
2100        sharedparley::text_size(font_request, text, max_width, scale_factor, text_wrap)
2101    }
2102
2103    fn font_metrics(
2104        &self,
2105        font_request: i_slint_core::graphics::FontRequest,
2106        _scale_factor: ScaleFactor,
2107    ) -> i_slint_core::items::FontMetrics {
2108        sharedparley::font_metrics(font_request)
2109    }
2110
2111    fn text_input_byte_offset_for_position(
2112        &self,
2113        text_input: Pin<&i_slint_core::items::TextInput>,
2114        pos: LogicalPoint,
2115        font_request: FontRequest,
2116        scale_factor: ScaleFactor,
2117    ) -> usize {
2118        sharedparley::text_input_byte_offset_for_position(
2119            text_input,
2120            pos,
2121            font_request,
2122            scale_factor,
2123        )
2124    }
2125
2126    fn text_input_cursor_rect_for_byte_offset(
2127        &self,
2128        text_input: Pin<&i_slint_core::items::TextInput>,
2129        byte_offset: usize,
2130        font_request: FontRequest,
2131        scale_factor: ScaleFactor,
2132    ) -> LogicalRect {
2133        sharedparley::text_input_cursor_rect_for_byte_offset(
2134            text_input,
2135            byte_offset,
2136            font_request,
2137            scale_factor,
2138        )
2139    }
2140
2141    fn register_font_from_memory(
2142        &self,
2143        data: &'static [u8],
2144    ) -> Result<(), Box<dyn std::error::Error>> {
2145        sharedfontique::get_collection().register_fonts(data.to_vec().into(), None);
2146        Ok(())
2147    }
2148
2149    fn register_font_from_path(
2150        &self,
2151        path: &std::path::Path,
2152    ) -> Result<(), Box<dyn std::error::Error>> {
2153        let requested_path = path.canonicalize().unwrap_or_else(|_| path.into());
2154        let contents = std::fs::read(requested_path)?;
2155        sharedfontique::get_collection().register_fonts(contents.into(), None);
2156        Ok(())
2157    }
2158
2159    fn default_font_size(&self) -> LogicalLength {
2160        let default_font_size = cpp!(unsafe[] -> i32 as "int" {
2161            return QFontInfo(qApp->font()).pixelSize();
2162        });
2163        // Ideally this would return the value from another property with a binding that's updated
2164        // as a FontChange event is received. This is relevant for the case of using the Qt backend
2165        // with a non-native style.
2166        LogicalLength::new(default_font_size as f32)
2167    }
2168
2169    fn free_graphics_resources(
2170        &self,
2171        component: ItemTreeRef,
2172        _items: &mut dyn Iterator<Item = Pin<i_slint_core::items::ItemRef<'_>>>,
2173    ) -> Result<(), i_slint_core::platform::PlatformError> {
2174        // Invalidate caches:
2175        self.cache.component_destroyed(component);
2176        Ok(())
2177    }
2178
2179    fn set_window_adapter(&self, _window_adapter: &Rc<dyn WindowAdapter>) {
2180        // No-op because QtWindow is also the WindowAdapter
2181    }
2182
2183    fn take_snapshot(&self) -> Result<SharedPixelBuffer<Rgba8Pixel>, PlatformError> {
2184        let widget_ptr = self.widget_ptr();
2185
2186        let size = cpp! {unsafe [widget_ptr as "QWidget*"] -> qttypes::QSize as "QSize" {
2187            return widget_ptr->size();
2188        }};
2189
2190        let rgba8_data = cpp! {unsafe [widget_ptr as "QWidget*"] -> qttypes::QByteArray as "QByteArray" {
2191            QPixmap pixmap = widget_ptr->grab();
2192            QImage image = pixmap.toImage();
2193            image.convertTo(QImage::Format_ARGB32);
2194            return QByteArray(reinterpret_cast<const char *>(image.constBits()), image.sizeInBytes());
2195        }};
2196
2197        let buffer = i_slint_core::graphics::SharedPixelBuffer::<i_slint_core::graphics::Rgba8Pixel>::clone_from_slice(
2198            rgba8_data.to_slice(),
2199            size.width,
2200            size.height,
2201        );
2202        Ok(buffer)
2203    }
2204
2205    fn supports_transformations(&self) -> bool {
2206        true
2207    }
2208}
2209
2210fn accessible_item(item: Option<ItemRc>) -> Option<ItemRc> {
2211    let mut current = item;
2212    while let Some(c) = current {
2213        if c.is_accessible() {
2214            return Some(c);
2215        } else {
2216            current = c.parent_item(ParentItemTraversalMode::StopAtPopups);
2217        }
2218    }
2219    None
2220}
2221
2222thread_local! {
2223    // FIXME: currently the window are never removed
2224    static ALL_WINDOWS: RefCell<Vec<Weak<QtWindow>>> = Default::default();
2225}
2226
2227/// Called by C++'s TimerHandler::timerEvent, or every time a timer might have been started
2228pub(crate) fn timer_event() {
2229    i_slint_core::platform::update_timers_and_animations();
2230    restart_timer();
2231}
2232
2233pub(crate) fn restart_timer() {
2234    let timeout = i_slint_core::timers::TimerList::next_timeout().map(|instant| {
2235        let now = std::time::Instant::now();
2236        let instant: std::time::Instant = instant.into();
2237        if instant > now {
2238            instant.duration_since(now).as_millis() as i32
2239        } else {
2240            0
2241        }
2242    });
2243    if let Some(timeout) = timeout {
2244        cpp! { unsafe [timeout as "int"] {
2245            ensure_initialized(true);
2246            TimerHandler::instance().timer.start(timeout, &TimerHandler::instance());
2247        }}
2248    }
2249}
2250
2251mod key_codes {
2252    macro_rules! define_qt_key_to_string_fn {
2253        ($($char:literal # $name:ident # $($qt:ident)|* # $($winit:ident $(($_pos:ident))?)|* # $($_xkb:ident)|*;)*) => {
2254            use crate::key_generated;
2255            pub fn qt_key_to_string(key: key_generated::Qt_Key) -> Option<i_slint_core::SharedString> {
2256
2257                let char = match(key) {
2258                    $($(key_generated::$qt => $char,)*)*
2259                    _ => return None,
2260                };
2261                Some(char.into())
2262            }
2263        };
2264    }
2265
2266    i_slint_common::for_each_special_keys!(define_qt_key_to_string_fn);
2267}
2268
2269fn qt_key_to_string(key: key_generated::Qt_Key, event_text: String) -> SharedString {
2270    // First try to see if we received one of the non-ascii keys that we have
2271    // a special representation for. If that fails, try to use the provided
2272    // text. If that's empty, then try to see if the provided key has an ascii
2273    // representation. The last step is needed because modifiers may result in
2274    // the text to be empty otherwise, for example Ctrl+C.
2275    if let Some(special_key_code) = key_codes::qt_key_to_string(key) {
2276        return special_key_code;
2277    };
2278
2279    // On Windows, X11 and Wayland, Ctrl+C for example sends a terminal control character,
2280    // which we choose not to supply to the application. Instead we fall through to translating
2281    // the supplied key code.
2282    if !event_text.is_empty() && !event_text.chars().any(|ch| ch.is_control()) {
2283        return event_text.into();
2284    }
2285
2286    match key {
2287        key_generated::Qt_Key_Key_Space => " ",
2288        key_generated::Qt_Key_Key_Exclam => "!",
2289        key_generated::Qt_Key_Key_QuoteDbl => "\"",
2290        key_generated::Qt_Key_Key_NumberSign => "#",
2291        key_generated::Qt_Key_Key_Dollar => "$",
2292        key_generated::Qt_Key_Key_Percent => "%",
2293        key_generated::Qt_Key_Key_Ampersand => "&",
2294        key_generated::Qt_Key_Key_Apostrophe => "'",
2295        key_generated::Qt_Key_Key_ParenLeft => "(",
2296        key_generated::Qt_Key_Key_ParenRight => ")",
2297        key_generated::Qt_Key_Key_Asterisk => "*",
2298        key_generated::Qt_Key_Key_Plus => "+",
2299        key_generated::Qt_Key_Key_Comma => ",",
2300        key_generated::Qt_Key_Key_Minus => "-",
2301        key_generated::Qt_Key_Key_Period => ".",
2302        key_generated::Qt_Key_Key_Slash => "/",
2303        key_generated::Qt_Key_Key_0 => "0",
2304        key_generated::Qt_Key_Key_1 => "1",
2305        key_generated::Qt_Key_Key_2 => "2",
2306        key_generated::Qt_Key_Key_3 => "3",
2307        key_generated::Qt_Key_Key_4 => "4",
2308        key_generated::Qt_Key_Key_5 => "5",
2309        key_generated::Qt_Key_Key_6 => "6",
2310        key_generated::Qt_Key_Key_7 => "7",
2311        key_generated::Qt_Key_Key_8 => "8",
2312        key_generated::Qt_Key_Key_9 => "9",
2313        key_generated::Qt_Key_Key_Colon => ":",
2314        key_generated::Qt_Key_Key_Semicolon => ";",
2315        key_generated::Qt_Key_Key_Less => "<",
2316        key_generated::Qt_Key_Key_Equal => "=",
2317        key_generated::Qt_Key_Key_Greater => ">",
2318        key_generated::Qt_Key_Key_Question => "?",
2319        key_generated::Qt_Key_Key_At => "@",
2320        key_generated::Qt_Key_Key_A => "a",
2321        key_generated::Qt_Key_Key_B => "b",
2322        key_generated::Qt_Key_Key_C => "c",
2323        key_generated::Qt_Key_Key_D => "d",
2324        key_generated::Qt_Key_Key_E => "e",
2325        key_generated::Qt_Key_Key_F => "f",
2326        key_generated::Qt_Key_Key_G => "g",
2327        key_generated::Qt_Key_Key_H => "h",
2328        key_generated::Qt_Key_Key_I => "i",
2329        key_generated::Qt_Key_Key_J => "j",
2330        key_generated::Qt_Key_Key_K => "k",
2331        key_generated::Qt_Key_Key_L => "l",
2332        key_generated::Qt_Key_Key_M => "m",
2333        key_generated::Qt_Key_Key_N => "n",
2334        key_generated::Qt_Key_Key_O => "o",
2335        key_generated::Qt_Key_Key_P => "p",
2336        key_generated::Qt_Key_Key_Q => "q",
2337        key_generated::Qt_Key_Key_R => "r",
2338        key_generated::Qt_Key_Key_S => "s",
2339        key_generated::Qt_Key_Key_T => "t",
2340        key_generated::Qt_Key_Key_U => "u",
2341        key_generated::Qt_Key_Key_V => "v",
2342        key_generated::Qt_Key_Key_W => "w",
2343        key_generated::Qt_Key_Key_X => "x",
2344        key_generated::Qt_Key_Key_Y => "y",
2345        key_generated::Qt_Key_Key_Z => "z",
2346        key_generated::Qt_Key_Key_BracketLeft => "[",
2347        key_generated::Qt_Key_Key_Backslash => "\\",
2348        key_generated::Qt_Key_Key_BracketRight => "]",
2349        key_generated::Qt_Key_Key_AsciiCircum => "^",
2350        key_generated::Qt_Key_Key_Underscore => "_",
2351        key_generated::Qt_Key_Key_QuoteLeft => "`",
2352        key_generated::Qt_Key_Key_BraceLeft => "{",
2353        key_generated::Qt_Key_Key_Bar => "|",
2354        key_generated::Qt_Key_Key_BraceRight => "}",
2355        key_generated::Qt_Key_Key_AsciiTilde => "~",
2356        _ => "",
2357    }
2358    .into()
2359}
2360
2361pub(crate) mod ffi {
2362    use std::ffi::c_void;
2363
2364    use super::QtWindow;
2365
2366    #[unsafe(no_mangle)]
2367    pub extern "C" fn slint_qt_get_widget(
2368        window_adapter: &i_slint_core::window::WindowAdapterRc,
2369    ) -> *mut c_void {
2370        window_adapter
2371            .internal(i_slint_core::InternalToken)
2372            .and_then(|wa| <dyn std::any::Any>::downcast_ref(wa.as_any()))
2373            .map_or(std::ptr::null_mut(), |win: &QtWindow| {
2374                win.widget_ptr().cast::<c_void>().as_ptr()
2375            })
2376    }
2377}
2378
2379fn qt_password_character() -> char {
2380    char::from_u32(cpp! { unsafe [] -> i32 as "int" {
2381        return qApp->style()->styleHint(QStyle::SH_LineEdit_PasswordCharacter, nullptr, nullptr);
2382    }} as u32)
2383    .unwrap_or('●')
2384}