/* LICENSE BEGIN This file is part of the SixtyFPS Project -- https://sixtyfps.io Copyright (c) 2020 Olivier Goffart Copyright (c) 2020 Simon Hausmann 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.60interface - 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::component::{ComponentRefPin, ComponentVTable}; use sixtyfps_corelib::eventloop::ComponentWindow; use sixtyfps_corelib::graphics::{HighLevelRenderingPrimitive, Rect, RenderingVariable, Resource}; use sixtyfps_corelib::input::{ FocusEvent, InputEventResult, KeyEvent, KeyEventResult, MouseEvent, MouseEventType, }; use sixtyfps_corelib::item_rendering::CachedRenderingData; use sixtyfps_corelib::items::{Item, ItemConsts, ItemVTable}; use sixtyfps_corelib::layout::LayoutInfo; use sixtyfps_corelib::rtti::*; use sixtyfps_corelib::{ItemVTable_static, Property, SharedArray, SharedString, Signal}; use sixtyfps_corelib_macros::*; use std::rc::Rc; use crate::qttypes; /// 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::FIELD_OFFSETS.width.apply_pin($self).get(); let height = Self::FIELD_OFFSETS.height.apply_pin($self).get(); if width < 1. || height < 1. { return Default::default(); }; qttypes::QSize { width: width as _, height: height as _ } }}; } struct QImageWrapArray { /// The image reference the array, so the array must outlive the image without being detached or accessed img: qttypes::QImage, array: SharedArray, } impl QImageWrapArray { pub fn new(size: qttypes::QSize, dpr: f32) -> Self { let mut array = SharedArray::default(); array.resize((size.width * size.height) as usize, 0u32); 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 to_resource(self) -> Resource { let size = self.img.size(); drop(self.img); Resource::EmbeddedRgbaImage { width: size.width, height: size.height, data: self.array } } } cpp! {{ #include #include #include #include #include #include void ensure_initialized() { static auto app [[maybe_unused]] = []{ 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, BuiltinItem)] #[pin] pub struct NativeButton { pub x: Property, pub y: Property, pub width: Property, pub height: Property, pub text: Property, pub enabled: Property, pub pressed: Property, pub clicked: Signal<()>, 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::FIELD_OFFSETS.x.apply_pin(self).get(), Self::FIELD_OFFSETS.y.apply_pin(self).get(), Self::FIELD_OFFSETS.width.apply_pin(self).get(), Self::FIELD_OFFSETS.height.apply_pin(self).get(), ) } fn rendering_primitive( self: Pin<&Self>, window: &ComponentWindow, ) -> HighLevelRenderingPrimitive { let down: bool = Self::FIELD_OFFSETS.pressed.apply_pin(self).get(); let text: qttypes::QString = Self::FIELD_OFFSETS.text.apply_pin(self).get().as_str().into(); let enabled = Self::FIELD_OFFSETS.enabled.apply_pin(self).get(); let size: qttypes::QSize = get_size!(self); let dpr = window.scale_factor(); let mut imgarray = QImageWrapArray::new(size, dpr); let img = &mut imgarray.img; cpp!(unsafe [ img as "QImage*", text as "QString", enabled as "bool", size as "QSize", down as "bool", dpr as "float" ] { QPainter p(img); 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; qApp->style()->drawControl(QStyle::CE_PushButton, &option, &p, nullptr); }); return HighLevelRenderingPrimitive::Image { source: imgarray.to_resource() }; } fn rendering_variables( self: Pin<&Self>, _window: &ComponentWindow, ) -> SharedArray { SharedArray::default() } fn layouting_info(self: Pin<&Self>, window: &ComponentWindow) -> LayoutInfo { let text: qttypes::QString = Self::FIELD_OFFSETS.text.apply_pin(self).get().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 input_event( self: Pin<&Self>, event: MouseEvent, _window: &ComponentWindow, _app_component: ComponentRefPin, ) -> InputEventResult { let enabled = Self::FIELD_OFFSETS.enabled.apply_pin(self).get(); 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::FIELD_OFFSETS.pressed.apply_pin(self).get() { InputEventResult::GrabMouse } else { InputEventResult::EventIgnored } } }); if matches!(event.what, MouseEventType::MouseReleased) { Self::FIELD_OFFSETS.clicked.apply_pin(self).emit(&()); 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) {} } impl ItemConsts for NativeButton { const cached_rendering_data_offset: const_field_offset::FieldOffset = Self::FIELD_OFFSETS.cached_rendering_data.as_unpinned_projection(); } ItemVTable_static! { #[no_mangle] pub static NativeButtonVTable for NativeButton } #[repr(C)] #[derive(FieldOffsets, Default, BuiltinItem)] #[pin] pub struct NativeCheckBox { pub x: Property, pub y: Property, pub width: Property, pub height: Property, pub toggled: Signal<()>, pub text: Property, pub checked: Property, 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::FIELD_OFFSETS.x.apply_pin(self).get(), Self::FIELD_OFFSETS.y.apply_pin(self).get(), Self::FIELD_OFFSETS.width.apply_pin(self).get(), Self::FIELD_OFFSETS.height.apply_pin(self).get(), ) } fn rendering_primitive( self: Pin<&Self>, window: &ComponentWindow, ) -> HighLevelRenderingPrimitive { let checked: bool = Self::FIELD_OFFSETS.checked.apply_pin(self).get(); let text: qttypes::QString = Self::FIELD_OFFSETS.text.apply_pin(self).get().as_str().into(); let size: qttypes::QSize = get_size!(self); let dpr = window.scale_factor(); let mut imgarray = QImageWrapArray::new(size, dpr); let img = &mut imgarray.img; cpp!(unsafe [ img as "QImage*", text as "QString", size as "QSize", checked as "bool", dpr as "float" ] { QPainter p(img); QStyleOptionButton option; option.text = std::move(text); option.rect = QRect(QPoint(), size / dpr); option.state |= checked ? QStyle::State_On : QStyle::State_Off; option.state |= QStyle::State_Enabled; qApp->style()->drawControl(QStyle::CE_CheckBox, &option, &p, nullptr); }); return HighLevelRenderingPrimitive::Image { source: imgarray.to_resource() }; } fn rendering_variables( self: Pin<&Self>, _window: &ComponentWindow, ) -> SharedArray { SharedArray::default() } fn layouting_info(self: Pin<&Self>, window: &ComponentWindow) -> LayoutInfo { let text: qttypes::QString = Self::FIELD_OFFSETS.text.apply_pin(self).get().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 input_event( self: Pin<&Self>, event: MouseEvent, _window: &ComponentWindow, _app_component: ComponentRefPin, ) -> InputEventResult { if matches!(event.what, MouseEventType::MouseReleased) { Self::FIELD_OFFSETS .checked .apply_pin(self) .set(!Self::FIELD_OFFSETS.checked.apply_pin(self).get()); Self::FIELD_OFFSETS.toggled.apply_pin(self).emit(&()) } InputEventResult::EventAccepted } fn key_event(self: Pin<&Self>, _: &KeyEvent, _window: &ComponentWindow) -> KeyEventResult { KeyEventResult::EventIgnored } fn focus_event(self: Pin<&Self>, _: &FocusEvent, _window: &ComponentWindow) {} } impl ItemConsts for NativeCheckBox { const cached_rendering_data_offset: const_field_offset::FieldOffset = 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, BuiltinItem)] #[pin] pub struct NativeSpinBox { pub x: Property, pub y: Property, pub width: Property, pub height: Property, pub value: Property, pub cached_rendering_data: CachedRenderingData, data: Property, } cpp! {{ void initQSpinBoxOptions(QStyleOptionSpinBox &option, bool pressed, 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}; option.state = QStyle::State_Enabled | 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::FIELD_OFFSETS.x.apply_pin(self).get(), Self::FIELD_OFFSETS.y.apply_pin(self).get(), Self::FIELD_OFFSETS.width.apply_pin(self).get(), Self::FIELD_OFFSETS.height.apply_pin(self).get(), ) } fn rendering_primitive( self: Pin<&Self>, window: &ComponentWindow, ) -> HighLevelRenderingPrimitive { let value: i32 = Self::FIELD_OFFSETS.value.apply_pin(self).get(); let size: qttypes::QSize = get_size!(self); let dpr = window.scale_factor(); let data = Self::FIELD_OFFSETS.data.apply_pin(self).get(); let active_controls = data.active_controls; let pressed = data.pressed; let mut imgarray = QImageWrapArray::new(size, dpr); let img = &mut imgarray.img; cpp!(unsafe [ img as "QImage*", value as "int", size as "QSize", active_controls as "int", pressed as "bool", dpr as "float" ] { QPainter p(img); auto style = qApp->style(); QStyleOptionSpinBox option; option.rect = QRect(QPoint(), size / dpr); initQSpinBoxOptions(option, pressed, active_controls); style->drawComplexControl(QStyle::CC_SpinBox, &option, &p, nullptr); auto text_rect = style->subControlRect(QStyle::CC_SpinBox, &option, QStyle::SC_SpinBoxEditField, nullptr); p.drawText(text_rect, QString::number(value)); }); return HighLevelRenderingPrimitive::Image { source: imgarray.to_resource() }; } fn rendering_variables( self: Pin<&Self>, _window: &ComponentWindow, ) -> SharedArray { SharedArray::default() } fn layouting_info(self: Pin<&Self>, window: &ComponentWindow) -> LayoutInfo { //let value: i32 = Self::FIELD_OFFSETS.value.apply_pin(self).get(); let data = Self::FIELD_OFFSETS.data.apply_pin(self).get(); let active_controls = data.active_controls; let pressed = data.pressed; let dpr = window.scale_factor(); let size = cpp!(unsafe [ //value as "int", active_controls as "int", pressed as "bool", dpr as "float" ] -> qttypes::QSize as "QSize" { ensure_initialized(); auto style = qApp->style(); QStyleOptionSpinBox option; initQSpinBoxOptions(option, pressed, 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 input_event( self: Pin<&Self>, event: MouseEvent, _window: &ComponentWindow, _app_component: ComponentRefPin, ) -> InputEventResult { let size: qttypes::QSize = get_size!(self); let mut data = Self::FIELD_OFFSETS.data.apply_pin(self).get(); 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", 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, 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 | MouseEventType::MouseReleased => { data.pressed = false; if new_control == cpp!(unsafe []->u32 as "int" { return QStyle::SC_SpinBoxUp;}) { self.value.set(Self::FIELD_OFFSETS.value.apply_pin(self).get() + 1); } if new_control == cpp!(unsafe []->u32 as "int" { return QStyle::SC_SpinBoxDown;}) { self.value.set(Self::FIELD_OFFSETS.value.apply_pin(self).get() - 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) {} } impl ItemConsts for NativeSpinBox { const cached_rendering_data_offset: const_field_offset::FieldOffset = 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, BuiltinItem)] #[pin] pub struct NativeSlider { pub x: Property, pub y: Property, pub width: Property, pub height: Property, pub value: Property, pub min: Property, pub max: Property, pub cached_rendering_data: CachedRenderingData, data: Property, } cpp! {{ void initQSliderOptions(QStyleOptionSlider &option, bool pressed, 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; option.state = QStyle::State_Enabled | 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::FIELD_OFFSETS.x.apply_pin(self).get(), Self::FIELD_OFFSETS.y.apply_pin(self).get(), Self::FIELD_OFFSETS.width.apply_pin(self).get(), Self::FIELD_OFFSETS.height.apply_pin(self).get(), ) } fn rendering_primitive( self: Pin<&Self>, window: &ComponentWindow, ) -> HighLevelRenderingPrimitive { let value = Self::FIELD_OFFSETS.value.apply_pin(self).get() as i32; let min = Self::FIELD_OFFSETS.min.apply_pin(self).get() as i32; let max = Self::FIELD_OFFSETS.max.apply_pin(self).get() as i32; let size: qttypes::QSize = get_size!(self); let dpr = window.scale_factor(); let data = Self::FIELD_OFFSETS.data.apply_pin(self).get(); let active_controls = data.active_controls; let pressed = data.pressed; let mut imgarray = QImageWrapArray::new(size, dpr); let img = &mut imgarray.img; cpp!(unsafe [ img as "QImage*", value as "int", min as "int", max as "int", size as "QSize", active_controls as "int", pressed as "bool", dpr as "float" ] { QPainter p(img); QStyleOptionSlider option; option.rect = QRect(QPoint(), size / dpr); initQSliderOptions(option, pressed, active_controls, min, max, value); auto style = qApp->style(); style->drawComplexControl(QStyle::CC_Slider, &option, &p, nullptr); }); return HighLevelRenderingPrimitive::Image { source: imgarray.to_resource() }; } fn rendering_variables( self: Pin<&Self>, _window: &ComponentWindow, ) -> SharedArray { SharedArray::default() } fn layouting_info(self: Pin<&Self>, window: &ComponentWindow) -> LayoutInfo { let value = Self::FIELD_OFFSETS.value.apply_pin(self).get() as i32; let min = Self::FIELD_OFFSETS.min.apply_pin(self).get() as i32; let max = Self::FIELD_OFFSETS.max.apply_pin(self).get() as i32; let data = Self::FIELD_OFFSETS.data.apply_pin(self).get(); let active_controls = data.active_controls; let pressed = data.pressed; let dpr = window.scale_factor(); let size = cpp!(unsafe [ 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, 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 input_event( self: Pin<&Self>, event: MouseEvent, _window: &ComponentWindow, _app_component: ComponentRefPin, ) -> InputEventResult { let size: qttypes::QSize = get_size!(self); let value = Self::FIELD_OFFSETS.value.apply_pin(self).get() as f32; let min = Self::FIELD_OFFSETS.min.apply_pin(self).get() as f32; let max = Self::FIELD_OFFSETS.max.apply_pin(self).get() as f32; let mut data = Self::FIELD_OFFSETS.data.apply_pin(self).get(); 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", 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, 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 => { 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 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 } } }; 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) {} } impl ItemConsts for NativeSlider { const cached_rendering_data_offset: const_field_offset::FieldOffset = Self::FIELD_OFFSETS.cached_rendering_data.as_unpinned_projection(); } ItemVTable_static! { #[no_mangle] pub static NativeSliderVTable for NativeSlider } #[repr(C)] #[derive(FieldOffsets, Default, BuiltinItem)] #[pin] pub struct NativeGroupBox { pub x: Property, pub y: Property, pub width: Property, pub height: Property, pub title: Property, pub cached_rendering_data: CachedRenderingData, pub native_padding_left: Property, pub native_padding_right: Property, pub native_padding_top: Property, pub native_padding_bottom: Property, } #[repr(C)] #[derive(FieldOffsets, Default)] #[pin] struct GroupBoxData { title: Property, paddings: Property, } 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::FIELD_OFFSETS.x.apply_pin(self).get(), Self::FIELD_OFFSETS.y.apply_pin(self).get(), Self::FIELD_OFFSETS.width.apply_pin(self).get(), Self::FIELD_OFFSETS.height.apply_pin(self).get(), ) } fn rendering_primitive( self: Pin<&Self>, window: &ComponentWindow, ) -> HighLevelRenderingPrimitive { let text: qttypes::QString = Self::FIELD_OFFSETS.title.apply_pin(self).get().as_str().into(); let size: qttypes::QSize = get_size!(self); let dpr = window.scale_factor(); let mut imgarray = QImageWrapArray::new(size, dpr); let img = &mut imgarray.img; cpp!(unsafe [ img as "QImage*", text as "QString", size as "QSize", dpr as "float" ] { QPainter p(img); QStyleOptionGroupBox option; option.state |= QStyle::State_Enabled; 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, &p, nullptr); }); return HighLevelRenderingPrimitive::Image { source: imgarray.to_resource() }; } fn rendering_variables( self: Pin<&Self>, _window: &ComponentWindow, ) -> SharedArray { SharedArray::default() } fn layouting_info(self: Pin<&Self>, _window: &ComponentWindow) -> LayoutInfo { let left = Self::FIELD_OFFSETS.native_padding_left.apply_pin(self).get(); let right = Self::FIELD_OFFSETS.native_padding_right.apply_pin(self).get(); let top = Self::FIELD_OFFSETS.native_padding_top.apply_pin(self).get(); let bottom = Self::FIELD_OFFSETS.native_padding_bottom.apply_pin(self).get(); LayoutInfo { min_width: left + right, min_height: top + bottom, horizontal_stretch: 1., vertical_stretch: 1., ..LayoutInfo::default() } } fn input_event( self: Pin<&Self>, _: MouseEvent, _window: &ComponentWindow, _app_component: ComponentRefPin, ) -> InputEventResult { InputEventResult::EventIgnored } fn key_event(self: Pin<&Self>, _: &KeyEvent, _window: &ComponentWindow) -> KeyEventResult { KeyEventResult::EventIgnored } fn focus_event(self: Pin<&Self>, _: &FocusEvent, _window: &ComponentWindow) {} } impl ItemConsts for NativeGroupBox { const cached_rendering_data_offset: const_field_offset::FieldOffset = Self::FIELD_OFFSETS.cached_rendering_data.as_unpinned_projection(); } ItemVTable_static! { #[no_mangle] pub static NativeGroupBoxVTable for NativeGroupBox } #[repr(C)] #[derive(FieldOffsets, Default, BuiltinItem)] #[pin] pub struct NativeLineEdit { pub x: Property, pub y: Property, pub width: Property, pub height: Property, pub cached_rendering_data: CachedRenderingData, pub native_padding_left: Property, pub native_padding_right: Property, pub native_padding_top: Property, pub native_padding_bottom: Property, pub focused: Property, pub enabled: Property, } 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::FIELD_OFFSETS.x.apply_pin(self).get(), Self::FIELD_OFFSETS.y.apply_pin(self).get(), Self::FIELD_OFFSETS.width.apply_pin(self).get(), Self::FIELD_OFFSETS.height.apply_pin(self).get(), ) } fn rendering_primitive( self: Pin<&Self>, window: &ComponentWindow, ) -> HighLevelRenderingPrimitive { let size: qttypes::QSize = get_size!(self); let dpr = window.scale_factor(); let focused: bool = Self::FIELD_OFFSETS.focused.apply_pin(self).get(); let enabled: bool = Self::FIELD_OFFSETS.enabled.apply_pin(self).get(); let mut imgarray = QImageWrapArray::new(size, dpr); let img = &mut imgarray.img; cpp!(unsafe [ img as "QImage*", size as "QSize", dpr as "float", enabled as "bool", focused as "bool" ] { QPainter p(img); 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; qApp->style()->drawPrimitive(QStyle::PE_PanelLineEdit, &option, &p, nullptr); }); return HighLevelRenderingPrimitive::Image { source: imgarray.to_resource() }; } fn rendering_variables( self: Pin<&Self>, _window: &ComponentWindow, ) -> SharedArray { SharedArray::default() } fn layouting_info(self: Pin<&Self>, _window: &ComponentWindow) -> LayoutInfo { let left = Self::FIELD_OFFSETS.native_padding_left.apply_pin(self).get(); let right = Self::FIELD_OFFSETS.native_padding_right.apply_pin(self).get(); let top = Self::FIELD_OFFSETS.native_padding_top.apply_pin(self).get(); let bottom = Self::FIELD_OFFSETS.native_padding_bottom.apply_pin(self).get(); LayoutInfo { min_width: left + right, min_height: top + bottom, horizontal_stretch: 1., ..LayoutInfo::default() } } fn input_event( self: Pin<&Self>, _: MouseEvent, _window: &ComponentWindow, _app_component: ComponentRefPin, ) -> InputEventResult { InputEventResult::EventIgnored } fn key_event(self: Pin<&Self>, _: &KeyEvent, _window: &ComponentWindow) -> KeyEventResult { KeyEventResult::EventIgnored } fn focus_event(self: Pin<&Self>, _: &FocusEvent, _window: &ComponentWindow) {} } impl ItemConsts for NativeLineEdit { const cached_rendering_data_offset: const_field_offset::FieldOffset = Self::FIELD_OFFSETS.cached_rendering_data.as_unpinned_projection(); } ItemVTable_static! { #[no_mangle] pub static NativeLineEditVTable for NativeLineEdit } #[repr(C)] #[derive(FieldOffsets, Default, BuiltinItem)] #[pin] pub struct NativeScrollView { pub x: Property, pub y: Property, pub width: Property, pub height: Property, pub horizontal_max: Property, pub horizontal_page_size: Property, pub horizontal_value: Property, pub vertical_max: Property, pub vertical_page_size: Property, pub vertical_value: Property, pub cached_rendering_data: CachedRenderingData, pub native_padding_left: Property, pub native_padding_right: Property, pub native_padding_top: Property, pub native_padding_bottom: Property, data: Property, } 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, 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::FIELD_OFFSETS.x.apply_pin(self).get(), Self::FIELD_OFFSETS.y.apply_pin(self).get(), Self::FIELD_OFFSETS.width.apply_pin(self).get(), Self::FIELD_OFFSETS.height.apply_pin(self).get(), ) } fn rendering_primitive( self: Pin<&Self>, window: &ComponentWindow, ) -> HighLevelRenderingPrimitive { let size: qttypes::QSize = get_size!(self); let dpr = window.scale_factor(); let mut imgarray = QImageWrapArray::new(size, dpr); let data = Self::FIELD_OFFSETS.data.apply_pin(self).get(); let left = Self::FIELD_OFFSETS.native_padding_left.apply_pin(self).get(); let right = Self::FIELD_OFFSETS.native_padding_right.apply_pin(self).get(); let top = Self::FIELD_OFFSETS.native_padding_top.apply_pin(self).get(); let bottom = Self::FIELD_OFFSETS.native_padding_bottom.apply_pin(self).get(); 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 _, }; let img: &mut qttypes::QImage = &mut imgarray.img; cpp!(unsafe [img as "QImage*", corner_rect as "QRectF"] { ensure_initialized(); QStyleOptionFrame frameOption; frameOption.frameShape = QFrame::StyledPanel; frameOption.lineWidth = 1; frameOption.midLineWidth = 0; frameOption.rect = corner_rect.toAlignedRect(); QPainter p(img); qApp->style()->drawPrimitive(QStyle::PE_PanelScrollAreaCorner, &frameOption, &p, nullptr); frameOption.rect = QRect(QPoint(), corner_rect.toAlignedRect().topLeft()); qApp->style()->drawControl(QStyle::CE_ShapedFrame, &frameOption, &p, nullptr); }); let draw_scrollbar = |horizontal: bool, rect: qttypes::QRectF, value: i32, page_size: i32, max: i32, active_controls: u32, pressed: bool| { cpp!(unsafe [ img as "QImage*", 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); #else QPainter p(img); p.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, 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, &p, nullptr); p.end(); #if defined(Q_OS_MAC) p.begin(img); p.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 _, }, Self::FIELD_OFFSETS.vertical_value.apply_pin(self).get() as i32, Self::FIELD_OFFSETS.vertical_page_size.apply_pin(self).get() as i32, Self::FIELD_OFFSETS.vertical_max.apply_pin(self).get() 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 _, }, Self::FIELD_OFFSETS.horizontal_value.apply_pin(self).get() as i32, Self::FIELD_OFFSETS.horizontal_page_size.apply_pin(self).get() as i32, Self::FIELD_OFFSETS.horizontal_max.apply_pin(self).get() as i32, data.active_controls, data.pressed == 1, ); return HighLevelRenderingPrimitive::Image { source: imgarray.to_resource() }; } fn rendering_variables( self: Pin<&Self>, _window: &ComponentWindow, ) -> SharedArray { SharedArray::default() } fn layouting_info(self: Pin<&Self>, _window: &ComponentWindow) -> LayoutInfo { let left = Self::FIELD_OFFSETS.native_padding_left.apply_pin(self).get(); let right = Self::FIELD_OFFSETS.native_padding_right.apply_pin(self).get(); let top = Self::FIELD_OFFSETS.native_padding_top.apply_pin(self).get(); let bottom = Self::FIELD_OFFSETS.native_padding_bottom.apply_pin(self).get(); LayoutInfo { min_width: left + right, min_height: top + bottom, horizontal_stretch: 1., vertical_stretch: 1., ..LayoutInfo::default() } } fn input_event( self: Pin<&Self>, event: MouseEvent, window: &ComponentWindow, _app_component: ComponentRefPin, ) -> InputEventResult { let dpr = window.scale_factor(); let size: qttypes::QSize = get_size!(self); let mut data = Self::FIELD_OFFSETS.data.apply_pin(self).get(); let active_controls = data.active_controls; let pressed = data.pressed; let left = Self::FIELD_OFFSETS.native_padding_left.apply_pin(self).get(); let right = Self::FIELD_OFFSETS.native_padding_right.apply_pin(self).get(); let top = Self::FIELD_OFFSETS.native_padding_top.apply_pin(self).get(); let bottom = Self::FIELD_OFFSETS.native_padding_bottom.apply_pin(self).get(); let mut handle_scrollbar = |horizontal: bool, pos: qttypes::QPoint, size: qttypes::QSize, value_prop: Pin<&Property>, 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, 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::FIELD_OFFSETS.vertical_page_size.apply_pin(self).get() as i32, Self::FIELD_OFFSETS.vertical_max.apply_pin(self).get() 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::FIELD_OFFSETS.horizontal_page_size.apply_pin(self).get() as i32, Self::FIELD_OFFSETS.horizontal_max.apply_pin(self).get() 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) {} } impl ItemConsts for NativeScrollView { const cached_rendering_data_offset: const_field_offset::FieldOffset = Self::FIELD_OFFSETS.cached_rendering_data.as_unpinned_projection(); } ItemVTable_static! { #[no_mangle] pub static NativeScrollViewVTable for NativeScrollView } #[repr(C)] #[derive(FieldOffsets, Default, BuiltinItem)] #[pin] pub struct NativeStandardListViewItem { pub x: Property, pub y: Property, pub width: Property, pub height: Property, pub item: Property, pub index: Property, pub is_selected: Property, 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::FIELD_OFFSETS.x.apply_pin(self).get(), Self::FIELD_OFFSETS.y.apply_pin(self).get(), Self::FIELD_OFFSETS.width.apply_pin(self).get(), Self::FIELD_OFFSETS.height.apply_pin(self).get(), ) } fn rendering_primitive( self: Pin<&Self>, window: &ComponentWindow, ) -> HighLevelRenderingPrimitive { let size: qttypes::QSize = get_size!(self); let dpr = window.scale_factor(); let index: i32 = Self::FIELD_OFFSETS.index.apply_pin(self).get(); let is_selected: bool = Self::FIELD_OFFSETS.is_selected.apply_pin(self).get(); let item = Self::FIELD_OFFSETS.item.apply_pin(self).get(); let text: qttypes::QString = item.text.as_str().into(); let mut imgarray = QImageWrapArray::new(size, dpr); let img = &mut imgarray.img; cpp!(unsafe [ img as "QImage*", size as "QSize", dpr as "float", index as "int", is_selected as "bool", text as "QString" ] { QPainter p(img); 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, &p, nullptr); qApp->style()->drawControl(QStyle::CE_ItemViewItem, &option, &p, nullptr); }); return HighLevelRenderingPrimitive::Image { source: imgarray.to_resource() }; } fn rendering_variables( self: Pin<&Self>, _window: &ComponentWindow, ) -> SharedArray { SharedArray::default() } fn layouting_info(self: Pin<&Self>, window: &ComponentWindow) -> LayoutInfo { let dpr = window.scale_factor(); let index: i32 = Self::FIELD_OFFSETS.index.apply_pin(self).get(); let item = Self::FIELD_OFFSETS.item.apply_pin(self).get(); 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 input_event( self: Pin<&Self>, _event: MouseEvent, _window: &ComponentWindow, _app_component: ComponentRefPin, ) -> InputEventResult { InputEventResult::EventIgnored } fn key_event(self: Pin<&Self>, _: &KeyEvent, _window: &ComponentWindow) -> KeyEventResult { KeyEventResult::EventIgnored } fn focus_event(self: Pin<&Self>, _: &FocusEvent, _window: &ComponentWindow) {} } impl ItemConsts for NativeStandardListViewItem { const cached_rendering_data_offset: const_field_offset::FieldOffset = Self::FIELD_OFFSETS.cached_rendering_data.as_unpinned_projection(); } ItemVTable_static! { #[no_mangle] pub static NativeStandardListViewItemVTable for NativeStandardListViewItem } #[repr(C)] #[derive(FieldOffsets, Default, BuiltinItem)] #[pin] pub struct NativeComboBox { pub x: Property, pub y: Property, pub width: Property, pub height: Property, pub enabled: Property, pub pressed: Property, pub is_open: Property, pub current_value: Property, pub cached_rendering_data: CachedRenderingData, } impl Item for NativeComboBox { fn init(self: Pin<&Self>, _window: &ComponentWindow) {} fn geometry(self: Pin<&Self>) -> Rect { euclid::rect( Self::FIELD_OFFSETS.x.apply_pin(self).get(), Self::FIELD_OFFSETS.y.apply_pin(self).get(), Self::FIELD_OFFSETS.width.apply_pin(self).get(), Self::FIELD_OFFSETS.height.apply_pin(self).get(), ) } fn rendering_primitive( self: Pin<&Self>, window: &ComponentWindow, ) -> HighLevelRenderingPrimitive { let down: bool = Self::FIELD_OFFSETS.pressed.apply_pin(self).get(); let is_open: bool = Self::FIELD_OFFSETS.is_open.apply_pin(self).get(); let text: qttypes::QString = Self::FIELD_OFFSETS.current_value.apply_pin(self).get().as_str().into(); let enabled = Self::FIELD_OFFSETS.enabled.apply_pin(self).get(); let size: qttypes::QSize = get_size!(self); let dpr = window.scale_factor(); let mut imgarray = QImageWrapArray::new(size, dpr); let img = &mut imgarray.img; cpp!(unsafe [ img as "QImage*", text as "QString", enabled as "bool", size as "QSize", down as "bool", is_open as "bool", dpr as "float" ] { QPainter p(img); 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; if (is_open) option.state |= QStyle::State_On; option.subControls = QStyle::SC_All; qApp->style()->drawComplexControl(QStyle::CC_ComboBox, &option, &p, nullptr); qApp->style()->drawControl(QStyle::CE_ComboBoxLabel, &option, &p, nullptr); }); return HighLevelRenderingPrimitive::Image { source: imgarray.to_resource() }; } fn rendering_variables( self: Pin<&Self>, _window: &ComponentWindow, ) -> SharedArray { SharedArray::default() } fn layouting_info(self: Pin<&Self>, window: &ComponentWindow) -> LayoutInfo { let text: qttypes::QString = Self::FIELD_OFFSETS.current_value.apply_pin(self).get().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 input_event( self: Pin<&Self>, event: MouseEvent, _window: &ComponentWindow, _app_component: ComponentRefPin, ) -> InputEventResult { let enabled = Self::FIELD_OFFSETS.enabled.apply_pin(self).get(); 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::FIELD_OFFSETS.pressed.apply_pin(self).get() { InputEventResult::GrabMouse } else { InputEventResult::EventIgnored } } }); if matches!(event.what, MouseEventType::MouseReleased) { Self::FIELD_OFFSETS.is_open.apply_pin(self).set(true); 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) {} } impl ItemConsts for NativeComboBox { const cached_rendering_data_offset: const_field_offset::FieldOffset = Self::FIELD_OFFSETS.cached_rendering_data.as_unpinned_projection(); } ItemVTable_static! { #[no_mangle] pub static NativeComboBoxVTable for NativeComboBox } #[repr(C)] #[derive(FieldOffsets, BuiltinItem)] #[pin] pub struct NativeStyleMetrics { pub layout_spacing: Property, pub layout_padding: Property, } impl Default for NativeStyleMetrics { fn default() -> Self { let s = NativeStyleMetrics { layout_spacing: Default::default(), layout_padding: Default::default(), }; sixtyfps_init_native_style_metrics(&s); s } } impl NativeStyleMetrics { pub fn new() -> Pin> { 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)); }