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.
- **`minimum`** (_in_ _float_): The minimum value (default: 0)
- **`maximum`** (_in_ _float_): The maximum value (default: 100)
- **`vertical`** (_in_ _bool_): If set to true the Slider is displayed vertical (default: false).
### Callbacks

View file

@ -46,7 +46,7 @@ impl Item for NativeScrollView {
cpp!(unsafe [] -> qttypes::QMargins as "QMargins" {
ensure_initialized();
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 sliderMin = qApp->style()->pixelMetric(QStyle::PM_ScrollBarSliderMin, &option, nullptr);
@ -157,7 +157,7 @@ impl Item for NativeScrollView {
] -> u32 as "int" {
ensure_initialized();
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;
if (!horizontal) {
option.state ^= QStyle::State_Horizontal;
@ -383,7 +383,7 @@ impl Item for NativeScrollView {
QStyleOptionSlider option;
option.state |= QStyle::State(initial_state);
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.pageStep = page_size / dpr;
if (has_focus)

View file

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

View file

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

View file

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

View file

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