Files

113 lines
3.4 KiB
QML

import QtQuick
import qs.config
// A themed 0-1 slider. Reports intent through `moved` rather than driving
// `value` itself, the same way ToggleSwitch does, so the caller can keep it
// bound to the real state — the volume pipewire ended up at, the brightness the
// backlight accepted — and let the slider follow.
//
// While a drag is in flight the knob tracks the pointer instead, so a backend
// that answers a moment late cannot make it stutter under the finger.
Item {
id: root
property real value: 0
// One wheel notch, one arrow key.
property real stepSize: 0.05
property color fillColor: Theme.accent
signal moved(real value)
// Where the drag has got to, used in place of `value` until it ends.
property real dragValue: 0
readonly property real position: mouse.pressed ? root.dragValue : Math.max(0, Math.min(1, root.value))
// The hit area is a good deal taller than the groove: this panel gets used
// with a finger as often as a pointer.
implicitWidth: 120
implicitHeight: Theme.sliderKnob + Theme.padding * 2
readonly property real travel: root.width - Theme.sliderKnob
function setFromX(x: real): void {
const position = root.travel > 0 ? (x - Theme.sliderKnob / 2) / root.travel : 0;
const value = Math.max(0, Math.min(1, position));
root.dragValue = value;
root.moved(value);
}
function step(delta: real): void {
root.moved(Math.max(0, Math.min(1, root.position + delta)));
}
Rectangle {
id: groove
anchors {
left: parent.left
right: parent.right
verticalCenter: parent.verticalCenter
}
height: Theme.sliderTrack
radius: height / 2
color: Theme.background
border.width: Theme.borderWidth
border.color: Theme.border
Rectangle {
// Stops under the middle of the knob, so the fill and the knob move
// as one piece rather than the fill running out ahead of it.
width: Math.min(groove.width, Theme.sliderKnob / 2 + root.travel * root.position)
height: groove.height
radius: groove.radius
color: root.enabled ? root.fillColor : Theme.textDim
}
}
Rectangle {
id: knob
x: root.travel * root.position
anchors.verticalCenter: parent.verticalCenter
width: Theme.sliderKnob
height: width
radius: width / 2
color: root.enabled ? Theme.text : Theme.textDim
border.width: Theme.borderWidth
border.color: Theme.background
scale: mouse.pressed ? 1.15 : 1
Behavior on scale {
NumberAnimation {
duration: Theme.animDuration
easing.type: Theme.animEasing
}
}
}
MouseArea {
id: mouse
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
preventStealing: true
// A press anywhere on the groove jumps there: on a touchscreen there is
// no way to grab the knob first.
onPressed: event => root.setFromX(event.x)
onPositionChanged: event => {
if (mouse.pressed)
root.setFromX(event.x);
}
onWheel: event => {
const notches = event.angleDelta.y / 120;
if (notches !== 0)
root.step(notches * root.stepSize);
}
}
}