170 lines
5.1 KiB
QML
170 lines
5.1 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
|
|
property bool interactionActive: false
|
|
property string accessibleName: "Brightness"
|
|
|
|
readonly property bool pressed: root.interactionActive
|
|
|
|
signal previewChanged(int value)
|
|
signal committed(int value)
|
|
|
|
implicitWidth: 160
|
|
implicitHeight: 32
|
|
activeFocusOnTab: root.enabled
|
|
opacity: root.enabled ? 1 : 0.42
|
|
|
|
Accessible.role: Accessible.Slider
|
|
Accessible.name: root.accessibleName
|
|
// Qt 6.11's installed Accessible attached type has no structured value or
|
|
// range properties, so expose both in the supported live description.
|
|
Accessible.description: root.previewValue + " percent, range 0 to 100"
|
|
Accessible.focusable: root.enabled
|
|
Accessible.focused: root.activeFocus
|
|
Accessible.onIncreaseAction: root.commitStep(5)
|
|
Accessible.onDecreaseAction: root.commitStep(-5)
|
|
|
|
onValueChanged: {
|
|
if (!root.interactionActive)
|
|
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: !root.interactionActive
|
|
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
|
|
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
|
|
|
onPressed: event => {
|
|
root.forceActiveFocus();
|
|
root.beginPointerInteraction(event.x);
|
|
event.accepted = true;
|
|
}
|
|
onPositionChanged: event => root.movePointerInteraction(event.x)
|
|
onReleased: event => {
|
|
root.releasePointerInteraction();
|
|
event.accepted = true;
|
|
}
|
|
onCanceled: root.cancelPointerInteraction()
|
|
onWheel: event => {
|
|
root.commitWheel(event.angleDelta.y);
|
|
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 beginPointerInteraction(pointerX: real): void {
|
|
if (!root.enabled)
|
|
return;
|
|
root.interactionActive = true;
|
|
root.previewAt(pointerX);
|
|
}
|
|
|
|
function movePointerInteraction(pointerX: real): void {
|
|
if (root.interactionActive)
|
|
root.previewAt(pointerX);
|
|
}
|
|
|
|
function releasePointerInteraction(): void {
|
|
if (!root.interactionActive)
|
|
return;
|
|
root.interactionActive = false;
|
|
root.committed(root.previewValue);
|
|
}
|
|
|
|
function cancelPointerInteraction(): void {
|
|
root.interactionActive = false;
|
|
root.updatePreview(root.value, false);
|
|
}
|
|
|
|
function commitWheel(delta: int): void {
|
|
if (delta === 0)
|
|
return;
|
|
root.commitStep(delta > 0 ? 5 : -5);
|
|
}
|
|
|
|
function commitStep(delta: int): void {
|
|
if (!root.enabled)
|
|
return;
|
|
root.updatePreview(root.previewValue + delta, true);
|
|
root.committed(root.previewValue);
|
|
}
|
|
}
|