fix: bind a drag when its contact begins, not on the tap edge - #55
Open
winst0niuss wants to merge 2 commits into
Open
fix: bind a drag when its contact begins, not on the tap edge#55winst0niuss wants to merge 2 commits into
winst0niuss wants to merge 2 commits into
Conversation
winst0niuss
force-pushed
the
fix/slider-drag-grab
branch
from
August 24, 2026 10:22
8f53a4d to
3bcc6d3
Compare
A drag is only bound to its element on InputSnapshot::touchPressed, so a slider that never sees that edge ignores every held frame that follows: the handle does not move at all, and the release carries no coordinates either, so the gesture does nothing. Consumers gate that edge on the contact reading as a tap first — in CrossPoint, MappedInputManager::wasScreenTouchDown() requires the contact to stay within InputManager's 28 px tap slop for 90 ms. A finger that starts moving as soon as it lands never satisfies it, so a natural quick drag on a slider is dropped, while a slower one that dwells first works. That is the reported "the handle only drags on the second try". Bind on the frame the contact begins as well, hit-testing where it landed rather than where it has since travelled to: binding against the live position would let a contact that started elsewhere grab a slider merely by passing over it. findDrag() requires the InputDrag bit strictly (unlike findTouch(), which falls back to InputTouch), so buttons, tiles and list rows are never bound and keep their press-then-release semantics.
winst0niuss
force-pushed
the
fix/slider-drag-grab
branch
from
August 24, 2026 10:24
3bcc6d3 to
35b37d5
Compare
Contributor
|
Yep I noticed this too. Ready to merge on this? |
Contributor
Author
|
Yes, it's ready |
findDrag duplicated findTouch's loop to skip its InputTouch fallback. Excluding InputDrag from that fallback the way InputLongPress already is makes the two identical, so the copy goes. Binding on the contact edge no longer waits for active_ to be clear: a fresh contact must not inherit what the previous one bound, which is what the guard allowed when a contact ended without a release edge. The block moves above touchPressed so an adapter reporting both on one frame still keeps its pressed-element highlight. testDragRouting is the suite's first InputDrag coverage. Verified by mutation: restoring the fallback, the guard, or hit-testing the live position each fails it.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The problem
On a touch board, dragging a slider only works if the finger pauses before it starts moving. A drag that begins immediately — the natural gesture — does nothing at all: the handle stays put, and nothing happens on release either. Try again, dwell for a moment first, and it drags normally.
Reported against CrossPoint's frontlight panel (brightness and warmth sliders), but nothing there is specific to that screen — it is the shared routing.
Root cause
InteractionTable::routeAgainst()binds a drag to its element only on the press edge:If
active_is never set, every held frame is dropped silently.Consumers derive
touchPressedfrom a contact that has already read as a tap. In CrossPoint that isMappedInputManager::wasScreenTouchDown(): it needsInputManager::isTouchTapCandidate()— false once the contact passes the 28 pxTOUCH_TAP_SLOP_PX— and a hold of at least 90 ms. A finger that starts moving as it lands fails both halves, so the press edge never arrives and the drag is never bound. On release,wasTouchTap()fails too (slop exceeded), so the snapshot carries no coordinates and even the tap-to-position path is skipped.The fix
Bind on the frame the contact begins as well, hit-testing where it landed rather than where it has since travelled to.
The landing point matters: binding against the live position would let a contact that started somewhere else grab a slider just by passing over it, which is a worse bug than the one being fixed.
contactHeld_mirrors the previous routed frame's contact so the opening frame is recognisable, and nothing rebinds until the contact ends.findDrag()requires theInputDragbit strictly, unlikefindTouch(), which also matches anything acceptingInputTouch. Buttons, tiles and list rows are therefore never bound and keep their press-then-release semantics unchanged.One case this does not cover: a screen appearing while a contact is already down would see its first held frame mid-gesture. It does not arise today — swipes are recognised on release (
InputManager::wasSwipe()requirestouchReleasedEvent), so a gesture-opened screen never inherits a live contact. Carrying the real down point inInputSnapshotwould close it off for good, at the cost of a snapshot field every consumer has to fill.Exercised in the desktop simulator on an X4 Pro profile, not yet on physical hardware — contact timing there differs, so a check on a real device would be worth having before merge.