// A light dimmer that previews locally and sends one value when interaction // ends. Home Assistant never sees the intermediate pointer positions. import QtQuick import qs.config Item { id: root property int value: 0 property int previewValue: 0 readonly property bool pressed: drag.pressed signal previewChanged(int value) signal committed(int value) implicitWidth: 160 implicitHeight: 32 activeFocusOnTab: root.enabled opacity: root.enabled ? 1 : 0.42 onValueChanged: { if (!drag.pressed) root.updatePreview(root.value, false); } Component.onCompleted: root.updatePreview(root.value, false) Keys.onLeftPressed: root.commitStep(-5) Keys.onDownPressed: root.commitStep(-5) Keys.onRightPressed: root.commitStep(5) Keys.onUpPressed: root.commitStep(5) Rectangle { id: track anchors.left: parent.left anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter height: 10 radius: 5 color: Theme.alpha(Theme.fg, 0.105) border.width: root.activeFocus ? 1 : 0 border.color: Theme.alpha(Theme.warn, 0.72) Rectangle { id: fill anchors.left: parent.left anchors.top: parent.top anchors.bottom: parent.bottom width: track.width * root.previewValue / 100 radius: track.radius color: Theme.warn Behavior on width { enabled: !drag.pressed NumberAnimation { duration: Theme.durFast easing.type: Easing.OutQuad } } } Rectangle { id: knob anchors.verticalCenter: parent.verticalCenter x: Math.max(0, Math.min(track.width - width, track.width * root.previewValue / 100 - width / 2)) width: 16 height: 16 radius: 8 color: Theme.fg border.width: 1 border.color: Theme.alpha(Theme.bgDark, 0.38) } } // The 32 px interaction surface is intentionally much taller than the // 10 px track, while remaining inside this component's layout bounds. MouseArea { id: drag anchors.fill: parent enabled: root.enabled hoverEnabled: true preventStealing: true cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor onPressed: event => { root.forceActiveFocus(); root.previewAt(event.x); event.accepted = true; } onPositionChanged: event => { if (pressed) root.previewAt(event.x); } onReleased: event => { root.committed(root.previewValue); event.accepted = true; } onCanceled: root.updatePreview(root.value, false) onWheel: event => { const direction = event.angleDelta.y >= 0 ? 5 : -5; root.commitStep(direction); event.accepted = true; } } function clamp(candidate: real): int { return Math.max(0, Math.min(100, Math.round(candidate))); } function updatePreview(candidate: real, announce: bool): void { const nextValue = root.clamp(candidate); if (root.previewValue === nextValue) return; root.previewValue = nextValue; if (announce) root.previewChanged(nextValue); } function previewAt(pointerX: real): void { root.updatePreview(pointerX / Math.max(1, drag.width) * 100, true); } function commitStep(delta: int): void { if (!root.enabled) return; root.updatePreview(root.previewValue + delta, true); root.committed(root.previewValue); } }