mirror of
https://github.com/slint-ui/slint.git
synced 2025-10-20 07:12:28 +00:00
2385 lines
94 KiB
Rust
2385 lines
94 KiB
Rust
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
|
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
|
|
|
|
// cSpell: ignore frameless qbrush qpointf qreal qwidgetsize svgz
|
|
|
|
use cpp::*;
|
|
use i_slint_common::sharedfontique;
|
|
use i_slint_core::graphics::rendering_metrics_collector::{
|
|
RenderingMetrics, RenderingMetricsCollector,
|
|
};
|
|
use i_slint_core::graphics::{
|
|
euclid, Brush, Color, FontRequest, IntRect, Point, Rgba8Pixel, SharedImageBuffer,
|
|
SharedPixelBuffer,
|
|
};
|
|
use i_slint_core::input::{KeyEvent, KeyEventType, MouseEvent};
|
|
use i_slint_core::item_rendering::{
|
|
CachedRenderingData, ItemCache, ItemRenderer, RenderBorderRectangle, RenderImage,
|
|
RenderRectangle, RenderText,
|
|
};
|
|
use i_slint_core::item_tree::ParentItemTraversalMode;
|
|
use i_slint_core::item_tree::{ItemTreeRc, ItemTreeRef};
|
|
use i_slint_core::items::{
|
|
self, ColorScheme, FillRule, ImageRendering, ItemRc, ItemRef, Layer, LineCap, MouseCursor,
|
|
Opacity, PointerEventButton, RenderingResult, TextWrap,
|
|
};
|
|
use i_slint_core::layout::Orientation;
|
|
use i_slint_core::lengths::{
|
|
LogicalBorderRadius, LogicalLength, LogicalPoint, LogicalRect, LogicalSize, LogicalVector,
|
|
PhysicalPx, ScaleFactor,
|
|
};
|
|
use i_slint_core::platform::{PlatformError, WindowEvent};
|
|
use i_slint_core::textlayout::sharedparley::{self, parley, GlyphRenderer};
|
|
use i_slint_core::window::{WindowAdapter, WindowAdapterInternal, WindowInner};
|
|
use i_slint_core::{ImageInner, Property, SharedString};
|
|
|
|
use std::cell::RefCell;
|
|
use std::collections::HashMap;
|
|
use std::pin::Pin;
|
|
use std::ptr::NonNull;
|
|
use std::rc::{Rc, Weak};
|
|
|
|
use crate::key_generated;
|
|
use i_slint_core::renderer::Renderer;
|
|
use std::cell::OnceCell;
|
|
|
|
cpp! {{
|
|
#include <QtWidgets/QtWidgets>
|
|
#include <QtWidgets/QGraphicsScene>
|
|
#include <QtWidgets/QGraphicsBlurEffect>
|
|
#include <QtWidgets/QGraphicsPixmapItem>
|
|
#include <QtGui/QAccessible>
|
|
#include <QtGui/QPainter>
|
|
#include <QtGui/QPaintEngine>
|
|
#include <QtGui/QPainterPath>
|
|
#include <QtGui/QWindow>
|
|
#include <QtGui/QResizeEvent>
|
|
#include <QtGui/QTextLayout>
|
|
#include <QtGui/QImageReader>
|
|
#include <QtGui/QCursor>
|
|
#include <QtCore/QBasicTimer>
|
|
#include <QtCore/QTimer>
|
|
#include <QtCore/QPointer>
|
|
#include <QtCore/QBuffer>
|
|
#include <QtCore/QEvent>
|
|
#include <QtCore/QFileInfo>
|
|
|
|
#include <memory>
|
|
|
|
void ensure_initialized(bool from_qt_backend);
|
|
|
|
using QPainterPtr = std::unique_ptr<QPainter>;
|
|
|
|
struct TimerHandler : QObject {
|
|
QBasicTimer timer;
|
|
static TimerHandler& instance() {
|
|
static TimerHandler instance;
|
|
return instance;
|
|
}
|
|
|
|
void timerEvent(QTimerEvent *event) override {
|
|
if (event->timerId() != timer.timerId()) {
|
|
QObject::timerEvent(event);
|
|
return;
|
|
}
|
|
timer.stop();
|
|
rust!(Slint_timerEvent [] { timer_event() });
|
|
}
|
|
|
|
};
|
|
|
|
struct SlintWidget : QWidget {
|
|
void *rust_window = nullptr;
|
|
bool isMouseButtonDown = false;
|
|
QRect ime_position;
|
|
QString ime_text;
|
|
int ime_cursor = 0;
|
|
int ime_anchor = 0;
|
|
|
|
SlintWidget() {
|
|
setMouseTracking(true);
|
|
setFocusPolicy(Qt::StrongFocus);
|
|
setAttribute(Qt::WA_TranslucentBackground);
|
|
// WA_TranslucentBackground sets WA_NoSystemBackground, but we actually need WA_NoSystemBackground
|
|
// to draw the window background which is set on the palette.
|
|
// (But the window background might not be opaque)
|
|
setAttribute(Qt::WA_NoSystemBackground, false);
|
|
}
|
|
|
|
void paintEvent(QPaintEvent *) override {
|
|
if (!rust_window)
|
|
return;
|
|
auto painter = std::unique_ptr<QPainter>(new QPainter(this));
|
|
painter->setClipRect(rect());
|
|
painter->setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
|
|
QPainterPtr *painter_ptr = &painter;
|
|
rust!(Slint_paintEvent [rust_window: &QtWindow as "void*", painter_ptr: &mut QPainterPtr as "QPainterPtr*"] {
|
|
rust_window.paint_event(std::mem::take(painter_ptr))
|
|
});
|
|
}
|
|
|
|
void resizeEvent(QResizeEvent *) override {
|
|
if (!rust_window)
|
|
return;
|
|
|
|
// On windows, the size in the event is not reliable during
|
|
// fullscreen changes. Querying the widget itself seems to work
|
|
// better, see: https://stackoverflow.com/questions/52157587/why-qresizeevent-qwidgetsize-gives-different-when-fullscreen
|
|
QSize size = this->size();
|
|
rust!(Slint_resizeEvent [rust_window: &QtWindow as "void*", size: qttypes::QSize as "QSize"] {
|
|
rust_window.resize_event(size)
|
|
});
|
|
}
|
|
|
|
/// 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
|
|
/// Returns the position and the rust_window to which we need to deliver the event
|
|
std::tuple<QPoint, void*> adjust_mouse_event_to_popup_parent(QMouseEvent *event) {
|
|
auto pos = event->pos();
|
|
if (auto p = dynamic_cast<const SlintWidget*>(parent()); p && !rect().contains(pos)) {
|
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
|
QPoint eventPos = event->globalPosition().toPoint();
|
|
#else
|
|
QPoint eventPos = event->globalPos();
|
|
#endif
|
|
while (auto pp = dynamic_cast<const SlintWidget*>(p->parent())) {
|
|
if (p->rect().contains(p->mapFromGlobal(eventPos)))
|
|
break;
|
|
p = pp;
|
|
}
|
|
return { p->mapFromGlobal(eventPos), p->rust_window };
|
|
} else {
|
|
return { pos, rust_window };
|
|
}
|
|
}
|
|
|
|
void mousePressEvent(QMouseEvent *event) override {
|
|
isMouseButtonDown = true;
|
|
auto [pos, rust_window] = adjust_mouse_event_to_popup_parent(event);
|
|
if (!rust_window)
|
|
return;
|
|
int button = event->button();
|
|
rust!(Slint_mousePressEvent [rust_window: &QtWindow as "void*", pos: qttypes::QPoint as "QPoint", button: u32 as "int" ] {
|
|
let position = LogicalPoint::new(pos.x as _, pos.y as _);
|
|
let button = from_qt_button(button);
|
|
rust_window.mouse_event(MouseEvent::Pressed{ position, button, click_count: 0 })
|
|
});
|
|
}
|
|
void mouseReleaseEvent(QMouseEvent *event) override {
|
|
auto [pos, rust_window] = adjust_mouse_event_to_popup_parent(event);
|
|
if (!rust_window)
|
|
return;
|
|
|
|
// HACK: Qt on windows is a bit special when clicking on the window
|
|
// close button and when the resulting close event is ignored.
|
|
// In that case a release event that was not preceded by
|
|
// a press event is sent on Windows.
|
|
// This confuses Slint, so eat this event.
|
|
//
|
|
// One example is a popup is shown in the close event that
|
|
// then ignores the close request to ask the user what to
|
|
// do. The stray release event will then close the popup
|
|
// straight away
|
|
//
|
|
// However, we must still forward the event to the right popup menu
|
|
// that's why we compare rust_window with this->rust_window
|
|
if (!isMouseButtonDown && rust_window == this->rust_window) {
|
|
return;
|
|
}
|
|
isMouseButtonDown = event->button() != Qt::NoButton;
|
|
|
|
int button = event->button();
|
|
rust!(Slint_mouseReleaseEvent [rust_window: &QtWindow as "void*", pos: qttypes::QPoint as "QPoint", button: u32 as "int" ] {
|
|
let position = LogicalPoint::new(pos.x as _, pos.y as _);
|
|
let button = from_qt_button(button);
|
|
rust_window.mouse_event(MouseEvent::Released{ position, button, click_count: 0 })
|
|
});
|
|
}
|
|
void mouseMoveEvent(QMouseEvent *event) override {
|
|
auto [pos, rust_window] = adjust_mouse_event_to_popup_parent(event);
|
|
if (!rust_window)
|
|
return;
|
|
rust!(Slint_mouseMoveEvent [rust_window: &QtWindow as "void*", pos: qttypes::QPoint as "QPoint"] {
|
|
let position = LogicalPoint::new(pos.x as _, pos.y as _);
|
|
rust_window.mouse_event(MouseEvent::Moved{position})
|
|
});
|
|
}
|
|
void wheelEvent(QWheelEvent *event) override {
|
|
if (!rust_window)
|
|
return;
|
|
QPointF pos = event->position();
|
|
QPoint delta = event->pixelDelta();
|
|
if (delta.isNull()) {
|
|
delta = event->angleDelta();
|
|
}
|
|
rust!(Slint_mouseWheelEvent [rust_window: &QtWindow as "void*", pos: qttypes::QPointF as "QPointF", delta: qttypes::QPoint as "QPoint"] {
|
|
let position = LogicalPoint::new(pos.x as _, pos.y as _);
|
|
rust_window.mouse_event(MouseEvent::Wheel{position, delta_x: delta.x as _, delta_y: delta.y as _})
|
|
});
|
|
}
|
|
void leaveEvent(QEvent *) override {
|
|
if (!rust_window)
|
|
return;
|
|
rust!(Slint_mouseLeaveEvent [rust_window: &QtWindow as "void*"] {
|
|
rust_window.mouse_event(MouseEvent::Exit)
|
|
});
|
|
}
|
|
|
|
void keyPressEvent(QKeyEvent *event) override {
|
|
if (!rust_window)
|
|
return;
|
|
QString text = event->text();
|
|
int key = event->key();
|
|
bool repeat = event->isAutoRepeat();
|
|
rust!(Slint_keyPress [rust_window: &QtWindow as "void*", key: i32 as "int", text: qttypes::QString as "QString", repeat: bool as "bool"] {
|
|
rust_window.key_event(key, text.clone(), false, repeat);
|
|
});
|
|
}
|
|
void keyReleaseEvent(QKeyEvent *event) override {
|
|
if (!rust_window)
|
|
return;
|
|
// Qt sends repeated releases together with presses for auto-repeat events, but Slint only sends presses in that case.
|
|
// This matches the behavior of at least winit, Web and Android.
|
|
if (event->isAutoRepeat())
|
|
return;
|
|
|
|
QString text = event->text();
|
|
int key = event->key();
|
|
rust!(Slint_keyRelease [rust_window: &QtWindow as "void*", key: i32 as "int", text: qttypes::QString as "QString"] {
|
|
rust_window.key_event(key, text.clone(), true, false);
|
|
});
|
|
}
|
|
|
|
void changeEvent(QEvent *event) override {
|
|
if (!rust_window)
|
|
return QWidget::changeEvent(event);
|
|
|
|
if (event->type() == QEvent::ActivationChange) {
|
|
bool active = isActiveWindow();
|
|
rust!(Slint_updateWindowActivation [rust_window: &QtWindow as "void*", active: bool as "bool"] {
|
|
rust_window.window.dispatch_event(WindowEvent::WindowActiveChanged(active));
|
|
});
|
|
} else if (event->type() == QEvent::PaletteChange || event->type() == QEvent::StyleChange) {
|
|
bool dark_color_scheme = qApp->palette().color(QPalette::Window).valueF() < 0.5;
|
|
rust!(Slint_updateWindowDarkColorScheme [rust_window: &QtWindow as "void*", dark_color_scheme: bool as "bool"] {
|
|
if let Some(ds) = rust_window.color_scheme.get() {
|
|
ds.as_ref().set(if dark_color_scheme {
|
|
ColorScheme::Dark
|
|
} else {
|
|
ColorScheme::Light
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
// Entering fullscreen, maximizing or minimizing the window will
|
|
// trigger a change event. We need to update the internal window
|
|
// state to match the actual window state.
|
|
if (event->type() == QEvent::WindowStateChange)
|
|
{
|
|
rust!(Slint_syncWindowState [rust_window: &QtWindow as "void*"]{
|
|
rust_window.window_state_event();
|
|
});
|
|
}
|
|
|
|
|
|
QWidget::changeEvent(event);
|
|
}
|
|
|
|
void closeEvent(QCloseEvent *event) override {
|
|
if (!rust_window)
|
|
return;
|
|
rust!(Slint_requestClose [rust_window: &QtWindow as "void*"] {
|
|
rust_window.window.dispatch_event(WindowEvent::CloseRequested);
|
|
});
|
|
event->ignore();
|
|
}
|
|
|
|
QSize sizeHint() const override {
|
|
if (!rust_window)
|
|
return {};
|
|
auto preferred_size = rust!(Slint_sizeHint [rust_window: &QtWindow as "void*"] -> qttypes::QSize as "QSize" {
|
|
let component_rc = WindowInner::from_pub(&rust_window.window).component();
|
|
let component = ItemTreeRc::borrow_pin(&component_rc);
|
|
let layout_info_h = component.as_ref().layout_info(Orientation::Horizontal);
|
|
let layout_info_v = component.as_ref().layout_info(Orientation::Vertical);
|
|
qttypes::QSize {
|
|
width: layout_info_h.preferred_bounded() as _,
|
|
height: layout_info_v.preferred_bounded() as _,
|
|
}
|
|
});
|
|
if (!preferred_size.isEmpty()) {
|
|
return preferred_size;
|
|
} else {
|
|
return QWidget::sizeHint();
|
|
}
|
|
}
|
|
|
|
QVariant inputMethodQuery(Qt::InputMethodQuery query) const override {
|
|
switch (query) {
|
|
case Qt::ImCursorRectangle: return ime_position;
|
|
case Qt::ImCursorPosition: return ime_cursor;
|
|
case Qt::ImSurroundingText: return ime_text;
|
|
case Qt::ImCurrentSelection: return ime_text.mid(qMin(ime_cursor, ime_anchor), qAbs(ime_cursor - ime_anchor));
|
|
case Qt::ImAnchorPosition: return ime_anchor;
|
|
case Qt::ImTextBeforeCursor: return ime_text.left(ime_cursor);
|
|
case Qt::ImTextAfterCursor: return ime_text.right(ime_cursor);
|
|
default: break;
|
|
}
|
|
return QWidget::inputMethodQuery(query);
|
|
}
|
|
|
|
void inputMethodEvent(QInputMethodEvent *event) override {
|
|
if (!rust_window)
|
|
return;
|
|
QString commit_string = event->commitString();
|
|
QString preedit_string = event->preeditString();
|
|
int replacement_start = event->replacementStart();
|
|
QStringView ime_text(this->ime_text);
|
|
replacement_start = replacement_start < 0 ?
|
|
-ime_text.mid(ime_cursor,-replacement_start).toUtf8().size() :
|
|
ime_text.mid(ime_cursor,replacement_start).toUtf8().size();
|
|
int replacement_length = qMax(0, event->replacementLength());
|
|
ime_text.mid(ime_cursor + replacement_start, replacement_length).toUtf8().size();
|
|
int preedit_cursor = -1;
|
|
for (const QInputMethodEvent::Attribute &attribute: event->attributes()) {
|
|
if (attribute.type == QInputMethodEvent::Cursor) {
|
|
if (attribute.length > 0) {
|
|
preedit_cursor = QStringView(preedit_string).left(attribute.start).toUtf8().size();
|
|
}
|
|
}
|
|
}
|
|
event->accept();
|
|
rust!(Slint_inputMethodEvent [rust_window: &QtWindow as "void*", commit_string: qttypes::QString as "QString",
|
|
preedit_string: qttypes::QString as "QString", replacement_start: i32 as "int", replacement_length: i32 as "int",
|
|
preedit_cursor: i32 as "int"] {
|
|
let runtime_window = WindowInner::from_pub(&rust_window.window);
|
|
|
|
let event = KeyEvent {
|
|
event_type: KeyEventType::UpdateComposition,
|
|
text: i_slint_core::format!("{}", commit_string),
|
|
preedit_text: i_slint_core::format!("{}", preedit_string),
|
|
preedit_selection: (preedit_cursor >= 0).then_some(preedit_cursor..preedit_cursor),
|
|
replacement_range: (!commit_string.is_empty() || !preedit_string.is_empty() || preedit_cursor >= 0)
|
|
.then_some(replacement_start..replacement_start+replacement_length),
|
|
..Default::default()
|
|
};
|
|
runtime_window.process_key_input(event);
|
|
});
|
|
}
|
|
};
|
|
|
|
QPainterPath to_painter_path(const QRectF &rect, qreal top_left_radius, qreal top_right_radius, qreal bottom_right_radius, qreal bottom_left_radius) {
|
|
QPainterPath path;
|
|
if (qFuzzyCompare(top_left_radius, top_right_radius) && qFuzzyCompare(top_left_radius, bottom_right_radius) && qFuzzyCompare(top_left_radius, bottom_left_radius)) {
|
|
path.addRoundedRect(rect, top_left_radius, top_left_radius);
|
|
} else {
|
|
QSizeF half = rect.size() / 2.0;
|
|
|
|
qreal tl_rx = qMin(top_left_radius, half.width());
|
|
qreal tl_ry = qMin(top_left_radius, half.height());
|
|
QRectF top_left(rect.left(), rect.top(), 2 * tl_rx, 2 * tl_ry);
|
|
|
|
qreal tr_rx = qMin(top_right_radius, half.width());
|
|
qreal tr_ry = qMin(top_right_radius, half.height());
|
|
QRectF top_right(rect.right() - 2 * tr_rx, rect.top(), 2 * tr_rx, 2 * tr_ry);
|
|
|
|
qreal br_rx = qMin(bottom_right_radius, half.width());
|
|
qreal br_ry = qMin(bottom_right_radius, half.height());
|
|
QRectF bottom_right(rect.right() - 2 * br_rx, rect.bottom() - 2 * br_ry, 2 * br_rx, 2 * br_ry);
|
|
|
|
qreal bl_rx = qMin(bottom_left_radius, half.width());
|
|
qreal bl_ry = qMin(bottom_left_radius, half.height());
|
|
QRectF bottom_left(rect.left(), rect.bottom() - 2 * bl_ry, 2 * bl_rx, 2 * bl_ry);
|
|
|
|
if (top_left.isNull()) {
|
|
path.moveTo(rect.topLeft());
|
|
} else {
|
|
path.arcMoveTo(top_left, 180);
|
|
path.arcTo(top_left, 180, -90);
|
|
}
|
|
if (top_right.isNull()) {
|
|
path.lineTo(rect.topRight());
|
|
} else {
|
|
path.arcTo(top_right, 90, -90);
|
|
}
|
|
if (bottom_right.isNull()) {
|
|
path.lineTo(rect.bottomRight());
|
|
} else {
|
|
path.arcTo(bottom_right, 0, -90);
|
|
}
|
|
if (bottom_left.isNull()) {
|
|
path.lineTo(rect.bottomLeft());
|
|
} else {
|
|
path.arcTo(bottom_left, -90, -90);
|
|
}
|
|
path.closeSubpath();
|
|
}
|
|
return path;
|
|
};
|
|
}}
|
|
|
|
cpp_class!(
|
|
/// Wrapper around a pointer to a QPainter.
|
|
// We can't use [`qttypes::QPainter`] because it is not sound <https://github.com/woboq/qmetaobject-rs/issues/267>
|
|
pub unsafe struct QPainterPtr as "QPainterPtr"
|
|
);
|
|
impl QPainterPtr {
|
|
pub fn restore(&mut self) {
|
|
cpp!(unsafe [self as "QPainterPtr*"] {
|
|
(*self)->restore();
|
|
});
|
|
}
|
|
|
|
pub fn save(&mut self) {
|
|
cpp!(unsafe [self as "QPainterPtr*"] {
|
|
(*self)->save();
|
|
});
|
|
}
|
|
}
|
|
|
|
cpp_class! {pub unsafe struct QPainterPath as "QPainterPath"}
|
|
|
|
impl QPainterPath {
|
|
/*
|
|
pub fn reserve(&mut self, size: usize) {
|
|
cpp! { unsafe [self as "QPainterPath*", size as "long long"] {
|
|
self->reserve(size);
|
|
}}
|
|
}*/
|
|
|
|
pub fn move_to(&mut self, to: qttypes::QPointF) {
|
|
cpp! { unsafe [self as "QPainterPath*", to as "QPointF"] {
|
|
self->moveTo(to);
|
|
}}
|
|
}
|
|
pub fn line_to(&mut self, to: qttypes::QPointF) {
|
|
cpp! { unsafe [self as "QPainterPath*", to as "QPointF"] {
|
|
self->lineTo(to);
|
|
}}
|
|
}
|
|
pub fn quad_to(&mut self, ctrl: qttypes::QPointF, to: qttypes::QPointF) {
|
|
cpp! { unsafe [self as "QPainterPath*", ctrl as "QPointF", to as "QPointF"] {
|
|
self->quadTo(ctrl, to);
|
|
}}
|
|
}
|
|
pub fn cubic_to(
|
|
&mut self,
|
|
ctrl1: qttypes::QPointF,
|
|
ctrl2: qttypes::QPointF,
|
|
to: qttypes::QPointF,
|
|
) {
|
|
cpp! { unsafe [self as "QPainterPath*", ctrl1 as "QPointF", ctrl2 as "QPointF", to as "QPointF"] {
|
|
self->cubicTo(ctrl1, ctrl2, to);
|
|
}}
|
|
}
|
|
|
|
pub fn close(&mut self) {
|
|
cpp! { unsafe [self as "QPainterPath*"] {
|
|
self->closeSubpath();
|
|
}}
|
|
}
|
|
|
|
pub fn set_fill_rule(&mut self, rule: key_generated::Qt_FillRule) {
|
|
cpp! { unsafe [self as "QPainterPath*", rule as "Qt::FillRule" ] {
|
|
self->setFillRule(rule);
|
|
}}
|
|
}
|
|
}
|
|
|
|
fn into_qbrush(
|
|
brush: i_slint_core::Brush,
|
|
width: qttypes::qreal,
|
|
height: qttypes::qreal,
|
|
) -> qttypes::QBrush {
|
|
/// Mangle the position to work around the fact that Qt merge stop at equal position
|
|
fn mangle_position(position: f32, idx: usize, count: usize) -> f32 {
|
|
// Add or subtract a small amount to make sure each stop is different but still in [0..1].
|
|
// It is possible that we swap stops that are both really really close to 0.54321+ε,
|
|
// but that is really unlikely
|
|
if position < 0.54321 + 67.8 * f32::EPSILON {
|
|
position + f32::EPSILON * idx as f32
|
|
} else {
|
|
position - f32::EPSILON * (count - idx - 1) as f32
|
|
}
|
|
}
|
|
match brush {
|
|
i_slint_core::Brush::SolidColor(color) => {
|
|
let color: u32 = color.as_argb_encoded();
|
|
cpp!(unsafe [color as "QRgb"] -> qttypes::QBrush as "QBrush" {
|
|
return QBrush(QColor::fromRgba(color));
|
|
})
|
|
}
|
|
i_slint_core::Brush::LinearGradient(g) => {
|
|
let (start, end) = i_slint_core::graphics::line_for_angle(
|
|
g.angle(),
|
|
[width as f32, height as f32].into(),
|
|
);
|
|
let p1 = qttypes::QPointF { x: start.x as _, y: start.y as _ };
|
|
let p2 = qttypes::QPointF { x: end.x as _, y: end.y as _ };
|
|
cpp_class!(unsafe struct QLinearGradient as "QLinearGradient");
|
|
let mut qlg = cpp! {
|
|
unsafe [p1 as "QPointF", p2 as "QPointF"] -> QLinearGradient as "QLinearGradient" {
|
|
QLinearGradient qlg(p1, p2);
|
|
return qlg;
|
|
}
|
|
};
|
|
let count = g.stops().count();
|
|
for (idx, s) in g.stops().enumerate() {
|
|
let pos: f32 = mangle_position(s.position, idx, count);
|
|
let color: u32 = s.color.as_argb_encoded();
|
|
cpp! {unsafe [mut qlg as "QLinearGradient", pos as "float", color as "QRgb"] {
|
|
qlg.setColorAt(pos, QColor::fromRgba(color));
|
|
}};
|
|
}
|
|
cpp! {unsafe [qlg as "QLinearGradient"] -> qttypes::QBrush as "QBrush" {
|
|
return QBrush(qlg);
|
|
}}
|
|
}
|
|
i_slint_core::Brush::RadialGradient(g) => {
|
|
cpp_class!(unsafe struct QRadialGradient as "QRadialGradient");
|
|
let mut qrg = cpp! {
|
|
unsafe [width as "qreal", height as "qreal"] -> QRadialGradient as "QRadialGradient" {
|
|
QRadialGradient qrg(width / 2, height / 2, sqrt(width * width + height * height) / 2);
|
|
return qrg;
|
|
}
|
|
};
|
|
let count = g.stops().count();
|
|
for (idx, s) in g.stops().enumerate() {
|
|
let pos: f32 = mangle_position(s.position, idx, count);
|
|
let color: u32 = s.color.as_argb_encoded();
|
|
cpp! {unsafe [mut qrg as "QRadialGradient", pos as "float", color as "QRgb"] {
|
|
qrg.setColorAt(pos, QColor::fromRgba(color));
|
|
}};
|
|
}
|
|
cpp! {unsafe [qrg as "QRadialGradient"] -> qttypes::QBrush as "QBrush" {
|
|
return QBrush(qrg);
|
|
}}
|
|
}
|
|
i_slint_core::Brush::ConicGradient(g) => {
|
|
cpp_class!(unsafe struct QConicalGradient as "QConicalGradient");
|
|
// QConicalGradient uses angles where 0 degrees is at 3 o'clock (east)
|
|
// We want gradient position 0 at 12 o'clock (north), so start at -90°
|
|
let mut qcg = cpp! {
|
|
unsafe [width as "qreal", height as "qreal"] -> QConicalGradient as "QConicalGradient" {
|
|
QConicalGradient qcg(width / 2, height / 2, 90);
|
|
return qcg;
|
|
}
|
|
};
|
|
let count = g.stops().count();
|
|
for (idx, s) in g.stops().enumerate() {
|
|
// Qt's conical gradient goes counter-clockwise, but Slint expects clockwise
|
|
// So we need to invert the positions: Qt position = 1.0 - Slint position
|
|
let pos: f32 = 1.0 - mangle_position(s.position, idx, count);
|
|
let color: u32 = s.color.as_argb_encoded();
|
|
cpp! {unsafe [mut qcg as "QConicalGradient", pos as "float", color as "QRgb"] {
|
|
qcg.setColorAt(pos, QColor::fromRgba(color));
|
|
}};
|
|
}
|
|
cpp! {unsafe [qcg as "QConicalGradient"] -> qttypes::QBrush as "QBrush" {
|
|
return QBrush(qcg);
|
|
}}
|
|
}
|
|
_ => qttypes::QBrush::default(),
|
|
}
|
|
}
|
|
|
|
fn from_qt_button(qt_button: u32) -> PointerEventButton {
|
|
match qt_button {
|
|
// https://doc.qt.io/qt-6/qt.html#MouseButton-enum
|
|
1 => PointerEventButton::Left,
|
|
2 => PointerEventButton::Right,
|
|
4 => PointerEventButton::Middle,
|
|
8 => PointerEventButton::Back,
|
|
16 => PointerEventButton::Forward,
|
|
_ => PointerEventButton::Other,
|
|
}
|
|
}
|
|
|
|
/// Given a position offset and an object of a given type that has x,y,width,height properties,
|
|
/// create a QRectF that fits it.
|
|
macro_rules! check_geometry {
|
|
($size:expr) => {{
|
|
let size = $size;
|
|
if size.width < 1. || size.height < 1. {
|
|
return Default::default();
|
|
};
|
|
qttypes::QRectF { x: 0., y: 0., width: size.width as _, height: size.height as _ }
|
|
}};
|
|
}
|
|
|
|
fn adjust_rect_and_border_for_inner_drawing(rect: &mut qttypes::QRectF, border_width: &mut f32) {
|
|
// If the border width exceeds the width, just fill the rectangle.
|
|
*border_width = border_width.min((rect.width as f32) / 2.);
|
|
// adjust the size so that the border is drawn within the geometry
|
|
rect.x += *border_width as f64 / 2.;
|
|
rect.y += *border_width as f64 / 2.;
|
|
rect.width -= *border_width as f64;
|
|
rect.height -= *border_width as f64;
|
|
}
|
|
|
|
struct QtItemRenderer<'a> {
|
|
painter: QPainterPtr,
|
|
cache: &'a ItemCache<qttypes::QPixmap>,
|
|
window: &'a i_slint_core::api::Window,
|
|
metrics: RenderingMetrics,
|
|
}
|
|
|
|
impl ItemRenderer for QtItemRenderer<'_> {
|
|
fn draw_rectangle(
|
|
&mut self,
|
|
rect_: Pin<&dyn RenderRectangle>,
|
|
_: &ItemRc,
|
|
size: LogicalSize,
|
|
_cache: &CachedRenderingData,
|
|
) {
|
|
let rect: qttypes::QRectF = check_geometry!(size);
|
|
let brush: qttypes::QBrush = into_qbrush(rect_.background(), rect.width, rect.height);
|
|
let painter: &mut QPainterPtr = &mut self.painter;
|
|
cpp! { unsafe [painter as "QPainterPtr*", brush as "QBrush", rect as "QRectF"] {
|
|
(*painter)->fillRect(rect, brush);
|
|
}}
|
|
}
|
|
|
|
fn draw_border_rectangle(
|
|
&mut self,
|
|
rect: Pin<&dyn RenderBorderRectangle>,
|
|
_: &ItemRc,
|
|
size: LogicalSize,
|
|
_: &CachedRenderingData,
|
|
) {
|
|
Self::draw_rectangle_impl(
|
|
&mut self.painter,
|
|
check_geometry!(size),
|
|
rect.background(),
|
|
rect.border_color(),
|
|
rect.border_width().get(),
|
|
rect.border_radius(),
|
|
);
|
|
}
|
|
|
|
fn draw_window_background(
|
|
&mut self,
|
|
_rect: Pin<&dyn RenderRectangle>,
|
|
_self_rc: &ItemRc,
|
|
_size: LogicalSize,
|
|
_cache: &CachedRenderingData,
|
|
) {
|
|
// Background is applied via WindowProperties::background()
|
|
}
|
|
|
|
fn draw_image(
|
|
&mut self,
|
|
image: Pin<&dyn RenderImage>,
|
|
item_rc: &ItemRc,
|
|
size: LogicalSize,
|
|
_: &CachedRenderingData,
|
|
) {
|
|
self.draw_image_impl(item_rc, size, image);
|
|
}
|
|
|
|
fn draw_text(
|
|
&mut self,
|
|
text: Pin<&dyn RenderText>,
|
|
self_rc: &ItemRc,
|
|
size: LogicalSize,
|
|
_: &CachedRenderingData,
|
|
) {
|
|
sharedparley::draw_text(self, text, Some(text.font_request(self_rc)), size);
|
|
}
|
|
|
|
fn draw_text_input(
|
|
&mut self,
|
|
text_input: Pin<&items::TextInput>,
|
|
self_rc: &ItemRc,
|
|
size: LogicalSize,
|
|
) {
|
|
sharedparley::draw_text_input(
|
|
self,
|
|
text_input,
|
|
Some(text_input.font_request(self_rc)),
|
|
size,
|
|
Some(qt_password_character),
|
|
);
|
|
}
|
|
|
|
fn draw_path(&mut self, path: Pin<&items::Path>, item_rc: &ItemRc, size: LogicalSize) {
|
|
let (offset, path_events) = match path.fitted_path_events(item_rc) {
|
|
Some(offset_and_events) => offset_and_events,
|
|
None => return,
|
|
};
|
|
let rect: qttypes::QRectF = check_geometry!(size);
|
|
let fill_brush: qttypes::QBrush = into_qbrush(path.fill(), rect.width, rect.height);
|
|
let stroke_brush: qttypes::QBrush = into_qbrush(path.stroke(), rect.width, rect.height);
|
|
let stroke_width: f32 = path.stroke_width().get();
|
|
let stroke_pen_cap_style: i32 = match path.stroke_line_cap() {
|
|
LineCap::Butt => 0x00,
|
|
LineCap::Round => 0x20,
|
|
LineCap::Square => 0x10,
|
|
};
|
|
let pos = qttypes::QPoint { x: offset.x as _, y: offset.y as _ };
|
|
let mut painter_path = QPainterPath::default();
|
|
|
|
painter_path.set_fill_rule(match path.fill_rule() {
|
|
FillRule::Nonzero => key_generated::Qt_FillRule_WindingFill,
|
|
FillRule::Evenodd => key_generated::Qt_FillRule_OddEvenFill,
|
|
});
|
|
|
|
for x in path_events.iter() {
|
|
fn to_qpointf(p: Point) -> qttypes::QPointF {
|
|
qttypes::QPointF { x: p.x as _, y: p.y as _ }
|
|
}
|
|
match x {
|
|
lyon_path::Event::Begin { at } => {
|
|
painter_path.move_to(to_qpointf(at));
|
|
}
|
|
lyon_path::Event::Line { from: _, to } => {
|
|
painter_path.line_to(to_qpointf(to));
|
|
}
|
|
lyon_path::Event::Quadratic { from: _, ctrl, to } => {
|
|
painter_path.quad_to(to_qpointf(ctrl), to_qpointf(to));
|
|
}
|
|
|
|
lyon_path::Event::Cubic { from: _, ctrl1, ctrl2, to } => {
|
|
painter_path.cubic_to(to_qpointf(ctrl1), to_qpointf(ctrl2), to_qpointf(to));
|
|
}
|
|
lyon_path::Event::End { last: _, first: _, close } => {
|
|
// FIXME: are we supposed to do something with last and first?
|
|
if close {
|
|
painter_path.close()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
let anti_alias: bool = path.anti_alias();
|
|
|
|
let painter: &mut QPainterPtr = &mut self.painter;
|
|
cpp! { unsafe [
|
|
painter as "QPainterPtr*",
|
|
pos as "QPoint",
|
|
mut painter_path as "QPainterPath",
|
|
fill_brush as "QBrush",
|
|
stroke_brush as "QBrush",
|
|
stroke_width as "float",
|
|
stroke_pen_cap_style as "int",
|
|
anti_alias as "bool"] {
|
|
(*painter)->save();
|
|
auto cleanup = qScopeGuard([&] { (*painter)->restore(); });
|
|
(*painter)->translate(pos);
|
|
(*painter)->setPen(stroke_width > 0 ? QPen(stroke_brush, stroke_width, Qt::SolidLine, Qt::PenCapStyle(stroke_pen_cap_style)) : Qt::NoPen);
|
|
(*painter)->setBrush(fill_brush);
|
|
(*painter)->setRenderHint(QPainter::Antialiasing, anti_alias);
|
|
(*painter)->drawPath(painter_path);
|
|
}}
|
|
}
|
|
|
|
fn draw_box_shadow(
|
|
&mut self,
|
|
box_shadow: Pin<&items::BoxShadow>,
|
|
item_rc: &ItemRc,
|
|
_size: LogicalSize,
|
|
) {
|
|
let pixmap : qttypes::QPixmap = self.cache.get_or_update_cache_entry( item_rc, || {
|
|
let shadow_rect = check_geometry!(item_rc.geometry().size);
|
|
|
|
let source_size = qttypes::QSize {
|
|
width: shadow_rect.width.ceil() as _,
|
|
height: shadow_rect.height.ceil() as _,
|
|
};
|
|
|
|
let mut source_image =
|
|
qttypes::QImage::new(source_size, qttypes::ImageFormat::ARGB32_Premultiplied);
|
|
source_image.fill(qttypes::QColor::from_rgba_f(0., 0., 0., 0.));
|
|
|
|
let img = &mut source_image;
|
|
let mut painter_ = cpp!(unsafe [img as "QImage*"] -> QPainterPtr as "QPainterPtr" {
|
|
return std::make_unique<QPainter>(img);
|
|
});
|
|
|
|
Self::draw_rectangle_impl(
|
|
&mut painter_,
|
|
qttypes::QRectF { x: 0., y: 0., width: shadow_rect.width, height: shadow_rect.height },
|
|
Brush::SolidColor(box_shadow.color()),
|
|
Brush::default(),
|
|
0.,
|
|
LogicalBorderRadius::new_uniform(box_shadow.border_radius().get()),
|
|
);
|
|
|
|
drop(painter_);
|
|
|
|
let blur_radius = box_shadow.blur().get();
|
|
|
|
if blur_radius > 0. {
|
|
cpp! {
|
|
unsafe[img as "QImage*", blur_radius as "float"] -> qttypes::QPixmap as "QPixmap" {
|
|
QGraphicsScene scene;
|
|
auto pixmap_item = scene.addPixmap(QPixmap::fromImage(*img));
|
|
|
|
auto blur_effect = new QGraphicsBlurEffect;
|
|
blur_effect->setBlurRadius(blur_radius);
|
|
blur_effect->setBlurHints(QGraphicsBlurEffect::QualityHint);
|
|
|
|
// takes ownership of the effect and registers the item with
|
|
// the effect as source.
|
|
pixmap_item->setGraphicsEffect(blur_effect);
|
|
|
|
QImage blurred_scene(img->width() + 2 * blur_radius, img->height() + 2 * blur_radius, QImage::Format_ARGB32_Premultiplied);
|
|
blurred_scene.fill(Qt::transparent);
|
|
|
|
QPainter p(&blurred_scene);
|
|
scene.render(&p,
|
|
QRectF(0, 0, blurred_scene.width(), blurred_scene.height()),
|
|
QRectF(-blur_radius, -blur_radius, blurred_scene.width(), blurred_scene.height()));
|
|
p.end();
|
|
|
|
return QPixmap::fromImage(blurred_scene);
|
|
}}
|
|
} else {
|
|
cpp! { unsafe[img as "QImage*"] -> qttypes::QPixmap as "QPixmap" {
|
|
return QPixmap::fromImage(*img);
|
|
}}
|
|
}
|
|
});
|
|
|
|
let blur_radius = box_shadow.blur();
|
|
|
|
let shadow_offset = qttypes::QPointF {
|
|
x: (box_shadow.offset_x() - blur_radius).get() as f64,
|
|
y: (box_shadow.offset_y() - blur_radius).get() as f64,
|
|
};
|
|
|
|
let painter: &mut QPainterPtr = &mut self.painter;
|
|
cpp! { unsafe [
|
|
painter as "QPainterPtr*",
|
|
shadow_offset as "QPointF",
|
|
pixmap as "QPixmap"
|
|
] {
|
|
(*painter)->drawPixmap(shadow_offset, pixmap);
|
|
}}
|
|
}
|
|
|
|
fn visit_opacity(
|
|
&mut self,
|
|
opacity_item: Pin<&Opacity>,
|
|
item_rc: &ItemRc,
|
|
_size: LogicalSize,
|
|
) -> RenderingResult {
|
|
let opacity = opacity_item.opacity();
|
|
if Opacity::need_layer(item_rc, opacity) {
|
|
self.render_and_blend_layer(opacity, item_rc)
|
|
} else {
|
|
self.apply_opacity(opacity);
|
|
self.cache.release(item_rc);
|
|
RenderingResult::ContinueRenderingChildren
|
|
}
|
|
}
|
|
|
|
fn visit_layer(
|
|
&mut self,
|
|
layer_item: Pin<&Layer>,
|
|
self_rc: &ItemRc,
|
|
_size: LogicalSize,
|
|
) -> RenderingResult {
|
|
if layer_item.cache_rendering_hint() {
|
|
self.render_and_blend_layer(1.0, self_rc)
|
|
} else {
|
|
RenderingResult::ContinueRenderingChildren
|
|
}
|
|
}
|
|
|
|
fn combine_clip(
|
|
&mut self,
|
|
rect: LogicalRect,
|
|
radius: LogicalBorderRadius,
|
|
border_width: LogicalLength,
|
|
) -> bool {
|
|
let mut border_width: f32 = border_width.get();
|
|
let mut clip_rect = qttypes::QRectF {
|
|
x: rect.min_x() as _,
|
|
y: rect.min_y() as _,
|
|
width: rect.width() as _,
|
|
height: rect.height() as _,
|
|
};
|
|
adjust_rect_and_border_for_inner_drawing(&mut clip_rect, &mut border_width);
|
|
let painter: &mut QPainterPtr = &mut self.painter;
|
|
let top_left_radius = radius.top_left;
|
|
let top_right_radius = radius.top_right;
|
|
let bottom_left_radius = radius.bottom_left;
|
|
let bottom_right_radius = radius.bottom_right;
|
|
cpp! { unsafe [
|
|
painter as "QPainterPtr*",
|
|
clip_rect as "QRectF",
|
|
top_left_radius as "float",
|
|
top_right_radius as "float",
|
|
bottom_right_radius as "float",
|
|
bottom_left_radius as "float"] -> bool as "bool" {
|
|
if (top_left_radius <= 0 && top_right_radius <= 0 && bottom_right_radius <= 0 && bottom_left_radius <= 0) {
|
|
(*painter)->setClipRect(clip_rect, Qt::IntersectClip);
|
|
} else {
|
|
QPainterPath path = to_painter_path(clip_rect, top_left_radius, top_right_radius, bottom_right_radius, bottom_left_radius);
|
|
(*painter)->setClipPath(path, Qt::IntersectClip);
|
|
}
|
|
return !(*painter)->clipBoundingRect().isEmpty();
|
|
}}
|
|
}
|
|
|
|
fn get_current_clip(&self) -> LogicalRect {
|
|
let painter: &QPainterPtr = &self.painter;
|
|
let res = cpp! { unsafe [painter as "const QPainterPtr*" ] -> qttypes::QRectF as "QRectF" {
|
|
return (*painter)->clipBoundingRect();
|
|
}};
|
|
LogicalRect::new(
|
|
LogicalPoint::new(res.x as _, res.y as _),
|
|
LogicalSize::new(res.width as _, res.height as _),
|
|
)
|
|
}
|
|
|
|
fn save_state(&mut self) {
|
|
self.painter.save()
|
|
}
|
|
|
|
fn restore_state(&mut self) {
|
|
self.painter.restore()
|
|
}
|
|
|
|
fn scale_factor(&self) -> f32 {
|
|
1.
|
|
/* cpp! { unsafe [painter as "QPainterPtr*"] -> f32 as "float" {
|
|
return (*painter)->paintEngine()->paintDevice()->devicePixelRatioF();
|
|
}} */
|
|
}
|
|
|
|
fn draw_cached_pixmap(
|
|
&mut self,
|
|
_item_rc: &ItemRc,
|
|
update_fn: &dyn Fn(&mut dyn FnMut(u32, u32, &[u8])),
|
|
) {
|
|
update_fn(&mut |width: u32, height: u32, data: &[u8]| {
|
|
let data = data.as_ptr();
|
|
let painter: &mut QPainterPtr = &mut self.painter;
|
|
cpp! { unsafe [painter as "QPainterPtr*", width as "int", height as "int", data as "const unsigned char *"] {
|
|
QImage img(data, width, height, width * 4, QImage::Format_RGBA8888_Premultiplied);
|
|
(*painter)->drawImage(QPoint(), img);
|
|
}}
|
|
})
|
|
}
|
|
|
|
fn draw_string(&mut self, string: &str, color: Color) {
|
|
sharedparley::draw_text(
|
|
self,
|
|
std::pin::pin!((SharedString::from(string), Brush::from(color))),
|
|
None,
|
|
LogicalSize::new(1., 1.), // Non-zero size to avoid an early return
|
|
);
|
|
}
|
|
|
|
fn draw_image_direct(&mut self, _image: i_slint_core::graphics::Image) {
|
|
todo!()
|
|
}
|
|
|
|
fn window(&self) -> &i_slint_core::window::WindowInner {
|
|
i_slint_core::window::WindowInner::from_pub(self.window)
|
|
}
|
|
|
|
fn as_any(&mut self) -> Option<&mut dyn std::any::Any> {
|
|
Some(&mut self.painter)
|
|
}
|
|
|
|
fn translate(&mut self, distance: LogicalVector) {
|
|
let x: f32 = distance.x;
|
|
let y: f32 = distance.y;
|
|
let painter: &mut QPainterPtr = &mut self.painter;
|
|
cpp! { unsafe [painter as "QPainterPtr*", x as "float", y as "float"] {
|
|
(*painter)->translate(x, y);
|
|
}}
|
|
}
|
|
|
|
fn rotate(&mut self, angle_in_degrees: f32) {
|
|
let painter: &mut QPainterPtr = &mut self.painter;
|
|
cpp! { unsafe [painter as "QPainterPtr*", angle_in_degrees as "float"] {
|
|
(*painter)->rotate(angle_in_degrees);
|
|
}}
|
|
}
|
|
|
|
fn scale(&mut self, x_factor: f32, y_factor: f32) {
|
|
let painter: &mut QPainterPtr = &mut self.painter;
|
|
cpp! { unsafe [painter as "QPainterPtr*", x_factor as "float", y_factor as "float"] {
|
|
(*painter)->scale(x_factor, y_factor);
|
|
}}
|
|
}
|
|
|
|
fn apply_opacity(&mut self, opacity: f32) {
|
|
let painter: &mut QPainterPtr = &mut self.painter;
|
|
cpp! { unsafe [painter as "QPainterPtr*", opacity as "float"] {
|
|
(*painter)->setOpacity((*painter)->opacity() * opacity);
|
|
}}
|
|
}
|
|
|
|
fn metrics(&self) -> RenderingMetrics {
|
|
self.metrics.clone()
|
|
}
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub enum GlyphBrush {
|
|
Fill(qttypes::QBrush),
|
|
Stroke(qttypes::QPen),
|
|
}
|
|
|
|
impl GlyphRenderer for QtItemRenderer<'_> {
|
|
type PlatformBrush = GlyphBrush;
|
|
|
|
fn platform_text_fill_brush(
|
|
&mut self,
|
|
brush: i_slint_core::Brush,
|
|
size: LogicalSize,
|
|
) -> Option<Self::PlatformBrush> {
|
|
Some(GlyphBrush::Fill(into_qbrush(brush, size.width as _, size.height as _)))
|
|
}
|
|
|
|
fn platform_brush_for_color(
|
|
&mut self,
|
|
color: &i_slint_core::Color,
|
|
) -> Option<Self::PlatformBrush> {
|
|
let color: u32 = color.as_argb_encoded();
|
|
Some(GlyphBrush::Fill(cpp!(unsafe [color as "QRgb"] -> qttypes::QBrush as "QBrush" {
|
|
return QBrush(QColor::fromRgba(color));
|
|
})))
|
|
}
|
|
|
|
fn platform_text_stroke_brush(
|
|
&mut self,
|
|
brush: i_slint_core::Brush,
|
|
physical_stroke_width: f32,
|
|
size: LogicalSize,
|
|
) -> Option<Self::PlatformBrush> {
|
|
let brush = into_qbrush(brush, size.width as _, size.height as _);
|
|
Some(GlyphBrush::Stroke(
|
|
cpp!(unsafe [brush as "QBrush", physical_stroke_width as "float"] -> qttypes::QPen as "QPen" {
|
|
QPen pen(brush, physical_stroke_width);
|
|
pen.setJoinStyle(Qt::MiterJoin);
|
|
return pen;
|
|
}),
|
|
))
|
|
}
|
|
|
|
fn draw_glyph_run(
|
|
&mut self,
|
|
font: &sharedparley::parley::FontData,
|
|
font_size: sharedparley::PhysicalLength,
|
|
brush: Self::PlatformBrush,
|
|
y_offset: sharedparley::PhysicalLength,
|
|
glyphs_it: &mut dyn Iterator<Item = sharedparley::parley::layout::Glyph>,
|
|
) {
|
|
let Some(mut raw_font) = FONT_CACHE.with(|cache| cache.borrow_mut().font(font)) else {
|
|
return;
|
|
};
|
|
|
|
raw_font.set_pixel_size(font_size.get());
|
|
|
|
let (glyph_indices, positions): (Vec<u32>, Vec<qttypes::QPointF>) = glyphs_it
|
|
.into_iter()
|
|
.map(|g| {
|
|
(g.id, qttypes::QPointF { x: g.x as f64, y: g.y as f64 + y_offset.get() as f64 })
|
|
})
|
|
.unzip();
|
|
|
|
let glyph_indices_ptr = glyph_indices.as_ptr();
|
|
let glyph_positions_ptr = positions.as_ptr();
|
|
let size: u32 = glyph_indices.len() as u32;
|
|
if size == 0 {
|
|
return;
|
|
}
|
|
|
|
let painter: &mut QPainterPtr = &mut self.painter;
|
|
|
|
match brush {
|
|
GlyphBrush::Fill(qt_brush) => {
|
|
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"] {
|
|
// drawGlyphRun uses QPen to fill glyphs
|
|
(*painter)->setPen(QPen(qt_brush, 1));
|
|
(*painter)->setBrush(Qt::NoBrush);
|
|
|
|
QGlyphRun glyphRun;
|
|
glyphRun.setRawFont(raw_font);
|
|
glyphRun.setRawData(glyph_indices_ptr, glyph_positions_ptr, size);
|
|
(*painter)->drawGlyphRun(QPointF(0, 0), glyphRun);
|
|
}}
|
|
}
|
|
GlyphBrush::Stroke(qt_pen) => {
|
|
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"] {
|
|
(*painter)->setPen(qt_pen);
|
|
(*painter)->setBrush(Qt::NoBrush);
|
|
|
|
QPainterPath path;
|
|
for (int i = 0; i < size; i++) {
|
|
QPainterPath glyphPath = raw_font.pathForGlyph(glyph_indices_ptr[i]);
|
|
glyphPath.translate(glyph_positions_ptr[i]);
|
|
path.addPath(glyphPath);
|
|
}
|
|
(*painter)->drawPath(path);
|
|
}}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn fill_rectangle(
|
|
&mut self,
|
|
physical_rect: sharedparley::PhysicalRect,
|
|
color: i_slint_core::Color,
|
|
) {
|
|
let rect = qttypes::QRectF {
|
|
x: physical_rect.min_x() as _,
|
|
y: physical_rect.min_y() as _,
|
|
width: physical_rect.width() as _,
|
|
height: physical_rect.height() as _,
|
|
};
|
|
let color: u32 = color.as_argb_encoded();
|
|
let painter: &mut QPainterPtr = &mut self.painter;
|
|
cpp! { unsafe [painter as "QPainterPtr*", color as "QRgb", rect as "QRectF"] {
|
|
(*painter)->fillRect(rect, QBrush(QColor::fromRgba(color)));
|
|
}}
|
|
}
|
|
}
|
|
|
|
cpp_class! {pub unsafe struct QRawFont as "QRawFont"}
|
|
|
|
impl QRawFont {
|
|
pub fn load_from_data(&mut self, data: &[u8], pixel_size: f32) {
|
|
let font_data = qttypes::QByteArray::from(data);
|
|
cpp! { unsafe [ self as "QRawFont*", font_data as "QByteArray", pixel_size as "float"] {
|
|
self->loadFromData(font_data, pixel_size, QFont::PreferDefaultHinting);
|
|
}}
|
|
}
|
|
|
|
pub fn set_pixel_size(&mut self, pixel_size: f32) {
|
|
cpp! { unsafe [ self as "QRawFont*", pixel_size as "float"] {
|
|
self->setPixelSize(pixel_size);
|
|
}}
|
|
}
|
|
|
|
pub fn is_valid(&self) -> bool {
|
|
cpp! { unsafe [ self as "const QRawFont*"] -> bool as "bool" {
|
|
return self->isValid();
|
|
}}
|
|
}
|
|
}
|
|
|
|
pub struct FontCache {
|
|
/// Fonts are indexed by unique blob id (atomically incremented in fontique) and the font collection index.
|
|
fonts: HashMap<(u64, u32), Option<QRawFont>>,
|
|
}
|
|
|
|
impl Default for FontCache {
|
|
fn default() -> Self {
|
|
Self { fonts: Default::default() }
|
|
}
|
|
}
|
|
|
|
impl FontCache {
|
|
pub fn font(&mut self, font: &parley::FontData) -> Option<QRawFont> {
|
|
self.fonts
|
|
.entry((font.data.id(), font.index))
|
|
.or_insert_with(move || {
|
|
let mut raw_font = QRawFont::default();
|
|
raw_font.load_from_data(font.data.as_ref(), 12.0);
|
|
if raw_font.is_valid() {
|
|
Some(raw_font)
|
|
} else {
|
|
None
|
|
}
|
|
})
|
|
.clone()
|
|
}
|
|
}
|
|
|
|
thread_local! {
|
|
pub static FONT_CACHE: RefCell<FontCache> = RefCell::new(Default::default())
|
|
}
|
|
|
|
fn shared_image_buffer_to_pixmap(buffer: &SharedImageBuffer) -> Option<qttypes::QPixmap> {
|
|
let (format, bytes_per_line, buffer_ptr) = match buffer {
|
|
SharedImageBuffer::RGBA8(img) => {
|
|
(qttypes::ImageFormat::RGBA8888, img.width() * 4, img.as_bytes().as_ptr())
|
|
}
|
|
SharedImageBuffer::RGBA8Premultiplied(img) => {
|
|
(qttypes::ImageFormat::RGBA8888_Premultiplied, img.width() * 4, img.as_bytes().as_ptr())
|
|
}
|
|
SharedImageBuffer::RGB8(img) => {
|
|
(qttypes::ImageFormat::RGB888, img.width() * 3, img.as_bytes().as_ptr())
|
|
}
|
|
};
|
|
let width: i32 = buffer.width() as _;
|
|
let height: i32 = buffer.height() as _;
|
|
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" {
|
|
QImage img(buffer_ptr, width, height, bytes_per_line, format);
|
|
return QPixmap::fromImage(img);
|
|
} };
|
|
Some(pixmap)
|
|
}
|
|
|
|
pub(crate) fn image_to_pixmap(
|
|
image: &ImageInner,
|
|
source_size: Option<euclid::Size2D<u32, PhysicalPx>>,
|
|
) -> Option<qttypes::QPixmap> {
|
|
shared_image_buffer_to_pixmap(&image.render_to_buffer(source_size)?)
|
|
}
|
|
|
|
impl QtItemRenderer<'_> {
|
|
fn draw_image_impl(
|
|
&mut self,
|
|
item_rc: &ItemRc,
|
|
size: LogicalSize,
|
|
image: Pin<&dyn i_slint_core::item_rendering::RenderImage>,
|
|
) {
|
|
let source_rect = image.source_clip();
|
|
|
|
let pixmap: qttypes::QPixmap = self.cache.get_or_update_cache_entry(item_rc, || {
|
|
let source = image.source();
|
|
let origin = source.size();
|
|
let source: &ImageInner = (&source).into();
|
|
|
|
// Query target_width/height here again to ensure that changes will invalidate the item rendering cache.
|
|
let scale_factor = ScaleFactor::new(self.scale_factor());
|
|
let t = (image.target_size() * scale_factor).cast();
|
|
|
|
let source_size = if source.is_svg() {
|
|
let has_source_clipping = source_rect.map_or(false, |rect| {
|
|
rect.origin.x != 0
|
|
|| rect.origin.y != 0
|
|
|| !rect.size.width != t.width
|
|
|| !rect.size.height != t.height
|
|
});
|
|
if has_source_clipping {
|
|
// Source size & clipping is not implemented yet
|
|
None
|
|
} else {
|
|
Some(
|
|
i_slint_core::graphics::fit(
|
|
image.image_fit(),
|
|
t.cast(),
|
|
IntRect::from_size(origin.cast()),
|
|
scale_factor,
|
|
Default::default(), // We only care about the size, so alignments don't matter
|
|
image.tiling(),
|
|
)
|
|
.size
|
|
.cast(),
|
|
)
|
|
}
|
|
} else {
|
|
None
|
|
};
|
|
|
|
image_to_pixmap(source, source_size).map_or_else(
|
|
Default::default,
|
|
|mut pixmap: qttypes::QPixmap| {
|
|
let colorize = image.colorize();
|
|
if !colorize.is_transparent() {
|
|
let pixmap_size = pixmap.size();
|
|
let brush: qttypes::QBrush = into_qbrush(
|
|
colorize,
|
|
pixmap_size.width.into(),
|
|
pixmap_size.height.into(),
|
|
);
|
|
cpp!(unsafe [mut pixmap as "QPixmap", brush as "QBrush"] {
|
|
QPainter p(&pixmap);
|
|
p.setCompositionMode(QPainter::CompositionMode_SourceIn);
|
|
p.fillRect(QRect(QPoint(), pixmap.size()), brush);
|
|
});
|
|
}
|
|
pixmap
|
|
},
|
|
)
|
|
});
|
|
|
|
let image_size = pixmap.size();
|
|
let source_rect = source_rect
|
|
.unwrap_or_else(|| euclid::rect(0, 0, image_size.width as _, image_size.height as _));
|
|
let scale_factor = ScaleFactor::new(self.scale_factor());
|
|
|
|
let fit = if let &i_slint_core::ImageInner::NineSlice(ref nine) = (&image.source()).into() {
|
|
i_slint_core::graphics::fit9slice(
|
|
nine.0.size(),
|
|
nine.1,
|
|
size * scale_factor,
|
|
scale_factor,
|
|
image.alignment(),
|
|
image.tiling(),
|
|
)
|
|
.collect::<Vec<_>>()
|
|
} else {
|
|
vec![i_slint_core::graphics::fit(
|
|
image.image_fit(),
|
|
size * scale_factor,
|
|
source_rect,
|
|
scale_factor,
|
|
image.alignment(),
|
|
image.tiling(),
|
|
)]
|
|
};
|
|
|
|
for fit in fit {
|
|
let dest_rect = qttypes::QRectF {
|
|
x: fit.offset.x as _,
|
|
y: fit.offset.y as _,
|
|
width: fit.size.width as _,
|
|
height: fit.size.height as _,
|
|
};
|
|
let source_rect = qttypes::QRectF {
|
|
x: fit.clip_rect.origin.x as _,
|
|
y: fit.clip_rect.origin.y as _,
|
|
width: fit.clip_rect.size.width as _,
|
|
height: fit.clip_rect.size.height as _,
|
|
};
|
|
|
|
let painter: &mut QPainterPtr = &mut self.painter;
|
|
let smooth: bool = image.rendering() == ImageRendering::Smooth;
|
|
if let Some(offset) = fit.tiled {
|
|
let scale_x: f32 = fit.source_to_target_x;
|
|
let scale_y: f32 = fit.source_to_target_y;
|
|
let offset = qttypes::QPoint { x: offset.x as _, y: offset.y as _ };
|
|
cpp! { unsafe [
|
|
painter as "QPainterPtr*", pixmap as "QPixmap", source_rect as "QRectF",
|
|
dest_rect as "QRectF", smooth as "bool", scale_x as "float", scale_y as "float",
|
|
offset as "QPoint"
|
|
] {
|
|
(*painter)->save();
|
|
(*painter)->setRenderHint(QPainter::SmoothPixmapTransform, smooth);
|
|
auto transform = QTransform::fromScale(1 / scale_x, 1 / scale_y);
|
|
auto scaled_destination = (dest_rect * transform).boundingRect();
|
|
QPixmap source_pixmap = pixmap.copy(source_rect.toRect());
|
|
(*painter)->scale(scale_x, scale_y);
|
|
(*painter)->drawTiledPixmap(scaled_destination, source_pixmap, offset);
|
|
(*painter)->restore();
|
|
}
|
|
};
|
|
} else {
|
|
cpp! { unsafe [
|
|
painter as "QPainterPtr*",
|
|
pixmap as "QPixmap",
|
|
source_rect as "QRectF",
|
|
dest_rect as "QRectF",
|
|
smooth as "bool"] {
|
|
(*painter)->save();
|
|
(*painter)->setRenderHint(QPainter::SmoothPixmapTransform, smooth);
|
|
(*painter)->drawPixmap(dest_rect, pixmap, source_rect);
|
|
(*painter)->restore();
|
|
}};
|
|
}
|
|
}
|
|
}
|
|
|
|
fn draw_rectangle_impl(
|
|
painter: &mut QPainterPtr,
|
|
mut rect: qttypes::QRectF,
|
|
brush: Brush,
|
|
border_color: Brush,
|
|
mut border_width: f32,
|
|
border_radius: LogicalBorderRadius,
|
|
) {
|
|
if border_color.is_transparent() {
|
|
border_width = 0.;
|
|
};
|
|
let brush: qttypes::QBrush = into_qbrush(brush, rect.width, rect.height);
|
|
let border_color: qttypes::QBrush = into_qbrush(border_color, rect.width, rect.height);
|
|
let top_left_radius = border_radius.top_left;
|
|
let top_right_radius = border_radius.top_right;
|
|
let bottom_left_radius = border_radius.bottom_left;
|
|
let bottom_right_radius = border_radius.bottom_right;
|
|
border_width = border_width.min(rect.height.min(rect.width) as f32 / 2.);
|
|
cpp! { unsafe [
|
|
painter as "QPainterPtr*",
|
|
brush as "QBrush",
|
|
border_color as "QBrush",
|
|
border_width as "float",
|
|
top_left_radius as "float",
|
|
top_right_radius as "float",
|
|
bottom_left_radius as "float",
|
|
bottom_right_radius as "float",
|
|
mut rect as "QRectF"] {
|
|
(*painter)->save();
|
|
auto cleanup = qScopeGuard([&] { (*painter)->restore(); });
|
|
(*painter)->setBrush(brush);
|
|
QPen pen = border_width > 0 ? QPen(border_color, border_width, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin) : Qt::NoPen;
|
|
if (top_left_radius <= 0 && top_right_radius <= 0 && bottom_left_radius <= 0 && bottom_right_radius <= 0) {
|
|
if (!border_color.isOpaque() && border_width > 1) {
|
|
// In case of transparent pen, we want the background to cover the whole rectangle, which Qt doesn't do.
|
|
// So first draw the background, then draw the pen over it
|
|
(*painter)->setPen(Qt::NoPen);
|
|
(*painter)->drawRect(rect);
|
|
(*painter)->setBrush(QBrush());
|
|
}
|
|
rect.adjust(border_width / 2, border_width / 2, -border_width / 2, -border_width / 2);
|
|
(*painter)->setPen(pen);
|
|
(*painter)->drawRect(rect);
|
|
} else {
|
|
if (!border_color.isOpaque() && border_width > 1) {
|
|
// See adjustment below
|
|
float tl_r = qFuzzyIsNull(top_left_radius) ? top_left_radius : qMax(border_width/2, top_left_radius);
|
|
float tr_r = qFuzzyIsNull(top_right_radius) ? top_right_radius : qMax(border_width/2, top_right_radius);
|
|
float br_r = qFuzzyIsNull(bottom_right_radius) ? bottom_right_radius : qMax(border_width/2, bottom_right_radius);
|
|
float bl_r = qFuzzyIsNull(bottom_left_radius) ? bottom_left_radius : qMax(border_width/2, bottom_left_radius);
|
|
// In case of transparent pen, we want the background to cover the whole rectangle, which Qt doesn't do.
|
|
// So first draw the background, then draw the pen over it
|
|
(*painter)->setPen(Qt::NoPen);
|
|
(*painter)->drawPath(to_painter_path(rect, tl_r, tr_r, br_r, bl_r));
|
|
(*painter)->setBrush(QBrush());
|
|
}
|
|
// Qt's border radius is in the middle of the border. But we want it to be the radius of the rectangle itself.
|
|
// 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
|
|
float tl_r = qMax(0.0f, top_left_radius - border_width / 2);
|
|
float tr_r = qMax(0.0f, top_right_radius - border_width / 2);
|
|
float br_r = qMax(0.0f, bottom_right_radius - border_width / 2);
|
|
float bl_r = qMax(0.0f, bottom_left_radius - border_width / 2);
|
|
rect.adjust(border_width / 2, border_width / 2, -border_width / 2, -border_width / 2);
|
|
(*painter)->setPen(pen);
|
|
(*painter)->drawPath(to_painter_path(rect, tl_r, tr_r, br_r, bl_r));
|
|
}
|
|
}}
|
|
}
|
|
|
|
fn render_layer(
|
|
&mut self,
|
|
item_rc: &ItemRc,
|
|
layer_size_fn: &dyn Fn() -> LogicalSize,
|
|
) -> qttypes::QPixmap {
|
|
self.cache.get_or_update_cache_entry(item_rc, || {
|
|
let painter: &mut QPainterPtr = &mut self.painter;
|
|
let dpr = cpp! { unsafe [painter as "QPainterPtr*"] -> f32 as "float" {
|
|
return (*painter)->paintEngine()->paintDevice()->devicePixelRatioF();
|
|
}};
|
|
|
|
let layer_size = layer_size_fn();
|
|
let layer_size = qttypes::QSize {
|
|
width: (layer_size.width * dpr) as _,
|
|
height: (layer_size.height * dpr) as _,
|
|
};
|
|
|
|
let mut layer_image = qttypes::QImage::new(layer_size, qttypes::ImageFormat::ARGB32_Premultiplied);
|
|
layer_image.fill(qttypes::QColor::from_rgba_f(0., 0., 0., 0.));
|
|
|
|
*self.metrics.layers_created.as_mut().unwrap() += 1;
|
|
|
|
let img_ref: &mut qttypes::QImage = &mut layer_image;
|
|
let mut layer_painter = cpp!(unsafe [img_ref as "QImage*", dpr as "float"] -> QPainterPtr as "QPainterPtr" {
|
|
img_ref->setDevicePixelRatio(dpr);
|
|
auto painter = std::make_unique<QPainter>(img_ref);
|
|
painter->setClipRect(0, 0, img_ref->width(), img_ref->height());
|
|
painter->setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
|
|
return painter;
|
|
});
|
|
|
|
std::mem::swap(&mut self.painter, &mut layer_painter);
|
|
|
|
let window_adapter = self.window().window_adapter();
|
|
|
|
i_slint_core::item_rendering::render_item_children(
|
|
self,
|
|
&item_rc.item_tree(),
|
|
item_rc.index() as isize, &window_adapter
|
|
);
|
|
|
|
std::mem::swap(&mut self.painter, &mut layer_painter);
|
|
drop(layer_painter);
|
|
|
|
qttypes::QPixmap::from(layer_image)
|
|
})
|
|
}
|
|
|
|
fn render_and_blend_layer(&mut self, alpha_tint: f32, self_rc: &ItemRc) -> RenderingResult {
|
|
let current_clip = self.get_current_clip();
|
|
let mut layer_image = self.render_layer(self_rc, &|| {
|
|
// We don't need to include the size of the opacity item itself, since it has no content.
|
|
let children_rect = i_slint_core::properties::evaluate_no_tracking(|| {
|
|
self_rc.geometry().union(
|
|
&i_slint_core::item_rendering::item_children_bounding_rect(
|
|
&self_rc.item_tree(),
|
|
self_rc.index() as isize,
|
|
¤t_clip,
|
|
),
|
|
)
|
|
});
|
|
children_rect.size
|
|
});
|
|
self.save_state();
|
|
self.apply_opacity(alpha_tint);
|
|
{
|
|
let painter: &mut QPainterPtr = &mut self.painter;
|
|
let layer_image_ref: &mut qttypes::QPixmap = &mut layer_image;
|
|
cpp! { unsafe [
|
|
painter as "QPainterPtr*",
|
|
layer_image_ref as "QPixmap*"
|
|
] {
|
|
(*painter)->drawPixmap(0, 0, *layer_image_ref);
|
|
}}
|
|
}
|
|
self.restore_state();
|
|
RenderingResult::ContinueRenderingWithoutChildren
|
|
}
|
|
}
|
|
|
|
cpp! {{
|
|
struct QWidgetDeleteLater
|
|
{
|
|
void operator()(QWidget *widget_ptr)
|
|
{
|
|
if (widget_ptr->parent()) {
|
|
// if the widget is a popup, use deleteLater (#4129)
|
|
widget_ptr->hide();
|
|
widget_ptr->deleteLater();
|
|
} else {
|
|
// Otherwise, use normal delete as it would otherwise cause crash at exit (#7570)
|
|
delete widget_ptr;
|
|
}
|
|
}
|
|
};
|
|
}}
|
|
|
|
cpp_class!(pub(crate) unsafe struct QWidgetPtr as "std::unique_ptr<QWidget, QWidgetDeleteLater>");
|
|
|
|
pub struct QtWindow {
|
|
widget_ptr: QWidgetPtr,
|
|
pub(crate) window: i_slint_core::api::Window,
|
|
self_weak: Weak<Self>,
|
|
|
|
rendering_metrics_collector: RefCell<Option<Rc<RenderingMetricsCollector>>>,
|
|
|
|
cache: ItemCache<qttypes::QPixmap>,
|
|
|
|
tree_structure_changed: RefCell<bool>,
|
|
|
|
color_scheme: OnceCell<Pin<Box<Property<ColorScheme>>>>,
|
|
}
|
|
|
|
impl Drop for QtWindow {
|
|
fn drop(&mut self) {
|
|
let widget_ptr = self.widget_ptr();
|
|
cpp! {unsafe [widget_ptr as "SlintWidget*"] {
|
|
// widget_ptr uses deleteLater to destroy the SlintWidget, we must prevent events to still call us
|
|
widget_ptr->rust_window = nullptr;
|
|
}};
|
|
}
|
|
}
|
|
|
|
impl QtWindow {
|
|
pub fn new() -> Rc<Self> {
|
|
let rc = Rc::new_cyclic(|self_weak| {
|
|
let window_ptr = self_weak.clone().into_raw();
|
|
let widget_ptr = cpp! {unsafe [window_ptr as "void*"] -> QWidgetPtr as "std::unique_ptr<QWidget, QWidgetDeleteLater>" {
|
|
ensure_initialized(true);
|
|
auto widget = std::unique_ptr<SlintWidget, QWidgetDeleteLater>(new SlintWidget, QWidgetDeleteLater());
|
|
|
|
auto accessibility = new Slint_accessible_window(widget.get(), window_ptr);
|
|
QAccessible::registerAccessibleInterface(accessibility);
|
|
|
|
return widget;
|
|
}};
|
|
|
|
QtWindow {
|
|
widget_ptr,
|
|
window: i_slint_core::api::Window::new(self_weak.clone() as _),
|
|
self_weak: self_weak.clone(),
|
|
rendering_metrics_collector: Default::default(),
|
|
cache: Default::default(),
|
|
tree_structure_changed: RefCell::new(false),
|
|
color_scheme: Default::default(),
|
|
}
|
|
});
|
|
let widget_ptr = rc.widget_ptr();
|
|
let rust_window = Rc::as_ptr(&rc);
|
|
cpp! {unsafe [widget_ptr as "SlintWidget*", rust_window as "void*"] {
|
|
widget_ptr->rust_window = rust_window;
|
|
}};
|
|
ALL_WINDOWS.with(|aw| aw.borrow_mut().push(rc.self_weak.clone()));
|
|
rc
|
|
}
|
|
|
|
/// Return the QWidget*
|
|
pub fn widget_ptr(&self) -> NonNull<()> {
|
|
unsafe { std::mem::transmute_copy::<QWidgetPtr, NonNull<_>>(&self.widget_ptr) }
|
|
}
|
|
|
|
fn paint_event(&self, painter: QPainterPtr) {
|
|
let runtime_window = WindowInner::from_pub(&self.window);
|
|
let window_adapter = runtime_window.window_adapter();
|
|
runtime_window.draw_contents(|components| {
|
|
i_slint_core::animations::update_animations();
|
|
let mut renderer = QtItemRenderer {
|
|
painter,
|
|
cache: &self.cache,
|
|
window: &self.window,
|
|
metrics: RenderingMetrics { layers_created: Some(0) },
|
|
};
|
|
|
|
for (component, origin) in components {
|
|
i_slint_core::item_rendering::render_component_items(
|
|
component,
|
|
&mut renderer,
|
|
*origin,
|
|
&window_adapter,
|
|
);
|
|
}
|
|
|
|
if let Some(collector) = &*self.rendering_metrics_collector.borrow() {
|
|
collector.measure_frame_rendered(&mut renderer);
|
|
}
|
|
|
|
if self.window.has_active_animations() {
|
|
self.request_redraw();
|
|
}
|
|
});
|
|
|
|
// Update the accessibility tree (if the component tree has changed)
|
|
if self.tree_structure_changed.replace(false) {
|
|
let widget_ptr = self.widget_ptr();
|
|
cpp! { unsafe [widget_ptr as "QWidget*"] {
|
|
auto accessible = dynamic_cast<Slint_accessible_window*>(QAccessible::queryAccessibleInterface(widget_ptr));
|
|
if (accessible->isUsed()) { accessible->updateAccessibilityTree(); }
|
|
}};
|
|
}
|
|
|
|
timer_event();
|
|
}
|
|
|
|
fn resize_event(&self, size: qttypes::QSize) {
|
|
self.window().dispatch_event(WindowEvent::Resized {
|
|
size: i_slint_core::api::LogicalSize::new(size.width as _, size.height as _),
|
|
});
|
|
}
|
|
|
|
fn mouse_event(&self, event: MouseEvent) {
|
|
WindowInner::from_pub(&self.window).process_mouse_input(event);
|
|
timer_event();
|
|
}
|
|
|
|
fn key_event(&self, key: i32, text: qttypes::QString, released: bool, repeat: bool) {
|
|
i_slint_core::animations::update_animations();
|
|
let text: String = text.into();
|
|
|
|
let text = qt_key_to_string(key as key_generated::Qt_Key, text);
|
|
|
|
let event = if released {
|
|
WindowEvent::KeyReleased { text }
|
|
} else if repeat {
|
|
WindowEvent::KeyPressRepeated { text }
|
|
} else {
|
|
WindowEvent::KeyPressed { text }
|
|
};
|
|
self.window.dispatch_event(event);
|
|
|
|
timer_event();
|
|
}
|
|
|
|
fn window_state_event(&self) {
|
|
let widget_ptr = self.widget_ptr();
|
|
|
|
// This function is called from the changeEvent slot which triggers whenever
|
|
// one of these properties changes. To prevent recursive call issues (e.g.,
|
|
// set_fullscreen -> update_window_properties -> changeEvent ->
|
|
// window_state_event -> set_fullscreen), we avoid resetting the internal state
|
|
// when it already matches the Qt state.
|
|
|
|
let minimized = cpp! { unsafe [widget_ptr as "QWidget*"] -> bool as "bool" {
|
|
return widget_ptr->isMinimized();
|
|
}};
|
|
|
|
if minimized != self.window().is_minimized() {
|
|
self.window().set_minimized(minimized);
|
|
}
|
|
|
|
let maximized = cpp! { unsafe [widget_ptr as "QWidget*"] -> bool as "bool" {
|
|
return widget_ptr->isMaximized();
|
|
}};
|
|
|
|
if maximized != self.window().is_maximized() {
|
|
self.window().set_maximized(maximized);
|
|
}
|
|
|
|
let fullscreen = cpp! { unsafe [widget_ptr as "QWidget*"] -> bool as "bool" {
|
|
return widget_ptr->isFullScreen();
|
|
}};
|
|
|
|
if fullscreen != self.window().is_fullscreen() {
|
|
self.window().set_fullscreen(fullscreen);
|
|
}
|
|
}
|
|
}
|
|
|
|
impl WindowAdapter for QtWindow {
|
|
fn window(&self) -> &i_slint_core::api::Window {
|
|
&self.window
|
|
}
|
|
|
|
fn renderer(&self) -> &dyn Renderer {
|
|
self
|
|
}
|
|
|
|
fn set_visible(&self, visible: bool) -> Result<(), PlatformError> {
|
|
if let Some(xdg_app_id) = WindowInner::from_pub(&self.window)
|
|
.xdg_app_id()
|
|
.map(|s| qttypes::QString::from(s.as_str()))
|
|
{
|
|
cpp! {unsafe [xdg_app_id as "QString"] {
|
|
QGuiApplication::setDesktopFileName(xdg_app_id);
|
|
}};
|
|
}
|
|
|
|
if visible {
|
|
let widget_ptr = self.widget_ptr();
|
|
cpp! {unsafe [widget_ptr as "QWidget*"] {
|
|
widget_ptr->show();
|
|
}};
|
|
let qt_platform_name = cpp! {unsafe [] -> qttypes::QString as "QString" {
|
|
return QGuiApplication::platformName();
|
|
}};
|
|
*self.rendering_metrics_collector.borrow_mut() = RenderingMetricsCollector::new(
|
|
&format!("Qt backend (platform {})", qt_platform_name),
|
|
);
|
|
Ok(())
|
|
} else {
|
|
self.rendering_metrics_collector.take();
|
|
let widget_ptr = self.widget_ptr();
|
|
cpp! {unsafe [widget_ptr as "QWidget*"] {
|
|
|
|
bool wasVisible = widget_ptr->isVisible();
|
|
|
|
widget_ptr->hide();
|
|
if (wasVisible) {
|
|
// Since we don't call close(), try to compute whether this was the last window and that
|
|
// we must end the application
|
|
auto windows = QGuiApplication::topLevelWindows();
|
|
bool visible_windows_left = std::any_of(windows.begin(), windows.end(), [](auto window) {
|
|
return window->isVisible() || window->transientParent();
|
|
});
|
|
g_lastWindowClosed = !visible_windows_left;
|
|
}
|
|
}};
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
fn position(&self) -> Option<i_slint_core::api::PhysicalPosition> {
|
|
let widget_ptr = self.widget_ptr();
|
|
let qp = cpp! {unsafe [widget_ptr as "QWidget*"] -> qttypes::QPoint as "QPoint" {
|
|
return widget_ptr->pos();
|
|
}};
|
|
// Qt returns logical coordinates, so scale those!
|
|
i_slint_core::api::LogicalPosition::new(qp.x as _, qp.y as _)
|
|
.to_physical(self.window().scale_factor())
|
|
.into()
|
|
}
|
|
|
|
fn set_position(&self, position: i_slint_core::api::WindowPosition) {
|
|
let physical_position = position.to_physical(self.window().scale_factor());
|
|
let widget_ptr = self.widget_ptr();
|
|
let pos = qttypes::QPoint { x: physical_position.x as _, y: physical_position.y as _ };
|
|
cpp! {unsafe [widget_ptr as "QWidget*", pos as "QPoint"] {
|
|
widget_ptr->move(pos);
|
|
}};
|
|
}
|
|
|
|
fn set_size(&self, size: i_slint_core::api::WindowSize) {
|
|
let logical_size = size.to_logical(self.window().scale_factor());
|
|
let widget_ptr = self.widget_ptr();
|
|
let sz: qttypes::QSize = into_qsize(logical_size);
|
|
|
|
// Qt uses logical units!
|
|
cpp! {unsafe [widget_ptr as "QWidget*", sz as "QSize"] {
|
|
widget_ptr->resize(sz);
|
|
}};
|
|
|
|
self.resize_event(sz);
|
|
}
|
|
|
|
fn size(&self) -> i_slint_core::api::PhysicalSize {
|
|
let widget_ptr = self.widget_ptr();
|
|
let s = cpp! {unsafe [widget_ptr as "QWidget*"] -> qttypes::QSize as "QSize" {
|
|
return widget_ptr->size();
|
|
}};
|
|
i_slint_core::api::PhysicalSize::new(s.width as _, s.height as _)
|
|
}
|
|
|
|
fn request_redraw(&self) {
|
|
let widget_ptr = self.widget_ptr();
|
|
cpp! {unsafe [widget_ptr as "QWidget*"] {
|
|
// If embedded as a QWidget, just use regular QWidget::update(), but if we're a top-level,
|
|
// then use requestUpdate() to achieve frame-throttling.
|
|
if (widget_ptr->parentWidget()) {
|
|
widget_ptr->update();
|
|
} else if (auto w = widget_ptr->window()->windowHandle()) {
|
|
w->requestUpdate();
|
|
}
|
|
}}
|
|
}
|
|
|
|
/// Apply windows property such as title to the QWidget*
|
|
fn update_window_properties(&self, properties: i_slint_core::window::WindowProperties<'_>) {
|
|
let widget_ptr = self.widget_ptr();
|
|
let title: qttypes::QString = properties.title().as_str().into();
|
|
let Some(window_item) = WindowInner::from_pub(&self.window).window_item() else { return };
|
|
let window_item = window_item.as_pin_ref();
|
|
let no_frame = window_item.no_frame();
|
|
let always_on_top = window_item.always_on_top();
|
|
let mut size = qttypes::QSize {
|
|
width: window_item.width().get().ceil() as _,
|
|
height: window_item.height().get().ceil() as _,
|
|
};
|
|
|
|
if size.width == 0 || size.height == 0 {
|
|
let existing_size = cpp!(unsafe [widget_ptr as "QWidget*"] -> qttypes::QSize as "QSize" {
|
|
return widget_ptr->size();
|
|
});
|
|
if size.width == 0 {
|
|
window_item.width.set(LogicalLength::new(existing_size.width as _));
|
|
size.width = existing_size.width;
|
|
}
|
|
if size.height == 0 {
|
|
window_item.height.set(LogicalLength::new(existing_size.height as _));
|
|
size.height = existing_size.height;
|
|
}
|
|
}
|
|
|
|
let background =
|
|
into_qbrush(properties.background(), size.width.into(), size.height.into());
|
|
|
|
match (&window_item.icon()).into() {
|
|
&ImageInner::None => (),
|
|
r => {
|
|
if let Some(pixmap) = image_to_pixmap(r, None) {
|
|
cpp! {unsafe [widget_ptr as "QWidget*", pixmap as "QPixmap"] {
|
|
widget_ptr->setWindowIcon(QIcon(pixmap));
|
|
}};
|
|
}
|
|
}
|
|
};
|
|
|
|
let fullscreen: bool = properties.is_fullscreen();
|
|
let minimized: bool = properties.is_minimized();
|
|
let maximized: bool = properties.is_maximized();
|
|
|
|
cpp! {unsafe [widget_ptr as "QWidget*", title as "QString", size as "QSize", background as "QBrush", no_frame as "bool", always_on_top as "bool",
|
|
fullscreen as "bool", minimized as "bool", maximized as "bool"] {
|
|
|
|
if (size != widget_ptr->size()) {
|
|
widget_ptr->resize(size.expandedTo({1, 1}));
|
|
}
|
|
|
|
widget_ptr->setWindowFlag(Qt::FramelessWindowHint, no_frame);
|
|
widget_ptr->setWindowFlag(Qt::WindowStaysOnTopHint, always_on_top);
|
|
|
|
{
|
|
// Depending on the request, we either set or clear the bits.
|
|
// See also: https://doc.qt.io/qt-6/qt.html#WindowState-enum
|
|
auto state = widget_ptr->windowState();
|
|
|
|
if (fullscreen != widget_ptr->isFullScreen()) {
|
|
state = state ^ Qt::WindowFullScreen;
|
|
}
|
|
if (minimized != widget_ptr->isMinimized()) {
|
|
state = state ^ Qt::WindowMinimized;
|
|
}
|
|
if (maximized != widget_ptr->isMaximized()) {
|
|
state = state ^ Qt::WindowMaximized;
|
|
}
|
|
|
|
widget_ptr->setWindowState(state);
|
|
}
|
|
|
|
widget_ptr->setWindowTitle(title);
|
|
auto pal = widget_ptr->palette();
|
|
|
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
|
// If the background color is the same as what NativeStyleMetrics supplied from QGuiApplication::palette().color(QPalette::Window),
|
|
// then the setColor (implicitly setBrush) call will not detach the palette. However it will set the resolveMask, which due to the
|
|
// lack of a detach changes QGuiApplicationPrivate::app_pal's resolve mask and thus breaks future theme based palette changes.
|
|
// Therefore we force a detach.
|
|
// https://bugreports.qt.io/browse/QTBUG-98762
|
|
{
|
|
pal.setResolveMask(~pal.resolveMask());
|
|
pal.setResolveMask(~pal.resolveMask());
|
|
}
|
|
#endif
|
|
pal.setBrush(QPalette::Window, background);
|
|
widget_ptr->setPalette(pal);
|
|
}};
|
|
|
|
let constraints = properties.layout_constraints();
|
|
|
|
let min_size: qttypes::QSize = constraints.min.map_or_else(
|
|
|| qttypes::QSize { width: 0, height: 0 }, // (0x0) means unset min size for QWidget
|
|
into_qsize,
|
|
);
|
|
|
|
const WIDGET_SIZE_MAX: u32 = 16_777_215;
|
|
|
|
let max_size: qttypes::QSize = constraints.max.map_or_else(
|
|
|| qttypes::QSize { width: WIDGET_SIZE_MAX, height: WIDGET_SIZE_MAX },
|
|
into_qsize,
|
|
);
|
|
|
|
cpp! {unsafe [widget_ptr as "QWidget*", min_size as "QSize", max_size as "QSize"] {
|
|
widget_ptr->setMinimumSize(min_size);
|
|
widget_ptr->setMaximumSize(max_size);
|
|
}};
|
|
}
|
|
|
|
fn internal(&self, _: i_slint_core::InternalToken) -> Option<&dyn WindowAdapterInternal> {
|
|
Some(self)
|
|
}
|
|
}
|
|
|
|
fn into_qsize(logical_size: i_slint_core::api::LogicalSize) -> qttypes::QSize {
|
|
qttypes::QSize {
|
|
width: logical_size.width.round() as _,
|
|
height: logical_size.height.round() as _,
|
|
}
|
|
}
|
|
|
|
impl WindowAdapterInternal for QtWindow {
|
|
fn register_item_tree(&self) {
|
|
self.tree_structure_changed.replace(true);
|
|
}
|
|
|
|
fn unregister_item_tree(
|
|
&self,
|
|
_component: ItemTreeRef,
|
|
_: &mut dyn Iterator<Item = Pin<ItemRef<'_>>>,
|
|
) {
|
|
self.tree_structure_changed.replace(true);
|
|
}
|
|
|
|
fn create_popup(&self, geometry: LogicalRect) -> Option<Rc<dyn WindowAdapter>> {
|
|
let popup_window = QtWindow::new();
|
|
|
|
let size = qttypes::QSize { width: geometry.width() as _, height: geometry.height() as _ };
|
|
|
|
let popup_ptr = popup_window.widget_ptr();
|
|
let pos = qttypes::QPoint { x: geometry.origin.x as _, y: geometry.origin.y as _ };
|
|
let widget_ptr = self.widget_ptr();
|
|
cpp! {unsafe [widget_ptr as "QWidget*", popup_ptr as "QWidget*", pos as "QPoint", size as "QSize"] {
|
|
popup_ptr->setParent(widget_ptr, Qt::Popup);
|
|
popup_ptr->setGeometry(QRect(pos + widget_ptr->mapToGlobal(QPoint(0,0)), size));
|
|
popup_ptr->show();
|
|
}};
|
|
Some(popup_window as _)
|
|
}
|
|
|
|
fn set_mouse_cursor(&self, cursor: MouseCursor) {
|
|
let widget_ptr = self.widget_ptr();
|
|
//unidirectional resize cursors are replaced with bidirectional ones
|
|
let cursor_shape = match cursor {
|
|
MouseCursor::Default => key_generated::Qt_CursorShape_ArrowCursor,
|
|
MouseCursor::None => key_generated::Qt_CursorShape_BlankCursor,
|
|
MouseCursor::Help => key_generated::Qt_CursorShape_WhatsThisCursor,
|
|
MouseCursor::Pointer => key_generated::Qt_CursorShape_PointingHandCursor,
|
|
MouseCursor::Progress => key_generated::Qt_CursorShape_BusyCursor,
|
|
MouseCursor::Wait => key_generated::Qt_CursorShape_WaitCursor,
|
|
MouseCursor::Crosshair => key_generated::Qt_CursorShape_CrossCursor,
|
|
MouseCursor::Text => key_generated::Qt_CursorShape_IBeamCursor,
|
|
MouseCursor::Alias => key_generated::Qt_CursorShape_DragLinkCursor,
|
|
MouseCursor::Copy => key_generated::Qt_CursorShape_DragCopyCursor,
|
|
MouseCursor::Move => key_generated::Qt_CursorShape_DragMoveCursor,
|
|
MouseCursor::NoDrop => key_generated::Qt_CursorShape_ForbiddenCursor,
|
|
MouseCursor::NotAllowed => key_generated::Qt_CursorShape_ForbiddenCursor,
|
|
MouseCursor::Grab => key_generated::Qt_CursorShape_OpenHandCursor,
|
|
MouseCursor::Grabbing => key_generated::Qt_CursorShape_ClosedHandCursor,
|
|
MouseCursor::ColResize => key_generated::Qt_CursorShape_SplitHCursor,
|
|
MouseCursor::RowResize => key_generated::Qt_CursorShape_SplitVCursor,
|
|
MouseCursor::NResize => key_generated::Qt_CursorShape_SizeVerCursor,
|
|
MouseCursor::EResize => key_generated::Qt_CursorShape_SizeHorCursor,
|
|
MouseCursor::SResize => key_generated::Qt_CursorShape_SizeVerCursor,
|
|
MouseCursor::WResize => key_generated::Qt_CursorShape_SizeHorCursor,
|
|
MouseCursor::NeResize => key_generated::Qt_CursorShape_SizeBDiagCursor,
|
|
MouseCursor::NwResize => key_generated::Qt_CursorShape_SizeFDiagCursor,
|
|
MouseCursor::SeResize => key_generated::Qt_CursorShape_SizeFDiagCursor,
|
|
MouseCursor::SwResize => key_generated::Qt_CursorShape_SizeBDiagCursor,
|
|
MouseCursor::EwResize => key_generated::Qt_CursorShape_SizeHorCursor,
|
|
MouseCursor::NsResize => key_generated::Qt_CursorShape_SizeVerCursor,
|
|
MouseCursor::NeswResize => key_generated::Qt_CursorShape_SizeBDiagCursor,
|
|
MouseCursor::NwseResize => key_generated::Qt_CursorShape_SizeFDiagCursor,
|
|
};
|
|
cpp! {unsafe [widget_ptr as "QWidget*", cursor_shape as "Qt::CursorShape"] {
|
|
widget_ptr->setCursor(QCursor{cursor_shape});
|
|
}};
|
|
}
|
|
|
|
fn input_method_request(&self, request: i_slint_core::window::InputMethodRequest) {
|
|
let widget_ptr = self.widget_ptr();
|
|
let props = match request {
|
|
i_slint_core::window::InputMethodRequest::Enable(props) => {
|
|
cpp! {unsafe [widget_ptr as "QWidget*"] {
|
|
widget_ptr->setAttribute(Qt::WA_InputMethodEnabled, true);
|
|
}};
|
|
props
|
|
}
|
|
i_slint_core::window::InputMethodRequest::Disable => {
|
|
cpp! {unsafe [widget_ptr as "SlintWidget*"] {
|
|
widget_ptr->ime_text = "";
|
|
widget_ptr->ime_cursor = 0;
|
|
widget_ptr->ime_anchor = 0;
|
|
widget_ptr->setAttribute(Qt::WA_InputMethodEnabled, false);
|
|
}};
|
|
return;
|
|
}
|
|
i_slint_core::window::InputMethodRequest::Update(props) => props,
|
|
_ => return,
|
|
};
|
|
|
|
let rect = qttypes::QRectF {
|
|
x: props.cursor_rect_origin.x as _,
|
|
y: props.cursor_rect_origin.y as _,
|
|
width: props.cursor_rect_size.width as _,
|
|
height: props.cursor_rect_size.height as _,
|
|
};
|
|
let cursor: i32 = props.text[..props.cursor_position].encode_utf16().count() as _;
|
|
let anchor: i32 =
|
|
props.anchor_position.map_or(cursor, |a| props.text[..a].encode_utf16().count() as _);
|
|
let text: qttypes::QString = props.text.as_str().into();
|
|
cpp! {unsafe [widget_ptr as "SlintWidget*", rect as "QRectF", cursor as "int", anchor as "int", text as "QString"] {
|
|
widget_ptr->ime_position = rect.toRect();
|
|
widget_ptr->ime_text = text;
|
|
widget_ptr->ime_cursor = cursor;
|
|
widget_ptr->ime_anchor = anchor;
|
|
QGuiApplication::inputMethod()->update(Qt::ImQueryInput);
|
|
}};
|
|
}
|
|
|
|
fn as_any(&self) -> &dyn std::any::Any {
|
|
self
|
|
}
|
|
|
|
fn handle_focus_change(&self, _old: Option<ItemRc>, new: Option<ItemRc>) {
|
|
let widget_ptr = self.widget_ptr();
|
|
if let Some(ai) = accessible_item(new) {
|
|
let item = &ai;
|
|
cpp! {unsafe [widget_ptr as "QWidget*", item as "void*"] {
|
|
auto accessible = QAccessible::queryAccessibleInterface(widget_ptr);
|
|
if (auto slint_accessible = dynamic_cast<Slint_accessible*>(accessible)) {
|
|
slint_accessible->clearFocus();
|
|
slint_accessible->focusItem(item);
|
|
}
|
|
}};
|
|
}
|
|
}
|
|
|
|
fn color_scheme(&self) -> ColorScheme {
|
|
let ds = self.color_scheme.get_or_init(|| {
|
|
Box::pin(Property::new(
|
|
if cpp! {unsafe [] -> bool as "bool" {
|
|
return qApp->palette().color(QPalette::Window).valueF() < 0.5;
|
|
}} {
|
|
ColorScheme::Dark
|
|
} else {
|
|
ColorScheme::Light
|
|
},
|
|
))
|
|
});
|
|
ds.as_ref().get()
|
|
}
|
|
|
|
fn bring_to_front(&self) -> Result<(), i_slint_core::platform::PlatformError> {
|
|
let widget_ptr = self.widget_ptr();
|
|
cpp! {unsafe [widget_ptr as "QWidget*"] {
|
|
widget_ptr->raise();
|
|
widget_ptr->activateWindow();
|
|
}};
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl i_slint_core::renderer::RendererSealed for QtWindow {
|
|
fn text_size(
|
|
&self,
|
|
font_request: FontRequest,
|
|
text: &str,
|
|
max_width: Option<LogicalLength>,
|
|
scale_factor: ScaleFactor,
|
|
text_wrap: TextWrap,
|
|
) -> LogicalSize {
|
|
sharedparley::text_size(font_request, text, max_width, scale_factor, text_wrap)
|
|
}
|
|
|
|
fn font_metrics(
|
|
&self,
|
|
font_request: i_slint_core::graphics::FontRequest,
|
|
_scale_factor: ScaleFactor,
|
|
) -> i_slint_core::items::FontMetrics {
|
|
sharedparley::font_metrics(font_request)
|
|
}
|
|
|
|
fn text_input_byte_offset_for_position(
|
|
&self,
|
|
text_input: Pin<&i_slint_core::items::TextInput>,
|
|
pos: LogicalPoint,
|
|
font_request: FontRequest,
|
|
scale_factor: ScaleFactor,
|
|
) -> usize {
|
|
sharedparley::text_input_byte_offset_for_position(
|
|
text_input,
|
|
pos,
|
|
font_request,
|
|
scale_factor,
|
|
)
|
|
}
|
|
|
|
fn text_input_cursor_rect_for_byte_offset(
|
|
&self,
|
|
text_input: Pin<&i_slint_core::items::TextInput>,
|
|
byte_offset: usize,
|
|
font_request: FontRequest,
|
|
scale_factor: ScaleFactor,
|
|
) -> LogicalRect {
|
|
sharedparley::text_input_cursor_rect_for_byte_offset(
|
|
text_input,
|
|
byte_offset,
|
|
font_request,
|
|
scale_factor,
|
|
)
|
|
}
|
|
|
|
fn register_font_from_memory(
|
|
&self,
|
|
data: &'static [u8],
|
|
) -> Result<(), Box<dyn std::error::Error>> {
|
|
sharedfontique::get_collection().register_fonts(data.to_vec().into(), None);
|
|
Ok(())
|
|
}
|
|
|
|
fn register_font_from_path(
|
|
&self,
|
|
path: &std::path::Path,
|
|
) -> Result<(), Box<dyn std::error::Error>> {
|
|
let requested_path = path.canonicalize().unwrap_or_else(|_| path.into());
|
|
let contents = std::fs::read(requested_path)?;
|
|
sharedfontique::get_collection().register_fonts(contents.into(), None);
|
|
Ok(())
|
|
}
|
|
|
|
fn default_font_size(&self) -> LogicalLength {
|
|
let default_font_size = cpp!(unsafe[] -> i32 as "int" {
|
|
return QFontInfo(qApp->font()).pixelSize();
|
|
});
|
|
// Ideally this would return the value from another property with a binding that's updated
|
|
// as a FontChange event is received. This is relevant for the case of using the Qt backend
|
|
// with a non-native style.
|
|
LogicalLength::new(default_font_size as f32)
|
|
}
|
|
|
|
fn free_graphics_resources(
|
|
&self,
|
|
component: ItemTreeRef,
|
|
_items: &mut dyn Iterator<Item = Pin<i_slint_core::items::ItemRef<'_>>>,
|
|
) -> Result<(), i_slint_core::platform::PlatformError> {
|
|
// Invalidate caches:
|
|
self.cache.component_destroyed(component);
|
|
Ok(())
|
|
}
|
|
|
|
fn set_window_adapter(&self, _window_adapter: &Rc<dyn WindowAdapter>) {
|
|
// No-op because QtWindow is also the WindowAdapter
|
|
}
|
|
|
|
fn take_snapshot(&self) -> Result<SharedPixelBuffer<Rgba8Pixel>, PlatformError> {
|
|
let widget_ptr = self.widget_ptr();
|
|
|
|
let size = cpp! {unsafe [widget_ptr as "QWidget*"] -> qttypes::QSize as "QSize" {
|
|
return widget_ptr->size();
|
|
}};
|
|
|
|
let rgba8_data = cpp! {unsafe [widget_ptr as "QWidget*"] -> qttypes::QByteArray as "QByteArray" {
|
|
QPixmap pixmap = widget_ptr->grab();
|
|
QImage image = pixmap.toImage();
|
|
image.convertTo(QImage::Format_ARGB32);
|
|
return QByteArray(reinterpret_cast<const char *>(image.constBits()), image.sizeInBytes());
|
|
}};
|
|
|
|
let buffer = i_slint_core::graphics::SharedPixelBuffer::<i_slint_core::graphics::Rgba8Pixel>::clone_from_slice(
|
|
rgba8_data.to_slice(),
|
|
size.width,
|
|
size.height,
|
|
);
|
|
Ok(buffer)
|
|
}
|
|
|
|
fn supports_transformations(&self) -> bool {
|
|
true
|
|
}
|
|
}
|
|
|
|
fn accessible_item(item: Option<ItemRc>) -> Option<ItemRc> {
|
|
let mut current = item;
|
|
while let Some(c) = current {
|
|
if c.is_accessible() {
|
|
return Some(c);
|
|
} else {
|
|
current = c.parent_item(ParentItemTraversalMode::StopAtPopups);
|
|
}
|
|
}
|
|
None
|
|
}
|
|
|
|
thread_local! {
|
|
// FIXME: currently the window are never removed
|
|
static ALL_WINDOWS: RefCell<Vec<Weak<QtWindow>>> = Default::default();
|
|
}
|
|
|
|
/// Called by C++'s TimerHandler::timerEvent, or every time a timer might have been started
|
|
pub(crate) fn timer_event() {
|
|
i_slint_core::platform::update_timers_and_animations();
|
|
restart_timer();
|
|
}
|
|
|
|
pub(crate) fn restart_timer() {
|
|
let timeout = i_slint_core::timers::TimerList::next_timeout().map(|instant| {
|
|
let now = std::time::Instant::now();
|
|
let instant: std::time::Instant = instant.into();
|
|
if instant > now {
|
|
instant.duration_since(now).as_millis() as i32
|
|
} else {
|
|
0
|
|
}
|
|
});
|
|
if let Some(timeout) = timeout {
|
|
cpp! { unsafe [timeout as "int"] {
|
|
ensure_initialized(true);
|
|
TimerHandler::instance().timer.start(timeout, &TimerHandler::instance());
|
|
}}
|
|
}
|
|
}
|
|
|
|
mod key_codes {
|
|
macro_rules! define_qt_key_to_string_fn {
|
|
($($char:literal # $name:ident # $($qt:ident)|* # $($winit:ident $(($_pos:ident))?)|* # $($_xkb:ident)|*;)*) => {
|
|
use crate::key_generated;
|
|
pub fn qt_key_to_string(key: key_generated::Qt_Key) -> Option<i_slint_core::SharedString> {
|
|
|
|
let char = match(key) {
|
|
$($(key_generated::$qt => $char,)*)*
|
|
_ => return None,
|
|
};
|
|
Some(char.into())
|
|
}
|
|
};
|
|
}
|
|
|
|
i_slint_common::for_each_special_keys!(define_qt_key_to_string_fn);
|
|
}
|
|
|
|
fn qt_key_to_string(key: key_generated::Qt_Key, event_text: String) -> SharedString {
|
|
// First try to see if we received one of the non-ascii keys that we have
|
|
// a special representation for. If that fails, try to use the provided
|
|
// text. If that's empty, then try to see if the provided key has an ascii
|
|
// representation. The last step is needed because modifiers may result in
|
|
// the text to be empty otherwise, for example Ctrl+C.
|
|
if let Some(special_key_code) = key_codes::qt_key_to_string(key) {
|
|
return special_key_code;
|
|
};
|
|
|
|
// On Windows, X11 and Wayland, Ctrl+C for example sends a terminal control character,
|
|
// which we choose not to supply to the application. Instead we fall through to translating
|
|
// the supplied key code.
|
|
if !event_text.is_empty() && !event_text.chars().any(|ch| ch.is_control()) {
|
|
return event_text.into();
|
|
}
|
|
|
|
match key {
|
|
key_generated::Qt_Key_Key_Space => " ",
|
|
key_generated::Qt_Key_Key_Exclam => "!",
|
|
key_generated::Qt_Key_Key_QuoteDbl => "\"",
|
|
key_generated::Qt_Key_Key_NumberSign => "#",
|
|
key_generated::Qt_Key_Key_Dollar => "$",
|
|
key_generated::Qt_Key_Key_Percent => "%",
|
|
key_generated::Qt_Key_Key_Ampersand => "&",
|
|
key_generated::Qt_Key_Key_Apostrophe => "'",
|
|
key_generated::Qt_Key_Key_ParenLeft => "(",
|
|
key_generated::Qt_Key_Key_ParenRight => ")",
|
|
key_generated::Qt_Key_Key_Asterisk => "*",
|
|
key_generated::Qt_Key_Key_Plus => "+",
|
|
key_generated::Qt_Key_Key_Comma => ",",
|
|
key_generated::Qt_Key_Key_Minus => "-",
|
|
key_generated::Qt_Key_Key_Period => ".",
|
|
key_generated::Qt_Key_Key_Slash => "/",
|
|
key_generated::Qt_Key_Key_0 => "0",
|
|
key_generated::Qt_Key_Key_1 => "1",
|
|
key_generated::Qt_Key_Key_2 => "2",
|
|
key_generated::Qt_Key_Key_3 => "3",
|
|
key_generated::Qt_Key_Key_4 => "4",
|
|
key_generated::Qt_Key_Key_5 => "5",
|
|
key_generated::Qt_Key_Key_6 => "6",
|
|
key_generated::Qt_Key_Key_7 => "7",
|
|
key_generated::Qt_Key_Key_8 => "8",
|
|
key_generated::Qt_Key_Key_9 => "9",
|
|
key_generated::Qt_Key_Key_Colon => ":",
|
|
key_generated::Qt_Key_Key_Semicolon => ";",
|
|
key_generated::Qt_Key_Key_Less => "<",
|
|
key_generated::Qt_Key_Key_Equal => "=",
|
|
key_generated::Qt_Key_Key_Greater => ">",
|
|
key_generated::Qt_Key_Key_Question => "?",
|
|
key_generated::Qt_Key_Key_At => "@",
|
|
key_generated::Qt_Key_Key_A => "a",
|
|
key_generated::Qt_Key_Key_B => "b",
|
|
key_generated::Qt_Key_Key_C => "c",
|
|
key_generated::Qt_Key_Key_D => "d",
|
|
key_generated::Qt_Key_Key_E => "e",
|
|
key_generated::Qt_Key_Key_F => "f",
|
|
key_generated::Qt_Key_Key_G => "g",
|
|
key_generated::Qt_Key_Key_H => "h",
|
|
key_generated::Qt_Key_Key_I => "i",
|
|
key_generated::Qt_Key_Key_J => "j",
|
|
key_generated::Qt_Key_Key_K => "k",
|
|
key_generated::Qt_Key_Key_L => "l",
|
|
key_generated::Qt_Key_Key_M => "m",
|
|
key_generated::Qt_Key_Key_N => "n",
|
|
key_generated::Qt_Key_Key_O => "o",
|
|
key_generated::Qt_Key_Key_P => "p",
|
|
key_generated::Qt_Key_Key_Q => "q",
|
|
key_generated::Qt_Key_Key_R => "r",
|
|
key_generated::Qt_Key_Key_S => "s",
|
|
key_generated::Qt_Key_Key_T => "t",
|
|
key_generated::Qt_Key_Key_U => "u",
|
|
key_generated::Qt_Key_Key_V => "v",
|
|
key_generated::Qt_Key_Key_W => "w",
|
|
key_generated::Qt_Key_Key_X => "x",
|
|
key_generated::Qt_Key_Key_Y => "y",
|
|
key_generated::Qt_Key_Key_Z => "z",
|
|
key_generated::Qt_Key_Key_BracketLeft => "[",
|
|
key_generated::Qt_Key_Key_Backslash => "\\",
|
|
key_generated::Qt_Key_Key_BracketRight => "]",
|
|
key_generated::Qt_Key_Key_AsciiCircum => "^",
|
|
key_generated::Qt_Key_Key_Underscore => "_",
|
|
key_generated::Qt_Key_Key_QuoteLeft => "`",
|
|
key_generated::Qt_Key_Key_BraceLeft => "{",
|
|
key_generated::Qt_Key_Key_Bar => "|",
|
|
key_generated::Qt_Key_Key_BraceRight => "}",
|
|
key_generated::Qt_Key_Key_AsciiTilde => "~",
|
|
_ => "",
|
|
}
|
|
.into()
|
|
}
|
|
|
|
pub(crate) mod ffi {
|
|
use std::ffi::c_void;
|
|
|
|
use super::QtWindow;
|
|
|
|
#[unsafe(no_mangle)]
|
|
pub extern "C" fn slint_qt_get_widget(
|
|
window_adapter: &i_slint_core::window::WindowAdapterRc,
|
|
) -> *mut c_void {
|
|
window_adapter
|
|
.internal(i_slint_core::InternalToken)
|
|
.and_then(|wa| <dyn std::any::Any>::downcast_ref(wa.as_any()))
|
|
.map_or(std::ptr::null_mut(), |win: &QtWindow| {
|
|
win.widget_ptr().cast::<c_void>().as_ptr()
|
|
})
|
|
}
|
|
}
|
|
|
|
fn qt_password_character() -> char {
|
|
char::from_u32(cpp! { unsafe [] -> i32 as "int" {
|
|
return qApp->style()->styleHint(QStyle::SH_LineEdit_PasswordCharacter, nullptr, nullptr);
|
|
}} as u32)
|
|
.unwrap_or('●')
|
|
}
|