mirror of
https://github.com/slint-ui/slint.git
synced 2025-10-01 06:11:16 +00:00
Strawman implementation of a combo box
This commit is contained in:
parent
a6dbd0d7fa
commit
8a95b806c7
9 changed files with 291 additions and 5 deletions
|
@ -50,6 +50,7 @@ extern const cbindgen_private::ItemVTable NativeGroupBoxVTable;
|
||||||
extern const cbindgen_private::ItemVTable NativeLineEditVTable;
|
extern const cbindgen_private::ItemVTable NativeLineEditVTable;
|
||||||
extern const cbindgen_private::ItemVTable NativeScrollViewVTable;
|
extern const cbindgen_private::ItemVTable NativeScrollViewVTable;
|
||||||
extern const cbindgen_private::ItemVTable NativeStandardListViewItemVTable;
|
extern const cbindgen_private::ItemVTable NativeStandardListViewItemVTable;
|
||||||
|
extern const cbindgen_private::ItemVTable NativeComboBoxVTable;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,6 +135,7 @@ using cbindgen_private::NativeSlider;
|
||||||
using cbindgen_private::NativeSpinBox;
|
using cbindgen_private::NativeSpinBox;
|
||||||
using cbindgen_private::NativeStandardListViewItem;
|
using cbindgen_private::NativeStandardListViewItem;
|
||||||
using cbindgen_private::NativeStyleMetrics;
|
using cbindgen_private::NativeStyleMetrics;
|
||||||
|
using cbindgen_private::NativeComboBox;
|
||||||
|
|
||||||
namespace private_api {
|
namespace private_api {
|
||||||
constexpr inline ItemTreeNode make_item_node(std::uintptr_t offset,
|
constexpr inline ItemTreeNode make_item_node(std::uintptr_t offset,
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
This file is also available under commercial licensing terms.
|
This file is also available under commercial licensing terms.
|
||||||
Please contact info@sixtyfps.io for more information.
|
Please contact info@sixtyfps.io for more information.
|
||||||
LICENSE END */
|
LICENSE END */
|
||||||
import { Button, CheckBox, SpinBox, Slider, GroupBox, LineEdit, StandardListView } from "sixtyfps_widgets.60";
|
import { Button, CheckBox, SpinBox, Slider, GroupBox, LineEdit, StandardListView, ComboBox } from "sixtyfps_widgets.60";
|
||||||
|
|
||||||
App := Window {
|
App := Window {
|
||||||
width: 500px;
|
width: 500px;
|
||||||
|
@ -67,6 +67,13 @@ App := Window {
|
||||||
value: 42;
|
value: 42;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GroupBox {
|
||||||
|
title: "ComboBox";
|
||||||
|
ComboBox {
|
||||||
|
model: ["Select Something", "From this", "Combobox"];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GroupBox {
|
GroupBox {
|
||||||
|
|
|
@ -532,6 +532,20 @@ impl TypeRegister {
|
||||||
],
|
],
|
||||||
&[],
|
&[],
|
||||||
);
|
);
|
||||||
|
native_class(
|
||||||
|
&mut register,
|
||||||
|
"NativeComboBox",
|
||||||
|
&[
|
||||||
|
("x", Type::Length),
|
||||||
|
("y", Type::Length),
|
||||||
|
("width", Type::Length),
|
||||||
|
("height", Type::Length),
|
||||||
|
("current_value", Type::String),
|
||||||
|
("is_open", Type::Bool),
|
||||||
|
("enabled", Type::Bool),
|
||||||
|
],
|
||||||
|
&[],
|
||||||
|
);
|
||||||
|
|
||||||
let mut native_style_metrics =
|
let mut native_style_metrics =
|
||||||
BuiltinElement::new(Rc::new(NativeClass::new_with_properties(
|
BuiltinElement::new(Rc::new(NativeClass::new_with_properties(
|
||||||
|
|
|
@ -103,3 +103,37 @@ export StandardListView := ListView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export ComboBox := NativeComboBox {
|
||||||
|
property <[string]> model;
|
||||||
|
property <int> current_index : -1;
|
||||||
|
enabled: true;
|
||||||
|
|
||||||
|
if (is_open && enabled) : Rectangle {
|
||||||
|
y: root.height;
|
||||||
|
width: root.width;
|
||||||
|
for value[idx] in root.model: Rectangle {
|
||||||
|
y: idx * height;
|
||||||
|
width: 100%;
|
||||||
|
height: root.height;
|
||||||
|
color: idx == root.current_index ? lightblue : white;
|
||||||
|
Text {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
text: value;
|
||||||
|
}
|
||||||
|
TouchArea {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
clicked => {
|
||||||
|
if (root.enabled) {
|
||||||
|
root.current_index = idx;
|
||||||
|
root.current_value = value;
|
||||||
|
}
|
||||||
|
is_open = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -418,3 +418,72 @@ export StandardListView := ListView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export ComboBox := Rectangle {
|
||||||
|
property <[string]> model;
|
||||||
|
property <int> current_index : -1;
|
||||||
|
property <string> current_value;
|
||||||
|
property <bool> is_open: false;
|
||||||
|
property <bool> enabled: true;
|
||||||
|
|
||||||
|
border_width: 2px;
|
||||||
|
border_radius: 10px;
|
||||||
|
border_color: Palette.text_color;
|
||||||
|
color: touch_area.pressed ? Palette.heighlight_background : Palette.button_background;
|
||||||
|
animate color { duration: 100ms; }
|
||||||
|
horizontal-stretch: 0;
|
||||||
|
vertical-stretch: 0;
|
||||||
|
minimum_width: 170px;
|
||||||
|
|
||||||
|
HorizontalLayout {
|
||||||
|
padding: root.border_radius;
|
||||||
|
t := Text {
|
||||||
|
text <=> root.current_value;
|
||||||
|
horizontal_alignment: align_center;
|
||||||
|
vertical_alignment: align_center;
|
||||||
|
color: root.enabled ? Palette.text_color : Palette.text_color_disabled;
|
||||||
|
horizontal-stretch: 1;
|
||||||
|
}
|
||||||
|
Text {
|
||||||
|
text:"▼";
|
||||||
|
color: root.enabled ? Palette.text_color : Palette.text_color_disabled;
|
||||||
|
horizontal-stretch: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
touch_area := TouchArea {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
clicked => {
|
||||||
|
if (root.enabled) {
|
||||||
|
is_open = !is_open;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_open && enabled) : Rectangle {
|
||||||
|
y: root.height;
|
||||||
|
width: root.width;
|
||||||
|
for value[idx] in root.model: Rectangle {
|
||||||
|
y: idx * height;
|
||||||
|
width: 100%;
|
||||||
|
height: t.height;
|
||||||
|
color: idx == root.current_index ? Palette.heighlight_background : Palette.base_background_color;
|
||||||
|
Text {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
text: value;
|
||||||
|
}
|
||||||
|
TouchArea {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
clicked => {
|
||||||
|
if (root.enabled) {
|
||||||
|
root.current_index = idx;
|
||||||
|
root.current_value = value;
|
||||||
|
}
|
||||||
|
is_open = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -14,11 +14,10 @@ When adding an item or a property, it needs to be kept in sync with different pl
|
||||||
(This is less than ideal and maybe we can have some automation later)
|
(This is less than ideal and maybe we can have some automation later)
|
||||||
|
|
||||||
- It needs to be changed in this module
|
- It needs to be changed in this module
|
||||||
- The ItemVTable_static at the end of datastructures.rs (new items only)
|
|
||||||
- In the compiler: typeregister.rs
|
- In the compiler: typeregister.rs
|
||||||
- In the vewer: main.rs
|
- In the vewer: main.rs
|
||||||
- For the C++ code (new item only): the build.rs to export the new item, and the `using` declaration in sixtyfps.h
|
- 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(unsafe_code)]
|
#![allow(unsafe_code)]
|
||||||
|
|
|
@ -64,7 +64,8 @@ pub type NativeWidgets =
|
||||||
(widgets::NativeLineEdit,
|
(widgets::NativeLineEdit,
|
||||||
(widgets::NativeScrollView,
|
(widgets::NativeScrollView,
|
||||||
(widgets::NativeStandardListViewItem,
|
(widgets::NativeStandardListViewItem,
|
||||||
()))))))));
|
(widgets::NativeComboBox,
|
||||||
|
())))))))));
|
||||||
|
|
||||||
#[cfg(not(no_qt))]
|
#[cfg(not(no_qt))]
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
|
|
|
@ -7,7 +7,23 @@
|
||||||
This file is also available under commercial licensing terms.
|
This file is also available under commercial licensing terms.
|
||||||
Please contact info@sixtyfps.io for more information.
|
Please contact info@sixtyfps.io for more information.
|
||||||
LICENSE END */
|
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: typeregister.rs
|
||||||
|
- 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)]
|
#![allow(non_upper_case_globals)]
|
||||||
|
|
||||||
use const_field_offset::FieldOffsets;
|
use const_field_offset::FieldOffsets;
|
||||||
use core::pin::Pin;
|
use core::pin::Pin;
|
||||||
use cpp::cpp;
|
use cpp::cpp;
|
||||||
|
@ -1600,6 +1616,149 @@ impl ItemConsts for NativeStandardListViewItem {
|
||||||
|
|
||||||
ItemVTable_static! { #[no_mangle] pub static NativeStandardListViewItemVTable for NativeStandardListViewItem }
|
ItemVTable_static! { #[no_mangle] pub static NativeStandardListViewItemVTable for NativeStandardListViewItem }
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(FieldOffsets, Default, BuiltinItem)]
|
||||||
|
#[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,
|
||||||
|
}
|
||||||
|
|
||||||
|
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<RenderingVariable> {
|
||||||
|
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, CachedRenderingData> =
|
||||||
|
Self::FIELD_OFFSETS.cached_rendering_data.as_unpinned_projection();
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemVTable_static! { #[no_mangle] pub static NativeComboBoxVTable for NativeComboBox }
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(FieldOffsets, BuiltinItem)]
|
#[derive(FieldOffsets, BuiltinItem)]
|
||||||
#[pin]
|
#[pin]
|
||||||
|
|
|
@ -236,6 +236,7 @@ fn gen_backend_qt(include_dir: &Path) -> anyhow::Result<()> {
|
||||||
"NativeLineEdit",
|
"NativeLineEdit",
|
||||||
"NativeScrollView",
|
"NativeScrollView",
|
||||||
"NativeStandardListViewItem",
|
"NativeStandardListViewItem",
|
||||||
|
"NativeComboBox",
|
||||||
]
|
]
|
||||||
.iter()
|
.iter()
|
||||||
.map(|x| x.to_string())
|
.map(|x| x.to_string())
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue