mirror of
https://github.com/slint-ui/slint.git
synced 2025-10-01 22:31:14 +00:00
add property for slider to support vertical orientation (#3236)
This commit is contained in:
parent
12e064b07d
commit
4b9fb89332
6 changed files with 109 additions and 44 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue