Make Home light ordering explicit and durable

This commit is contained in:
Gabriel Brown
2026-08-18 06:14:07 -04:00
parent eff0bc44ff
commit 39a26adcc2
4 changed files with 48 additions and 74 deletions
@@ -9,93 +9,61 @@ Rectangle {
required property string sourceName required property string sourceName
required property int index required property int index
required property bool featured required property bool featured
required property bool canMoveEarlier
required property bool canMoveLater
signal aliasCommitted(string id, string alias) signal aliasCommitted(string id, string alias)
signal removeRequested(string id) signal removeRequested(string id)
signal moveRequested(string id, int targetIndex) signal moveRequested(string id, int targetIndex)
readonly property bool dragging: dragHandler.active
implicitHeight: 108 implicitHeight: 108
radius: Theme.cardRadius radius: Theme.cardRadius
color: root.dragging color: Theme.alpha(Theme.bgDark, 0.7)
? Theme.mix(Theme.bgDark, Theme.accent, 0.09) border.width: 1
: Theme.alpha(Theme.bgDark, 0.7) border.color: Theme.alpha(Theme.fg, 0.07)
border.width: root.dragging ? 2 : 1
border.color: root.dragging
? Theme.alpha(Theme.accent, 0.82)
: Theme.alpha(Theme.fg, 0.07)
z: root.dragging ? 10 : 0
transform: Translate {
x: root.dragging ? dragHandler.translation.x : 0
y: root.dragging ? dragHandler.translation.y : 0
}
PrismEdge { PrismEdge {
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
inset: root.radius inset: root.radius
opacity: root.dragging ? 0.82 : 0.2 opacity: 0.2
} }
Rectangle { Column {
id: dragHandle id: reorderControls
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: 11 anchors.leftMargin: 8
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
width: 30 width: 30
height: 42 spacing: 4
radius: 9
activeFocusOnTab: true
color: root.dragging || activeFocus
? Theme.alpha(Theme.accent, 0.14)
: (handleMouse.containsMouse ? Theme.alpha(Theme.fg, 0.09) : Theme.alpha(Theme.fg, 0.045))
border.width: activeFocus ? 2 : 1
border.color: activeFocus ? Theme.accent : Theme.alpha(Theme.fg, 0.06)
Text { SettingsButton {
anchors.centerIn: parent width: 30
text: "⠿" height: 29
color: root.dragging ? Theme.accent : Theme.fgDim text: "↑"
font.family: Theme.fontFamily enabled: root.canMoveEarlier
font.pixelSize: 16 activeFocusOnTab: enabled
onClicked: root.moveRequested(root.favorite.id, root.index - 1)
Keys.onReturnPressed: if (enabled) root.moveRequested(root.favorite.id, root.index - 1)
Keys.onSpacePressed: if (enabled) root.moveRequested(root.favorite.id, root.index - 1)
} }
MouseArea { SettingsButton {
id: handleMouse width: 30
anchors.fill: parent height: 29
hoverEnabled: true text: "↓"
acceptedButtons: Qt.NoButton enabled: root.canMoveLater
cursorShape: Qt.SizeAllCursor activeFocusOnTab: enabled
} onClicked: root.moveRequested(root.favorite.id, root.index + 1)
Keys.onReturnPressed: if (enabled) root.moveRequested(root.favorite.id, root.index + 1)
DragHandler { Keys.onSpacePressed: if (enabled) root.moveRequested(root.favorite.id, root.index + 1)
id: dragHandler
target: null
onActiveChanged: {
if (!active)
root.commitDrag();
}
}
Keys.onPressed: event => {
if (event.key === Qt.Key_Left || event.key === Qt.Key_Up) {
root.moveRequested(root.favorite.id, Math.max(0, root.index - 1));
event.accepted = true;
} else if (event.key === Qt.Key_Right || event.key === Qt.Key_Down) {
const grid = root.GridView.view;
const lastIndex = grid ? grid.count - 1 : root.index;
root.moveRequested(root.favorite.id, Math.min(lastIndex, root.index + 1));
event.accepted = true;
}
} }
} }
Rectangle { Rectangle {
id: aliasFrame id: aliasFrame
anchors.left: dragHandle.right anchors.left: reorderControls.right
anchors.leftMargin: 10 anchors.leftMargin: 10
anchors.right: removeButton.left anchors.right: removeButton.left
anchors.rightMargin: 12 anchors.rightMargin: 12
@@ -188,16 +156,4 @@ Rectangle {
Keys.onReturnPressed: root.removeRequested(root.favorite.id) Keys.onReturnPressed: root.removeRequested(root.favorite.id)
Keys.onSpacePressed: root.removeRequested(root.favorite.id) Keys.onSpacePressed: root.removeRequested(root.favorite.id)
} }
function commitDrag(): void {
const grid = root.GridView.view;
if (!grid || grid.count <= 0)
return;
const centerX = root.x + dragHandler.translation.x + root.width / 2;
const centerY = root.y + dragHandler.translation.y + root.height / 2;
const modelCount = grid.count;
const column = Math.max(0, Math.min(1, Math.floor(centerX / grid.cellWidth)));
const row = Math.max(0, Math.floor(centerY / grid.cellHeight));
root.moveRequested(root.favorite.id, Math.min(modelCount - 1, row * 2 + column));
}
} }
@@ -122,6 +122,8 @@ SettingsPage {
}) })
sourceName: modelData.sourceName sourceName: modelData.sourceName
featured: index < 4 featured: index < 4
canMoveEarlier: index > 0
canMoveLater: index < favoritesGrid.count - 1
onAliasCommitted: (id, alias) => HomePreferences.setAlias(id, alias) onAliasCommitted: (id, alias) => HomePreferences.setAlias(id, alias)
onMoveRequested: (id, targetIndex) => HomePreferences.move(id, targetIndex) onMoveRequested: (id, targetIndex) => HomePreferences.move(id, targetIndex)
onRemoveRequested: id => HomePreferences.remove(id) onRemoveRequested: id => HomePreferences.remove(id)
@@ -111,7 +111,15 @@ fi
assert_contains 'signal aliasCommitted(string id, string alias)' "$favorite_card" assert_contains 'signal aliasCommitted(string id, string alias)' "$favorite_card"
assert_contains 'signal removeRequested(string id)' "$favorite_card" assert_contains 'signal removeRequested(string id)' "$favorite_card"
assert_contains 'signal moveRequested(string id, int targetIndex)' "$favorite_card" assert_contains 'signal moveRequested(string id, int targetIndex)' "$favorite_card"
assert_contains 'DragHandler {' "$favorite_card" assert_contains 'text: "↑"' "$favorite_card"
assert_contains 'text: "↓"' "$favorite_card"
assert_contains 'enabled: root.canMoveEarlier' "$favorite_card"
assert_contains 'enabled: root.canMoveLater' "$favorite_card"
if rg -Fq 'DragHandler {' "$favorite_card"; then
fail 'Home light cards still expose the broken drag affordance'
fi
assert_contains 'canMoveEarlier: index > 0' "$home_page"
assert_contains 'canMoveLater: index < favoritesGrid.count - 1' "$home_page"
assert_contains 'onEditingFinished:' "$favorite_card" assert_contains 'onEditingFinished:' "$favorite_card"
assert_contains 'text: "Control Center"' "$favorite_card" assert_contains 'text: "Control Center"' "$favorite_card"
assert_contains 'activeFocusOnTab: true' "$favorite_card" assert_contains 'activeFocusOnTab: true' "$favorite_card"
@@ -96,6 +96,8 @@ assert_reset_persists_without_debounce() {
initial_ids=' ["light.kitchen","light.hall","light.desk"]' initial_ids=' ["light.kitchen","light.hall","light.desk"]'
expected='{"initialized":true,"favorites":[{"id":"light.desk","alias":""},{"id":"light.kitchen","alias":"Island"}],"saveError":""}' expected='{"initialized":true,"favorites":[{"id":"light.desk","alias":""},{"id":"light.kitchen","alias":"Island"}],"saveError":""}'
expected_file='{"initialized":true,"favorites":[{"id":"light.desk","alias":""},{"id":"light.kitchen","alias":"Island"}]}' expected_file='{"initialized":true,"favorites":[{"id":"light.desk","alias":""},{"id":"light.kitchen","alias":"Island"}]}'
reordered_expected='{"initialized":true,"favorites":[{"id":"light.desk","alias":""},{"id":"light.kitchen","alias":"Island"},{"id":"light.hall","alias":""}],"saveError":""}'
reordered_file='{"initialized":true,"favorites":[{"id":"light.desk","alias":""},{"id":"light.kitchen","alias":"Island"},{"id":"light.hall","alias":""}]}'
empty_expected='{"initialized":true,"favorites":[],"saveError":""}' empty_expected='{"initialized":true,"favorites":[],"saveError":""}'
empty_file='{"initialized":true,"favorites":[]}' empty_file='{"initialized":true,"favorites":[]}'
reset_expected='{"initialized":false,"favorites":[],"saveError":""}' reset_expected='{"initialized":false,"favorites":[],"saveError":""}'
@@ -106,6 +108,12 @@ start_harness
qs_for_harness ipc call home-pref-test initialize "$initial_ids" >/dev/null qs_for_harness ipc call home-pref-test initialize "$initial_ids" >/dev/null
qs_for_harness ipc call home-pref-test alias light.kitchen ' Island ' >/dev/null qs_for_harness ipc call home-pref-test alias light.kitchen ' Island ' >/dev/null
qs_for_harness ipc call home-pref-test move light.desk 0 >/dev/null qs_for_harness ipc call home-pref-test move light.desk 0 >/dev/null
wait_for_status "$reordered_expected"
wait_for_file_content "$reordered_file"
stop_harness
start_harness
wait_for_status "$reordered_expected"
qs_for_harness ipc call home-pref-test remove light.hall >/dev/null qs_for_harness ipc call home-pref-test remove light.hall >/dev/null
wait_for_status "$expected" wait_for_status "$expected"
wait_for_file_content "$expected_file" wait_for_file_content "$expected_file"