bevy
bevy copied to clipboard
Support double click recognition in `bevy_picking`.
What problem does this solve or what need does it fill?
bevy_picking currently only able to recognize the type of clicks, but can't, for counts.
What solution would you like?
Add a DoubleClick type.
What alternative(s) have you considered?
Supporting MultipleClick<const N: usize> would be better.
Additional context
None
I guess the plugin needs a max_delay: Duration parameter.
https://learn.microsoft.com/en-us/windows/win32/inputdev/wm-lbuttondblclk https://developer.apple.com/documentation/appkit/nsevent/doubleclickinterval
Should the interval default to that set in the OS?
https://learn.microsoft.com/en-us/windows/win32/inputdev/wm-lbuttondblclk https://developer.apple.com/documentation/appkit/nsevent/doubleclickinterval
Should the interval default to that set in the OS?
It looks like Linux doesn't have a standard way to get this https://discourse.gnome.org/t/what-should-i-use-to-get-the-double-click-time/19917/2
How should this interact with single clicking? As in, do single clicks get delayed until the double click time has expired? Do both events happen?
I think the obvious answer is "both events happen", but I don't actually know how it is typically implemented.
In Godot, double_click is a boolean field on the InputEventMouseButton input event. When a double click is made, two InputEventMouseButton events are sent. One for the first click (with double_click false) and one for the second (with double_click true).
Delaying the single click event would introduce too much latency.
I don't propose the Godot way, as it's sort of messy (why have a redundant field on all pointer click events?).
Each click could send the primary Click event, but also spawn an observer which monitors for other clicks of the same type that removes itself after a configurable duration, if another click of the same type is made while the observer is still alive, then fire a DoubleClick event.
Alternatively, these could be chained to achieve MultipleClick as per OP, but the questions for me with this approach are:
- Are multiple clicks beyond double clicks something people actually use?
- If so, would we fire a
MultipleClick<2>and then aMultipleClick<3>if a triple click was made? - If not, how would we address latency on n-clicks listening for (n+1)-clicks that would exist for the reason that single clicks would be delayed if we withheld them until the double click window had elapsed.
I would like DoubleClick and TripleClick, porting bevy_cosmic_edit to using bevy::picking rather than getting the window cursor and doing manual math
For my use case, there is no delay for any click: The first click is handled immediately, and the second and third click should be handled immediately given the knowledge that this click is 'special' and is actually a double click
How should this interact with single clicking? As in, do single clicks get delayed until the double click time has expired? Do both events happen?
How about sending multiple different kinds of events, and then the user can listen to whichever behavior they want. And that would work together with triple and so on clicks too. And an additional piece of configuration on the clicked entity to limit the max amount of clicks (Let's name it ClickChainLengthLimit for this example) and the time treshhold where the next click could still occur (ClickChainTreshhold).
So for example a double click could (potentially) fire these events (and probably double them to have one for both keyup and keydown):
Example Entity 1: (ClickChainTreshhold(Duration::from_millis(100)), ClickChainLengthLimit(3))
Click { i: 0, isLast: false, anActualInput: true }// at 0msClick { i: 1, isLast: false, anActualInput: true }// at 70msClick { i: 1, isLast: true, anActualInput: false }// at 170ms (there's definitely not a third click, the threshold has elapsed)
And a single click this:
Click { i: 0, isLast: false, anActualInput: true }// at 0msClick { i: 0, isLast: true, anActualInput: false }// at 100ms (there's definitely not a second click, the threshold has elapsed)
Or if the target entity is configured to only receive single clicks:
Example Entity 2: ClickChainLengthLimit(1)
Click { i: 0, isLast: true, anActualInput: true }// at 0ms (Doubleclicks will be treated as two separate clicks)
Example Entity 3: (ClickChainTreshhold(Duration::from_millis(100)), ClickChainLengthLimit(2))
A doubleclick
Click { i: 0, isLast: false, anActualInput: true }// at 0msClick { i: 1, isLast: true, anActualInput: true }// at 70ms
The point of this would be to decouple the wants of the emitter and the receiver. The receiver wants to be sure whats happening, for some operations it doesn't matter if click is definitely a single click (like selecting a file on your desktop when you want to open it) and for others maybe it matters, but the systems can decide if they care about it or not, they will know if this behavioral difference makes any difference or not, the emitter of the event wont. And the configuration could help in those edge cases where both latency and "sureness" matters.
In this form this looks like a footgun in scenarios where doubleclick is used but single-click is also used in a "low-latency" way. If we take the desktop file select example, and our system only checks the click index, one could easily write a bug where a single click would select the file and after 100ms it would deselect it. I don't have a good answer to resolve this, but maybe if the two fields i and isLast are joined into one as an enum, the user is forced to properly check both aspects of the event. Maybe like: Click { kind: NotLastClick(i) } or Click { kind: LastClick(i) } this would make it really hard to make a mistake like that, but it does look very ugly.
The names here are not suggestions, just so the examples stay simple, for example instead of anActualInput it could include something more broad from which it could be deduced if the input happened when the event happened.