-
Notifications
You must be signed in to change notification settings - Fork 29.7k
Move TapAndDragGestureRecognizer code under gestures #119508
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
/cc @Renzo-Olivares |
|
Ah... the analyzer has explained why this now sits under widgets/ - services dependency... |
|
This pull request has been changed to a draft. The currently pending flutter-gold status will not be able to resolve until a new commit is pushed or the change is marked ready for review again. For more guidance, visit Writing a golden file test for Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing. |
|
Yeah it's a little unfortunate that it isn't under there. I ran into the same issue. Thank you for investigating this! |
| /// callback. | ||
| @protected | ||
| void onTapTrackStart() { | ||
| _keysPressedOnDown = HardwareKeyboard.instance.logicalKeysPressed; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
directly calling HardwareKeyboard.instance from this code means it can't be easily overridden, ideally we'd be receiving the keyboard instance from the class that created this one, or via the binding.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The use of HardwareKeyboard.instance came from the original PR that landed the new recogniser - it's similar to that in MenuAnchor. Below the getter is:
static HardwareKeyboard get instance => ServicesBinding.instance.keyboard;
I'm guessing this is enough to cover all test scenarios. Wondering if @Renzo-Olivares might have more thoughts.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This line originally came from TextSelectionGestureDetector as well. The original code block is below.
// Returns true iff either shift key is currently down.
bool get _isShiftPressed {
return HardwareKeyboard.instance.logicalKeysPressed
.any(<LogicalKeyboardKey>{
LogicalKeyboardKey.shiftLeft,
LogicalKeyboardKey.shiftRight,
}.contains);
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think there are many overriders of TextSelectionGestureDetectorBuilder (besides the framework material and cupertino TextFields) I think mostly because it holds the default behavior for text selection in a text field. We do expose some callbacks through TextField but thats about it. Someone could possibly extend it though but they would also have to extend TextField.
|
test-exempt: code refactor I guess the additions to TextSelectionGestureDetectorBuilder should probably be tested, actually... |
|
I think my main motivation for designing it this way was to accomplish the following as a This is some pseudo-code for the class DefaultSelectionGestures extends StatelessWidget {
const DefaultSelectionGestures({super.key, this.child});
final Widget child;
static bool _isShiftPressed(Set<LogicalKeyboardKey> pressed) {
return pressed
.any(<LogicalKeyboardKey>{
LogicalKeyboardKey.shiftLeft,
LogicalKeyboardKey.shiftRight,
}.contains);
}
static final GestureRecognizerFactoryWithHandlers<TapAndDragGestureRecognizer> testDefault =
GestureRecognizerFactoryWithHandlers<TapAndDragGestureRecognizer>(
() => TapAndDragGestureRecognizer(),
(TapAndDragGestureRecognizer instance) {
instance
..onTapDown = (TapDragDownDetails details) {
if (_isShiftPressed(details.keysPressedOnDown)) {
Actions.invoke(context, ShiftTapDownIntent(...));
} else {
Actions.invoke(context, TapDownIntent(...));
}
};
});
static final Map<Type, GestureRecognizerFactory<GestureRecognizer>> androidGestures = {
TapAndDragGestureRecognizer : testDefault,
};
static Map<Type, GestureRecognizerFactory<GestureRecognizer>> get _gestures {
switch (defaultTargetPlatform) {
case TargetPlatform.android:
return _androidGesture;
case TargetPlatform.fuchsia:
return _fuchsiaGestures;
case TargetPlatform.iOS:
return _iOSGestures;
case TargetPlatform.linux:
return _linuxGestures;
case TargetPlatform.macOS:
return _macGestures;
case TargetPlatform.windows:
return _windowsGestures;
}
}
@override
Widget build(BuildContext context) {
return SelectionGestures(gestures: _gestures, child: child);
}
}Versus having a class DefaultSelectionGestures extends StatefulWidget {
const DefaultSelectionGestures({super.key, this.child});
final Widget child;
@override
State<DefaultSelectionGestures> createState() => _DefaultSelectionGesturesState();
}
class _DefaultSelectionGesturesState extends State<DefaultSelectionGestures> {
static HardwareKeyboard get keysInstance => ServicesBinding.instance.keyboard;
static bool _isShiftPressed(Set<LogicalKeyboardKey> pressed) {
return pressed
.any(<LogicalKeyboardKey>{
LogicalKeyboardKey.shiftLeft,
LogicalKeyboardKey.shiftRight,
}.contains);
}
Set<LogicalKeyboardKey>? _keysPressedOnDown;
@override
Widget build(BuildContext context) {
final GestureRecognizerFactoryWithHandlers<TapAndDragGestureRecognizer> testDefault =
GestureRecognizerFactoryWithHandlers<TapAndDragGestureRecognizer>(
() => TapAndDragGestureRecognizer(),
(TapAndDragGestureRecognizer instance) {
instance
..onTapDown = (TapDragDownDetails details) {
_keysPressedOnDown = keysInstance.logicalKeysPressed;
if (_isShiftPressed(_keysPressedOnDown!)) {
Actions.invoke(context, ShiftTapDownIntent(...));
} else {
Actions.invoke(context, TapDownIntent(...));
}
};
});
final Map<Type, GestureRecognizerFactory<GestureRecognizer>> _androidGestures = {
TapAndDragGestureRecognizer : testDefault,
};
Map<Type, GestureRecognizerFactory<GestureRecognizer>> _gestures() {
switch (defaultTargetPlatform) {
case TargetPlatform.android:
return _androidGestures;
case TargetPlatform.fuchsia:
return _fuchsiaGestures;
case TargetPlatform.iOS:
return _iOSGestures;
case TargetPlatform.linux:
return _linuxGestures;
case TargetPlatform.macOS:
return _macGestures;
case TargetPlatform.windows:
return _windowsGestures;
}
}
return SelectionGestures(gestures: _gestures(), child: widget.child);
}
}I was mostly thinking about the performance implications of adding a For some extra context, this use-case is related to a work in progress project #105165. |
|
In both cases, can't you just use |
| Offset? _lastTapOffset; | ||
|
|
||
| // Callback used to indicate that a tap tracking has started. | ||
| VoidCallback? onTapTrackStart; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is cool! I just learned that mixins add instance fields to the class they are mixed into. :D
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How does this work for dart-docs? Ideally documentation for onTapTrackStart and onTapTrackReset would also show up in the api docs for TapAndDragGestureRecognizer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch - I'll convert these to ///. I can see they display nicely when hovering over TapAndDragGestureRecognizer.onTapTrack*.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if we should also remove maxConsecutiveTap from TapAndDragGestureRecognizer since the mixin already adds the instance fields. We would probably have to change int? get maxConsecutiveTap to int? maxConsecutiveTap.
My only concern is that these members are in a private class and public docs are being kept in there, do you have any thoughts on this @Hixie?
|
Hi @tgucio, i'm still thinking about this and have been prototyping some things, but I think we can make this change work. There is no current PR that this approach would break, the motivation behind the original implementation is explained here #119508 (comment) . There was a PR #105165, though it got stale due to other priorities taking precedence. I'm currently in the process of making a new design doc for overridable selection gestures. I think if I still want to go with that original approach I could do something like the code below and create a private version of tap and drag. It looks like some other
class _TapAndDragGestureRecognizerWithShiftAware extends TapAndDragGestureRecognizer {
_TapAndDragGestureRecognizerWithShiftAware();
// The set of [LogicalKeyboardKey]s pressed when the most recent [PointerDownEvent]
// was tracked in [addAllowedPointer].
//
// This value defaults to an empty set.
//
// When the timer between two taps elapses, the recognizer loses the arena, the gesture is cancelled
// or the recognizer is disposed of then this value is reset.
Set<LogicalKeyboardKey> get keysPressedOnDown => _keysPressedOnDown ?? <LogicalKeyboardKey>{};
Set<LogicalKeyboardKey>? _keysPressedOnDown;
@override
set onTapTrackStart(VoidCallback? startMethod) {
super.onTapTrackStart = () {
_keysPressedOnDown = HardwareKeyboard.instance.logicalKeysPressed;
};
}
@override
set onTapTrackReset(VoidCallback? resetMethod) {
super.onTapTrackReset = () {
_keysPressedOnDown = null;
};
}
}cc @chunhtai wondering if you had any thoughts on this change since |
|
(triage) @Renzo-Olivares @tgucio Do you still have plans for this PR? |
|
cc @justinmc from triage. |
|
Let me resolve the conflicts and we can start revisiting this.
|
chunhtai
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think moving into gesture is fine if you can cleanly separating the keyboard and tapanddraggesturerecognizer.
| /// callback. | ||
| @protected | ||
| void onTapTrackStart() { | ||
| _isShiftPressed = HardwareKeyboard.instance.logicalKeysPressed |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just curious what happen if shift is released mid drag
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The shift will not be reset until the next pointer down event. _TapStatusTrackerMixin does not call onTapTrackReset on a drag but it does reset _previousButton and _lastTapOffset to null so on the next tap down following a drag end _consecutiveTapCount is reset and keysPressedOnDown is updated to the latest value from the HardwareKeyboard.instance. This updated implementation I think follows that same protocol.
In regards to my future use-case I think I would just create a local extension, but I can figure that out. I think the current implementation you have in this PR works well. My remaining concerns are that |
justinmc
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
According to the breaking changes policy, since it doesn't seem to break customer_tests or Google tests, it's not automatically considered a breaking change. If we agree it's a good idea to remove keysPressedOnDown then it seems like it's ok to remove it.
I was hesitating to endorse this change since there is no direct use case for it, but I guess it does make sense to keep all tap gestures in the gestures library and to separate out keyboard handling. The way isShiftPressed is done is simple enough.
Also @tgucio FYI there are some merge conflicts now.
|
Hi @tgucio are you still working on this PR? I think all that is needed is to resolve the merge conflicts and then I can give it a final review. |
|
@Renzo-Olivares Yes, I still want to look into this. Will start by rebasing hopefully next wk. |
|
(Triage): @tgucio Do you still have plans for this PR? It is getting pretty stale... |
|
@goderbauer Yes, will look into the merge conflicts once I'm done with 109114. |
|
(Triage): Looks like #109114 got merged. Is this back on your radar? |
|
@goderbauer @justinmc @Renzo-Olivares I guess we can take another look now (the test failures appear unrelated at first sight). |
|
@tgucio The test failures look unrelated, but looks like there are still come merge conflicts. |
|
@Renzo-Olivares Thought this was a github issue but I just realised my master branch was a bit behind. Rebasing again. |
Renzo-Olivares
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM with some small nits. Thanks for working on this!
| required this.child, | ||
| }); | ||
|
|
||
| /// Called whenever [TapAndDragGestureRecognizer] has started tracking |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: maybe we should reference BaseTapAndDragGestureRecognizer here instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should point out when exactly this happens. For example on every PointerDownEvent.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I'll add a mention but without going too deep as these are BaseTapAndDragGestureRecognizer/_TapStatusTrackerMixin implementation details that could change.
| /// a tap gesture. | ||
| final VoidCallback? onTapTrackStart; | ||
|
|
||
| /// Called when [TapAndDragGestureRecognizer] tap tracking has been reset. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: maybe we should reference BaseTapAndDragGestureRecognizer here instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should also point out when this happens. For example of the next pointer down event, when the duration of [kDoubleTapTimeout] has elapsed between a [PointerUpEvent] and the following [PointerDownEvent].
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see you already do this in the gestures documentation. Maybe we can just make a template and insert it here? Same for onTapTrackStart.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yes, I prefer macros too.
| RenderEditable get renderEditable => editableText.renderEditable; | ||
|
|
||
| /// Whether the Shift key was pressed when the most recent [PointerDownEvent] | ||
| /// was tracked by the [TapAndDragGestureRecognizer]. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: maybe mention [BaseTapAndDragGestureRecognizer] instead.
| VoidCallback? onTapTrackStart; | ||
|
|
||
| /// Callback used to indicate that a tap tracking has been reset which happens | ||
| /// when the timer between two taps elapses, the recognizer loses the arena, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: which happens on the next [PointerDownEvent] after the timer between two taps elapses.
| Timer? _consecutiveTapTimer; | ||
| Offset? _lastTapOffset; | ||
|
|
||
| /// Callback used to indicate that a tap tracking has started. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe add that this happens on every [PointerDownEvent].
Roll Flutter from 6f09064 to d07e8ae (60 revisions) flutter/flutter@6f09064...d07e8ae 2023-07-19 [email protected] Roll Flutter Engine from 09389b16d684 to eb2285205f25 (3 revisions) (flutter/flutter#130879) 2023-07-19 [email protected] Roll Flutter Engine from 0293a7cb7887 to 09389b16d684 (5 revisions) (flutter/flutter#130867) 2023-07-19 [email protected] Move TapAndDragGestureRecognizer code under gestures (flutter/flutter#119508) 2023-07-19 [email protected] Roll Flutter Engine from 29de67c7d009 to 0293a7cb7887 (1 revision) (flutter/flutter#130858) 2023-07-19 [email protected] Roll Flutter Engine from 39d60be72ffb to 29de67c7d009 (4 revisions) (flutter/flutter#130855) 2023-07-19 [email protected] Roll Flutter Engine from b3bfc744bb61 to 39d60be72ffb (1 revision) (flutter/flutter#130847) 2023-07-19 [email protected] Roll Flutter Engine from adf6142f6738 to b3bfc744bb61 (7 revisions) (flutter/flutter#130843) 2023-07-18 [email protected] Update app_builder_test.dart for M3 (flutter/flutter#130794) 2023-07-18 [email protected] Catch errors in loadStructuredData (flutter/flutter#130748) 2023-07-18 [email protected] Fix super tiny space formatting (hope we have auto formatter in the future) (flutter/flutter#127479) 2023-07-18 [email protected] Roll Flutter Engine from 71bbecee3010 to adf6142f6738 (2 revisions) (flutter/flutter#130831) 2023-07-18 [email protected] Update SnackBar tests for M2/M3 (flutter/flutter#130717) 2023-07-18 [email protected] Roll pub packages (flutter/flutter#130821) 2023-07-18 [email protected] Relax syntax for gen-l10n (flutter/flutter#130736) 2023-07-18 [email protected] Roll Flutter Engine from 45851af55bd6 to 71bbecee3010 (7 revisions) (flutter/flutter#130820) 2023-07-18 [email protected] Roll pub packages (flutter/flutter#130608) 2023-07-18 [email protected] Roll Flutter Engine from 831da7e9dc3b to 45851af55bd6 (2 revisions) (flutter/flutter#130814) 2023-07-18 [email protected] [Android] Deletes deprecated splash screen meta-data element (flutter/flutter#130744) 2023-07-18 [email protected] Updated `ThemeData.useMaterial3` API doc, default is `true` (flutter/flutter#130764) 2023-07-18 [email protected] [labeler] Mark sync-labels as empty (flutter/flutter#130642) 2023-07-18 [email protected] Roll Flutter Engine from c27658cc5ade to 831da7e9dc3b (2 revisions) (flutter/flutter#130810) 2023-07-18 [email protected] Roll Packages from 6889cca to 3e8b813 (9 revisions) (flutter/flutter#130802) 2023-07-18 [email protected] Update `AppBar` and `AppBarTheme` tests for M2/M3 (flutter/flutter#130790) 2023-07-18 [email protected] Update app tests for M3 (flutter/flutter#130792) 2023-07-18 [email protected] Add lint check to make sure samples are linked and have tests (flutter/flutter#130523) 2023-07-18 [email protected] Roll Flutter Engine from aaec42812a1f to c27658cc5ade (2 revisions) (flutter/flutter#130799) 2023-07-18 [email protected] Extract common functionality of iOS platformviews into superclasses (flutter/flutter#128716) 2023-07-18 [email protected] Roll Flutter Engine from 88be39be7b07 to aaec42812a1f (1 revision) (flutter/flutter#130787) 2023-07-18 [email protected] Roll Flutter Engine from b46a8baf8ed9 to 88be39be7b07 (1 revision) (flutter/flutter#130784) 2023-07-18 [email protected] Roll Flutter Engine from 777fe158f4e7 to b46a8baf8ed9 (1 revision) (flutter/flutter#130782) 2023-07-18 [email protected] Fix `iconTheme` in `AppBar` doesn't apply custom `Colors.white` in the dark mode for M3 (flutter/flutter#130574) 2023-07-18 [email protected] Roll Flutter Engine from c6e23288db8d to 777fe158f4e7 (2 revisions) (flutter/flutter#130779) 2023-07-18 [email protected] Roll Flutter Engine from 116eedf769be to c6e23288db8d (3 revisions) (flutter/flutter#130778) 2023-07-18 [email protected] Roll Flutter Engine from 9d018f00d687 to 116eedf769be (2 revisions) (flutter/flutter#130774) 2023-07-18 [email protected] Roll Flutter Engine from 77ec92371846 to 9d018f00d687 (1 revision) (flutter/flutter#130772) 2023-07-18 [email protected] Document stack's clipping behaviour better (flutter/flutter#130749) 2023-07-18 [email protected] Roll Flutter Engine from f2958f9229a4 to 77ec92371846 (1 revision) (flutter/flutter#130769) 2023-07-18 [email protected] Roll Flutter Engine from 3cceb705007e to f2958f9229a4 (1 revision) (flutter/flutter#130767) 2023-07-18 [email protected] Roll Flutter Engine from 09689d37e1d6 to 3cceb705007e (2 revisions) (flutter/flutter#130763) 2023-07-18 [email protected] Stabilize hybrid_android_views_integration_test rendering tree (flutter/flutter#130751) 2023-07-17 [email protected] update link to good first issues (flutter/flutter#130759) 2023-07-17 [email protected] Prevent `InputDecorator` from supplying its descendants with non-normalized constraints (flutter/flutter#130460) 2023-07-17 [email protected] Roll Flutter Engine from 15c15fd75743 to 09689d37e1d6 (3 revisions) (flutter/flutter#130758) 2023-07-17 [email protected] [tools/ios_build_ipa] fallback to CFBundleName if CFBundleDisplayName is absent (flutter/flutter#130752) 2023-07-17 [email protected] Resolve TODOs in channels integration test (flutter/flutter#130745) 2023-07-17 [email protected] Roll Flutter Engine from ddbe23b374d8 to 15c15fd75743 (2 revisions) (flutter/flutter#130746) ...
This PR moves the
TapAndDragGestureRecognizercode from widgets/ to gestures/.Text-exempt: dependency removal + refactor, no change in behaviour
Pre-launch Checklist
///).