Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

Commit cd0f15a

Browse files
Renzo-OlivaresRenzo Olivares
and
Renzo Olivares
authored
Add support for double tap and drag for text selection (#109573)
* Replace PanGestureRecognizer in TextSelection with TapAndDragGestureRecognizer * add tracking of _DragState to new tap_and_drag recognizer and remove some legacy double tap code from text_selection.dart and add logs" * add dragTapCount, a tap count that is persistent for an entire drag and is set to null on drag end vs the regular tap count which is reset on a timer * basic double tap to drag functionality and add a local dragTapCount in text_selection.dart to use with the timer callback * Add offsetFromOrigin and localOffsetFromOrigin to DragUpdateDetails similar to LongPressMoveUpdateDetails, eliminates the need to hold the state of lastDragStartDetails * make a generic baselongpressgesturerecognizer * Revert "make a generic baselongpressgesturerecognizer" This reverts commit aad8f7433bd01e4cd016d527af832c3b1f15fac5. * rename tap_and_drag to selection_recognizers * add mixin for consecutivetap * tap and long press gesture recognizer * Revert "Revert "make a generic baselongpressgesturerecognizer"" This reverts commit 181350c36718f644eada3e45c1b7b5939f90a340. * Revert "Revert "Revert "make a generic baselongpressgesturerecognizer""" This reverts commit 4d69775967858dfd66dd9429e1713da598908a85. * Add support for secondary button clicks on drag gesture recognizer and separate drag end and tap up callback * get test running * rename tapCount to consecutiveTapCount * dispose timer properly * add some comments to tests * Add comments * Make ConsecutiveTapMixin private and move logic to increment tap count into mixin * stop tracking pointer when gesture is rejected and detect drags on touch devices * onCancel for TapAndDrag * have the TapAndDragGestureRecognizer handle tap downs and tap ups on touch and mouse devices * add drag to move cursor for android and iOS, and pointer device kind to DragUpdateDetails * get tests running * refactor TapAndDragGestureRecognizer moving some logic into _check methods * Handle cancel properly on TapAndDragGestureRecognizer, having both onTapCancel and onDragCancel, also fix tests * Fix test mouse drag selects and cannot drag cursor, save _initialPosition based on dragStartBehavior (either on tapDown or dragStart) * determine if drag has a sufficient global distance to accept and fix some cancel behavior, making _checkCancel clearer * give up pointer on drag end * properly stop tracking pointer, fixes test for right click on Apple and non-apple platforms * clean up some comments from last commit * remove drag on touch for now * fix Can select text by dragging with a mouse due to dragStart only being fired on the first PointerMoveEvent, the previous pan gesture recognizer would fire both dragStart and dragUpdate * Revert "fix Can select text by dragging with a mouse due to dragStart only being fired on the first PointerMoveEvent, the previous pan gesture recognizer would fire both dragStart and dragUpdate" This reverts commit 124dc79bc3389672c76d7c014ce04edab297abc6. * correctly use _initialPosition for checkStart and call _checkUpdate after _checkStart if localDelta is not zero * updates * fix double tap chains * Add docs * Address analyzer * more analyzer, only issues left are with print statements * add deadlineTimer to fix conflict with ForcePressGestureRecognizer * Revert "add deadlineTimer to fix conflict with ForcePressGestureRecognizer" This reverts commit 3b29ddfff4cde4845edd481ecefb789fea2a0781. * remove unecessary changes to tests * secondaryButton should not drag * Revert "Revert "add deadlineTimer to fix conflict with ForcePressGestureRecognizer"" This reverts commit 0a008f029f5796acd48c17c1897c0b700d5ef3a7. * updates * Revert "updates" This reverts commit 4803b8443a2b67f0b8d29e9a01f712dfcb0f588c. * Revert "Revert "Revert "add deadlineTimer to fix conflict with ForcePressGestureRecognizer""" This reverts commit 79251a7af88d5dbb1460a960afc77e65dea18bff. * fix shift + tap + drag tests, this was happening because a double tap + drag was being registered and not a single tap, added a duration to pumpAndSettle to fix this * remove TapAndLongPressGestureRecognizer * fix cupertino text field tests related to shift + tap + drag * deadline timer try 2 * more logs * Should reset taps when tap cancel is called, and should wait until gesture is accepted to initiate a drag * should clear _down and _up when gesture is rejected * remove erroneous log * fix selectable text double tap chains test * dont restart timer until tap up * reset consecutiveTapCount on drag end * fix selectableText test * fix material text field tests * reject TapAndDragGestureRecognizer when it is neither a tap nor a drag * remove prints * clean up * shift aware * clean up * fix cupertino test * fix text field focus tests * Add 100ms delay to cupertino test, to prevent a double tap * clean up test comments * add comment to test * uncomment test * remove longpress changes * Fix drag on mobile * remove debug * Fix drag to move cursor on iOS * left over from drag fix * add tests for drag on touch devices * add test for double tap + drag mouse devices * add tests * Fix bug where initialPosition was used before it was set * Address some review comments and fix issue where if double tap was held too long then long press gesture recognizer would take over * remove _isDoubleTap flag since it is no longer needed due to previous commit * Add docs for onTapCancel and onDragCancel * analyzer fixes * Do not test selection handles on macOS, since macOS does not support touch * Add assert for dragStartBehavior * add double tap + drag tests to cupertino * use kDoubleTapTimeout instead of const Duration(milliseconds: 300) for readability * analyzer issues * update docs * update more docs * address comments * more doc updates * fix docs * unused import * fix docs * Add more tests * Add more tests and reject a tap up if we have exceeded the tap tolerance * updates * Address comments * fix test naming * update documentation * move selection_recognizers to selection_gestures * fix analyzer * fix analyzer * keysPressedOnDown instead of isShiftPressed * update docs * update docs * Add drag update throttle to TapAndDragGestureRecognizer * update comments * missed from merge * Replace _ConsecutiveTapMixin with _TapStatusTrackerMixin * updates * correctly cancel tap when when past tap tolerance with new implementation * Should call tap and drag cancel if we are giving up a pointer without succesfully tracking a PointerUpEvent * comments * move pastTapTolerance to tap tracker * move pastTapTolerance to tap tracker * clean up check for nulls and remove use of consecutiveTapCountWhileDragging * move call to super.acceptGesture to top * remove print * clean up * Fix tests where both PanGestureRecognizer and TapAndDragGestureRecognizer lost * clean up * _GestureState -> _DragState * more docs clean up * more clean up * Add onSecondaryTapCancel * Add docs * more docs * Fix broken isPointerAllowed when attempting a right click drag - the _initialButtons is never reset * revert debug flag * make primaryPointer private * Add support for upper count limit in TapAndDragGestureRecognizer, the tap counter should not be allowed to grow infinitely unless that is desired * fix analyzer * Use new TapDrag details objects and callbacks * clean up docs * clean up and add test for upperLimit * Add docs for TapAndDragGestureRecognizer and remove some ambiguity of onStart onUpdate and onEnd parameters * Address review comments * analyzer fixes * Call cancel before rejecting the gesture so we can still access _initialButtons * Recognizer should reject any pointer differing from the original * Revert "Recognizer should reject any pointer differing from the original" This reverts commit afd9807480bd11e119bdd2b7d520631511973bab. * Address reviewer comments * Correct cancel behavior * Fix consecutive tap + drag because _dragStart state was not being set when consecutive tap is greater than one * Add more tests * Add documentation on behavior with TapGestureRecognizer and DragGestureRecognizer * more docs * more docs * remove comments * updates * fix multiple pointer behavior * only handle the primary pointer * Clean up dangerous assumptions in gesture details objects * forgot from rebase * update docs * updates * Clean up some redundant code * remove whitespace * fix tests as a result of #115849 * update test docs * Fix same test from last commit for material variants * More clean up of redundant code and update docs * Clean up didStopTrackingLastPointer and untie TapAndDragGestureRecognizer cancel behavior from TapStatusTrackerMixin.currentUp state * untie pastTapTolerance * updates * Add slopTolerance * update docs * Have secondary tap handled by TapGestureRecognizer * update docs * fix analyzer and address comments * Add more docs * Update cancel behavior tol not call on tap cancel when a drag has been accepted * Change cancel behavior to only cancel if the tap down callback has been sent and merge tapcancel and dragcancel * update docs; * Rename selection_gestures to tap_and_drag_gestures * Address some reviewer comments * make deadline and slopTolerance private * updates * updates * Address review comments * remove _initialButtons * fix docs * trackTrap -> trackTap * fix analyzer * Add test to verify that tap up is called when recognizer accepts before handleEvent is called * implement Diagnosticable for Details objects; * sentTapDown == wonArenaForPrimaryPointer, so the implementation now only uses sentTapDown * Count user tap up immediately and do not wait to win the arena * Do not need to call super from TapAndDragGestureRecognizer.acceptGesture anymore because mixin implementation is gone * Do not start selection drag on Android, iOS, and Fuchshsia touch devices if renderEditable does not have focus, this fixes many scubas * Address reviewer comments * fix test * TapAndDragGestureRecognizer should wait for other recognizer to lose before winning the arena * Address review comments * Dont check for drag if the start was already found * Only check for a drag if it has not already been found" * fix from rebase Co-authored-by: Renzo Olivares <[email protected]>
1 parent 014b8f7 commit cd0f15a

15 files changed

+2635
-215
lines changed

packages/flutter/lib/src/cupertino/text_field.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ class _CupertinoTextFieldSelectionGestureDetectorBuilder extends TextSelectionGe
102102
final _CupertinoTextFieldState _state;
103103

104104
@override
105-
void onSingleTapUp(TapUpDetails details) {
105+
void onSingleTapUp(TapDragUpDetails details) {
106106
// Because TextSelectionGestureDetector listens to taps that happen on
107107
// widgets in front of it, tapping the clear button will also trigger
108108
// this handler. If the clear button widget recognizes the up event,
@@ -120,7 +120,7 @@ class _CupertinoTextFieldSelectionGestureDetectorBuilder extends TextSelectionGe
120120
}
121121

122122
@override
123-
void onDragSelectionEnd(DragEndDetails details) {
123+
void onDragSelectionEnd(TapDragEndDetails details) {
124124
_state._requestKeyboard();
125125
}
126126
}

packages/flutter/lib/src/gestures/drag_details.dart

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,10 +109,12 @@ class DragStartDetails {
109109
String toString() => '${objectRuntimeType(this, 'DragStartDetails')}($globalPosition)';
110110
}
111111

112+
/// {@template flutter.gestures.dragdetails.GestureDragStartCallback}
112113
/// Signature for when a pointer has contacted the screen and has begun to move.
113114
///
114115
/// The `details` object provides the position of the touch when it first
115116
/// touched the surface.
117+
/// {@endtemplate}
116118
///
117119
/// See [DragGestureRecognizer.onStart].
118120
typedef GestureDragStartCallback = void Function(DragStartDetails details);
@@ -126,7 +128,7 @@ typedef GestureDragStartCallback = void Function(DragStartDetails details);
126128
/// * [DragStartDetails], the details for [GestureDragStartCallback].
127129
/// * [DragEndDetails], the details for [GestureDragEndCallback].
128130
class DragUpdateDetails {
129-
/// Creates details for a [DragUpdateDetails].
131+
/// Creates details for a [GestureDragUpdateCallback].
130132
///
131133
/// The [delta] argument must not be null.
132134
///
@@ -195,11 +197,13 @@ class DragUpdateDetails {
195197
String toString() => '${objectRuntimeType(this, 'DragUpdateDetails')}($delta)';
196198
}
197199

200+
/// {@template flutter.gestures.dragdetails.GestureDragUpdateCallback}
198201
/// Signature for when a pointer that is in contact with the screen and moving
199202
/// has moved again.
200203
///
201204
/// The `details` object provides the position of the touch and the distance it
202205
/// has traveled since the last update.
206+
/// {@endtemplate}
203207
///
204208
/// See [DragGestureRecognizer.onUpdate].
205209
typedef GestureDragUpdateCallback = void Function(DragUpdateDetails details);

packages/flutter/lib/src/gestures/monodrag.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,13 @@ enum _DragState {
2626
accepted,
2727
}
2828

29+
/// {@template flutter.gestures.monodrag.GestureDragEndCallback}
2930
/// Signature for when a pointer that was previously in contact with the screen
3031
/// and moving is no longer in contact with the screen.
3132
///
3233
/// The velocity at which the pointer was moving when it stopped contacting
3334
/// the screen is available in the `details`.
35+
/// {@endtemplate}
3436
///
3537
/// Used by [DragGestureRecognizer.onEnd].
3638
typedef GestureDragEndCallback = void Function(DragEndDetails details);
@@ -124,8 +126,10 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer {
124126
/// * [DragDownDetails], which is passed as an argument to this callback.
125127
GestureDragDownCallback? onDown;
126128

129+
/// {@template flutter.gestures.monodrag.DragGestureRecognizer.onStart}
127130
/// A pointer has contacted the screen with a primary button and has begun to
128131
/// move.
132+
/// {@endtemplate}
129133
///
130134
/// The position of the pointer is provided in the callback's `details`
131135
/// argument, which is a [DragStartDetails] object. The [dragStartBehavior]
@@ -137,8 +141,10 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer {
137141
/// * [DragStartDetails], which is passed as an argument to this callback.
138142
GestureDragStartCallback? onStart;
139143

144+
/// {@template flutter.gestures.monodrag.DragGestureRecognizer.onUpdate}
140145
/// A pointer that is in contact with the screen with a primary button and
141146
/// moving has moved again.
147+
/// {@endtemplate}
142148
///
143149
/// The distance traveled by the pointer since the last update is provided in
144150
/// the callback's `details` argument, which is a [DragUpdateDetails] object.
@@ -149,9 +155,11 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer {
149155
/// * [DragUpdateDetails], which is passed as an argument to this callback.
150156
GestureDragUpdateCallback? onUpdate;
151157

158+
/// {@template flutter.gestures.monodrag.DragGestureRecognizer.onEnd}
152159
/// A pointer that was previously in contact with the screen with a primary
153160
/// button and moving is no longer in contact with the screen and was moving
154161
/// at a specific velocity when it stopped contacting the screen.
162+
/// {@endtemplate}
155163
///
156164
/// The velocity is provided in the callback's `details` argument, which is a
157165
/// [DragEndDetails] object.

packages/flutter/lib/src/gestures/tap.dart

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,13 @@ class TapDownDetails {
4545
final Offset localPosition;
4646
}
4747

48+
/// {@template flutter.gestures.tap.GestureTapDownCallback}
4849
/// Signature for when a pointer that might cause a tap has contacted the
4950
/// screen.
5051
///
5152
/// The position at which the pointer contacted the screen is available in the
5253
/// `details`.
54+
/// {@endtemplate}
5355
///
5456
/// See also:
5557
///
@@ -82,11 +84,13 @@ class TapUpDetails {
8284
final PointerDeviceKind kind;
8385
}
8486

87+
/// {@template flutter.gestures.tap.GestureTapUpCallback}
8588
/// Signature for when a pointer that will trigger a tap has stopped contacting
8689
/// the screen.
8790
///
8891
/// The position at which the pointer stopped contacting the screen is available
8992
/// in the `details`.
93+
/// {@endtemplate}
9094
///
9195
/// See also:
9296
///
@@ -360,8 +364,10 @@ class TapGestureRecognizer extends BaseTapGestureRecognizer {
360364
/// {@macro flutter.gestures.GestureRecognizer.supportedDevices}
361365
TapGestureRecognizer({ super.debugOwner, super.supportedDevices });
362366

367+
/// {@template flutter.gestures.tap.TapGestureRecognizer.onTapDown}
363368
/// A pointer has contacted the screen at a particular location with a primary
364369
/// button, which might be the start of a tap.
370+
/// {@endtemplate}
365371
///
366372
/// This triggers after the down event, once a short timeout ([deadline]) has
367373
/// elapsed, or once the gestures has won the arena, whichever comes first.
@@ -378,8 +384,10 @@ class TapGestureRecognizer extends BaseTapGestureRecognizer {
378384
/// * [GestureDetector.onTapDown], which exposes this callback.
379385
GestureTapDownCallback? onTapDown;
380386

387+
/// {@template flutter.gestures.tap.TapGestureRecognizer.onTapUp}
381388
/// A pointer has stopped contacting the screen at a particular location,
382389
/// which is recognized as a tap of a primary button.
390+
/// {@endtemplate}
383391
///
384392
/// This triggers on the up event, if the recognizer wins the arena with it
385393
/// or has previously won, immediately followed by [onTap].
@@ -411,8 +419,10 @@ class TapGestureRecognizer extends BaseTapGestureRecognizer {
411419
/// * [GestureDetector.onTap], which exposes this callback.
412420
GestureTapCallback? onTap;
413421

422+
/// {@template flutter.gestures.tap.TapGestureRecognizer.onTapCancel}
414423
/// A pointer that previously triggered [onTapDown] will not end up causing
415424
/// a tap.
425+
/// {@endtemplate}
416426
///
417427
/// This triggers once the gesture loses the arena if [onTapDown] has
418428
/// previously been triggered.
@@ -428,8 +438,10 @@ class TapGestureRecognizer extends BaseTapGestureRecognizer {
428438
/// * [GestureDetector.onTapCancel], which exposes this callback.
429439
GestureTapCancelCallback? onTapCancel;
430440

441+
/// {@template flutter.gestures.tap.TapGestureRecognizer.onSecondaryTap}
431442
/// A pointer has stopped contacting the screen, which is recognized as a tap
432443
/// of a secondary button.
444+
/// {@endtemplate}
433445
///
434446
/// This triggers on the up event, if the recognizer wins the arena with it or
435447
/// has previously won, immediately following [onSecondaryTapUp].
@@ -444,8 +456,10 @@ class TapGestureRecognizer extends BaseTapGestureRecognizer {
444456
/// * [GestureDetector.onSecondaryTap], which exposes this callback.
445457
GestureTapCallback? onSecondaryTap;
446458

459+
/// {@template flutter.gestures.tap.TapGestureRecognizer.onSecondaryTapDown}
447460
/// A pointer has contacted the screen at a particular location with a
448461
/// secondary button, which might be the start of a secondary tap.
462+
/// {@endtemplate}
449463
///
450464
/// This triggers after the down event, once a short timeout ([deadline]) has
451465
/// elapsed, or once the gestures has won the arena, whichever comes first.
@@ -462,8 +476,10 @@ class TapGestureRecognizer extends BaseTapGestureRecognizer {
462476
/// * [GestureDetector.onSecondaryTapDown], which exposes this callback.
463477
GestureTapDownCallback? onSecondaryTapDown;
464478

479+
/// {@template flutter.gestures.tap.TapGestureRecognizer.onSecondaryTapUp}
465480
/// A pointer has stopped contacting the screen at a particular location,
466481
/// which is recognized as a tap of a secondary button.
482+
/// {@endtemplate}
467483
///
468484
/// This triggers on the up event if the recognizer wins the arena with it
469485
/// or has previously won.
@@ -482,8 +498,10 @@ class TapGestureRecognizer extends BaseTapGestureRecognizer {
482498
/// * [GestureDetector.onSecondaryTapUp], which exposes this callback.
483499
GestureTapUpCallback? onSecondaryTapUp;
484500

501+
/// {@template flutter.gestures.tap.TapGestureRecognizer.onSecondaryTapCancel}
485502
/// A pointer that previously triggered [onSecondaryTapDown] will not end up
486503
/// causing a tap.
504+
/// {@endtemplate}
487505
///
488506
/// This triggers once the gesture loses the arena if [onSecondaryTapDown]
489507
/// has previously been triggered.

packages/flutter/lib/src/material/selectable_text.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ class _SelectableTextSelectionGestureDetectorBuilder extends TextSelectionGestur
8585
}
8686

8787
@override
88-
void onSingleTapUp(TapUpDetails details) {
88+
void onSingleTapUp(TapDragUpDetails details) {
8989
editableText.hideToolbar();
9090
if (delegate.selectionEnabled) {
9191
switch (Theme.of(_state.context).platform) {

packages/flutter/lib/src/material/text_field.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ class _TextFieldSelectionGestureDetectorBuilder extends TextSelectionGestureDete
6565
}
6666

6767
@override
68-
void onSingleTapUp(TapUpDetails details) {
68+
void onSingleTapUp(TapDragUpDetails details) {
6969
super.onSingleTapUp(details);
7070
_state._requestKeyboard();
7171
_state.widget.onTap?.call();

0 commit comments

Comments
 (0)