Skip to content

Conversation

@jonahwilliams
Copy link
Contributor

@jonahwilliams jonahwilliams commented Apr 30, 2021

Currently flutter web and desktop apps allow scrolling by dragging with the mouse. This feels unnatural compared to other desktop applications, and makes it harder to use more complex mouse gestures on items that are contained within a scrollable and impossible to accurately select text (since the text selection drag competes with the scroll drag).

This is likely a breaking change, so the old scrollable behavior is configurable via scroll behavior: enableMouseDrag

Nothing broke!

Fixes #71322

@flutter-dashboard flutter-dashboard bot added a: tests "flutter test", flutter_test, or one of our tests framework flutter/packages/flutter repository. See also f: labels. labels Apr 30, 2021
@google-cla google-cla bot added the cla: yes label Apr 30, 2021
///
/// This is used by scrollables to force mouse interactions to go through the
/// scroll wheel or scrollbar, instead of allowing drag gestures to move them.
final bool rejectMousePointers;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This could be inverted to something like touchOnly?

Copy link
Contributor

@chunhtai chunhtai Apr 30, 2021

Choose a reason for hiding this comment

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

I wonder should this be platform dependent? for example set it to true when platform is desktop or web. Do you know what is the android behavior it user uses mouse?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This isn't really a platform adaption, its an adaption based on how the user is interacting with the device. My impression of android/ios is that mouse interaction is uncommon, and if it is apps are likely to do the wrong thing anyway.

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe we can use a set of acceptedPointerDevices, so it's not mouse-specific, and put it in ScrollBehavior so we can configure it by platform like @chunhtai suggests.

Copy link
Contributor

Choose a reason for hiding this comment

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

Is the idea that when it is mouse, prevent drag scroll regardless of the platform?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Generally speaking scrolling with a mousewheel or scrollbar is more efficient that scrolling by a simulated touch gesture. By enabling mouse based drag in scrollables, flutter forces any gesture accepting widgets within those lists to compete with the scroll drag events. Disambiguation of these gestures isn't perfect and neither are the users movements - this leads to a harder to use application.

For example, try and drag to scroll to the right/left on the flutter dashboard. Because the left/right drags compete with the up/down drags its easy to get stuck accidentally. Of couse that also implies we need provide a way to keep this enabled, to support the nested scrollable with no scrollbar pattern.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I also don't think you should try to generalize this much. Its really unlikely that a gesture detector wants to disambiguate between, say, touch and inverse stylus. And if it does, it might be doing something entirely custom which would make extra configurability on our end a waste.

mouse and touch (stylus/inverse stylus) are really very separate kinds of interaction.

Copy link
Contributor

@Piinks Piinks left a comment

Choose a reason for hiding this comment

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

This is awesome thank you!

I linked the issue this looks likely to resolve.

An earlier implementation of this broke DraggableScrollableSheet, but it looks like this isn't breaking anything 🎉 (still need to check g3, I've fired those tests off to see).

