Skip to content
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

Add an option to let GestureDetector/InkWell trigger onTap/onTapUp when doubleclick #110300

Open
ngugcx opened this issue Aug 26, 2022 · 13 comments
Assignees
Labels
c: proposal A detailed proposal for a change to Flutter customer: google Various Google teams f: material design flutter/packages/flutter/material repository. framework flutter/packages/flutter repository. See also f: labels. P1 High-priority issues at the top of the work list team-design Owned by Design Languages team triaged-design Triaged by Design Languages team

Comments

@ngugcx
Copy link

ngugcx commented Aug 26, 2022

Use case

Currently, GestureDetector/InkWell:

This design may not be a big problem on mobile platforms, since double taps are not used very often.
But on desktop, it's everywhere and the user experience is poor due to the onTap latency.
onTapDown is not delayed, but it cannot replace onTap/onTapUp.

Proposal

It's better to add a global option to:

  • Trigger onTap/onTapUp without a delay on single tap no matter the onDoubleTap is enabled or not.
  • Make GestureDetector/InkWell also trigger onTap/onTap(without a delay) on double tap.Take Microsoft's WPF framework as an example, on double tap, it triggers MouseLeftButtonDown/MouseLeftButtonUp twice and MouseDoubleClick once.
@ngugcx ngugcx changed the title Add an option to let GestureDetector/InkWell trigger onTap when doubleclick Add an option to let GestureDetector/InkWell trigger onTap/onTapUp when doubleclick Aug 26, 2022
@huycozy huycozy added the in triage Presently being triaged by the triage team label Aug 26, 2022
@huycozy huycozy added framework flutter/packages/flutter repository. See also f: labels. f: material design flutter/packages/flutter/material repository. c: proposal A detailed proposal for a change to Flutter and removed in triage Presently being triaged by the triage team labels Aug 26, 2022
@ngugcx
Copy link
Author

ngugcx commented Aug 28, 2022

This issue affects all widgets using GestureDetector/InkWell, all of them feel lag on desktop if onDoubleTap is enabled.
Is anybody working on this?

@Renzo-Olivares
Copy link
Contributor

Renzo-Olivares commented Sep 7, 2022

Hi @ngugcx, thank you for reporting this issue. However, the behavior you are describing is expected when using a TapGestureRecognizer and a DoubleTapGestureRecognizer, which GestureDetector does if given both callbacks.

