add property for slider to support vertical orientation (#3236)

This commit is contained in:
Eric 2023-08-16 15:58:50 +08:00 committed by GitHub
parent 12e064b07d
commit 4b9fb89332
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 109 additions and 44 deletions

View file

@ -7,6 +7,7 @@
- **`value`** (_in-out_ _float_): The value. - **`value`** (_in-out_ _float_): The value.
- **`minimum`** (_in_ _float_): The minimum value (default: 0) - **`minimum`** (_in_ _float_): The minimum value (default: 0)
- **`maximum`** (_in_ _float_): The maximum value (default: 100) - **`maximum`** (_in_ _float_): The maximum value (default: 100)
- **`vertical`** (_in_ _bool_): If set to true the Slider is displayed vertical (default: false).
### Callbacks ### Callbacks

View file

@ -46,7 +46,7 @@ impl Item for NativeScrollView {
cpp!(unsafe [] -> qttypes::QMargins as "QMargins" { cpp!(unsafe [] -> qttypes::QMargins as "QMargins" {
ensure_initialized(); ensure_initialized();
QStyleOptionSlider option; QStyleOptionSlider option;
initQSliderOptions(option, false, true, 0, 0, 1000, 1000); initQSliderOptions(option, false, true, 0, 0, 1000, 1000, false);
int extent = qApp->style()->pixelMetric(QStyle::PM_ScrollBarExtent, &option, nullptr); int extent = qApp->style()->pixelMetric(QStyle::PM_ScrollBarExtent, &option, nullptr);
int sliderMin = qApp->style()->pixelMetric(QStyle::PM_ScrollBarSliderMin, &option, nullptr); int sliderMin = qApp->style()->pixelMetric(QStyle::PM_ScrollBarSliderMin, &option, nullptr);
@ -157,7 +157,7 @@ impl Item for NativeScrollView {
] -> u32 as "int" { ] -> u32 as "int" {
ensure_initialized(); ensure_initialized();
QStyleOptionSlider option; QStyleOptionSlider option;
initQSliderOptions(option, pressed, true, active_controls, 0, max, -value); initQSliderOptions(option, pressed, true, active_controls, 0, max, -value, false);
option.pageStep = page_size; option.pageStep = page_size;
if (!horizontal) { if (!horizontal) {
option.state ^= QStyle::State_Horizontal; option.state ^= QStyle::State_Horizontal;
@ -383,7 +383,7 @@ impl Item for NativeScrollView {
QStyleOptionSlider option; QStyleOptionSlider option;
option.state |= QStyle::State(initial_state); option.state |= QStyle::State(initial_state);
option.rect = QRect(QPoint(), r.size()); option.rect = QRect(QPoint(), r.size());
initQSliderOptions(option, pressed, true, active_controls, 0, max / dpr, -value / dpr); initQSliderOptions(option, pressed, true, active_controls, 0, max / dpr, -value / dpr, false);
option.subControls = QStyle::SC_All; option.subControls = QStyle::SC_All;
option.pageStep = page_size / dpr; option.pageStep = page_size / dpr;
if (has_focus) if (has_focus)

View file

@ -26,6 +26,7 @@ pub struct NativeSlider {
pub y: Property<LogicalLength>, pub y: Property<LogicalLength>,
pub width: Property<LogicalLength>, pub width: Property<LogicalLength>,
pub height: Property<LogicalLength>, pub height: Property<LogicalLength>,
pub vertical: Property<bool>,
pub enabled: Property<bool>, pub enabled: Property<bool>,
pub value: Property<f32>, pub value: Property<f32>,
pub minimum: Property<f32>, pub minimum: Property<f32>,
@ -38,10 +39,15 @@ pub struct NativeSlider {
} }
cpp! {{ cpp! {{
void initQSliderOptions(QStyleOptionSlider &option, bool pressed, bool enabled, int active_controls, float minimum, float maximum, float float_value) { void initQSliderOptions(QStyleOptionSlider &option, bool pressed, bool enabled, int active_controls, float minimum, float maximum, float float_value, bool vertical) {
option.subControls = QStyle::SC_SliderGroove | QStyle::SC_SliderHandle; option.subControls = QStyle::SC_SliderGroove | QStyle::SC_SliderHandle;
option.activeSubControls = { active_controls }; option.activeSubControls = { active_controls };
if (vertical) {
option.orientation = Qt::Vertical;
} else {
option.orientation = Qt::Horizontal; option.orientation = Qt::Horizontal;
option.state |= QStyle::State_Horizontal;
}
// Slint slider supports floating point ranges, while Qt uses integer. To support (0..1) ranges // Slint slider supports floating point ranges, while Qt uses integer. To support (0..1) ranges
// of values, scale up a little, before truncating to integer values. // of values, scale up a little, before truncating to integer values.
option.maximum = maximum * 1024; option.maximum = maximum * 1024;
@ -54,7 +60,6 @@ void initQSliderOptions(QStyleOptionSlider &option, bool pressed, bool enabled,
} else { } else {
option.palette.setCurrentColorGroup(QPalette::Disabled); option.palette.setCurrentColorGroup(QPalette::Disabled);
} }
option.state |= QStyle::State_Horizontal;
if (pressed) { if (pressed) {
option.state |= QStyle::State_Sunken | QStyle::State_MouseOver; option.state |= QStyle::State_Sunken | QStyle::State_MouseOver;
} }
@ -88,6 +93,7 @@ impl Item for NativeSlider {
let data = self.data(); let data = self.data();
let active_controls = data.active_controls; let active_controls = data.active_controls;
let pressed = data.pressed; let pressed = data.pressed;
let vertical = self.vertical();
let widget: NonNull<()> = SlintTypeErasedWidgetPtr::qwidget_ptr(&self.widget_ptr); let widget: NonNull<()> = SlintTypeErasedWidgetPtr::qwidget_ptr(&self.widget_ptr);
let size = cpp!(unsafe [ let size = cpp!(unsafe [
@ -97,24 +103,39 @@ impl Item for NativeSlider {
max as "float", max as "float",
active_controls as "int", active_controls as "int",
pressed as "bool", pressed as "bool",
vertical as "bool",
widget as "QWidget*" widget as "QWidget*"
] -> qttypes::QSize as "QSize" { ] -> qttypes::QSize as "QSize" {
ensure_initialized(); ensure_initialized();
QStyleOptionSlider option; QStyleOptionSlider option;
initQSliderOptions(option, pressed, enabled, active_controls, min, max, value); initQSliderOptions(option, pressed, enabled, active_controls, min, max, value, vertical);
auto style = qApp->style(); auto style = qApp->style();
auto thick = style->pixelMetric(QStyle::PM_SliderThickness, &option, widget); auto thick = style->pixelMetric(QStyle::PM_SliderThickness, &option, widget);
return style->sizeFromContents(QStyle::CT_Slider, &option, QSize(0, thick), widget); return style->sizeFromContents(QStyle::CT_Slider, &option, QSize(0, thick), widget);
}); });
match orientation { match orientation {
Orientation::Horizontal => { Orientation::Horizontal => {
if !vertical {
LayoutInfo { min: size.width as f32, stretch: 1., ..LayoutInfo::default() } LayoutInfo { min: size.width as f32, stretch: 1., ..LayoutInfo::default() }
} } else {
Orientation::Vertical => LayoutInfo { LayoutInfo {
min: size.height as f32, min: size.height as f32,
max: size.height as f32, max: size.height as f32,
..LayoutInfo::default() ..LayoutInfo::default()
}, }
}
}
Orientation::Vertical => {
if !vertical {
LayoutInfo {
min: size.height as f32,
max: size.height as f32,
..LayoutInfo::default()
}
} else {
LayoutInfo { min: size.width as f32, stretch: 1., ..LayoutInfo::default() }
}
}
} }
} }
@ -142,6 +163,7 @@ impl Item for NativeSlider {
let mut data = self.data(); let mut data = self.data();
let active_controls = data.active_controls; let active_controls = data.active_controls;
let pressed: bool = data.pressed != 0; let pressed: bool = data.pressed != 0;
let vertical = self.vertical();
let pos = event let pos = event
.position() .position()
.map(|p| qttypes::QPoint { x: p.x as _, y: p.y as _ }) .map(|p| qttypes::QPoint { x: p.x as _, y: p.y as _ })
@ -157,11 +179,12 @@ impl Item for NativeSlider {
max as "float", max as "float",
active_controls as "int", active_controls as "int",
pressed as "bool", pressed as "bool",
vertical as "bool",
widget as "QWidget*" widget as "QWidget*"
] -> u32 as "int" { ] -> u32 as "int" {
ensure_initialized(); ensure_initialized();
QStyleOptionSlider option; QStyleOptionSlider option;
initQSliderOptions(option, pressed, enabled, active_controls, min, max, value); initQSliderOptions(option, pressed, enabled, active_controls, min, max, value, vertical);
auto style = qApp->style(); auto style = qApp->style();
option.rect = { QPoint{}, size }; option.rect = { QPoint{}, size };
return style->hitTestComplexControl(QStyle::CC_Slider, &option, pos, widget); return style->hitTestComplexControl(QStyle::CC_Slider, &option, pos, widget);
@ -176,7 +199,7 @@ impl Item for NativeSlider {
button: PointerEventButton::Left, button: PointerEventButton::Left,
click_count: _, click_count: _,
} => { } => {
data.pressed_x = pos.x as f32; data.pressed_x = if vertical { pos.y as f32 } else { pos.x as f32 };
data.pressed = 1; data.pressed = 1;
data.pressed_val = value; data.pressed_val = value;
InputEventResult::GrabMouse InputEventResult::GrabMouse
@ -186,10 +209,12 @@ impl Item for NativeSlider {
InputEventResult::EventAccepted InputEventResult::EventAccepted
} }
MouseEvent::Moved { position: pos } => { MouseEvent::Moved { position: pos } => {
let (coord, size) =
if vertical { (pos.y, size.height) } else { (pos.x, size.width) };
if data.pressed != 0 { if data.pressed != 0 {
// FIXME: use QStyle::subControlRect to find out the actual size of the groove // FIXME: use QStyle::subControlRect to find out the actual size of the groove
let new_val = data.pressed_val let new_val = data.pressed_val
+ ((pos.x as f32) - data.pressed_x) * (max - min) / size.width as f32; + ((coord as f32) - data.pressed_x) * (max - min) / size as f32;
let new_val = new_val.max(min).min(max); let new_val = new_val.max(min).min(max);
self.value.set(new_val); self.value.set(new_val);
Self::FIELD_OFFSETS.changed.apply_pin(self).call(&(new_val,)); Self::FIELD_OFFSETS.changed.apply_pin(self).call(&(new_val,));
@ -242,6 +267,7 @@ impl Item for NativeSlider {
let data = this.data(); let data = this.data();
let active_controls = data.active_controls; let active_controls = data.active_controls;
let pressed = data.pressed; let pressed = data.pressed;
let vertical = this.vertical();
cpp!(unsafe [ cpp!(unsafe [
painter as "QPainterPtr*", painter as "QPainterPtr*",
@ -253,6 +279,7 @@ impl Item for NativeSlider {
size as "QSize", size as "QSize",
active_controls as "int", active_controls as "int",
pressed as "bool", pressed as "bool",
vertical as "bool",
dpr as "float", dpr as "float",
initial_state as "int" initial_state as "int"
] { ] {
@ -260,7 +287,7 @@ impl Item for NativeSlider {
option.initFrom(widget); option.initFrom(widget);
option.state |= QStyle::State(initial_state); option.state |= QStyle::State(initial_state);
option.rect = QRect(QPoint(), size / dpr); option.rect = QRect(QPoint(), size / dpr);
initQSliderOptions(option, pressed, enabled, active_controls, min, max, value); initQSliderOptions(option, pressed, enabled, active_controls, min, max, value, vertical);
auto style = qApp->style(); auto style = qApp->style();
style->drawComplexControl(QStyle::CC_Slider, &option, painter->get(), widget); style->drawComplexControl(QStyle::CC_Slider, &option, painter->get(), widget);
}); });

View file

@ -440,6 +440,7 @@ export component NativeSlider {
in-out property <float> value; in-out property <float> value;
in property <float> minimum; in property <float> minimum;
in property <float> maximum: 100; in property <float> maximum: 100;
in property <bool> vertical: false;
callback changed(float); callback changed(float);
//-is_internal //-is_internal
} }

View file

@ -6,15 +6,17 @@ import { Palette } from "styling.slint";
export component Slider { export component Slider {
callback changed(float /* value */); callback changed(float /* value */);
in property<bool> vertical: false;
in property<float> maximum: 100; in property<float> maximum: 100;
in property<float> minimum: 0; in property<float> minimum: 0;
in property<bool> enabled <=> i-touch-area.enabled; in property<bool> enabled <=> i-touch-area.enabled;
out property<bool> has-focus: i-focus-scope.has-focus; out property<bool> has-focus: i-focus-scope.has-focus;
in-out property<float> value; in-out property<float> value;
min-height: 20px; min-width: vertical ? 20px : 0px;
vertical-stretch: 0; min-height: vertical ? 0px : 20px;
horizontal-stretch: 1; vertical-stretch: vertical ? 1 : 0;
horizontal-stretch: vertical ? 0 : 1;
accessible-role: slider; accessible-role: slider;
accessible-value: root.value; accessible-value: root.value;
accessible-value-minimum: root.minimum; accessible-value-minimum: root.minimum;
@ -22,21 +24,24 @@ export component Slider {
accessible-value-step: (root.maximum - root.minimum) / 100; accessible-value-step: (root.maximum - root.minimum) / 100;
i-rail := Rectangle { i-rail := Rectangle {
height: 4px; width: vertical ? 4px : parent.width;
height: vertical ? parent.height : 4px;
background: Palette.control-stroke; background: Palette.control-stroke;
border-radius: 2px; border-radius: 2px;
} }
i-track := Rectangle { i-track := Rectangle {
x: 0; x: vertical ? (parent.width - self.width) / 2 : 0;
height: i-rail.height; y: vertical ? 0 : (parent.height - self.height) / 2;
width: vertical ? i-rail.width : i-thumb.x + (i-thumb.width / 2);
height: vertical ? i-thumb.y + (i-thumb.height / 2) : i-rail.height;
background: Palette.accent-default; background: Palette.accent-default;
border-radius: i-rail.border-radius; border-radius: i-rail.border-radius;
width: i-thumb.x + (i-thumb.width / 2);
} }
i-thumb := Rectangle { i-thumb := Rectangle {
x: (parent.width - self.width) * (root.value - root.minimum) / (root.maximum - root.minimum); x: vertical ? (parent.width - self.width) / 2 : (parent.width - self.width) * (root.value - root.minimum) / (root.maximum - root.minimum);
y: vertical ? (parent.height - self.height) * (root.value - root.minimum) / (root.maximum - root.minimum) : (parent.height - self.height) / 2;
width: 20px; width: 20px;
height: self.width; height: self.width;
border-radius: 10px; border-radius: 10px;
@ -75,23 +80,36 @@ export component Slider {
} }
moved => { moved => {
if (self.enabled && self.pressed) { if (!vertical && self.enabled && self.pressed) {
root.value = max(root.minimum, min(root.maximum, root.value = max(root.minimum, min(root.maximum,
self.pressed-value + (i-touch-area.mouse-x - i-touch-area.pressed-x) * (root.maximum - root.minimum) / (root.width - i-thumb.width))); self.pressed-value + (i-touch-area.mouse-x - i-touch-area.pressed-x) * (root.maximum - root.minimum) / (root.width - i-thumb.width)));
root.changed(root.value); root.changed(root.value);
} }
if (vertical && self.enabled && self.pressed) {
root.value = max(root.minimum, min(root.maximum,
self.pressed-value + (i-touch-area.mouse-y - i-touch-area.pressed-y) * (root.maximum - root.minimum) / (root.height - i-thumb.height)));
root.changed(root.value);
}
} }
} }
i-focus-scope := FocusScope { i-focus-scope := FocusScope {
x: 0; x: 0;
y: 0;
width: 0; width: 0;
height: 0;
key-pressed(event) => { key-pressed(event) => {
if (self.enabled && event.text == Key.RightArrow) { if (!vertical && self.enabled && event.text == Key.RightArrow) {
root.value = Math.min(root.value + 1, root.maximum); root.value = Math.min(root.value + 1, root.maximum);
accept accept
} else if (self.enabled && event.text == Key.LeftArrow) { } else if (!vertical && self.enabled && event.text == Key.LeftArrow) {
root.value = Math.max(root.value - 1, root.minimum);
accept
} else if (vertical && self.enabled && event.text == Key.DownArrow) {
root.value = Math.min(root.value + 1, root.maximum);
accept
} else if (vertical && self.enabled && event.text == Key.UpArrow) {
root.value = Math.max(root.value - 1, root.minimum); root.value = Math.max(root.value - 1, root.minimum);
accept accept
} else { } else {

View file

@ -8,13 +8,15 @@ import { Palette, Elevation } from "styling.slint";
export component Slider { export component Slider {
callback changed(float /* value */); callback changed(float /* value */);
in property<bool> vertical: false;
in property <float> maximum: 100; in property <float> maximum: 100;
in property <bool> enabled <=> i-touch-area.enabled; in property <bool> enabled <=> i-touch-area.enabled;
in property <float> minimum: 0; in property <float> minimum: 0;
out property <bool> has-focus: i-focus-scope.has-focus; out property <bool> has-focus: i-focus-scope.has-focus;
in-out property <float> value; in-out property <float> value;
min-height: 20px; min-width: vertical ? 20px : 0px;
min-height: vertical ? 0px : 20px;
accessible-role: slider; accessible-role: slider;
accessible-value: root.value; accessible-value: root.value;
@ -25,26 +27,27 @@ export component Slider {
i-background := Rectangle { i-background := Rectangle {
background: Palette.surface-variant; background: Palette.surface-variant;
opacity: 0.38; opacity: 0.38;
x: (parent.width - self.width) / 2;
y: (parent.height - self.height) / 2; y: (parent.height - self.height) / 2;
width: 100%; width: vertical ? 4px : parent.width;
height: 4px; height: vertical ? parent.height : 4px;
border-radius: 2px; border-radius: 2px;
} }
i-track := Rectangle { i-track := Rectangle {
background: Palette.primary; background: Palette.primary;
x: i-background.x; x: vertical ? (parent.width - self.width) / 2 : i-background.x;
y: (parent.height - self.height) / 2; y: vertical ? i-background.y : (parent.height - self.height) / 2;
width: i-handle.x + (i-handle.width / 2); width: vertical? i-background.width : i-handle.x + (i-handle.width / 2);
height: i-background.height; height: vertical? i-handle.y + (i-handle.height / 2) : i-background.height;
border-radius: i-background.border-radius; border-radius: i-background.border-radius;
} }
i-state-layer := Rectangle { i-state-layer := Rectangle {
opacity: 0; opacity: 0;
background: Palette.primary; background: Palette.primary;
x: i-handle.x - (self.width - i-handle.width) / 2; x: vertical ? (parent.width - self.width) / 2 : i-handle.x - (self.width - i-handle.width) / 2;
y: (parent.height - self.height) / 2; y: vertical ? i-handle.y - (self.height - i-handle.height) / 2 : (parent.height - self.height) / 2;
width: 40px; width: 40px;
height: 40px; height: 40px;
border-radius: max(self.width, self.height) / 2; border-radius: max(self.width, self.height) / 2;
@ -54,10 +57,10 @@ export component Slider {
i-handle := Rectangle { i-handle := Rectangle {
background: Palette.primary; background: Palette.primary;
x: (parent.width - i-handle.width) * (root.value - root.minimum) / (root.maximum - root.minimum); x: vertical ? (parent.width - self.width) / 2 : (parent.width - i-handle.width) * (root.value - root.minimum) / (root.maximum - root.minimum);
y: (parent.height - self.height) / 2; y: vertical ? (parent.height - i-handle.height) * (root.value - root.minimum) / (root.maximum - root.minimum) : (parent.height - self.height) / 2;
width: root.height; width: i-state-layer.width;
height: root.height; height: i-state-layer.height;
border-radius: max(self.width, self.height) / 2; border-radius: max(self.width, self.height) / 2;
drop-shadow-color: Palette.shadow; drop-shadow-color: Palette.shadow;
drop-shadow-blur: Elevation.level1; drop-shadow-blur: Elevation.level1;
@ -70,29 +73,44 @@ export component Slider {
property <float> pressed-value; property <float> pressed-value;
property <bool> i-handle-hover: self.has-hover && self.mouse-x >= i-handle.x && self.mouse-x <= i-handle.x + i-handle.width property <bool> i-handle-hover: self.has-hover && self.mouse-x >= i-handle.x && self.mouse-x <= i-handle.x + i-handle.width
&& self.mouse-y >= i-handle.y && self.mouse-y <= i-handle.y + i-handle.height; && self.mouse-y >= i-handle.y && self.mouse-y <= i-handle.y + i-handle.height;
pointer-event(event) => { pointer-event(event) => {
if (event.button == PointerEventButton.left && event.kind == PointerEventKind.down) { if (event.button == PointerEventButton.left && event.kind == PointerEventKind.down) {
self.pressed-value = root.value; self.pressed-value = root.value;
} }
} }
moved => { moved => {
if (self.enabled && self.pressed) { if (!vertical && self.enabled && self.pressed) {
root.value = max(root.minimum, min(root.maximum, root.value = max(root.minimum, min(root.maximum,
self.pressed-value + (i-touch-area.mouse-x - i-touch-area.pressed-x) * (root.maximum - root.minimum) / (root.width - i-handle.width))); self.pressed-value + (i-touch-area.mouse-x - i-touch-area.pressed-x) * (root.maximum - root.minimum) / (root.width - i-handle.width)));
root.changed(root.value); root.changed(root.value);
} }
if (vertical && self.enabled && self.pressed) {
root.value = max(root.minimum, min(root.maximum,
self.pressed-value + (i-touch-area.mouse-y - i-touch-area.pressed-y) * (root.maximum - root.minimum) / (root.height - i-handle.height)));
root.changed(root.value);
}
} }
} }
i-focus-scope := FocusScope { i-focus-scope := FocusScope {
x: 0; x: 0;
y: 0;
width: 0px; width: 0px;
height: 0px;
key-pressed(event) => { key-pressed(event) => {
if (self.enabled && event.text == Key.RightArrow) { if (!vertical && self.enabled && event.text == Key.RightArrow) {
root.value = Math.min(root.value + 1, root.maximum); root.value = Math.min(root.value + 1, root.maximum);
accept accept
} else if (self.enabled && event.text == Key.LeftArrow) { } else if (!vertical && self.enabled && event.text == Key.LeftArrow) {
root.value = Math.max(root.value - 1, root.minimum);
accept
} else if (vertical && self.enabled && event.text == Key.DownArrow) {
root.value = Math.min(root.value + 1, root.maximum);
accept
} else if (vertical && self.enabled && event.text == Key.UpArrow) {
root.value = Math.max(root.value - 1, root.minimum); root.value = Math.max(root.value - 1, root.minimum);
accept accept
} else { } else {