113 lines
3.4 KiB
QML
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);
|
|
}
|
|
}
|
|
}
|