The delay described is necessary when deciding which GestureRecognizer is to win in the GestureArena. The TapGestureRecognizer waits until the delay is over to see if DoubleTapGestureRecognizer has won the arena. As of now the GestureArena cannot have multiple winners. kDoubleTapTimeout is the time that the second tap should come before so the action can be considered a double tap. On native desktop (I'm on macOS), if the amount of time between the first tap and the second tap is too long then the double tap action is not initiated. An example would be if I tap down on a word in a text field and wait 1 second to release the tap and tap down again, then a double tap is not registered.

To achieve tap and double tap behaviors without a delay, you might want to use SerialTapGestureRecognizer. Which does not delay onTap/onTapUp because it declares victory in the GestureArena as soon as a tap is detected. The count given in SerialTapDownDetails and SerialTapUpDetails can then be used to determine what to do on a single tap/double tap.

There is one caveat, being that SerialTapGestureRecognizer is not available through GestureDetector, and you would have to use it by passing it to RawGestureDetector. If SerialTapGestureRecognizer matches your use cases, but you still want to use GestureDetector then it may be a good idea to open a proposal to add SerialTapGestureRecognizer to GestureDetector.

@Renzo-Olivares Renzo-Olivares reopened this Sep 7, 2022
@Renzo-Olivares Renzo-Olivares added the waiting for customer response The Flutter team cannot make further progress on this issue until the original reporter responds label Sep 7, 2022
@ngugcx
Copy link
Author

ngugcx commented Sep 8, 2022

Thanks! @Renzo-Olivares . I've not tried SerialTapGestureRecognizer, after reading the doc, I think it should match my use case.

Now the problem is that InkWell/GestureDetector are regarded as the higher level way to handle mouse events. Most of the docs recommend them, and lots of controls including the offical basic ones (tab, list ...) use InkWell/GestureDetector. This makes the default user experience of flutter desktop awful.
I know why the delay exists in InkWell/GestureDetector. But I don't understand why they are designed like that, where are they supposed to be used? Are there many cases where only double-tap is needed?

@github-actions github-actions bot removed the waiting for customer response The Flutter team cannot make further progress on this issue until the original reporter responds label Sep 8, 2022
@ngugcx
Copy link
Author

ngugcx commented Dec 13, 2022

For the desktop, the mouse is very important. If Flutter is perfunctory with the mouse, the user experience of the desktop will be out of the question. Single-tap, double-tap and scrolling, they are all needed to be optimized.

@sidetraxaudio
Copy link

Hi @Renzo-Olivares and @ngugcx

Ngugcx is correct. Gesture detector and Inkwell widgets should absolutely implement some sort of bool to override ontap() delay when ondoubletap() is implemented, especially on desktop. If onTap is benign (doesn't perform a critical action) as it is in most desktop-based systems, there is no reason it can't be implemented sequentially with onDoubleTap.

On desktop, the simple act of clicking once on an object to obtain selection focus and double clicking to execute has been around as long as graphical OSs have. It needs to be fixed without a complex implementation on our part to bring it inline with literally 'everything else in the world'. It makes all desktop apps sluggish and unresponsive.

@0xNF
Copy link

0xNF commented Apr 25, 2023

I'd also prefer a more standard solution to this problem than fiddling around with the SerialTapGestureRecognizer. I mean, look at how much more verbose and janky this code is:

RawGestureDetector(
          gestures: <Type, GestureRecognizerFactory>{
            SerialTapGestureRecognizer: GestureRecognizerFactoryWithHandlers<SerialTapGestureRecognizer>(
              () => SerialTapGestureRecognizer(),
              (SerialTapGestureRecognizer instance) {
                instance
                  ..onSerialTapDown = (SerialTapDownDetails details) {
                    print(details.count);
                    if (details.count == 1) {
                      widget.onTap();
                    } else if (details.count == 2) {
                      widget.onDoubleTap();
                    }
                  };
              },
            )
          },
      child: child
}

compared to the relative niceness of the standard GestureDetector:

 GestureDetector(
    onTap: widget.onTap;
    onDoubleTap: widget.onDoubeTap,
    child:  child,
)

And like ngugcx says, all the documentation focuses on the easier (but worse) solutions.

Some kind of overeride in gesture detector would be nice. Or a way to otherwise specify what kind of algorithm to use. Because on desktop, the standard click/double click interaction feels painfully laggy.

@flutter-triage-bot flutter-triage-bot bot added multiteam-retriage-candidate team-design Owned by Design Languages team triaged-design Triaged by Design Languages team labels Jul 8, 2023
@flutter-triage-bot
Copy link

This issue is assigned but has had no recent status updates. Please consider unassigning this issue if it is not going to be addressed in the near future. This allows people to have a clearer picture of what work is actually planned. Thanks!

@flutter-triage-bot flutter-triage-bot bot removed the triaged-design Triaged by Design Languages team label Jul 8, 2023
@ngugcx
Copy link
Author

ngugcx commented Jul 10, 2023

@Renzo-Olivares , almost one year passed, any progress on this issue and the new design? thanks.

@flutter-triage-bot flutter-triage-bot bot added the Bot is counting down the days until it unassigns the issue label Jul 30, 2023
@Renzo-Olivares Renzo-Olivares added triaged-design Triaged by Design Languages team P1 High-priority issues at the top of the work list labels Nov 6, 2023
@Renzo-Olivares
Copy link
Contributor

Apologies for the lack of updates as I have not been actively working on this issue. I have bumped the priority on this issue and will be giving it another look. Thanks for the patience.

@Renzo-Olivares Renzo-Olivares removed the Bot is counting down the days until it unassigns the issue label Nov 6, 2023
@jacobsimionato
Copy link
Contributor

This issue is affecting a production Google app using desktop web - see internal bug b/322887506 for context. The existing behavior does not break the functionality seeing as it is still possible to do everything, however it creates a perception of jank and makes the user experience feel very wrong.

While SerialTapGestureRecognizer works for simpler cases, it seems like there is a usability issue here. There should be an obvious way to achieve this functionality seeing as it's very common on desktop web and desktop native, and it should be possible even when it's not the same widget recognizing the single and double clicks. The widget tree could have a nested single click recognizer that is in a completely different file etc.

Here is some minimal repro code in case it's handy for anyone:

Repro code for Dartpad
import 'package:flutter/material.dart';

const Color darkBlue = Color.fromARGB(255, 18, 32, 47);

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData.dark().copyWith(
        scaffoldBackgroundColor: darkBlue,
      ),
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: Center(
          child: TappingDemo(),
        ),
      ),
    );
  }
}

