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

Click count on Pointer Events #3899

Open
xorgy opened this issue Sep 4, 2024 · 7 comments
Open

Click count on Pointer Events #3899

xorgy opened this issue Sep 4, 2024 · 7 comments
Labels
S - api Design and usability S - enhancement Wouldn't this be the coolest?

Comments

@xorgy
Copy link
Contributor

xorgy commented Sep 4, 2024

Click counting for double and triple clicks is a common need, and on some platforms is hard to address if they aren't determined in winit. In particular the Mac doesn't have a readily accessible API for getting the double click interval or detection rectangle separate from an individual event.

Some notes on implementation:

  • on Mac, NSEvent has clickCount; easy. (there is also a doubleClickInterval attached to the NSEvent)
  • Windows has an easy way to query the double click interval (GetDoubleClickTime) and a standard double click rectangle (SM_CXDOUBLECLICK/SM_CYDOUBLECLICK accessible with GetSystemMetrics), which is widely interpreted as extending to multiple clicks (e.g. in the WPF implementation and some browsers).
  • Neither X11 and Wayland themselves bother to expose an interval setting, nor is there a count on mouse button events; it's left up to toolkits to decide where to look for a click interval setting, if there even is one (some toolkits don't bother exposing the option). Chromium just hardcodes a 500ms interval and a 4px rectangle on platforms that don't handle it specifically, these are the Windows defaults.
  • Android doesn't specify any standard way to store this preference (and doesn't encode count on MotionEvent), which could be an issue for the minority of people who use mice with Android apps (on ChromeOS and DeX); could again just go with some well-known values (like Chromium and others do, when they need to synthesize a click count on a platform that doesn't have readily accessible settings)
  • On the web, click count is exposed in the detail property of mousedown/mouseup events, and browsers generally expose the best available platform behavior.

Overall, I think this would be not terribly hard to implement in a useful way; and even on platforms where there are shortcomings, it's better than not having it at all.

Blocked on #3833.

@xorgy xorgy added the S - enhancement Wouldn't this be the coolest? label Sep 4, 2024
@madsmtm madsmtm added the S - api Design and usability label Sep 4, 2024
@madsmtm madsmtm added this to the Version 0.31.0 milestone Sep 4, 2024
@Enyium
Copy link

Enyium commented Sep 21, 2024

Not to say you said that, but to clarify: I think winit's interpretation of higher-order clicks should not artificially be limited to support at most triple-clicks. It should be unbound.

Note that, on Windows, you should now use GetSystemMetricsForDpi() instead of GetSystemMetrics(). For me, GetSystemMetricsForDpi() on Windows 10 always returns 4 for SM_CXDOUBLECLK and SM_CYDOUBLECLK for the DPI values 1 to 0x7fff_ffff. But Microsoft could, of couse, change that any time.

Also, Windows treats the down-action as the double-click event, not the up-action. When the window class style CS_DBLCLKS is set, the messages sent are WM_LBUTTONDOWN, WM_LBUTTONUP, WM_LBUTTONDBLCLK, WM_LBUTTONUP (proof). CS_DBLCLKS shouldn't be set, if you're interpreting double- and higher-order clicks yourself, or you'd alternatively need to treat WM_LBUTTONDBLCLK the same as WM_LBUTTONUP (same for other buttons). (I don't know if winit wants to be resilient against unexpected style changes from other code via SetClassLongPtr(), in which case it could handle WM_LBUTTONDBLCLK [and all others ending in ...DBLCLK], even though it doesn't set CS_DBLCLKS.)

Notably, however, browsers send the dblclick DOM event on mouse-up, not on mouse-down (tested on Windows 10 with Firefox and Brave, which is Chromium, using this page).

I think, ideally, winit would adhere to the more snappy behavior of sending higher-order click events on mouse-down; at least on Windows where the OS sets this precedent.

The article "Implementing higher-order clicks" from Raymond Chen contains some valuable information, like on how SM_CXDOUBLECLK and SM_CYDOUBLECLK are to be interpreted.

@Enyium
Copy link

Enyium commented Sep 21, 2024

GPT-4o thinks slow drifting during the performance of a higher-order click is allowed. According to it, the spacial constraints would only apply between two consecutive clicks, and not from the first click in the series to the one currently judged.

Custom-drawing UI frameworks like Slint would need to reasonably assess whether a higher-order click (which starts with double-clicks) happened on a certain widget. In the old days, the widgets were different child windows with their own class styles and window procedures, which meant that double-clicks where one click happened outside and the next inside the widget, weren't interpreted as double-clicks onto the widget.

  1. To allow for everything to be handled perfectly, you actually seem to need to send the whole series of accumulated coordinates of the series of connected clicks to the event receiver, because widgets could have any shape (like circular).
  2. An easier way that wouldn't involve a list with a coordinate count not known from the beginning would be to combine all coordinates of a click series into a single rectangle and only send it. (Every additional click that's temporally and spacially validly connected to the click series extends the built up rectangle.)
  3. Yet another way would be to disallow drifts like mentioned at the start of the comment, and only send the coordinate of the latest click. This would then mean that the end user could theoretically have performed previous clicks outside the widget and only the last one inside it, and the UI framework would need to see this as a higher-order click onto the widget. But, with no drift allowed, this effect couldn't involve a large area.

