Widgets "unpress" when mouse is dragged a tiny distance.
Opened this issue · 2 comments
Describe the bug
A widget, e.g. button, becomes "unpressed" when the mouse moves while clicking. When this happens, it feels to the user like the button didn't recognize a click.
We see this in our app's button widget, which uses ui.allocate_exact_size(size, Sense::click())
. Also, below is a video showing the same behavior on on the web demo, showing just how short a drag it takes to have the button unpress. This is especially pronounced on touch screens, but was pronounced enough with a mouse that my coworker implemented a workaround for the mouse. Right now, our users are refusing to use it on a touch screen because it misses so many clicks (i.e. touches).
How can I prevent / workaround this?
Thanks!
To Reproduce
Steps to reproduce the behavior:
- Render a button
- Click-and-hold on it without moving the mouse
- Watch it "press"
- Drag very slowly, just a tiny amount
- Watch it "unpress"
Expected behavior
- The button should remained pressed as long as the pointer is in its sense rect (this is the bug)
- If the pointer leaves the button's sense rect plus some epsilon, it should unpress
- If the pointer re-enters the button's sense rect (not including the epsilon), it should press again
2 and 3 are also user expectations, though they are not the bug. The epsilon is to both provide hysteresis and to not miss "hairy edge" clicks where the mouse or finger touches down in the button but jitters off before its released.
Screenshots
Screen.Recording.2024-12-16.155227.mp4
Desktop (please complete the following information):
- Device: n/a
- OS: Windows 11, Ubuntu 24 (both native)
- Browser: Firefox (see the egui demo)
- Version: 29
This is actually a feature. It is controlled by these options:
egui/crates/egui/src/input_state/mod.rs
Lines 23 to 38 in 27a5803
This type is not in the crate docs for some reason, but you can access it through Context::options_mut.
ui.ctx().options_mut(|opt| opt.input_options.max_click_dist = f32::INFINITY);
You can also try it out in the demo app.
However, when you do that, the response records a click when you release the button outside the rect, which is different from what you want, and from how most GUIs work, I guess. I'm not sure if it is intentional or a bug, but maybe another option could be added to configure this behavior. A field to specify what counts as a click, with 3 variants:
- Pointer release anywhere (the current behavior)
- Pointer release inside the area (what you suggest, and the most common behavior)
- Pointer press (what John Carmack recommends)
Thanks, and especially for the undocumented tips! We'll tweak those for now, but this really seems like a behavior that should default out-of-the-box to the most common behavior because that's, well, what most UIs have trained their users to expect, and what I'm guessing most new UI frameworks hew to.
The John Carmack approach works well for things like ESTOP buttons, but leads to unexpected behaviors on other buttons--like the screen changing before they lift off. I'm guessing that there are a few UIs in gaming where the reaction time is similarly paramount, but probably not most, so the most-usable-in-general (i.e. most common) behavior seems like a natural default, so settings screens, lock screens, dialogs and other "normal" UI contexts act, well, normally. Also, some UIs need to react to press-and-hold actions, and defaulting to the "most common behavior" enables that.
I'm leaving this open so it can be resolved.