133 lines
3.8 KiB
QML
133 lines
3.8 KiB
QML
// 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);
|
|
}
|
|
}
|