mirror of
https://github.com/emilk/egui.git
synced 2026-08-29 04:40:03 -04:00
Treat a press that leaves a widget as a drag (#8365)
A widget that senses both clicks and drags postpones the click-versus-drag decision until the pointer moves past `max_click_dist` or is held for `max_click_duration`. But a click has to be released *on* the widget — so once the pointer leaves, the gesture can only be a drag, and there is nothing left to wait for. This matters for widgets thinner than `max_click_dist` (6px), such as panel resize handles. The pointer leaves such a widget almost immediately, which hands the hover to whatever is underneath, while `dragged()` was not true yet. So a handle highlighting on `hovered() || dragged()` blinked out mid-gesture. --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -197,7 +197,19 @@ pub(crate) fn interact(
|
||||
// This widget is sensitive to both clicks and drags.
|
||||
// When the mouse first is pressed, it could be either,
|
||||
// so we postpone the decision until we know.
|
||||
input.pointer.is_decidedly_dragging()
|
||||
//
|
||||
// …unless a click is no longer possible at all: a click has to be
|
||||
// released on the widget, and `hits.click` tells us whether the
|
||||
// pointer is still somewhere a release would land on this widget.
|
||||
// Note that this is not the same as being inside `interact_rect`:
|
||||
// the hit-test also picks up widgets within `interact_radius`, and
|
||||
// lets a widget on top take the hit.
|
||||
//
|
||||
// Deciding here means a thin drag handle (narrower than
|
||||
// `max_click_dist`) doesn't spend the decision window as neither
|
||||
// hovered nor dragged, which would make its highlight blink out.
|
||||
let could_still_be_clicked = hits.click.is_some_and(|hit| hit.id == widget.id);
|
||||
input.pointer.is_decidedly_dragging() || !could_still_be_clicked
|
||||
} else {
|
||||
// This widget is just sensitive to drags, so we can mark it as dragged right away:
|
||||
widget.sense.senses_drag()
|
||||
|
||||
@@ -309,6 +309,12 @@ impl Response {
|
||||
///
|
||||
/// In contrast to [`Self::contains_pointer`], this will be `false` whenever some other widget is being dragged.
|
||||
/// `hovered` is always `false` for disabled widgets.
|
||||
///
|
||||
/// While a widget is being clicked or dragged it is the only hovered widget,
|
||||
/// so this stays `true` even after the pointer moves off it. Together with
|
||||
/// how [`Self::dragged`] resolves a press that leaves the widget, that means
|
||||
/// `hovered() || dragged()` holds for a whole press-drag-release gesture,
|
||||
/// which is what you want for highlighting something like a drag handle.
|
||||
#[inline(always)]
|
||||
pub fn hovered(&self) -> bool {
|
||||
self.flags.contains(Flags::HOVERED)
|
||||
@@ -403,11 +409,21 @@ impl Response {
|
||||
/// To find out which button(s), use [`Self::dragged_by`].
|
||||
///
|
||||
/// If the widget is only sensitive to drags, this is `true` as soon as the pointer presses down on it.
|
||||
/// If the widget also senses clicks, this won't be true until the pointer has moved a bit,
|
||||
/// or the user has pressed down for long enough.
|
||||
///
|
||||
/// If the widget also senses clicks, the press could be either, so the
|
||||
/// decision is postponed until whichever of these comes first:
|
||||
/// * the pointer moves further than [`crate::InputOptions::max_click_dist`],
|
||||
/// * it is held longer than [`crate::InputOptions::max_click_duration`],
|
||||
/// * or it leaves the widget — a click has to be released on the widget, so
|
||||
/// once the pointer is outside, the gesture can only be a drag. This is what
|
||||
/// keeps a handle thinner than `max_click_dist` from spending the decision
|
||||
/// window as neither hovered nor dragged.
|
||||
///
|
||||
/// See [`crate::input_state::PointerState::is_decidedly_dragging`] for details.
|
||||
///
|
||||
/// If you want to avoid the delay, use [`Self::is_pointer_button_down_on`] instead.
|
||||
/// While the decision is pending the pointer is still on the widget, so
|
||||
/// [`Self::hovered`] is `true` throughout. If you want neither the delay nor
|
||||
/// the distinction, use [`Self::is_pointer_button_down_on`].
|
||||
///
|
||||
/// If the widget is NOT sensitive to drags, this will always be `false`.
|
||||
/// [`crate::DragValue`] senses drags; [`crate::Label`] does not (unless you call [`crate::Label::sense`]).
|
||||
@@ -571,6 +587,9 @@ impl Response {
|
||||
/// even when dragging outside the widget.
|
||||
///
|
||||
/// This could also be thought of as "is this widget being interacted with?".
|
||||
///
|
||||
/// Unlike [`Self::dragged`], this is `true` from the press frame onwards, with
|
||||
/// no click-versus-drag decision window.
|
||||
#[inline(always)]
|
||||
pub fn is_pointer_button_down_on(&self) -> bool {
|
||||
self.flags.contains(Flags::IS_POINTER_BUTTON_DOWN_ON)
|
||||
|
||||
Reference in New Issue
Block a user