Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[flutter] when primary mouse pointers don't contact a focused node, reset the focus #82575

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

Merged
merged 19 commits into from
Jun 9, 2021

Conversation

jonahwilliams
Copy link
Member

@jonahwilliams jonahwilliams commented May 14, 2021

Fixes #82515
Fixes #32433

Introduces a widget that checks if a pointer down event has hit the currently focused nodes' render object. This leads to odd behavior with text field decorations due to the focus being only owned by the inner editable text widget.

The modal route's focus scoped node is used to "reset" the focus when pointer did not contact either.

This is limited to mouse pointers on desktop and mobile web.

@flutter-dashboard flutter-dashboard bot added f: material design flutter/packages/flutter/material repository. framework flutter/packages/flutter repository. See also f: labels. labels May 14, 2021
@google-cla google-cla bot added the cla: yes label May 14, 2021
@jonahwilliams
Copy link
Member Author

This currently only applies for keyboard events, I'd need to do more research into how touch events should behave

@skia-gold
Copy link

Gold has detected about 2 untriaged digest(s) on patchset 2.
View them at https://flutter-gold.skia.org/cl/github/82575

@jonahwilliams jonahwilliams marked this pull request as draft May 14, 2021 21:46
@jonahwilliams
Copy link
Member Author

adding another widget here seems to be changing the layout slightly, which is unfortunate...

@flutter-dashboard
Copy link

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 package:flutter.

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.

@@ -841,11 +843,11 @@ class _ModalScopeState<T> extends State<_ModalScope<T>> {
key: widget.route._subtreeKey, // immutable
child: Builder(
builder: (BuildContext context) {
return widget.route.buildPage(
return _FocusTrap(child: widget.route.buildPage(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any reason we scope at route level?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i scoped it at the route level so that I can have this and the shortcuts widget used by the SelectionArea widget use the same focus node.

FocusNode get focusNode => widget.focusNode ?? _focusNode;

void _stealFocus(TapDownDetails details) {
if (details.kind != PointerDeviceKind.mouse)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you know what is the native behavior for mobile if you have an external mouse?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i don't have access to the hardware to test this. we could definitely just disable the behavior on iOS/android until we could test it

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(disabled for non-desktop platforms for now)

@MarcusWichelmann
Copy link

MarcusWichelmann commented May 19, 2021

Just an outsiders thought:

This looks great and all, but should it really be necessary to add a FocusTrap widget to the widget tree just to get a behaviour which one might expect to be there by default? Of course, this is a good first step, but maybe this functionallity should also be included into other widgets like e.g. MaterialApp so programmers get the behaviour they know from the web out of the box without having to google for it and adding the FocusTrap widget theirself?

@jonahwilliams
Copy link
Member Author

I agree, I'm not sure that this is the right solution exactly but i'm not sure how else you would do it. Widgets can't unfocus themselves if they aren't hit 🤔

@Wissperwind
Copy link

Wissperwind commented May 19, 2021

In HTML Web every element can be focused. Even if it is not editable, correct?
Can't we make every widget fucusable? This would change the focus away from a text field if you click for example on the background scaffold or the scroll bar.

I am not a flutter developer. So this is only an idea from outside but I wanted to post it.

For me it is rather important to get some working solution in the next two weeks than to get a really good solution.

@jonahwilliams
Copy link
Member Author

An html element isn't equivalent to a flutter widget. A widget not have any UI, only configuring other widgets themselves. Even making every widget with uI focusable would make pure focus traversal painful.

@jonahwilliams
Copy link
Member Author

Based on some discussion with @gspencergoog , I've updated this to use the unfocus functionality of the modal route's FocusScopeNode. Of course, using the gesture recognizer the way that I am is breaking the world, so I'll need to find a way to make this change not compete with any other recognizers somehow...

@skia-gold
Copy link

Gold has detected about 3 untriaged digest(s) on patchset 4.
View them at https://flutter-gold.skia.org/cl/github/82575

@jonahwilliams jonahwilliams marked this pull request as ready for review May 20, 2021 22:21
@@ -136,8 +136,8 @@ typedef GestureTapCancelCallback = void Function();
/// any buttons.
abstract class BaseTapGestureRecognizer extends PrimaryPointerGestureRecognizer {
/// Creates a tap gesture recognizer.
BaseTapGestureRecognizer({ Object? debugOwner })
: super(deadline: kPressTimeout , debugOwner: debugOwner);
BaseTapGestureRecognizer({ Object? debugOwner, Set<PointerDeviceKind>? supportedDevices })
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will revert, unrelated

_requestKeyboard();
},
onDidGainAccessibilityFocus: handleDidGainAccessibilityFocus,
return FocusArea(
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because we want clicks on the decorations of at text field to retain focus

@jonahwilliams
Copy link
Member Author

PTAL at the current approach

@jonahwilliams jonahwilliams changed the title [flutter] introduce a focus trap at the page level [flutter] when primary mouse pointers don't contact a focused node, reset the focus May 20, 2021
Copy link
Contributor

@gspencergoog gspencergoog left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

32384589-a60f0e74-c078-11e7-9bc1-e5b5287aea9d

|| event.buttons != kPrimaryButton
|| event.kind != PointerDeviceKind.mouse
|| _shouldIgnoreEvents
|| currentFocus == null)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Can you put braces on this conditional block? Having a really long multi-line conditional just reads weird without the braces.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will do

ignoring: ignoreEvents,
child: child,
child: _FocusTrap(
focusScopeNode: focusScopeNode,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I'm OK with the current structure: the widget using the node is a private class, so we have control over its lifetime, and it doesn't appear to be doing anything that would attach the node to the focus tree.

@jonahwilliams
Copy link
Member Author

FYI @renyou , not sure what is up with the google testing on this PR

@renyou
Copy link
Contributor

renyou commented Jun 9, 2021

It seems some google internal tests may be very slow and I am going to increase the timeout threshold. Hopefully it can resolve this.

@renyou
Copy link
Contributor

renyou commented Jun 9, 2021

@jonahwilliams Did you trigger a full test on this PR?

@jonahwilliams
Copy link
Member Author

yes, since it is a potentially a substantial change I wanted to be sure we ran as many tests as possible...

@renyou
Copy link
Contributor

renyou commented Jun 9, 2021

Yes! The tests are actually all done, just took longer to analyze all the test results. I manually set the results. Everything looks good!

@jonahwilliams
Copy link
Member Author

🎊

@HansMuller
Copy link
Contributor

@jonahwilliams - does this PR address #74354?

@jonahwilliams
Copy link
Member Author

This only handles clicks via mouse on desktop and mobile web, not taps - though the PR also mentions clicks. I'm not sure if that is trying to fix the same issue as this or a different input connection based focus issue

@jpnurmi
Copy link
Member

jpnurmi commented Jul 1, 2021

@Hixie
Copy link
Contributor

Hixie commented Jul 5, 2021

If we want this behaviour, why not just put a Focus widget around the route?

...but I'm not convinced we want this behaviour. It's not what happens on Windows and macOS as far as I can tell.

@Hixie
Copy link
Contributor

Hixie commented Jul 5, 2021

I think this is just something we want on Web, and on Web the behaviour is just that the root of the tree is itself focusable, no need to check whether a tap would have focused a node and all that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
f: material design flutter/packages/flutter/material repository. framework flutter/packages/flutter repository. See also f: labels.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Not possible to de-focus a text field without clicking another focusable widget [Web]: Click outside of text field should unfocus