fix(slint): track drag no longer stays glued after release

The drag-end relied on the row TouchArea's pointer-up, which only arrives
if the row still holds the grab. Inside a ListView (or a Flickable that
stays interactive) the scroll gesture can steal the grab mid-drag, so the
up never reached the row and the ghost stayed glued to the cursor. Add a
`changed pressed` safety net that ends the drag on any release or grab
cancel (both paths guard on ta.dragging, so end() runs once). Also give
LabelPageView's Flickable the interactive: !DragState.active flag the
other track views already have.

cc
This commit is contained in:
vicrodh committed 2026-05-30 20:18:11 -06:00
1 parent f249558573
commit 321d3ba116
2 files changed
+17 -1

No files matched your search

@@ -12,7 +12,7 @@
import { Theme } from "../foundation/semantic-colors.slint";
import { Typography } from "../foundation/typography.slint";
import { Radius } from "../foundation/radius.slint";
import { LabelState, LabelActions, TrackItem } from "../state.slint";
import { LabelState, LabelActions, TrackItem, DragState } from "../state.slint";
import { QbzIcon } from "../primitives/QbzIcon.slint";
import { TrackRow } from "../primitives/TrackRow.slint";
import { CircleAction } from "../primitives/CircleAction.slint";
@@ -47,6 +47,9 @@ export component LabelPageView inherits Rectangle {
background: Theme.surface-main;
page-flickable := Flickable {
// Don't scroll while a track is being dragged out (keeps the row's
// pointer grab so the drag-end fires) — matches the other track views.
interactive: !DragState.active;
viewport-width: self.width;
viewport-height: page.preferred-height;
@@ -105,6 +105,19 @@ export component TrackRow inherits Rectangle {
ta.dragging = false;
}
}
// Safety net: the `up` above only arrives if this TouchArea still holds
// the pointer grab. Inside a ListView (or a Flickable that didn't drop
// out of `interactive`), the scroll gesture can steal the grab mid-drag,
// so the `up` never reaches us and the ghost stays glued to the cursor.
// `pressed` flips to false on BOTH a normal release and a grab cancel,
// so ending the drag here guarantees it always clears. Both paths guard
// on `ta.dragging`, so whichever fires first wins and end() runs once.
changed pressed => {
if (!self.pressed && ta.dragging) {
DragActions.end();
ta.dragging = false;
}
}
moved => {
if (ta.pressed && root.draggable) {
if (!ta.dragging