Add sequential two-flick touch gestures#20086
Draft
kefaslungu wants to merge 1 commit into
Draft
Conversation
Adds twelve sequential two-flick gestures (opposite-direction and perpendicular L-shaped pairs) that can be bound to scripts. The two flicks may be performed either by lifting the finger between strokes or as a single continuous swipe with a sharp change in direction. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
seanbudd
reviewed
May 19, 2026
Member
seanbudd
left a comment
There was a problem hiding this comment.
Thanks @kefaslungu - just a few minor things
Comment on lines
+194
to
+202
| #: The set of single-direction flick actions that can begin a sequential flick gesture. | ||
| _flickActions: frozenset[str] = frozenset( | ||
| { | ||
| touchTracker.action_flickRight, | ||
| touchTracker.action_flickLeft, | ||
| touchTracker.action_flickUp, | ||
| touchTracker.action_flickDown, | ||
| }, | ||
| ) |
Member
There was a problem hiding this comment.
instead of comments like this, can you use triple quoted strings underneath the variable
Suggested change
| #: The set of single-direction flick actions that can begin a sequential flick gesture. | |
| _flickActions: frozenset[str] = frozenset( | |
| { | |
| touchTracker.action_flickRight, | |
| touchTracker.action_flickLeft, | |
| touchTracker.action_flickUp, | |
| touchTracker.action_flickDown, | |
| }, | |
| ) | |
| _flickActions: frozenset[str] = frozenset( | |
| { | |
| touchTracker.action_flickRight, | |
| touchTracker.action_flickLeft, | |
| touchTracker.action_flickUp, | |
| touchTracker.action_flickDown, | |
| }, | |
| ) | |
| """The set of single-direction flick actions that can begin a sequential flick gesture.""" |
| }, | ||
| ) | ||
|
|
||
| #: Maps (firstFlickAction, secondFlickAction) to the corresponding sequential flick action. |
Comment on lines
+435
to
+466
| if tracker.action in _flickActions: | ||
| if pendingFlick is not None and pendingFlick.tracker.numFingers == tracker.numFingers: | ||
| compoundAction = _flickSequenceMap.get((pendingFlick.tracker.action, tracker.action)) | ||
| if compoundAction is not None: | ||
| # Two matching flicks arrived in the same pump cycle: combine into a sequential gesture. | ||
| compoundTracker = touchTracker.MultiTouchTracker( | ||
| compoundAction, | ||
| pendingFlick.tracker.x, | ||
| pendingFlick.tracker.y, | ||
| pendingFlick.tracker.startTime, | ||
| tracker.endTime, | ||
| numFingers=tracker.numFingers, | ||
| ) | ||
| sequentialGesture = TouchInputGesture( | ||
| pendingFlick.preheldTracker, | ||
| compoundTracker, | ||
| pendingFlick.mode, | ||
| ) | ||
| pendingFlick = None | ||
| self._executeGesture(sequentialGesture) | ||
| continue | ||
| # No match yet: flush any earlier flick, then hold this one for the rest of this cycle. | ||
| if pendingFlick is not None: | ||
| self._executeGesture(pendingFlick) | ||
| pendingFlick = gesture | ||
| else: | ||
| if pendingFlick is not None: | ||
| self._executeGesture(pendingFlick) | ||
| pendingFlick = None | ||
| self._executeGesture(gesture) | ||
| if pendingFlick is not None: | ||
| self._executeGesture(pendingFlick) |
Member
There was a problem hiding this comment.
can this be moved to a helper function (or a few helper functions?) this is a lot of complex nested code added under what should be a simple function to execute the pump
| #: Two fingers moving away from each other. | ||
| action_pinchOut: str = "pinchout" | ||
| # Sequential two-flick gesture actions (opposite directions) | ||
| action_flickRightThenLeft = "flickrightflickleft" |
Member
There was a problem hiding this comment.
can this be turned into a StrEnum easily? ideally a displayStringStrEnum with the action labels
| self.complete = False | ||
| self._samples: list[tuple[int, int, float]] = [] | ||
|
|
||
| def update(self, x, y, complete=False): |
| "_samples", | ||
| ] | ||
|
|
||
| def __init__(self, ID, x, y): |
| ### New Features | ||
|
|
||
| * Added pinch in and pinch out touch gestures, allowing two-finger pinch gestures to be bound to scripts. (#19938, @kefaslungu) | ||
| * Added sequential two-flick touch gestures that combine two flicks performed in quick succession into a single gesture, increasing the number of touch gestures that can be bound to scripts. (#19938, @kefaslungu) |
Member
There was a problem hiding this comment.
please migrate this to 2026.3 changelog
| ##### Sequential Flicks {#SequentialFlicks} | ||
|
|
||
| You can perform two flicks in quick succession to trigger a sequential flick gesture. | ||
| This greatly expands the number of touch gestures that can be assigned to NVDA commands. |
Member
There was a problem hiding this comment.
I'm not sure if this sentence is necessary, thoughts @Qchristensen ?
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.
Link to issue number:
#19938
Summary of the issue:
This is the second of 3 PRs to address the above issue.
Adds twelve sequential two-flick gestures (opposite-direction and perpendicular L-shaped pairs) that can be bound to scripts. The two flicks may be performed either by lifting the finger between strokes or as a single continuous swipe with a sharp change in direction.
Description of user facing changes:
Touch screen users can now perform two flicks in quick succession to trigger a sequential flick gesture. This expands the number of touch gestures that can be assigned to NVDA commands.
Twelve combinations are recognised:
• Four opposite-direction pairs: flick right then left, flick left then right, flick up then down, flick down then up.
• Eight perpendicular L-shaped pairs: flick right then up, flick right then down, flick left then up, flick left then down, flick up then right, flick up then left, flick down then right, flick down then left.
The two flicks can be performed either by lifting the finger between strokes (within the multi-touch timeout) or as a single continuous swipe that sharply changes direction.
Description of developer facing changes:
• Twelve new action_flickThen constants in touchTracker, with translatable labels in actionLabels.
• SingleTouchTracker now records peakX/peakY (farthest point reached on each axis) and a rolling window of recent samples for velocity calculation. getVelocity() returns velocity in pixels per second using least-squares linear regression.
• SingleTouchTracker.update() now uses velocity direction rather than total displacement direction to classify flicks, so late-stroke direction changes are reflected correctly.
• TrackerManager._checkContinuousFlick() detects a direction change without a finger lift; when the finger has moved far enough away from its peak, the first flick is queued and the tracker is reset to track the return stroke as the second gesture.
• TouchHandler.pump() now combines two matching flicks that arrive in the same pump cycle into a single sequential gesture using _flickSequenceMap. Single flicks that aren't followed by a second flick still fire immediately at end of cycle, so normal flick latency is unchanged.
• New tunables in touchTracker: minFlickVelocity (100 px/s), continuousFlickTimeout (0.6 s), _VELOCITY_SAMPLE_WINDOW (0.1 s).
Description of development approach:
The detection is split between the two layers that already existed:
• The low-level layer (touchTracker) classifies a single finger as a flick using velocity direction and detects mid-swipe direction changes. The first flick of a continuous gesture is emitted as soon as the second stroke begins; the tracker is then reset so the return stroke is classified independently.
• The high-level layer (touchHandler.pump) combines two flicks emitted in the same pump cycle into a compound action. This is a local pump-cycle decision — no timer, no cross-pump buffering — so a single flick fires immediately at end of cycle without waiting for a possible second flick.
This keeps single-flick latency unchanged while still allowing sequential flicks to be detected reliably.
Testing strategy:
Build from the PR branch and test on a touch screen enabled computer. Must self sign the build.
Known issues with pull request:
None.
Code Review Checklist: