mirror of
https://github.com/slint-ui/slint.git
synced 2025-09-30 22:01:13 +00:00
1840 lines
66 KiB
Rust
1840 lines
66 KiB
Rust
/* LICENSE BEGIN
|
|
This file is part of the SixtyFPS Project -- https://sixtyfps.io
|
|
Copyright (c) 2020 Olivier Goffart <olivier.goffart@sixtyfps.io>
|
|
Copyright (c) 2020 Simon Hausmann <simon.hausmann@sixtyfps.io>
|
|
|
|
SPDX-License-Identifier: GPL-3.0-only
|
|
This file is also available under commercial licensing terms.
|
|
Please contact info@sixtyfps.io for more information.
|
|
LICENSE END */
|
|
|
|
/*!
|
|
|
|
This module contains all the native Qt widgetimplementation that forwards to QStyle.
|
|
|
|
Same as in sixtyfps_corelib::items, when When adding an item or a property,
|
|
it needs to be kept in sync with different place.
|
|
|
|
- It needs to be changed in this module
|
|
- the Widget list in lib.rs
|
|
- In the compiler: builtins.60
|
|
- For the C++ code (new item only): the build.rs to export the new item, and the `using` declaration in sixtyfps.h
|
|
- Don't forget to update the documentation
|
|
*/
|
|
|
|
#![allow(non_upper_case_globals)]
|
|
|
|
use const_field_offset::FieldOffsets;
|
|
use core::pin::Pin;
|
|
use cpp::cpp;
|
|
use sixtyfps_corelib::graphics::{Color, Rect, Size};
|
|
use sixtyfps_corelib::input::{
|
|
FocusEvent, InputEventFilterResult, InputEventResult, KeyEvent, KeyEventResult, MouseEvent,
|
|
MouseEventType,
|
|
};
|
|
use sixtyfps_corelib::item_rendering::{CachedRenderingData, ItemRenderer};
|
|
use sixtyfps_corelib::items::{Item, ItemConsts, ItemRc, ItemVTable, VoidArg};
|
|
use sixtyfps_corelib::layout::LayoutInfo;
|
|
use sixtyfps_corelib::rtti::*;
|
|
use sixtyfps_corelib::window::ComponentWindow;
|
|
use sixtyfps_corelib::{Callback, ItemVTable_static, Property, SharedString, SharedVector};
|
|
use sixtyfps_corelib_macros::*;
|
|
use std::rc::Rc;
|
|
|
|
type ItemRendererRef<'a> = &'a mut dyn ItemRenderer;
|
|
|
|
use crate::qt_window::QPainter;
|
|
|
|
/// Helper macro to get the size from the width and height property,
|
|
/// and return Default::default in case the size is too small
|
|
macro_rules! get_size {
|
|
($self:ident) => {{
|
|
let width = $self.width();
|
|
let height = $self.height();
|
|
if width < 1. || height < 1. {
|
|
return Default::default();
|
|
};
|
|
qttypes::QSize { width: width as _, height: height as _ }
|
|
}};
|
|
}
|
|
|
|
macro_rules! fn_render {
|
|
($this:ident $dpr:ident $size:ident $painter:ident => $($tt:tt)*) => {
|
|
fn render(self: Pin<&Self>, backend: &mut &mut dyn ItemRenderer) {
|
|
let $dpr: f32 = backend.scale_factor();
|
|
if let Some(painter) = std::any::Any::downcast_mut::<QPainter>(backend.as_any()) {
|
|
let $size: qttypes::QSize = get_size!(self);
|
|
let $this = self;
|
|
painter.save_state();
|
|
let $painter = painter;
|
|
$($tt)*
|
|
$painter.restore_state();
|
|
} else {
|
|
// Fallback: this happen when the Qt backend is not used and the gl backend is used instead
|
|
backend.draw_cached_pixmap(
|
|
&self.cached_rendering_data,
|
|
&mut |callback| {
|
|
let $size: qttypes::QSize = get_size!(self);
|
|
let mut imgarray = QImageWrapArray::new($size, $dpr);
|
|
let img = &mut imgarray.img;
|
|
let mut painter_ = cpp!(unsafe [img as "QImage*"] -> QPainter as "QPainter" { return QPainter(img); });
|
|
let $painter = &mut painter_;
|
|
let $this = self;
|
|
$($tt)*
|
|
drop(painter_);
|
|
imgarray.draw(callback);
|
|
},
|
|
);
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
struct QImageWrapArray {
|
|
/// The image reference the array, so the array must outlive the image without being detached or accessed
|
|
img: qttypes::QImage,
|
|
array: SharedVector<u8>,
|
|
}
|
|
|
|
impl QImageWrapArray {
|
|
pub fn new(size: qttypes::QSize, dpr: f32) -> Self {
|
|
let mut array = SharedVector::default();
|
|
array.resize((size.width * size.height * 4) as usize, 0);
|
|
let array_ptr = array.as_slice_mut().as_mut_ptr();
|
|
let img = cpp!(unsafe [size as "QSize", array_ptr as "uchar*", dpr as "float"] -> qttypes::QImage as "QImage" {
|
|
QImage img(array_ptr, size.width(), size.height(), size.width() * 4, QImage::Format_ARGB32_Premultiplied);
|
|
img.setDevicePixelRatio(dpr);
|
|
return img;
|
|
});
|
|
QImageWrapArray { img, array }
|
|
}
|
|
|
|
pub fn draw(&self, callback: &mut dyn FnMut(u32, u32, &[u8])) {
|
|
let size = self.img.size();
|
|
callback(size.width, size.height, self.array.as_slice());
|
|
}
|
|
}
|
|
|
|
cpp! {{
|
|
#include <QtWidgets/QApplication>
|
|
#include <QtWidgets/QStyle>
|
|
#include <QtWidgets/QStyleOption>
|
|
#include <QtWidgets/QStyleFactory>
|
|
#include <QtGui/QPainter>
|
|
#include <QtGui/QClipboard>
|
|
#include <QtCore/QMimeData>
|
|
#include <QtCore/QDebug>
|
|
#include <QtCore/QScopeGuard>
|
|
|
|
void ensure_initialized()
|
|
{
|
|
static auto app [[maybe_unused]] = []{
|
|
if (qApp) {
|
|
return qApp;
|
|
}
|
|
QCoreApplication::setAttribute(Qt::AA_PluginApplication, true);
|
|
static int argc = 1;
|
|
static char argv[] = "sixtyfps";
|
|
static char *argv2[] = { argv };
|
|
// Leak the QApplication, otherwise it crashes on exit
|
|
// (because the QGuiApplication destructor access some Q_GLOBAL_STATIC which are already gone)
|
|
return new QApplication(argc, argv2);
|
|
}();
|
|
}
|
|
}}
|
|
|
|
#[repr(C)]
|
|
#[derive(FieldOffsets, Default, SixtyFPSElement)]
|
|
#[pin]
|
|
pub struct NativeButton {
|
|
pub x: Property<f32>,
|
|
pub y: Property<f32>,
|
|
pub width: Property<f32>,
|
|
pub height: Property<f32>,
|
|
pub text: Property<SharedString>,
|
|
pub enabled: Property<bool>,
|
|
pub pressed: Property<bool>,
|
|
pub clicked: Callback<VoidArg>,
|
|
pub cached_rendering_data: CachedRenderingData,
|
|
}
|
|
|
|
impl Item for NativeButton {
|
|
fn init(self: Pin<&Self>, _window: &ComponentWindow) {}
|
|
|
|
fn geometry(self: Pin<&Self>) -> Rect {
|
|
euclid::rect(self.x(), self.y(), self.width(), self.height())
|
|
}
|
|
|
|
fn layouting_info(self: Pin<&Self>, window: &ComponentWindow) -> LayoutInfo {
|
|
let text: qttypes::QString = self.text().as_str().into();
|
|
let dpr = window.scale_factor();
|
|
let size = cpp!(unsafe [
|
|
text as "QString",
|
|
dpr as "float"
|
|
] -> qttypes::QSize as "QSize" {
|
|
ensure_initialized();
|
|
QStyleOptionButton option;
|
|
option.rect = option.fontMetrics.boundingRect(text);
|
|
option.text = std::move(text);
|
|
return qApp->style()->sizeFromContents(QStyle::CT_PushButton, &option, option.rect.size(), nullptr) * dpr;
|
|
});
|
|
LayoutInfo {
|
|
min_width: size.width as f32,
|
|
min_height: size.height as f32,
|
|
..LayoutInfo::default()
|
|
}
|
|
}
|
|
|
|
fn implicit_size(self: Pin<&Self>, _window: &ComponentWindow) -> Size {
|
|
Default::default()
|
|
}
|
|
|
|
fn input_event_filter_before_children(
|
|
self: Pin<&Self>,
|
|
_: MouseEvent,
|
|
_window: &ComponentWindow,
|
|
_self_rc: &ItemRc,
|
|
) -> InputEventFilterResult {
|
|
InputEventFilterResult::ForwardEvent
|
|
}
|
|
|
|
fn input_event(
|
|
self: Pin<&Self>,
|
|
event: MouseEvent,
|
|
_window: &ComponentWindow,
|
|
_self_rc: &sixtyfps_corelib::items::ItemRc,
|
|
) -> InputEventResult {
|
|
let enabled = self.enabled();
|
|
if !enabled {
|
|
return InputEventResult::EventIgnored;
|
|
}
|
|
|
|
Self::FIELD_OFFSETS.pressed.apply_pin(self).set(match event.what {
|
|
MouseEventType::MousePressed => true,
|
|
MouseEventType::MouseExit | MouseEventType::MouseReleased => false,
|
|
MouseEventType::MouseMoved => {
|
|
return if self.pressed() {
|
|
InputEventResult::GrabMouse
|
|
} else {
|
|
InputEventResult::EventIgnored
|
|
}
|
|
}
|
|
});
|
|
if matches!(event.what, MouseEventType::MouseReleased) {
|
|
Self::FIELD_OFFSETS.clicked.apply_pin(self).call(&());
|
|
InputEventResult::EventAccepted
|
|
} else {
|
|
InputEventResult::GrabMouse
|
|
}
|
|
}
|
|
|
|
fn key_event(self: Pin<&Self>, _: &KeyEvent, _window: &ComponentWindow) -> KeyEventResult {
|
|
KeyEventResult::EventIgnored
|
|
}
|
|
|
|
fn focus_event(self: Pin<&Self>, _: &FocusEvent, _window: &ComponentWindow) {}
|
|
|
|
fn_render! { this dpr size painter =>
|
|
let down: bool = this.pressed();
|
|
let text: qttypes::QString = this.text().as_str().into();
|
|
let enabled = this.enabled();
|
|
|
|
cpp!(unsafe [
|
|
painter as "QPainter*",
|
|
text as "QString",
|
|
enabled as "bool",
|
|
size as "QSize",
|
|
down as "bool",
|
|
dpr as "float"
|
|
] {
|
|
QStyleOptionButton option;
|
|
option.text = std::move(text);
|
|
option.rect = QRect(QPoint(), size / dpr);
|
|
if (down)
|
|
option.state |= QStyle::State_Sunken;
|
|
else
|
|
option.state |= QStyle::State_Raised;
|
|
if (enabled) {
|
|
option.state |= QStyle::State_Enabled;
|
|
} else {
|
|
option.palette.setCurrentColorGroup(QPalette::Disabled);
|
|
}
|
|
qApp->style()->drawControl(QStyle::CE_PushButton, &option, painter, nullptr);
|
|
});
|
|
}
|
|
}
|
|
|
|
impl ItemConsts for NativeButton {
|
|
const cached_rendering_data_offset: const_field_offset::FieldOffset<Self, CachedRenderingData> =
|
|
Self::FIELD_OFFSETS.cached_rendering_data.as_unpinned_projection();
|
|
}
|
|
|
|
ItemVTable_static! { #[no_mangle] pub static NativeButtonVTable for NativeButton }
|
|
|
|
#[repr(C)]
|
|
#[derive(FieldOffsets, Default, SixtyFPSElement)]
|
|
#[pin]
|
|
pub struct NativeCheckBox {
|
|
pub x: Property<f32>,
|
|
pub y: Property<f32>,
|
|
pub width: Property<f32>,
|
|
pub height: Property<f32>,
|
|
pub enabled: Property<bool>,
|
|
pub toggled: Callback<VoidArg>,
|
|
pub text: Property<SharedString>,
|
|
pub checked: Property<bool>,
|
|
pub cached_rendering_data: CachedRenderingData,
|
|
}
|
|
|
|
impl Item for NativeCheckBox {
|
|
fn init(self: Pin<&Self>, _window: &ComponentWindow) {}
|
|
|
|
fn geometry(self: Pin<&Self>) -> Rect {
|
|
euclid::rect(self.x(), self.y(), self.width(), self.height())
|
|
}
|
|
|
|
fn layouting_info(self: Pin<&Self>, window: &ComponentWindow) -> LayoutInfo {
|
|
let text: qttypes::QString = self.text().as_str().into();
|
|
let dpr = window.scale_factor();
|
|
let size = cpp!(unsafe [
|
|
text as "QString",
|
|
dpr as "float"
|
|
] -> qttypes::QSize as "QSize" {
|
|
ensure_initialized();
|
|
QStyleOptionButton option;
|
|
option.rect = option.fontMetrics.boundingRect(text);
|
|
option.text = std::move(text);
|
|
return qApp->style()->sizeFromContents(QStyle::CT_CheckBox, &option, option.rect.size(), nullptr) * dpr;
|
|
});
|
|
LayoutInfo {
|
|
min_width: size.width as f32,
|
|
min_height: size.height as f32,
|
|
max_height: size.height as f32,
|
|
horizontal_stretch: 1.,
|
|
..LayoutInfo::default()
|
|
}
|
|
}
|
|
|
|
fn implicit_size(self: Pin<&Self>, _window: &ComponentWindow) -> Size {
|
|
Default::default()
|
|
}
|
|
|
|
fn input_event_filter_before_children(
|
|
self: Pin<&Self>,
|
|
_: MouseEvent,
|
|
_window: &ComponentWindow,
|
|
_self_rc: &ItemRc,
|
|
) -> InputEventFilterResult {
|
|
InputEventFilterResult::ForwardEvent
|
|
}
|
|
|
|
fn input_event(
|
|
self: Pin<&Self>,
|
|
event: MouseEvent,
|
|
_window: &ComponentWindow,
|
|
_self_rc: &sixtyfps_corelib::items::ItemRc,
|
|
) -> InputEventResult {
|
|
if !self.enabled() {
|
|
return InputEventResult::EventIgnored;
|
|
}
|
|
if matches!(event.what, MouseEventType::MouseReleased) {
|
|
Self::FIELD_OFFSETS.checked.apply_pin(self).set(!self.checked());
|
|
Self::FIELD_OFFSETS.toggled.apply_pin(self).call(&())
|
|
}
|
|
InputEventResult::EventAccepted
|
|
}
|
|
|
|
fn key_event(self: Pin<&Self>, _: &KeyEvent, _window: &ComponentWindow) -> KeyEventResult {
|
|
KeyEventResult::EventIgnored
|
|
}
|
|
|
|
fn focus_event(self: Pin<&Self>, _: &FocusEvent, _window: &ComponentWindow) {}
|
|
|
|
fn_render! { this dpr size painter =>
|
|
let checked: bool = this.checked();
|
|
let enabled = this.enabled();
|
|
let text: qttypes::QString = this.text().as_str().into();
|
|
|
|
cpp!(unsafe [
|
|
painter as "QPainter*",
|
|
enabled as "bool",
|
|
text as "QString",
|
|
size as "QSize",
|
|
checked as "bool",
|
|
dpr as "float"
|
|
] {
|
|
QStyleOptionButton option;
|
|
option.text = std::move(text);
|
|
option.rect = QRect(QPoint(), size / dpr);
|
|
option.state |= checked ? QStyle::State_On : QStyle::State_Off;
|
|
if (enabled) {
|
|
option.state |= QStyle::State_Enabled;
|
|
} else {
|
|
option.palette.setCurrentColorGroup(QPalette::Disabled);
|
|
}
|
|
qApp->style()->drawControl(QStyle::CE_CheckBox, &option, painter, nullptr);
|
|
});
|
|
}
|
|
}
|
|
|
|
impl ItemConsts for NativeCheckBox {
|
|
const cached_rendering_data_offset: const_field_offset::FieldOffset<Self, CachedRenderingData> =
|
|
Self::FIELD_OFFSETS.cached_rendering_data.as_unpinned_projection();
|
|
}
|
|
|
|
ItemVTable_static! { #[no_mangle] pub static NativeCheckBoxVTable for NativeCheckBox }
|
|
|
|
#[derive(Default, Copy, Clone, Debug, PartialEq)]
|
|
#[repr(C)]
|
|
struct NativeSpinBoxData {
|
|
active_controls: u32,
|
|
pressed: bool,
|
|
}
|
|
|
|
#[repr(C)]
|
|
#[derive(FieldOffsets, Default, SixtyFPSElement)]
|
|
#[pin]
|
|
pub struct NativeSpinBox {
|
|
pub x: Property<f32>,
|
|
pub y: Property<f32>,
|
|
pub width: Property<f32>,
|
|
pub height: Property<f32>,
|
|
pub enabled: Property<bool>,
|
|
pub value: Property<i32>,
|
|
pub minimum: Property<i32>,
|
|
pub maximum: Property<i32>,
|
|
pub cached_rendering_data: CachedRenderingData,
|
|
data: Property<NativeSpinBoxData>,
|
|
}
|
|
|
|
cpp! {{
|
|
void initQSpinBoxOptions(QStyleOptionSpinBox &option, bool pressed, bool enabled, int active_controls) {
|
|
auto style = qApp->style();
|
|
option.activeSubControls = QStyle::SC_None;
|
|
option.subControls = QStyle::SC_SpinBoxEditField | QStyle::SC_SpinBoxUp | QStyle::SC_SpinBoxDown;
|
|
if (style->styleHint(QStyle::SH_SpinBox_ButtonsInsideFrame, nullptr, nullptr))
|
|
option.subControls |= QStyle::SC_SpinBoxFrame;
|
|
option.activeSubControls = {active_controls};
|
|
if (enabled) {
|
|
option.state |= QStyle::State_Enabled;
|
|
} else {
|
|
option.palette.setCurrentColorGroup(QPalette::Disabled);
|
|
}
|
|
option.state |= QStyle::State_Active;
|
|
if (pressed) {
|
|
option.state |= QStyle::State_Sunken | QStyle::State_MouseOver;
|
|
}
|
|
/*if (active_controls) {
|
|
option.state |= QStyle::State_MouseOver;
|
|
}*/
|
|
option.stepEnabled = QAbstractSpinBox::StepDownEnabled | QAbstractSpinBox::StepUpEnabled;
|
|
option.frame = true;
|
|
}
|
|
}}
|
|
|
|
impl Item for NativeSpinBox {
|
|
fn init(self: Pin<&Self>, _window: &ComponentWindow) {}
|
|
|
|
fn geometry(self: Pin<&Self>) -> Rect {
|
|
euclid::rect(self.x(), self.y(), self.width(), self.height())
|
|
}
|
|
|
|
fn layouting_info(self: Pin<&Self>, window: &ComponentWindow) -> LayoutInfo {
|
|
//let value: i32 = self.value();
|
|
let data = self.data();
|
|
let active_controls = data.active_controls;
|
|
let pressed = data.pressed;
|
|
let enabled = self.enabled();
|
|
let dpr = window.scale_factor();
|
|
|
|
let size = cpp!(unsafe [
|
|
//value as "int",
|
|
active_controls as "int",
|
|
pressed as "bool",
|
|
enabled as "bool",
|
|
dpr as "float"
|
|
] -> qttypes::QSize as "QSize" {
|
|
ensure_initialized();
|
|
auto style = qApp->style();
|
|
|
|
QStyleOptionSpinBox option;
|
|
initQSpinBoxOptions(option, pressed, enabled, active_controls);
|
|
|
|
auto content = option.fontMetrics.boundingRect("0000");
|
|
|
|
return style->sizeFromContents(QStyle::CT_SpinBox, &option, content.size(), nullptr) * dpr;
|
|
});
|
|
LayoutInfo {
|
|
min_width: size.width as f32,
|
|
min_height: size.height as f32,
|
|
max_height: size.height as f32,
|
|
horizontal_stretch: 1.,
|
|
..LayoutInfo::default()
|
|
}
|
|
}
|
|
|
|
fn implicit_size(self: Pin<&Self>, _window: &ComponentWindow) -> Size {
|
|
Default::default()
|
|
}
|
|
|
|
fn input_event_filter_before_children(
|
|
self: Pin<&Self>,
|
|
_: MouseEvent,
|
|
_window: &ComponentWindow,
|
|
_self_rc: &ItemRc,
|
|
) -> InputEventFilterResult {
|
|
InputEventFilterResult::ForwardEvent
|
|
}
|
|
|
|
fn input_event(
|
|
self: Pin<&Self>,
|
|
event: MouseEvent,
|
|
_window: &ComponentWindow,
|
|
_self_rc: &sixtyfps_corelib::items::ItemRc,
|
|
) -> InputEventResult {
|
|
let size: qttypes::QSize = get_size!(self);
|
|
let enabled = self.enabled();
|
|
let mut data = self.data();
|
|
let active_controls = data.active_controls;
|
|
let pressed = data.pressed;
|
|
|
|
let pos = qttypes::QPoint { x: event.pos.x as _, y: event.pos.y as _ };
|
|
|
|
let new_control = cpp!(unsafe [
|
|
pos as "QPoint",
|
|
size as "QSize",
|
|
enabled as "bool",
|
|
active_controls as "int",
|
|
pressed as "bool"
|
|
] -> u32 as "int" {
|
|
ensure_initialized();
|
|
auto style = qApp->style();
|
|
|
|
QStyleOptionSpinBox option;
|
|
option.rect = { QPoint{}, size };
|
|
initQSpinBoxOptions(option, pressed, enabled, active_controls);
|
|
|
|
return style->hitTestComplexControl(QStyle::CC_SpinBox, &option, pos, nullptr);
|
|
});
|
|
let changed = new_control != active_controls
|
|
|| match event.what {
|
|
MouseEventType::MousePressed => {
|
|
data.pressed = true;
|
|
true
|
|
}
|
|
MouseEventType::MouseExit => {
|
|
data.pressed = false;
|
|
true
|
|
}
|
|
MouseEventType::MouseReleased => {
|
|
data.pressed = false;
|
|
if new_control == cpp!(unsafe []->u32 as "int" { return QStyle::SC_SpinBoxUp;})
|
|
&& enabled
|
|
{
|
|
let v = self.value();
|
|
if v < self.maximum() {
|
|
self.value.set(v + 1);
|
|
}
|
|
}
|
|
if new_control
|
|
== cpp!(unsafe []->u32 as "int" { return QStyle::SC_SpinBoxDown;})
|
|
&& enabled
|
|
{
|
|
let v = self.value();
|
|
if v > self.minimum() {
|
|
self.value.set(v - 1);
|
|
}
|
|
}
|
|
true
|
|
}
|
|
MouseEventType::MouseMoved => false,
|
|
};
|
|
data.active_controls = new_control;
|
|
if changed {
|
|
self.data.set(data);
|
|
}
|
|
InputEventResult::EventAccepted
|
|
}
|
|
|
|
fn key_event(self: Pin<&Self>, _: &KeyEvent, _window: &ComponentWindow) -> KeyEventResult {
|
|
KeyEventResult::EventIgnored
|
|
}
|
|
|
|
fn focus_event(self: Pin<&Self>, _: &FocusEvent, _window: &ComponentWindow) {}
|
|
|
|
fn_render! { this dpr size painter =>
|
|
let value: i32 = this.value();
|
|
let enabled = this.enabled();
|
|
let data = this.data();
|
|
let active_controls = data.active_controls;
|
|
let pressed = data.pressed;
|
|
|
|
cpp!(unsafe [
|
|
painter as "QPainter*",
|
|
value as "int",
|
|
enabled as "bool",
|
|
size as "QSize",
|
|
active_controls as "int",
|
|
pressed as "bool",
|
|
dpr as "float"
|
|
] {
|
|
auto style = qApp->style();
|
|
QStyleOptionSpinBox option;
|
|
option.rect = QRect(QPoint(), size / dpr);
|
|
initQSpinBoxOptions(option, pressed, enabled, active_controls);
|
|
style->drawComplexControl(QStyle::CC_SpinBox, &option, painter, nullptr);
|
|
|
|
auto text_rect = style->subControlRect(QStyle::CC_SpinBox, &option, QStyle::SC_SpinBoxEditField, nullptr);
|
|
painter->setPen(option.palette.color(QPalette::Text));
|
|
painter->drawText(text_rect, QString::number(value));
|
|
});
|
|
}
|
|
}
|
|
|
|
impl ItemConsts for NativeSpinBox {
|
|
const cached_rendering_data_offset: const_field_offset::FieldOffset<Self, CachedRenderingData> =
|
|
Self::FIELD_OFFSETS.cached_rendering_data.as_unpinned_projection();
|
|
}
|
|
|
|
ItemVTable_static! { #[no_mangle] pub static NativeSpinBoxVTable for NativeSpinBox }
|
|
|
|
#[derive(Default, Copy, Clone, Debug, PartialEq)]
|
|
#[repr(C)]
|
|
struct NativeSliderData {
|
|
active_controls: u32,
|
|
/// For sliders, this is a bool, For scroll area: 1 == horizontal, 2 == vertical
|
|
pressed: u8,
|
|
pressed_x: f32,
|
|
pressed_val: f32,
|
|
}
|
|
|
|
#[repr(C)]
|
|
#[derive(FieldOffsets, Default, SixtyFPSElement)]
|
|
#[pin]
|
|
pub struct NativeSlider {
|
|
pub x: Property<f32>,
|
|
pub y: Property<f32>,
|
|
pub width: Property<f32>,
|
|
pub height: Property<f32>,
|
|
pub enabled: Property<bool>,
|
|
pub value: Property<f32>,
|
|
pub minimum: Property<f32>,
|
|
pub maximum: Property<f32>,
|
|
pub cached_rendering_data: CachedRenderingData,
|
|
data: Property<NativeSliderData>,
|
|
}
|
|
|
|
cpp! {{
|
|
void initQSliderOptions(QStyleOptionSlider &option, bool pressed, bool enabled, int active_controls, int minimum, int maximum, int value) {
|
|
option.subControls = QStyle::SC_SliderGroove | QStyle::SC_SliderHandle;
|
|
option.activeSubControls = { active_controls };
|
|
option.orientation = Qt::Horizontal;
|
|
option.maximum = maximum;
|
|
option.minimum = minimum;
|
|
option.sliderPosition = value;
|
|
option.sliderValue = value;
|
|
if (enabled) {
|
|
option.state |= QStyle::State_Enabled;
|
|
} else {
|
|
option.palette.setCurrentColorGroup(QPalette::Disabled);
|
|
}
|
|
option.state |= QStyle::State_Active | QStyle::State_Horizontal;
|
|
if (pressed) {
|
|
option.state |= QStyle::State_Sunken | QStyle::State_MouseOver;
|
|
}
|
|
}
|
|
}}
|
|
|
|
impl Item for NativeSlider {
|
|
fn init(self: Pin<&Self>, _window: &ComponentWindow) {}
|
|
|
|
fn geometry(self: Pin<&Self>) -> Rect {
|
|
euclid::rect(self.x(), self.y(), self.width(), self.height())
|
|
}
|
|
|
|
fn layouting_info(self: Pin<&Self>, window: &ComponentWindow) -> LayoutInfo {
|
|
let enabled = self.enabled();
|
|
let value = self.value() as i32;
|
|
let min = self.minimum() as i32;
|
|
let max = self.maximum() as i32;
|
|
let data = self.data();
|
|
let active_controls = data.active_controls;
|
|
let pressed = data.pressed;
|
|
let dpr = window.scale_factor();
|
|
|
|
let size = cpp!(unsafe [
|
|
enabled as "bool",
|
|
value as "int",
|
|
min as "int",
|
|
max as "int",
|
|
active_controls as "int",
|
|
pressed as "bool",
|
|
dpr as "float"
|
|
] -> qttypes::QSize as "QSize" {
|
|
ensure_initialized();
|
|
QStyleOptionSlider option;
|
|
initQSliderOptions(option, pressed, enabled, active_controls, min, max, value);
|
|
auto style = qApp->style();
|
|
auto thick = style->pixelMetric(QStyle::PM_SliderThickness, &option, nullptr);
|
|
return style->sizeFromContents(QStyle::CT_Slider, &option, QSize(0, thick), nullptr) * dpr;
|
|
});
|
|
LayoutInfo {
|
|
min_width: size.width as f32,
|
|
min_height: size.height as f32,
|
|
max_height: size.height as f32,
|
|
horizontal_stretch: 1.,
|
|
..LayoutInfo::default()
|
|
}
|
|
}
|
|
|
|
fn implicit_size(self: Pin<&Self>, _window: &ComponentWindow) -> Size {
|
|
Default::default()
|
|
}
|
|
|
|
fn input_event_filter_before_children(
|
|
self: Pin<&Self>,
|
|
_: MouseEvent,
|
|
_window: &ComponentWindow,
|
|
_self_rc: &ItemRc,
|
|
) -> InputEventFilterResult {
|
|
InputEventFilterResult::ForwardEvent
|
|
}
|
|
|
|
fn input_event(
|
|
self: Pin<&Self>,
|
|
event: MouseEvent,
|
|
_window: &ComponentWindow,
|
|
_self_rc: &sixtyfps_corelib::items::ItemRc,
|
|
) -> InputEventResult {
|
|
let size: qttypes::QSize = get_size!(self);
|
|
let enabled = self.enabled();
|
|
let value = self.value() as f32;
|
|
let min = self.minimum() as f32;
|
|
let max = self.maximum() as f32;
|
|
let mut data = self.data();
|
|
let active_controls = data.active_controls;
|
|
let pressed: bool = data.pressed != 0;
|
|
let pos = qttypes::QPoint { x: event.pos.x as _, y: event.pos.y as _ };
|
|
|
|
let new_control = cpp!(unsafe [
|
|
pos as "QPoint",
|
|
size as "QSize",
|
|
enabled as "bool",
|
|
value as "float",
|
|
min as "float",
|
|
max as "float",
|
|
active_controls as "int",
|
|
pressed as "bool"
|
|
] -> u32 as "int" {
|
|
ensure_initialized();
|
|
QStyleOptionSlider option;
|
|
initQSliderOptions(option, pressed, enabled, active_controls, min, max, value);
|
|
auto style = qApp->style();
|
|
option.rect = { QPoint{}, size };
|
|
return style->hitTestComplexControl(QStyle::CC_Slider, &option, pos, nullptr);
|
|
});
|
|
let result = match event.what {
|
|
MouseEventType::MousePressed if enabled => {
|
|
data.pressed_x = event.pos.x as f32;
|
|
data.pressed = 1;
|
|
data.pressed_val = value;
|
|
InputEventResult::GrabMouse
|
|
}
|
|
MouseEventType::MouseExit | MouseEventType::MouseReleased => {
|
|
data.pressed = 0;
|
|
InputEventResult::EventAccepted
|
|
}
|
|
MouseEventType::MouseMoved if enabled => {
|
|
if data.pressed != 0 {
|
|
// FIXME: use QStyle::subControlRect to find out the actual size of the groove
|
|
let new_val = data.pressed_val
|
|
+ ((event.pos.x as f32) - data.pressed_x) * (max - min) / size.width as f32;
|
|
self.value.set(new_val.max(min).min(max));
|
|
InputEventResult::GrabMouse
|
|
} else {
|
|
InputEventResult::EventIgnored
|
|
}
|
|
}
|
|
_ => {
|
|
assert!(!enabled);
|
|
data.pressed = 0;
|
|
InputEventResult::EventIgnored
|
|
}
|
|
};
|
|
data.active_controls = new_control;
|
|
|
|
self.data.set(data);
|
|
result
|
|
}
|
|
|
|
fn key_event(self: Pin<&Self>, _: &KeyEvent, _window: &ComponentWindow) -> KeyEventResult {
|
|
KeyEventResult::EventIgnored
|
|
}
|
|
|
|
fn focus_event(self: Pin<&Self>, _: &FocusEvent, _window: &ComponentWindow) {}
|
|
|
|
fn_render! { this dpr size painter =>
|
|
let enabled = this.enabled();
|
|
let value = this.value() as i32;
|
|
let min = this.minimum() as i32;
|
|
let max = this.maximum() as i32;
|
|
let data = this.data();
|
|
let active_controls = data.active_controls;
|
|
let pressed = data.pressed;
|
|
|
|
cpp!(unsafe [
|
|
painter as "QPainter*",
|
|
enabled as "bool",
|
|
value as "int",
|
|
min as "int",
|
|
max as "int",
|
|
size as "QSize",
|
|
active_controls as "int",
|
|
pressed as "bool",
|
|
dpr as "float"
|
|
] {
|
|
QStyleOptionSlider option;
|
|
option.rect = QRect(QPoint(), size / dpr);
|
|
initQSliderOptions(option, pressed, enabled, active_controls, min, max, value);
|
|
auto style = qApp->style();
|
|
style->drawComplexControl(QStyle::CC_Slider, &option, painter, nullptr);
|
|
});
|
|
}
|
|
}
|
|
|
|
impl ItemConsts for NativeSlider {
|
|
const cached_rendering_data_offset: const_field_offset::FieldOffset<Self, CachedRenderingData> =
|
|
Self::FIELD_OFFSETS.cached_rendering_data.as_unpinned_projection();
|
|
}
|
|
|
|
ItemVTable_static! { #[no_mangle] pub static NativeSliderVTable for NativeSlider }
|
|
|
|
#[repr(C)]
|
|
#[derive(FieldOffsets, Default, SixtyFPSElement)]
|
|
#[pin]
|
|
pub struct NativeGroupBox {
|
|
pub x: Property<f32>,
|
|
pub y: Property<f32>,
|
|
pub width: Property<f32>,
|
|
pub height: Property<f32>,
|
|
pub enabled: Property<bool>,
|
|
pub title: Property<SharedString>,
|
|
pub cached_rendering_data: CachedRenderingData,
|
|
pub native_padding_left: Property<f32>,
|
|
pub native_padding_right: Property<f32>,
|
|
pub native_padding_top: Property<f32>,
|
|
pub native_padding_bottom: Property<f32>,
|
|
}
|
|
|
|
#[repr(C)]
|
|
#[derive(FieldOffsets, Default)]
|
|
#[pin]
|
|
struct GroupBoxData {
|
|
title: Property<SharedString>,
|
|
paddings: Property<qttypes::QMargins>,
|
|
}
|
|
|
|
impl Item for NativeGroupBox {
|
|
fn init(self: Pin<&Self>, window: &ComponentWindow) {
|
|
let shared_data = Rc::pin(GroupBoxData::default());
|
|
|
|
Property::link_two_way(
|
|
Self::FIELD_OFFSETS.title.apply_pin(self),
|
|
GroupBoxData::FIELD_OFFSETS.title.apply_pin(shared_data.as_ref()),
|
|
);
|
|
|
|
shared_data.paddings.set_binding({
|
|
let window_weak = Rc::downgrade(&window.0.clone());
|
|
let shared_data_weak = pin_weak::rc::PinWeak::downgrade(shared_data.clone());
|
|
move || {
|
|
let shared_data = shared_data_weak.upgrade().unwrap();
|
|
|
|
let text: qttypes::QString = GroupBoxData::FIELD_OFFSETS.title.apply_pin(shared_data.as_ref()).get().as_str().into();
|
|
let dpr = window_weak.upgrade().unwrap().scale_factor();
|
|
|
|
cpp!(unsafe [
|
|
text as "QString",
|
|
dpr as "float"
|
|
] -> qttypes::QMargins as "QMargins" {
|
|
ensure_initialized();
|
|
QStyleOptionGroupBox option;
|
|
option.text = text;
|
|
option.lineWidth = 1;
|
|
option.midLineWidth = 0;
|
|
option.subControls = QStyle::SC_GroupBoxFrame;
|
|
if (!text.isEmpty()) {
|
|
option.subControls |= QStyle::SC_GroupBoxLabel;
|
|
}
|
|
// Just some size big enough to be sure that the frame fitst in it
|
|
option.rect = QRect(0, 0, 10000, 10000);
|
|
option.textColor = QColor(qApp->style()->styleHint(
|
|
QStyle::SH_GroupBox_TextLabelColor, &option));
|
|
QRect contentsRect = qApp->style()->subControlRect(
|
|
QStyle::CC_GroupBox, &option, QStyle::SC_GroupBoxContents);
|
|
//QRect elementRect = qApp->style()->subElementRect(
|
|
// QStyle::SE_GroupBoxLayoutItem, &option);
|
|
|
|
auto hs = qApp->style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing, &option);
|
|
auto vs = qApp->style()->pixelMetric(QStyle::PM_LayoutVerticalSpacing, &option);
|
|
|
|
return {
|
|
qRound((contentsRect.left() + hs) * dpr),
|
|
qRound((contentsRect.top() + vs) * dpr),
|
|
qRound((option.rect.right() - contentsRect.right() + hs) * dpr),
|
|
qRound((option.rect.bottom() - contentsRect.bottom() + vs) * dpr) };
|
|
})
|
|
}
|
|
});
|
|
|
|
self.native_padding_left.set_binding({
|
|
let shared_data = shared_data.clone();
|
|
move || {
|
|
let margins =
|
|
GroupBoxData::FIELD_OFFSETS.paddings.apply_pin(shared_data.as_ref()).get();
|
|
margins.left as _
|
|
}
|
|
});
|
|
|
|
self.native_padding_right.set_binding({
|
|
let shared_data = shared_data.clone();
|
|
move || {
|
|
let margins =
|
|
GroupBoxData::FIELD_OFFSETS.paddings.apply_pin(shared_data.as_ref()).get();
|
|
margins.right as _
|
|
}
|
|
});
|
|
|
|
self.native_padding_top.set_binding({
|
|
let shared_data = shared_data.clone();
|
|
move || {
|
|
let margins =
|
|
GroupBoxData::FIELD_OFFSETS.paddings.apply_pin(shared_data.as_ref()).get();
|
|
margins.top as _
|
|
}
|
|
});
|
|
|
|
self.native_padding_bottom.set_binding({
|
|
let shared_data = shared_data.clone();
|
|
move || {
|
|
let margins =
|
|
GroupBoxData::FIELD_OFFSETS.paddings.apply_pin(shared_data.as_ref()).get();
|
|
margins.bottom as _
|
|
}
|
|
});
|
|
}
|
|
|
|
fn geometry(self: Pin<&Self>) -> Rect {
|
|
euclid::rect(self.x(), self.y(), self.width(), self.height())
|
|
}
|
|
|
|
fn layouting_info(self: Pin<&Self>, _window: &ComponentWindow) -> LayoutInfo {
|
|
let left = self.native_padding_left();
|
|
let right = self.native_padding_right();
|
|
let top = self.native_padding_top();
|
|
let bottom = self.native_padding_bottom();
|
|
LayoutInfo {
|
|
min_width: left + right,
|
|
min_height: top + bottom,
|
|
horizontal_stretch: 1.,
|
|
vertical_stretch: 1.,
|
|
..LayoutInfo::default()
|
|
}
|
|
}
|
|
|
|
fn implicit_size(self: Pin<&Self>, _window: &ComponentWindow) -> Size {
|
|
Default::default()
|
|
}
|
|
|
|
fn input_event_filter_before_children(
|
|
self: Pin<&Self>,
|
|
_: MouseEvent,
|
|
_window: &ComponentWindow,
|
|
_self_rc: &ItemRc,
|
|
) -> InputEventFilterResult {
|
|
InputEventFilterResult::ForwardEvent
|
|
}
|
|
|
|
fn input_event(
|
|
self: Pin<&Self>,
|
|
_: MouseEvent,
|
|
_window: &ComponentWindow,
|
|
_self_rc: &sixtyfps_corelib::items::ItemRc,
|
|
) -> InputEventResult {
|
|
InputEventResult::EventIgnored
|
|
}
|
|
|
|
fn key_event(self: Pin<&Self>, _: &KeyEvent, _window: &ComponentWindow) -> KeyEventResult {
|
|
KeyEventResult::EventIgnored
|
|
}
|
|
|
|
fn focus_event(self: Pin<&Self>, _: &FocusEvent, _window: &ComponentWindow) {}
|
|
|
|
fn_render! { this dpr size painter =>
|
|
let text: qttypes::QString =
|
|
this.title().as_str().into();
|
|
let enabled = this.enabled();
|
|
|
|
cpp!(unsafe [
|
|
painter as "QPainter*",
|
|
text as "QString",
|
|
enabled as "bool",
|
|
size as "QSize",
|
|
dpr as "float"
|
|
] {
|
|
QStyleOptionGroupBox option;
|
|
if (enabled) {
|
|
option.state |= QStyle::State_Enabled;
|
|
} else {
|
|
option.palette.setCurrentColorGroup(QPalette::Disabled);
|
|
}
|
|
option.rect = QRect(QPoint(), size / dpr);
|
|
option.text = text;
|
|
option.lineWidth = 1;
|
|
option.midLineWidth = 0;
|
|
option.subControls = QStyle::SC_GroupBoxFrame;
|
|
if (!text.isEmpty()) {
|
|
option.subControls |= QStyle::SC_GroupBoxLabel;
|
|
}
|
|
option.textColor = QColor(qApp->style()->styleHint(
|
|
QStyle::SH_GroupBox_TextLabelColor, &option));
|
|
qApp->style()->drawComplexControl(QStyle::CC_GroupBox, &option, painter, nullptr);
|
|
});
|
|
}
|
|
}
|
|
|
|
impl ItemConsts for NativeGroupBox {
|
|
const cached_rendering_data_offset: const_field_offset::FieldOffset<Self, CachedRenderingData> =
|
|
Self::FIELD_OFFSETS.cached_rendering_data.as_unpinned_projection();
|
|
}
|
|
|
|
ItemVTable_static! { #[no_mangle] pub static NativeGroupBoxVTable for NativeGroupBox }
|
|
|
|
#[repr(C)]
|
|
#[derive(FieldOffsets, Default, SixtyFPSElement)]
|
|
#[pin]
|
|
pub struct NativeLineEdit {
|
|
pub x: Property<f32>,
|
|
pub y: Property<f32>,
|
|
pub width: Property<f32>,
|
|
pub height: Property<f32>,
|
|
pub cached_rendering_data: CachedRenderingData,
|
|
pub native_padding_left: Property<f32>,
|
|
pub native_padding_right: Property<f32>,
|
|
pub native_padding_top: Property<f32>,
|
|
pub native_padding_bottom: Property<f32>,
|
|
pub focused: Property<bool>,
|
|
pub enabled: Property<bool>,
|
|
}
|
|
|
|
impl Item for NativeLineEdit {
|
|
fn init(self: Pin<&Self>, window: &ComponentWindow) {
|
|
let paddings = Rc::pin(Property::default());
|
|
|
|
paddings.as_ref().set_binding({
|
|
let window_weak = Rc::downgrade(&window.0.clone());
|
|
move || {
|
|
let dpr = window_weak.upgrade().unwrap().scale_factor();
|
|
|
|
cpp!(unsafe [
|
|
dpr as "float"
|
|
] -> qttypes::QMargins as "QMargins" {
|
|
ensure_initialized();
|
|
QStyleOptionFrame option;
|
|
option.state |= QStyle::State_Enabled;
|
|
option.lineWidth = 1;
|
|
option.midLineWidth = 0;
|
|
// Just some size big enough to be sure that the frame fitst in it
|
|
option.rect = QRect(0, 0, 10000, 10000);
|
|
QRect contentsRect = qApp->style()->subElementRect(
|
|
QStyle::SE_LineEditContents, &option);
|
|
|
|
// ### remove extra margins
|
|
|
|
return {
|
|
qRound((2 + contentsRect.left()) * dpr),
|
|
qRound((4 + contentsRect.top()) * dpr),
|
|
qRound((2 + option.rect.right() - contentsRect.right()) * dpr),
|
|
qRound((4 + option.rect.bottom() - contentsRect.bottom()) * dpr) };
|
|
})
|
|
}
|
|
});
|
|
|
|
self.native_padding_left.set_binding({
|
|
let paddings = paddings.clone();
|
|
move || paddings.as_ref().get().left as _
|
|
});
|
|
self.native_padding_right.set_binding({
|
|
let paddings = paddings.clone();
|
|
move || paddings.as_ref().get().right as _
|
|
});
|
|
self.native_padding_top.set_binding({
|
|
let paddings = paddings.clone();
|
|
move || paddings.as_ref().get().top as _
|
|
});
|
|
self.native_padding_bottom.set_binding({
|
|
let paddings = paddings.clone();
|
|
move || paddings.as_ref().get().bottom as _
|
|
});
|
|
}
|
|
|
|
fn geometry(self: Pin<&Self>) -> Rect {
|
|
euclid::rect(self.x(), self.y(), self.width(), self.height())
|
|
}
|
|
|
|
fn layouting_info(self: Pin<&Self>, _window: &ComponentWindow) -> LayoutInfo {
|
|
let left = self.native_padding_left();
|
|
let right = self.native_padding_right();
|
|
let top = self.native_padding_top();
|
|
let bottom = self.native_padding_bottom();
|
|
LayoutInfo {
|
|
min_width: left + right,
|
|
min_height: top + bottom,
|
|
horizontal_stretch: 1.,
|
|
..LayoutInfo::default()
|
|
}
|
|
}
|
|
|
|
fn implicit_size(self: Pin<&Self>, _window: &ComponentWindow) -> Size {
|
|
Default::default()
|
|
}
|
|
|
|
fn input_event_filter_before_children(
|
|
self: Pin<&Self>,
|
|
_: MouseEvent,
|
|
_window: &ComponentWindow,
|
|
_self_rc: &ItemRc,
|
|
) -> InputEventFilterResult {
|
|
InputEventFilterResult::ForwardAndIgnore
|
|
}
|
|
|
|
fn input_event(
|
|
self: Pin<&Self>,
|
|
_: MouseEvent,
|
|
_window: &ComponentWindow,
|
|
_self_rc: &sixtyfps_corelib::items::ItemRc,
|
|
) -> InputEventResult {
|
|
InputEventResult::EventIgnored
|
|
}
|
|
|
|
fn key_event(self: Pin<&Self>, _: &KeyEvent, _window: &ComponentWindow) -> KeyEventResult {
|
|
KeyEventResult::EventIgnored
|
|
}
|
|
|
|
fn focus_event(self: Pin<&Self>, _: &FocusEvent, _window: &ComponentWindow) {}
|
|
|
|
fn_render! { this dpr size painter =>
|
|
let focused: bool = this.focused();
|
|
let enabled: bool = this.enabled();
|
|
|
|
cpp!(unsafe [
|
|
painter as "QPainter*",
|
|
size as "QSize",
|
|
dpr as "float",
|
|
enabled as "bool",
|
|
focused as "bool"
|
|
] {
|
|
QStyleOptionFrame option;
|
|
option.rect = QRect(QPoint(), size / dpr);
|
|
option.lineWidth = 1;
|
|
option.midLineWidth = 0;
|
|
if (focused)
|
|
option.state |= QStyle::State_HasFocus;
|
|
if (enabled) {
|
|
option.state |= QStyle::State_Enabled;
|
|
} else {
|
|
option.palette.setCurrentColorGroup(QPalette::Disabled);
|
|
}
|
|
qApp->style()->drawPrimitive(QStyle::PE_PanelLineEdit, &option, painter, nullptr);
|
|
});
|
|
}
|
|
}
|
|
|
|
impl ItemConsts for NativeLineEdit {
|
|
const cached_rendering_data_offset: const_field_offset::FieldOffset<Self, CachedRenderingData> =
|
|
Self::FIELD_OFFSETS.cached_rendering_data.as_unpinned_projection();
|
|
}
|
|
|
|
ItemVTable_static! { #[no_mangle] pub static NativeLineEditVTable for NativeLineEdit }
|
|
|
|
#[repr(C)]
|
|
#[derive(FieldOffsets, Default, SixtyFPSElement)]
|
|
#[pin]
|
|
pub struct NativeScrollView {
|
|
pub x: Property<f32>,
|
|
pub y: Property<f32>,
|
|
pub width: Property<f32>,
|
|
pub height: Property<f32>,
|
|
pub horizontal_max: Property<f32>,
|
|
pub horizontal_page_size: Property<f32>,
|
|
pub horizontal_value: Property<f32>,
|
|
pub vertical_max: Property<f32>,
|
|
pub vertical_page_size: Property<f32>,
|
|
pub vertical_value: Property<f32>,
|
|
pub cached_rendering_data: CachedRenderingData,
|
|
pub native_padding_left: Property<f32>,
|
|
pub native_padding_right: Property<f32>,
|
|
pub native_padding_top: Property<f32>,
|
|
pub native_padding_bottom: Property<f32>,
|
|
data: Property<NativeSliderData>,
|
|
}
|
|
|
|
impl Item for NativeScrollView {
|
|
fn init(self: Pin<&Self>, window: &ComponentWindow) {
|
|
let paddings = Rc::pin(Property::default());
|
|
|
|
paddings.as_ref().set_binding({
|
|
let window_weak = Rc::downgrade(&window.0.clone());
|
|
move || {
|
|
let dpr = window_weak.upgrade().unwrap().scale_factor();
|
|
|
|
cpp!(unsafe [
|
|
dpr as "float"
|
|
] -> qttypes::QMargins as "QMargins" {
|
|
ensure_initialized();
|
|
QStyleOptionSlider option;
|
|
initQSliderOptions(option, false, true, 0, 0, 1000, 1000);
|
|
|
|
int extent = qApp->style()->pixelMetric(QStyle::PM_ScrollBarExtent, &option, nullptr);
|
|
int sliderMin = qApp->style()->pixelMetric(QStyle::PM_ScrollBarSliderMin, &option, nullptr);
|
|
auto horizontal_size = qApp->style()->sizeFromContents(QStyle::CT_ScrollBar, &option, QSize(extent * 2 + sliderMin, extent), nullptr);
|
|
option.state ^= QStyle::State_Horizontal;
|
|
option.orientation = Qt::Vertical;
|
|
extent = qApp->style()->pixelMetric(QStyle::PM_ScrollBarExtent, &option, nullptr);
|
|
sliderMin = qApp->style()->pixelMetric(QStyle::PM_ScrollBarSliderMin, &option, nullptr);
|
|
auto vertical_size = qApp->style()->sizeFromContents(QStyle::CT_ScrollBar, &option, QSize(extent, extent * 2 + sliderMin), nullptr);
|
|
|
|
/*int hscrollOverlap = hbar->style()->pixelMetric(QStyle::PM_ScrollView_ScrollBarOverlap, &opt, hbar);
|
|
int vscrollOverlap = vbar->style()->pixelMetric(QStyle::PM_ScrollView_ScrollBarOverlap, &opt, vbar);*/
|
|
|
|
QStyleOptionFrame frameOption;
|
|
frameOption.rect = QRect(QPoint(), QSize(1000, 1000));
|
|
frameOption.frameShape = QFrame::StyledPanel;
|
|
frameOption.lineWidth = 1;
|
|
frameOption.midLineWidth = 0;
|
|
QRect cr = qApp->style()->subElementRect(QStyle::SE_ShapedFrameContents, &frameOption, nullptr);
|
|
return {
|
|
qRound(cr.left() * dpr),
|
|
qRound(cr.top() * dpr),
|
|
qRound((vertical_size.width() + frameOption.rect.right() - cr.right()) * dpr),
|
|
qRound((horizontal_size.height() + frameOption.rect.bottom() - cr.bottom()) * dpr) };
|
|
})
|
|
}
|
|
});
|
|
|
|
self.native_padding_left.set_binding({
|
|
let paddings = paddings.clone();
|
|
move || paddings.as_ref().get().left as _
|
|
});
|
|
self.native_padding_right.set_binding({
|
|
let paddings = paddings.clone();
|
|
move || paddings.as_ref().get().right as _
|
|
});
|
|
self.native_padding_top.set_binding({
|
|
let paddings = paddings.clone();
|
|
move || paddings.as_ref().get().top as _
|
|
});
|
|
self.native_padding_bottom.set_binding({
|
|
let paddings = paddings.clone();
|
|
move || paddings.as_ref().get().bottom as _
|
|
});
|
|
}
|
|
|
|
fn geometry(self: Pin<&Self>) -> Rect {
|
|
euclid::rect(self.x(), self.y(), self.width(), self.height())
|
|
}
|
|
|
|
fn layouting_info(self: Pin<&Self>, _window: &ComponentWindow) -> LayoutInfo {
|
|
let left = self.native_padding_left();
|
|
let right = self.native_padding_right();
|
|
let top = self.native_padding_top();
|
|
let bottom = self.native_padding_bottom();
|
|
LayoutInfo {
|
|
min_width: left + right,
|
|
min_height: top + bottom,
|
|
horizontal_stretch: 1.,
|
|
vertical_stretch: 1.,
|
|
..LayoutInfo::default()
|
|
}
|
|
}
|
|
|
|
fn implicit_size(self: Pin<&Self>, _window: &ComponentWindow) -> Size {
|
|
Default::default()
|
|
}
|
|
|
|
fn input_event_filter_before_children(
|
|
self: Pin<&Self>,
|
|
_: MouseEvent,
|
|
_window: &ComponentWindow,
|
|
_self_rc: &ItemRc,
|
|
) -> InputEventFilterResult {
|
|
InputEventFilterResult::ForwardEvent
|
|
}
|
|
|
|
fn input_event(
|
|
self: Pin<&Self>,
|
|
event: MouseEvent,
|
|
window: &ComponentWindow,
|
|
_self_rc: &sixtyfps_corelib::items::ItemRc,
|
|
) -> InputEventResult {
|
|
let dpr = window.scale_factor();
|
|
let size: qttypes::QSize = get_size!(self);
|
|
let mut data = self.data();
|
|
let active_controls = data.active_controls;
|
|
let pressed = data.pressed;
|
|
let left = self.native_padding_left();
|
|
let right = self.native_padding_right();
|
|
let top = self.native_padding_top();
|
|
let bottom = self.native_padding_bottom();
|
|
|
|
let mut handle_scrollbar = |horizontal: bool,
|
|
pos: qttypes::QPoint,
|
|
size: qttypes::QSize,
|
|
value_prop: Pin<&Property<f32>>,
|
|
page_size: i32,
|
|
max: i32| {
|
|
let pressed: bool = data.pressed != 0;
|
|
let value: i32 = value_prop.get() as i32;
|
|
let new_control = cpp!(unsafe [
|
|
pos as "QPoint",
|
|
value as "int",
|
|
page_size as "int",
|
|
max as "int",
|
|
size as "QSize",
|
|
active_controls as "int",
|
|
pressed as "bool",
|
|
dpr as "float",
|
|
horizontal as "bool"
|
|
] -> u32 as "int" {
|
|
ensure_initialized();
|
|
QStyleOptionSlider option;
|
|
initQSliderOptions(option, pressed, true, active_controls, 0, max / dpr, -value / dpr);
|
|
option.pageStep = page_size / dpr;
|
|
if (!horizontal) {
|
|
option.state ^= QStyle::State_Horizontal;
|
|
option.orientation = Qt::Vertical;
|
|
}
|
|
auto style = qApp->style();
|
|
option.rect = { QPoint{}, size / dpr };
|
|
return style->hitTestComplexControl(QStyle::CC_ScrollBar, &option, pos / dpr, nullptr);
|
|
});
|
|
|
|
#[allow(non_snake_case)]
|
|
let SC_ScrollBarSlider =
|
|
cpp!(unsafe []->u32 as "int" { return QStyle::SC_ScrollBarSlider;});
|
|
|
|
let (pos, size) = if horizontal { (pos.x, size.width) } else { (pos.y, size.height) };
|
|
|
|
let result = match event.what {
|
|
MouseEventType::MousePressed => {
|
|
data.pressed = if horizontal { 1 } else { 2 };
|
|
if new_control == SC_ScrollBarSlider {
|
|
data.pressed_x = pos as f32;
|
|
data.pressed_val = -value as f32;
|
|
}
|
|
data.active_controls = new_control;
|
|
InputEventResult::GrabMouse
|
|
}
|
|
MouseEventType::MouseExit => {
|
|
data.pressed = 0;
|
|
InputEventResult::EventIgnored
|
|
}
|
|
MouseEventType::MouseReleased => {
|
|
data.pressed = 0;
|
|
let new_val = cpp!(unsafe [active_controls as "int", value as "int", max as "int", page_size as "int", dpr as "float"] -> i32 as "int" {
|
|
switch (active_controls) {
|
|
case QStyle::SC_ScrollBarAddPage:
|
|
return -value + page_size;
|
|
case QStyle::SC_ScrollBarSubPage:
|
|
return -value - page_size;
|
|
case QStyle::SC_ScrollBarAddLine:
|
|
return -value + 3. * dpr;
|
|
case QStyle::SC_ScrollBarSubLine:
|
|
return -value - 3. * dpr;
|
|
case QStyle::SC_ScrollBarFirst:
|
|
return 0;
|
|
case QStyle::SC_ScrollBarLast:
|
|
return max;
|
|
default:
|
|
return -value;
|
|
}
|
|
});
|
|
value_prop.set(-(new_val.min(max).max(0) as f32));
|
|
InputEventResult::EventIgnored
|
|
}
|
|
MouseEventType::MouseMoved => {
|
|
if data.pressed != 0 && data.active_controls == SC_ScrollBarSlider {
|
|
let max = max as f32;
|
|
let new_val = data.pressed_val
|
|
+ ((pos as f32) - data.pressed_x) * (max + (page_size as f32))
|
|
/ size as f32;
|
|
value_prop.set(-new_val.min(max).max(0.));
|
|
InputEventResult::GrabMouse
|
|
} else {
|
|
InputEventResult::EventAccepted
|
|
}
|
|
}
|
|
};
|
|
self.data.set(data);
|
|
result
|
|
};
|
|
|
|
if pressed == 2 || (pressed == 0 && event.pos.x > (size.width as f32 - right)) {
|
|
handle_scrollbar(
|
|
false,
|
|
qttypes::QPoint {
|
|
x: (event.pos.x - (size.width as f32 - right)) as _,
|
|
y: (event.pos.y - top) as _,
|
|
},
|
|
qttypes::QSize {
|
|
width: (right - left) as _,
|
|
height: (size.height as f32 - (bottom + top)) as _,
|
|
},
|
|
Self::FIELD_OFFSETS.vertical_value.apply_pin(self),
|
|
self.vertical_page_size() as i32,
|
|
self.vertical_max() as i32,
|
|
)
|
|
} else if pressed == 1 || event.pos.y > (size.height as f32 - bottom) {
|
|
handle_scrollbar(
|
|
true,
|
|
qttypes::QPoint {
|
|
x: (event.pos.x - left) as _,
|
|
y: (event.pos.y - (size.height as f32 - bottom)) as _,
|
|
},
|
|
qttypes::QSize {
|
|
width: (size.width as f32 - (right + left)) as _,
|
|
height: (bottom - top) as _,
|
|
},
|
|
Self::FIELD_OFFSETS.horizontal_value.apply_pin(self),
|
|
self.horizontal_page_size() as i32,
|
|
self.horizontal_max() as i32,
|
|
)
|
|
} else {
|
|
Default::default()
|
|
}
|
|
}
|
|
|
|
fn key_event(self: Pin<&Self>, _: &KeyEvent, _window: &ComponentWindow) -> KeyEventResult {
|
|
KeyEventResult::EventIgnored
|
|
}
|
|
|
|
fn focus_event(self: Pin<&Self>, _: &FocusEvent, _window: &ComponentWindow) {}
|
|
|
|
fn_render! { this dpr size painter =>
|
|
|
|
let data = this.data();
|
|
let left = this.native_padding_left();
|
|
let right = this.native_padding_right();
|
|
let top = this.native_padding_top();
|
|
let bottom = this.native_padding_bottom();
|
|
let corner_rect = qttypes::QRectF {
|
|
x: ((size.width as f32 - (right - left)) / dpr) as _,
|
|
y: ((size.height as f32 - (bottom - top)) / dpr) as _,
|
|
width: ((right - left) / dpr) as _,
|
|
height: ((bottom - top) / dpr) as _,
|
|
};
|
|
cpp!(unsafe [painter as "QPainter*", corner_rect as "QRectF"] {
|
|
ensure_initialized();
|
|
QStyleOptionFrame frameOption;
|
|
frameOption.frameShape = QFrame::StyledPanel;
|
|
frameOption.lineWidth = 1;
|
|
frameOption.midLineWidth = 0;
|
|
frameOption.rect = corner_rect.toAlignedRect();
|
|
qApp->style()->drawPrimitive(QStyle::PE_PanelScrollAreaCorner, &frameOption, painter, nullptr);
|
|
frameOption.rect = QRect(QPoint(), corner_rect.toAlignedRect().topLeft());
|
|
qApp->style()->drawControl(QStyle::CE_ShapedFrame, &frameOption, painter, nullptr);
|
|
});
|
|
|
|
let draw_scrollbar = |horizontal: bool,
|
|
rect: qttypes::QRectF,
|
|
value: i32,
|
|
page_size: i32,
|
|
max: i32,
|
|
active_controls: u32,
|
|
pressed: bool| {
|
|
cpp!(unsafe [
|
|
painter as "QPainter*",
|
|
value as "int",
|
|
page_size as "int",
|
|
max as "int",
|
|
rect as "QRectF",
|
|
active_controls as "int",
|
|
pressed as "bool",
|
|
dpr as "float",
|
|
horizontal as "bool"
|
|
] {
|
|
auto r = rect.toAlignedRect();
|
|
// The mac style ignores painter translations (due to CGContextRef redirection) as well as
|
|
// option.rect's top-left - hence this hack with an intermediate buffer.
|
|
#if defined(Q_OS_MAC)
|
|
QImage scrollbar_image(r.size(), QImage::Format_ARGB32_Premultiplied);
|
|
scrollbar_image.fill(Qt::transparent);
|
|
{QPainter p(&scrollbar_image); QPainter *painter = &p;
|
|
#else
|
|
painter->save();
|
|
auto cleanup = qScopeGuard([&] { painter->restore(); });
|
|
painter->translate(r.topLeft()); // There is bugs in the styles if the scrollbar is not in (0,0)
|
|
#endif
|
|
QStyleOptionSlider option;
|
|
option.rect = QRect(QPoint(), r.size());
|
|
initQSliderOptions(option, pressed, true, active_controls, 0, max / dpr, -value / dpr);
|
|
option.subControls = QStyle::SC_All;
|
|
option.pageStep = page_size / dpr;
|
|
|
|
if (!horizontal) {
|
|
option.state ^= QStyle::State_Horizontal;
|
|
option.orientation = Qt::Vertical;
|
|
}
|
|
|
|
auto style = qApp->style();
|
|
style->drawComplexControl(QStyle::CC_ScrollBar, &option, painter, nullptr);
|
|
#if defined(Q_OS_MAC)
|
|
}
|
|
painter->drawImage(r.topLeft(), scrollbar_image);
|
|
#endif
|
|
});
|
|
};
|
|
|
|
draw_scrollbar(
|
|
false,
|
|
qttypes::QRectF {
|
|
x: ((size.width as f32 - right + left) / dpr) as _,
|
|
y: 0.,
|
|
width: ((right - left) / dpr) as _,
|
|
height: ((size.height as f32 - bottom + top) / dpr) as _,
|
|
},
|
|
this.vertical_value() as i32,
|
|
this.vertical_page_size() as i32,
|
|
this.vertical_max() as i32,
|
|
data.active_controls,
|
|
data.pressed == 2,
|
|
);
|
|
draw_scrollbar(
|
|
true,
|
|
qttypes::QRectF {
|
|
x: 0.,
|
|
y: ((size.height as f32 - bottom + top) / dpr) as _,
|
|
width: ((size.width as f32 - right + left) / dpr) as _,
|
|
height: ((bottom - top) / dpr) as _,
|
|
},
|
|
this.horizontal_value() as i32,
|
|
this.horizontal_page_size() as i32,
|
|
this.horizontal_max() as i32,
|
|
data.active_controls,
|
|
data.pressed == 1,
|
|
);
|
|
}
|
|
}
|
|
|
|
impl ItemConsts for NativeScrollView {
|
|
const cached_rendering_data_offset: const_field_offset::FieldOffset<Self, CachedRenderingData> =
|
|
Self::FIELD_OFFSETS.cached_rendering_data.as_unpinned_projection();
|
|
}
|
|
|
|
ItemVTable_static! { #[no_mangle] pub static NativeScrollViewVTable for NativeScrollView }
|
|
|
|
#[repr(C)]
|
|
#[derive(FieldOffsets, Default, SixtyFPSElement)]
|
|
#[pin]
|
|
pub struct NativeStandardListViewItem {
|
|
pub x: Property<f32>,
|
|
pub y: Property<f32>,
|
|
pub width: Property<f32>,
|
|
pub height: Property<f32>,
|
|
pub item: Property<sixtyfps_corelib::model::StandardListViewItem>,
|
|
pub index: Property<i32>,
|
|
pub is_selected: Property<bool>,
|
|
pub cached_rendering_data: CachedRenderingData,
|
|
}
|
|
|
|
impl Item for NativeStandardListViewItem {
|
|
fn init(self: Pin<&Self>, _window: &ComponentWindow) {}
|
|
|
|
fn geometry(self: Pin<&Self>) -> Rect {
|
|
euclid::rect(self.x(), self.y(), self.width(), self.height())
|
|
}
|
|
|
|
fn layouting_info(self: Pin<&Self>, window: &ComponentWindow) -> LayoutInfo {
|
|
let dpr = window.scale_factor();
|
|
let index: i32 = self.index();
|
|
let item = self.item();
|
|
let text: qttypes::QString = item.text.as_str().into();
|
|
|
|
let s = cpp!(unsafe [
|
|
index as "int",
|
|
text as "QString"
|
|
] -> qttypes::QSize as "QSize" {
|
|
ensure_initialized();
|
|
|
|
QStyleOptionViewItem option;
|
|
option.decorationPosition = QStyleOptionViewItem::Left;
|
|
option.decorationAlignment = Qt::AlignCenter;
|
|
option.displayAlignment = Qt::AlignLeft|Qt::AlignVCenter;
|
|
option.showDecorationSelected = qApp->style()->styleHint(QStyle::SH_ItemView_ShowDecorationSelected, nullptr, nullptr);
|
|
if (index % 2) {
|
|
option.features |= QStyleOptionViewItem::Alternate;
|
|
}
|
|
option.features |= QStyleOptionViewItem::HasDisplay;
|
|
option.text = text;
|
|
return qApp->style()->sizeFromContents(QStyle::CT_ItemViewItem, &option, QSize{}, nullptr);
|
|
});
|
|
let result = LayoutInfo {
|
|
min_width: s.width as f32 * dpr,
|
|
min_height: s.height as f32 * dpr,
|
|
..LayoutInfo::default()
|
|
};
|
|
result
|
|
}
|
|
|
|
fn implicit_size(self: Pin<&Self>, _window: &ComponentWindow) -> Size {
|
|
Default::default()
|
|
}
|
|
|
|
fn input_event_filter_before_children(
|
|
self: Pin<&Self>,
|
|
_: MouseEvent,
|
|
_window: &ComponentWindow,
|
|
_self_rc: &ItemRc,
|
|
) -> InputEventFilterResult {
|
|
InputEventFilterResult::ForwardAndIgnore
|
|
}
|
|
|
|
fn input_event(
|
|
self: Pin<&Self>,
|
|
_event: MouseEvent,
|
|
_window: &ComponentWindow,
|
|
_self_rc: &sixtyfps_corelib::items::ItemRc,
|
|
) -> InputEventResult {
|
|
InputEventResult::EventIgnored
|
|
}
|
|
|
|
fn key_event(self: Pin<&Self>, _: &KeyEvent, _window: &ComponentWindow) -> KeyEventResult {
|
|
KeyEventResult::EventIgnored
|
|
}
|
|
|
|
fn focus_event(self: Pin<&Self>, _: &FocusEvent, _window: &ComponentWindow) {}
|
|
|
|
fn_render! { this dpr size painter =>
|
|
let index: i32 = this.index();
|
|
let is_selected: bool = this.is_selected();
|
|
let item = this.item();
|
|
let text: qttypes::QString = item.text.as_str().into();
|
|
cpp!(unsafe [
|
|
painter as "QPainter*",
|
|
size as "QSize",
|
|
dpr as "float",
|
|
index as "int",
|
|
is_selected as "bool",
|
|
text as "QString"
|
|
] {
|
|
QStyleOptionViewItem option;
|
|
option.rect = QRect(QPoint(), size / dpr);
|
|
option.state = QStyle::State_Enabled | QStyle::State_Active;
|
|
if (is_selected) {
|
|
option.state |= QStyle::State_Selected;
|
|
}
|
|
option.decorationPosition = QStyleOptionViewItem::Left;
|
|
option.decorationAlignment = Qt::AlignCenter;
|
|
option.displayAlignment = Qt::AlignLeft|Qt::AlignVCenter;
|
|
option.showDecorationSelected = qApp->style()->styleHint(QStyle::SH_ItemView_ShowDecorationSelected, nullptr, nullptr);
|
|
if (index % 2) {
|
|
option.features |= QStyleOptionViewItem::Alternate;
|
|
}
|
|
option.features |= QStyleOptionViewItem::HasDisplay;
|
|
option.text = text;
|
|
qApp->style()->drawPrimitive(QStyle::PE_PanelItemViewRow, &option, painter, nullptr);
|
|
qApp->style()->drawControl(QStyle::CE_ItemViewItem, &option, painter, nullptr);
|
|
});
|
|
}
|
|
}
|
|
|
|
impl ItemConsts for NativeStandardListViewItem {
|
|
const cached_rendering_data_offset: const_field_offset::FieldOffset<Self, CachedRenderingData> =
|
|
Self::FIELD_OFFSETS.cached_rendering_data.as_unpinned_projection();
|
|
}
|
|
|
|
ItemVTable_static! { #[no_mangle] pub static NativeStandardListViewItemVTable for NativeStandardListViewItem }
|
|
|
|
#[repr(C)]
|
|
#[derive(FieldOffsets, Default, SixtyFPSElement)]
|
|
#[pin]
|
|
pub struct NativeComboBox {
|
|
pub x: Property<f32>,
|
|
pub y: Property<f32>,
|
|
pub width: Property<f32>,
|
|
pub height: Property<f32>,
|
|
pub enabled: Property<bool>,
|
|
pub pressed: Property<bool>,
|
|
pub is_open: Property<bool>,
|
|
pub current_value: Property<SharedString>,
|
|
pub cached_rendering_data: CachedRenderingData,
|
|
pub open_popup: Callback<VoidArg>,
|
|
}
|
|
|
|
impl Item for NativeComboBox {
|
|
fn init(self: Pin<&Self>, _window: &ComponentWindow) {}
|
|
|
|
fn geometry(self: Pin<&Self>) -> Rect {
|
|
euclid::rect(self.x(), self.y(), self.width(), self.height())
|
|
}
|
|
|
|
fn layouting_info(self: Pin<&Self>, window: &ComponentWindow) -> LayoutInfo {
|
|
let text: qttypes::QString = self.current_value().as_str().into();
|
|
let dpr = window.scale_factor();
|
|
let size = cpp!(unsafe [
|
|
text as "QString",
|
|
dpr as "float"
|
|
] -> qttypes::QSize as "QSize" {
|
|
ensure_initialized();
|
|
QStyleOptionButton option;
|
|
// FIXME
|
|
option.rect = option.fontMetrics.boundingRect("*****************");
|
|
option.text = std::move(text);
|
|
return qApp->style()->sizeFromContents(QStyle::CT_ComboBox, &option, option.rect.size(), nullptr) * dpr;
|
|
});
|
|
LayoutInfo {
|
|
min_width: size.width as f32,
|
|
min_height: size.height as f32,
|
|
..LayoutInfo::default()
|
|
}
|
|
}
|
|
|
|
fn implicit_size(self: Pin<&Self>, _window: &ComponentWindow) -> Size {
|
|
Default::default()
|
|
}
|
|
|
|
fn input_event_filter_before_children(
|
|
self: Pin<&Self>,
|
|
_: MouseEvent,
|
|
_window: &ComponentWindow,
|
|
_self_rc: &ItemRc,
|
|
) -> InputEventFilterResult {
|
|
InputEventFilterResult::ForwardEvent
|
|
}
|
|
|
|
fn input_event(
|
|
self: Pin<&Self>,
|
|
event: MouseEvent,
|
|
_window: &ComponentWindow,
|
|
_self_rc: &sixtyfps_corelib::items::ItemRc,
|
|
) -> InputEventResult {
|
|
let enabled = self.enabled();
|
|
if !enabled {
|
|
return InputEventResult::EventIgnored;
|
|
}
|
|
// FIXME: this is the input event of a button, but we need to do the proper hit test
|
|
|
|
Self::FIELD_OFFSETS.pressed.apply_pin(self).set(match event.what {
|
|
MouseEventType::MousePressed => true,
|
|
MouseEventType::MouseExit | MouseEventType::MouseReleased => false,
|
|
MouseEventType::MouseMoved => {
|
|
return if self.pressed() {
|
|
InputEventResult::GrabMouse
|
|
} else {
|
|
InputEventResult::EventIgnored
|
|
}
|
|
}
|
|
});
|
|
if matches!(event.what, MouseEventType::MouseReleased) {
|
|
Self::FIELD_OFFSETS.is_open.apply_pin(self).set(true);
|
|
Self::FIELD_OFFSETS.open_popup.apply_pin(self).call(&());
|
|
InputEventResult::EventAccepted
|
|
} else {
|
|
InputEventResult::GrabMouse
|
|
}
|
|
}
|
|
|
|
fn key_event(self: Pin<&Self>, _: &KeyEvent, _window: &ComponentWindow) -> KeyEventResult {
|
|
KeyEventResult::EventIgnored
|
|
}
|
|
|
|
fn focus_event(self: Pin<&Self>, _: &FocusEvent, _window: &ComponentWindow) {}
|
|
|
|
fn_render! { this dpr size painter =>
|
|
let down: bool = this.pressed();
|
|
let is_open: bool = this.is_open();
|
|
let text: qttypes::QString =
|
|
this.current_value().as_str().into();
|
|
let enabled = this.enabled();
|
|
cpp!(unsafe [
|
|
painter as "QPainter*",
|
|
text as "QString",
|
|
enabled as "bool",
|
|
size as "QSize",
|
|
down as "bool",
|
|
is_open as "bool",
|
|
dpr as "float"
|
|
] {
|
|
QStyleOptionComboBox option;
|
|
option.currentText = std::move(text);
|
|
option.rect = QRect(QPoint(), size / dpr);
|
|
if (down)
|
|
option.state |= QStyle::State_Sunken;
|
|
else
|
|
option.state |= QStyle::State_Raised;
|
|
if (enabled) {
|
|
option.state |= QStyle::State_Enabled;
|
|
} else {
|
|
option.palette.setCurrentColorGroup(QPalette::Disabled);
|
|
}
|
|
if (is_open)
|
|
option.state |= QStyle::State_On;
|
|
option.subControls = QStyle::SC_All;
|
|
qApp->style()->drawComplexControl(QStyle::CC_ComboBox, &option, painter, nullptr);
|
|
qApp->style()->drawControl(QStyle::CE_ComboBoxLabel, &option, painter, nullptr);
|
|
});
|
|
}
|
|
}
|
|
|
|
impl ItemConsts for NativeComboBox {
|
|
const cached_rendering_data_offset: const_field_offset::FieldOffset<Self, CachedRenderingData> =
|
|
Self::FIELD_OFFSETS.cached_rendering_data.as_unpinned_projection();
|
|
}
|
|
|
|
ItemVTable_static! { #[no_mangle] pub static NativeComboBoxVTable for NativeComboBox }
|
|
|
|
#[repr(C)]
|
|
#[derive(FieldOffsets, SixtyFPSElement)]
|
|
#[pin]
|
|
pub struct NativeStyleMetrics {
|
|
pub layout_spacing: Property<f32>,
|
|
pub layout_padding: Property<f32>,
|
|
pub text_cursor_width: Property<f32>,
|
|
pub window_background: Property<Color>,
|
|
}
|
|
|
|
impl Default for NativeStyleMetrics {
|
|
fn default() -> Self {
|
|
let s = NativeStyleMetrics {
|
|
layout_spacing: Default::default(),
|
|
layout_padding: Default::default(),
|
|
text_cursor_width: Default::default(),
|
|
window_background: Default::default(),
|
|
};
|
|
sixtyfps_init_native_style_metrics(&s);
|
|
s
|
|
}
|
|
}
|
|
|
|
impl NativeStyleMetrics {
|
|
pub fn new() -> Pin<Rc<Self>> {
|
|
Rc::pin(Self::default())
|
|
}
|
|
}
|
|
|
|
/// Initialize the native style metrics
|
|
#[no_mangle]
|
|
pub extern "C" fn sixtyfps_init_native_style_metrics(self_: &NativeStyleMetrics) {
|
|
let layout_spacing = cpp!(unsafe [] -> f32 as "float" {
|
|
ensure_initialized();
|
|
return qApp->style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing);
|
|
});
|
|
self_.layout_spacing.set(layout_spacing.max(0.0));
|
|
let text_cursor_width = cpp!(unsafe [] -> f32 as "float" {
|
|
return qApp->style()->pixelMetric(QStyle::PM_TextCursorWidth);
|
|
});
|
|
self_.text_cursor_width.set(text_cursor_width.max(0.0));
|
|
let window_background = cpp!(unsafe[] -> u32 as "QRgb" {
|
|
return qApp->palette().color(QPalette::Window).rgba();
|
|
});
|
|
self_.window_background.set(Color::from_argb_encoded(window_background))
|
|
}
|