But maybe a reset API would be appropriate, so coordinates would be checked one by one by the UI framework to identify the widget that was hit, and the framework would be responsible to reset winit's click count when the latest higher-order click event's widget isn't the same as the previous click event's widget. In this case, the UI framework would tell winit to reset the click count and treat the click event as a regular click.

@kchibisov
Copy link
Member

No event should be added for that, it should be just a property of the existing button event telling how deep you're in the click sequence(or whatever you call it).

Users can figure out themselves how they want to handle double clicks, etc, we just should provide with that information, nothing more, at least for now.

@Enyium
Copy link

Enyium commented Sep 21, 2024

I wasn't saying another event should be added. winit just needs to provide enough information, so higher-order clicks can behave in the best way possible, which is the case when the UI framework at the end of the line can safely attribute the event to a certain widget. I'm tending towards the approach outlined in my last paragraph.

It may only be a little awkward regarding the data that events bring with them, if on some platforms, higher-order clicks happen on mouse-down, and on other platforms on mouse-up. Having the same data (like click count) in both events, and having to handle them in both wouldn't be that great. But possible, if necessary, maybe as num_clicks: Option<NonZeroUsize>, where the Option would convey whether the event type (down/up) is the one on the platform that is associated with higher-order clicks as well as whether a higher-order click (double-clicks and up) happened. Then, the UI framework must converge mouse-down and mouse-up to a higher-order handler itself.

@xorgy
Copy link
Contributor Author

xorgy commented Sep 22, 2024

It's not that awkward. There is only one major environment where click count is directly provided on pointer related events (macOS). We should match the cases where it is available on macOS events, using a combination of platform settings (on Windows) and sensible defaults (on X11, Wayland, Android, etc.). It will most likely be Option<NonZero> anyway (though usize is gratuitous).
It is still possible for toolkits and applications to implement their own click counting, though generally it wouldn't be any better than this. Double click behavior is rarely based on widget bounds, it is usually based on a fixed maximum distance per click from the previous click; but if somebody wanted to implement that nobody is going to stop them.

@kchibisov
Copy link
Member

The alternative here is that we may just provide a data to detect double clicks and let the application developer decide how they want to process all of that. Like e.g. define a region for double click and interval, but let the actual count be on the user.

Though, given how small it is I don't think it hurt to do in winit based on macOS as suggested.

@xorgy
Copy link
Contributor Author

xorgy commented Sep 24, 2024

The attraction to having it in the event from Winit for Masonry specifically is that it covers everything in our internal PointerEvent once we have click counts. The other side is that it may not be possible to implement it correctly on macOS without taking the click count from the NSEvent.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S - api Design and usability S - enhancement Wouldn't this be the coolest?
Development

No branches or pull requests

4 participants