Share pointer movement gate across menus

This commit is contained in:
David Heinemeier Hansson
2026-06-29 11:56:21 -05:00
parent a3f8c494e3
commit 472b5cd557
9 changed files with 163 additions and 43 deletions
+7 -3
View File
@@ -131,11 +131,15 @@ assert(
'clipboard keyboard navigation disarms pointer selection'
)
assert(
/function pointerMovedInCard\(item, mouse\)[\s\S]*item\.mapToItem\(card, mouse\.x, mouse\.y\)/.test(clipboardQml),
'clipboard compares pointer movement in card coordinates'
/PointerMoveGate\s*\{[\s\S]*id: pointerGate[\s\S]*referenceItem: card[\s\S]*\}/.test(clipboardQml),
'clipboard uses shared pointer movement gate in card coordinates'
)
assert(
/function selectFromPointer\(index, item, mouse\)[\s\S]*pointerMovedInCard\(item, mouse\)[\s\S]*root\.selectedIndex = index/.test(clipboardQml),
/function disarmPointer\(\)[\s\S]*pointerGate\.reset\(\)/.test(clipboardQml),
'clipboard resets pointer movement gate when pointer selection is disarmed'
)
assert(
/function selectFromPointer\(index, item, mouse\)[\s\S]*pointerGate\.moved\(item, mouse\)[\s\S]*root\.selectedIndex = index/.test(clipboardQml),
'clipboard only selects from pointer after real movement'
)
assert(
+11 -3
View File
@@ -73,17 +73,25 @@ assert(
'launcher keyboard navigation disarms stale hover before moving selection'
)
assert(
/function pointerMovedInCard\(item, mouse\)[\s\S]*item\.mapToItem\(card, mouse\.x, mouse\.y\)/.test(launcherQml),
'launcher compares pointer movement in card coordinates'
/PointerMoveGate\s*\{[\s\S]*id: pointerGate[\s\S]*referenceItem: card[\s\S]*\}/.test(launcherQml),
'launcher uses shared pointer movement gate in card coordinates'
)
assert(
/function selectFromPointer\(index, item, mouse\)[\s\S]*pointerMovedInCard\(item, mouse\)[\s\S]*root\.selectedIndex = index/.test(launcherQml),
/function disarmHover\(\)[\s\S]*pointerGate\.reset\(\)/.test(launcherQml),
'launcher resets pointer movement gate when hover is disarmed'
)
assert(
/function selectFromPointer\(index, item, mouse\)[\s\S]*pointerGate\.moved\(item, mouse\)[\s\S]*root\.selectedIndex = index/.test(launcherQml),
'launcher only selects from pointer after real movement'
)
assert(
/onPositionChanged: function\(mouse\) \{\s*root\.selectFromPointer\(row\.index, row, mouse\)\s*\}/.test(launcherQml),
'launcher row hover routes through pointer movement gate'
)
assert(
!/onContainsMouseChanged:[\s\S]*root\.selectedIndex/.test(launcherQml),
'launcher does not select rows from containsMouse'
)
const confirmDeleteMatch = launcherQml.match(/function confirmDelete\(\) \{([\s\S]*?)\n \}/)
assert(confirmDeleteMatch, 'launcher confirmDelete function exists')
+31
View File
@@ -5,7 +5,9 @@ set -euo pipefail
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
run_node_test <<'JS'
const fs = require('fs')
const menu = requireFromRoot('shell/plugins/menu/MenuModel.js')
const menuQml = fs.readFileSync(path.join(root, 'shell/plugins/menu/Menu.qml'), 'utf8')
const parsed = menu.parseMenuJsonc(`
{
@@ -85,4 +87,33 @@ assertDeepEqual(
},
'menu builds display rows'
)
assert(
/function select\(delta\)[\s\S]*root\.disarmPointer\(\)[\s\S]*selectedIndex =/.test(menuQml),
'menu keyboard navigation disarms pointer selection'
)
assert(
/function setFilter\(nextFilter\)[\s\S]*root\.disarmPointer\(\)/.test(menuQml),
'menu filter changes disarm pointer selection'
)
assert(
/function setActiveMenu\(id, pushHistory\)[\s\S]*root\.disarmPointer\(\)/.test(menuQml),
'menu route changes disarm pointer selection'
)
assert(
/PointerMoveGate\s*\{[\s\S]*id: pointerGate[\s\S]*referenceItem: card[\s\S]*\}/.test(menuQml),
'menu uses shared pointer movement gate in card coordinates'
)
assert(
/function disarmPointer\(\)[\s\S]*pointerGate\.reset\(\)/.test(menuQml),
'menu resets pointer movement gate when pointer selection is disarmed'
)
assert(
/function selectFromPointer\(index, item, mouse\)[\s\S]*pointerGate\.moved\(item, mouse\)[\s\S]*root\.selectedIndex = index/.test(menuQml),
'menu only selects from pointer after real movement'
)
assert(
/onPositionChanged: function\(mouse\) \{\s*root\.selectFromPointer\(row\.index, row, mouse\)\s*\}/.test(menuQml),
'menu row hover routes through pointer movement gate'
)
JS
+36
View File
@@ -0,0 +1,36 @@
#!/bin/bash
set -euo pipefail
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
run_node_test <<'JS'
const fs = require('fs')
const gateQml = fs.readFileSync(path.join(root, 'shell/Ui/PointerMoveGate.qml'), 'utf8')
const uiQmldir = fs.readFileSync(path.join(root, 'shell/Ui/qmldir'), 'utf8')
assert(
/PointerMoveGate 1\.0 PointerMoveGate\.qml/.test(uiQmldir),
'pointer movement gate is exported from qs.Ui'
)
assert(
/property Item referenceItem: null/.test(gateQml),
'pointer movement gate accepts a stable reference item'
)
assert(
/property real threshold: 1/.test(gateQml),
'pointer movement gate ignores single-pixel hover jitter'
)
assert(
/function reset\(\)[\s\S]*root\.primed = false/.test(gateQml),
'pointer movement gate can be disarmed after keyboard or list changes'
)
assert(
/item\.mapToItem\(target, mouse\.x, mouse\.y\)/.test(gateQml),
'pointer movement gate compares movement in stable target coordinates'
)
assert(
/var didMove = root\.primed[\s\S]*Math\.abs\(point\.x - root\.lastX\) > root\.threshold[\s\S]*Math\.abs\(point\.y - root\.lastY\) > root\.threshold/.test(gateQml),
'pointer movement gate only reports real movement after an initial sample'
)
JS