Fix pointer navigation in Quattro launcher menus (#6343)

Accumulate sub-threshold movement so slow motion with a flat acceleration profile updates selection. Sample pointer state on row entry and carry pointer intent through subordinate menus while preserving predictable keyboard selection.
This commit is contained in:
ssupt
2026-07-22 15:27:56 -07:00
committed by GitHub
parent 4a02da20d5
commit 3481b5614b
7 changed files with 184 additions and 17 deletions
+21 -5
View File
@@ -2,22 +2,31 @@ import QtQuick
// Filters synthetic hover churn from moving delegates under a stationary
// pointer. Call reset() after keyboard/list mutations, then moved() from a
// row MouseArea's onPositionChanged before changing cursor selection.
// row MouseArea's onPositionChanged before changing cursor selection. A
// transition known to originate from the pointer can call allowInitialSample()
// so the item under the stationary pointer remains selected.
QtObject {
id: root
property Item referenceItem: null
property real threshold: 1
property bool primed: false
property bool initialSampleAllowed: false
property real lastX: 0
property real lastY: 0
function reset() {
root.primed = false
root.initialSampleAllowed = false
root.lastX = 0
root.lastY = 0
}
function allowInitialSample() {
root.reset()
root.initialSampleAllowed = true
}
function moved(item, mouse) {
if (!item || !mouse) {
root.reset()
@@ -26,12 +35,19 @@ QtObject {
var target = root.referenceItem || item
var point = item.mapToItem(target, mouse.x, mouse.y)
var didMove = root.primed
&& (Math.abs(point.x - root.lastX) > root.threshold || Math.abs(point.y - root.lastY) > root.threshold)
var firstSample = !root.primed
var didMove = !firstSample
? Math.abs(point.x - root.lastX) > root.threshold || Math.abs(point.y - root.lastY) > root.threshold
: root.initialSampleAllowed
root.lastX = point.x
root.lastY = point.y
// Keep the previous accepted position while filtering jitter so slow,
// sub-threshold steps accumulate into deliberate pointer movement.
if (firstSample || didMove) {
root.lastX = point.x
root.lastY = point.y
}
root.primed = true
root.initialSampleAllowed = false
return didMove
}
+6 -1
View File
@@ -74,11 +74,11 @@ Item {
? root.contentMargin * 2 + root.searchHeight + root.contentSpacing + requestedListHeight
: 400
root.opened = true
root.filterText = payload.query || ""
root.selectedIndex = 0
root.cursorActive = true
root.disarmHover()
root.opened = true
root.rebuildDisplay()
// The shell may start before first-install packages have finished placing
// their icons. Refresh here even when the desktop entry list did not change.
@@ -626,9 +626,14 @@ Item {
}
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onEntered: root.selectFromPointer(row.index, row, {
x: mouseArea.mouseX,
y: mouseArea.mouseY
})
onPositionChanged: function(mouse) {
root.selectFromPointer(row.index, row, mouse)
}
+10 -5
View File
@@ -520,14 +520,15 @@ Item {
root.rebuildDisplay()
}
function setActiveMenu(id, pushHistory) {
function setActiveMenu(id, pushHistory, fromPointer) {
if (!root.item(id)) id = "root"
if (pushHistory && id !== root.activeMenu) root.navStack = root.navStack.concat([root.activeMenu])
root.activeMenu = id
root.filterText = ""
root.selectedIndex = 0
root.cursorActive = true
root.disarmPointer()
if (fromPointer) pointerGate.allowInitialSample()
else root.disarmPointer()
root.rebuildDisplay()
root.loadProviderForMenu(id)
}
@@ -547,7 +548,7 @@ Item {
return true
}
function activateIndex(index) {
function activateIndex(index, fromPointer) {
if (root.dmenuActive) {
if (root.mode === "input") {
root.applyDmenuSelection(root.filterText)
@@ -562,7 +563,7 @@ Item {
var row = displayModel.get(index)
if (row.kind === "menu" || row.kind === "link") {
root.setActiveMenu(row.target || row.itemId, true)
root.setActiveMenu(row.target || row.itemId, true, fromPointer)
} else {
root.applySelected(row.itemId, row.action)
}
@@ -1043,13 +1044,17 @@ Item {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onEntered: root.selectFromPointer(row.index, row, {
x: mouseArea.mouseX,
y: mouseArea.mouseY
})
onPositionChanged: function(mouse) {
root.selectFromPointer(row.index, row, mouse)
}
onClicked: {
root.cursorActive = true
root.selectedIndex = row.index
root.activateIndex(row.index)
root.activateIndex(row.index, true)
}
}
}