class TappingDemo extends StatefulWidget {
  @override
  State<TappingDemo> createState() => _TappingDemoState();
}

class _TappingDemoState extends State<TappingDemo> {
  String text = "";

  @override
  Widget build(BuildContext context) {
    return Column(children: [
      Text('Sequence of single and double taps: $text'),
      GestureDetector(
        onDoubleTap: () {
          setState(() {
            text = text + 'D';
          });
        },
        child: ElevatedButton(
            child: Text('Press me'),
            onPressed: () {
              setState(() {
                text = text + 'S';
              });
            }),
      ),
    ]);
  }
}

@jibbers42
Copy link

Inlining a slightly modified (reduced) version of the duplicate issue I opened at #113906...

I'd like a way to disable the delay, implying both events may fire. I've developed a couple small apps for Flutter Desktop and in each app, where I've wanted tap and double tap on the same widget, I've not wanted the delay imposed.

Issue #22950 discusses adjusting kDoubleTapTimeout to reduce the delay, but that's not an appropriate solution as it simply makes double clicks harder to achieve and still will have a noticeable delay if configured so that double click is usable.

In one instance I was able to use SerialTapGestureRecognizer (https://discord.com/channels/608014603317936148/1030649689802035251/1031017424935460926), but that isn't always easy or even possible. For example, DataTable forces you to pass DataCells, so there is no opportunity to wrap it. Even if you can use it, it would be nicer to use the onTap and onDoubleTap callbacks that a widget may already provide.

I'd like an option to disable the onTap delay when there is also an onDoubleTap. I'm not sure the best way of doing this... some kind of global config may work, or maybe instead/also a widget level config that will need to be added to widgets that expose onTap and onDoubleTap.

I do think "fixing" this experience on desktop is important. It simply doesn't work the way users on desktop platforms expect it to.

@Caffeinix
Copy link
Contributor

Similar problem to @jibbers42 here: we're using ListTile, which internally uses InkWell, which derives from InkResponse, which uses GestureRecognizer. There's no way to retrofit it with the behavior described here without reimplementing everything from selection to Material ink highlight effects from scratch. The best approach I can immediately think of for fixing it is to introduce a widget you can wrap around a subtree to prevent onTap events from waiting for kDoubleTapTimeout. GestureRecognizer could traverse the widget hierarchy upwards to see if there's one of those widgets present, and use that to determine its behavior. I think that's a lot more likely to be feasible than adding a parameter to GestureRecognizer, then propagating it up to InkWell, then propagating that up to every Material widget in existence.

@tdenniston
Copy link

It's possible that #106170 should be dup'd to this one.

Here is my own workaround for this issue, in case it is helpful:

class DoubleTapInkWell extends StatelessWidget {
  const DoubleTapInkWell({super.key});

  @override
  Widget build(BuildContext context) {
    return MultiTapListener(
      onDoubleTap: () {
        // Handle double tap. Adding this handler does not cause the 300ms delay in single-taps.
      },
      child: InkWell(
        onTap: () {
          // Handle normal tap. Called on every tap.
        },
      ),
    );
  }
}

using a simple MultiTapListener widget I wrote here: https://gist.github.com/tdenniston/9197f880d2ad7acea04e36fb14d91cfc. Importantly, it does not participate in the gesture arena, which is useful for cases where you want to keep the existing single-tap handler such as with InkWell or ListTile and add on a simple extra handler for a double-tap. But its lack of participation in the gesture arena may not fit all scenarios.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c: proposal A detailed proposal for a change to Flutter customer: google Various Google teams f: material design flutter/packages/flutter/material repository. framework flutter/packages/flutter repository. See also f: labels. P1 High-priority issues at the top of the work list team-design Owned by Design Languages team triaged-design Triaged by Design Languages team
Projects
None yet
Development

No branches or pull requests

10 participants