_gestureRecognizers = <Type, GestureRecognizerFactory>{
HorizontalDragGestureRecognizer: GestureRecognizerFactoryWithHandlers<HorizontalDragGestureRecognizer>(
() => HorizontalDragGestureRecognizer(),
() => HorizontalDragGestureRecognizer(rejectMousePointers: true),
Copy link
Contributor

Choose a reason for hiding this comment

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

Originally we thought about putting this feature in ScrollBehavior. Down below here the _configuration is the ScrollBehavior setting up the velocityTrackerBuilder. That way folks can configure this as they like, or target subtrees by putting in another ScrollConfiguration.

///
/// This is used by scrollables to force mouse interactions to go through the
/// scroll wheel or scrollbar, instead of allowing drag gestures to move them.
final bool rejectMousePointers;
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe we can use a set of acceptedPointerDevices, so it's not mouse-specific, and put it in ScrollBehavior so we can configure it by platform like @chunhtai suggests.

///
/// This is used by scrollables to force mouse interactions to go through the
/// scroll wheel or scrollbar, instead of allowing drag gestures to move them.
final bool rejectMousePointers;
Copy link
Contributor

Choose a reason for hiding this comment

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

Is the idea that when it is mouse, prevent drag scroll regardless of the platform?

_gestureRecognizers = <Type, GestureRecognizerFactory>{
VerticalDragGestureRecognizer: GestureRecognizerFactoryWithHandlers<VerticalDragGestureRecognizer>(
() => VerticalDragGestureRecognizer(),
() => VerticalDragGestureRecognizer(rejectMousePointers: true),
Copy link
Contributor

@chunhtai chunhtai Apr 30, 2021

Choose a reason for hiding this comment

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

Does the developer have a way to override this behavior? I wonder whether this should be a theme-thing. cc @HansMuller

edit: I don't know there was ScrollConfiguration, I think this can be in ScrollConfiguration

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ScrollConfiguration seems like a reasonable choice - assuming it is only created for lists and things? Like draggable sheets need to still be draggable, but that doesn't use a scrollable correct?

@jonahwilliams
Copy link
Contributor Author

Assuming we put this in the scroll configuration, I imagine the breaking change might go something like:

  1. expose the ability to turn of drags - breaking change announcement
  2. default to false?

@chunhtai
Copy link
Contributor

Assuming we put this in the scroll configuration, I imagine the breaking change might go something like:

  1. expose the ability to turn of drags - breaking change announcement
  2. default to false?

I vote for first, we should target the user experience

@jonahwilliams
Copy link
Contributor Author

Next patch is a little WIP, but moves the configuration onto the scroll behavior and then defaults it to the current incorrect behavior

@jonahwilliams
Copy link
Contributor Author

What is the over/under on adding a field to scroll behavior being a breaking change? i'm a bit concerned we're actually going to make this more breaking not less, to allow configuring a behavior that is IMO a bug.

@jonahwilliams
Copy link
Contributor Author

i guess as long as people are extending ScrollBehavior it should be OK

@chunhtai
Copy link
Contributor

chunhtai commented Apr 30, 2021

Next patch is a little WIP, but moves the configuration onto the scroll behavior and then defaults it to the current incorrect behavior

Oh sorry I overlooked the original comment. I meant I vote for defaulting it to true to prevent mouse scrolling (or whatever you think the correct behavior is unless it break internal customer, then you probably need to do three phase transitions...), and I don't have opinion whether we expose and override or not. We can always expose it later if people want to change it. Not exposing it will however, probably get a lot of complain though

@jonahwilliams
Copy link
Contributor Author

@Hixie considerations for naming? I think the correct name would be something like mouseDrag and not enableMouseDrag as I have it - but then maybe that should be mousedrag? Unsure 😄

@Hixie
Copy link
Contributor

Hixie commented May 1, 2021

How about rather than a boolean here we just have a set of pointer kinds that accept drags? Then you can name it just "devices" and "dragDevices" or some such. (That would also let people decide if styluses should allow drags. I suspect there are UIs where you'd want a stylus to draw and a finger to drag.)

@jonahwilliams
Copy link
Contributor Author

Yeah, that was the suggestion that @Piinks and @chunhtai made above 😅 - I'll give it a shot

@Piinks
Copy link
Contributor

Piinks commented May 3, 2021

What is the over/under on adding a field to scroll behavior being a breaking change? i'm a bit concerned we're actually going to make this more breaking not less, to allow configuring a behavior that is IMO a bug.

I don't think it will be that breaking, we just made a bunch of changes to ScrollBehavior and I already migrated the implementing customers to land those changes. This was actually on my list for the next change to make before migrating customers back. :)

@jonahwilliams
Copy link
Contributor Author

we just made a bunch of changes to ScrollBehavior and I already migrated the implementing customers to land those changes

Awesome!

Copy link
Contributor

@Piinks Piinks left a comment

Choose a reason for hiding this comment

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

LGTM!

Comment on lines 556 to 559
() => VerticalDragGestureRecognizer(supportedDevices: _configuration.dragDevices),
(VerticalDragGestureRecognizer instance) {
instance
..onDown = _handleDragDown
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: These could probably be chained to match the rest readability-wise. :)

Suggested change
() => VerticalDragGestureRecognizer(supportedDevices: _configuration.dragDevices),
(VerticalDragGestureRecognizer instance) {
instance
..onDown = _handleDragDown
() => VerticalDragGestureRecognizer(),
(VerticalDragGestureRecognizer instance) {
instance
..onDown = _handleDragDown
..supportedDevices = _configuration.dragDevices

/// The device types that this gesture recognizer will accept drags from.
///
/// If not specified, defaults to all pointer kinds.
final List<PointerDeviceKind> supportedDevices;
Copy link
Contributor

Choose a reason for hiding this comment

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

Use a set instead?

|| oldDelegate.overscrollIndicator != overscrollIndicator
|| oldDelegate.physics != physics
|| oldDelegate.platform != platform
|| listEquals(oldDelegate.dragDevices, dragDevices)
Copy link
Contributor

@chunhtai chunhtai May 3, 2021

Choose a reason for hiding this comment

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

we should probably use set otherwise the order difference may cause a rebuild.

also we should set the type? i.e. listEquals<PointerDeviceKind>

@jonahwilliams
Copy link
Contributor Author

I wasn't sure whether to prefer a set or list, I mostly chose a list because I could use PointerDeviceKind.values as a default and since contains on small lists is generally faster (but that doesn't really matter here). Will update to a set

@Piinks
Copy link
Contributor

Piinks commented May 3, 2021

Triggered a global test run manually, so it likely won't update here. Everything passed. 🎉 Will set the G check green manually.

@jonahwilliams
Copy link
Contributor Author

Updated to use a set, and to set the supported devices in the gesture factory callback

}

const Set<PointerDeviceKind> _kAllPointerDeviceKinds = <PointerDeviceKind>{
...PointerDeviceKind.values,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I didn't know we could do this!

Copy link
Contributor

Choose a reason for hiding this comment

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

TIL!

Copy link
Contributor

Choose a reason for hiding this comment

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

🤩

@talksik
Copy link

talksik commented Nov 2, 2022

I'm still having this problem. horizontal scroll within a vertical scroll. However, fluent-ui package has a HorizontalScollView that works. However, they don't show a scrollbar.

@Piinks
Copy link
Contributor

Piinks commented Nov 2, 2022

@talksik can you please file a new issue with:

  • a minimal code sample that reproduces the issue
  • the output of flutter doctor -v

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

a: tests "flutter test", flutter_test, or one of our tests framework flutter/packages/flutter repository. See also f: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feature_request] Disable scrolling by drag with a mouse

10 participants