pragma ComponentBehavior: Bound import QtQuick // A horizontal slider built from primitives. Reports every change through // moved() rather than writing back, so the owner stays the single source. Item { id: slider property real from: 0 property real to: 100 property real stepSize: 1 property real value: 0 property string suffix: "" signal moved(real value) readonly property real span: Math.max(0.0001, to - from) readonly property real position: Math.min(1, Math.max(0, (value - from) / span)) function valueAt(px) { const usable = Math.max(1, track.width - handle.width) const ratio = Math.min(1, Math.max(0, (px - handle.width / 2) / usable)) const raw = from + ratio * span return Math.min(to, Math.max(from, Math.round(raw / stepSize) * stepSize)) } function nudge(steps) { slider.moved(Math.min(to, Math.max(from, value + steps * stepSize))) } implicitWidth: 220 implicitHeight: 26 activeFocusOnTab: true Accessible.role: Accessible.Slider Accessible.name: qsTr("Slider") Accessible.onIncreaseAction: slider.nudge(1) Accessible.onDecreaseAction: slider.nudge(-1) Text { id: readout anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter width: slider.suffix.length > 0 ? 52 : 0 visible: slider.suffix.length > 0 horizontalAlignment: Text.AlignRight text: Math.round(slider.value) + " " + slider.suffix color: "#7a7484" font.pixelSize: 12 } Item { id: track anchors.left: parent.left anchors.right: readout.visible ? readout.left : parent.right anchors.rightMargin: readout.visible ? 8 : 0 anchors.verticalCenter: parent.verticalCenter height: parent.height Rectangle { anchors.verticalCenter: parent.verticalCenter width: parent.width height: 4 radius: 2 color: "#ded8cf" antialiasing: true Rectangle { width: parent.width * slider.position height: parent.height radius: parent.radius color: "#4d7ea8" antialiasing: true } } Rectangle { id: handle x: (track.width - width) * slider.position anchors.verticalCenter: parent.verticalCenter width: 18 height: 18 radius: 9 color: "white" border.width: slider.activeFocus ? 3 : 2 border.color: slider.activeFocus ? "#2f5f85" : "#4d7ea8" antialiasing: true } TapHandler { onTapped: function(point) { slider.moved(slider.valueAt(point.position.x)) } } DragHandler { target: null xAxis.enabled: true yAxis.enabled: false onCentroidChanged: { if (active) slider.moved(slider.valueAt(centroid.position.x)) } } } Keys.onPressed: function(event) { if (event.key === Qt.Key_Left || event.key === Qt.Key_Down) { slider.nudge(-1) event.accepted = true } else if (event.key === Qt.Key_Right || event.key === Qt.Key_Up) { slider.nudge(1) event.accepted = true } } }