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); } } }