From 2ab69503447fbb35cfebfdab745a046ba1ef89c0 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 25 Jan 2024 17:04:11 +0100 Subject: [PATCH] Delay `Response::dragged` and `drag_started` until it is decided --- crates/egui/src/containers/panel.rs | 4 +- crates/egui/src/containers/window.rs | 8 +- crates/egui/src/context.rs | 39 ++++++++-- crates/egui/src/input_state.rs | 12 +++ crates/egui/src/memory.rs | 24 ++++++ crates/egui/src/response.rs | 112 ++++++++++----------------- 6 files changed, 115 insertions(+), 84 deletions(-) diff --git a/crates/egui/src/containers/panel.rs b/crates/egui/src/containers/panel.rs index 734ea5fbd82..2ae769e7dc0 100644 --- a/crates/egui/src/containers/panel.rs +++ b/crates/egui/src/containers/panel.rs @@ -718,9 +718,9 @@ impl TopBottomPanel { if ui.input(|i| i.pointer.any_pressed() && i.pointer.any_down()) && mouse_over_resize_line { - ui.memory_mut(|mem| mem.interaction_mut().drag_id = Some(resize_id)); + ui.memory_mut(|mem| mem.set_dragged_id(resize_id)); } - is_resizing = ui.memory(|mem| mem.interaction().drag_id == Some(resize_id)); + is_resizing = ui.memory(|mem| mem.is_being_dragged(resize_id)); if is_resizing { let height = (pointer.y - side.side_y(panel_rect)).abs(); let height = diff --git a/crates/egui/src/containers/window.rs b/crates/egui/src/containers/window.rs index 4ea40637ce8..ac77c4250bd 100644 --- a/crates/egui/src/containers/window.rs +++ b/crates/egui/src/containers/window.rs @@ -726,12 +726,8 @@ fn window_interaction( id: Id, rect: Rect, ) -> Option { - { - let drag_id = ctx.memory(|mem| mem.interaction().drag_id); - - if drag_id.is_some() && drag_id != Some(id) { - return None; - } + if ctx.memory(|mem| mem.dragging_something_else(id)) { + return None; } let mut window_interaction = ctx.memory(|mem| mem.window_interaction()); diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index f3ece56458e..89f44fb1ba6 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -970,6 +970,7 @@ impl Context { clicked: Default::default(), double_clicked: Default::default(), triple_clicked: Default::default(), + drag_started: false, dragged: false, drag_released: false, is_pointer_button_down_on: false, @@ -1020,17 +1021,31 @@ impl Context { if sense.click || sense.drag { let interaction = memory.interaction_mut(); - interaction.click_interest |= res.hovered && sense.click; - interaction.drag_interest |= res.hovered && sense.drag; - - res.dragged = interaction.drag_id == Some(id); - res.is_pointer_button_down_on = interaction.click_id == Some(id) || res.dragged; + interaction.click_interest |= contains_pointer && sense.click; + interaction.drag_interest |= contains_pointer && sense.drag; + + res.is_pointer_button_down_on = + interaction.click_id == Some(id) || interaction.drag_id == Some(id); + + if sense.click && sense.drag { + // 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. + res.dragged = + interaction.drag_id == Some(id) && input.pointer.is_decidedly_dragging(); + res.drag_started = res.dragged && input.pointer.started_decidedly_dragging; + } else if sense.drag { + // We are just sensitive to drags, so we can mark ourself as dragged right away: + res.dragged = interaction.drag_id == Some(id); + // res.drag_started will be filled below if applicable + } for pointer_event in &input.pointer.pointer_events { match pointer_event { PointerEvent::Moved(_) => {} + PointerEvent::Pressed { .. } => { - if res.hovered { + if contains_pointer { let interaction = memory.interaction_mut(); if sense.click && interaction.click_id.is_none() { @@ -1051,11 +1066,21 @@ impl Context { interaction.drag_id = Some(id); interaction.drag_is_window = false; memory.set_window_interaction(None); // HACK: stop moving windows (if any) + res.is_pointer_button_down_on = true; - res.dragged = true; + + // Again, only if we are ONLY sensitive to drags can we decide that this is a drag now. + if sense.click { + res.dragged = false; + res.drag_started = false; + } else { + res.dragged = true; + res.drag_started = true; + } } } } + PointerEvent::Released { click, button } => { res.drag_released = res.dragged; res.dragged = false; diff --git a/crates/egui/src/input_state.rs b/crates/egui/src/input_state.rs index 8b82efbcd09..1a31c9dd069 100644 --- a/crates/egui/src/input_state.rs +++ b/crates/egui/src/input_state.rs @@ -638,6 +638,9 @@ pub struct PointerState { /// for it to be registered as a click. pub(crate) has_moved_too_much_for_a_click: bool, + /// Did [`Self::is_decidedly_dragging`] go from `false` to `true` this frame? + pub(crate) started_decidedly_dragging: bool, + /// When did the pointer get click last? /// Used to check for double-clicks. last_click_time: f64, @@ -667,6 +670,7 @@ impl Default for PointerState { press_origin: None, press_start_time: None, has_moved_too_much_for_a_click: false, + started_decidedly_dragging: false, last_click_time: std::f64::NEG_INFINITY, last_last_click_time: std::f64::NEG_INFINITY, last_move_time: std::f64::NEG_INFINITY, @@ -678,6 +682,8 @@ impl Default for PointerState { impl PointerState { #[must_use] pub(crate) fn begin_frame(mut self, time: f64, new: &RawInput) -> Self { + let was_decidedly_dragging = self.is_decidedly_dragging(); + self.time = time; self.pointer_events.clear(); @@ -798,6 +804,8 @@ impl PointerState { self.last_move_time = time; } + self.started_decidedly_dragging = self.is_decidedly_dragging() && !was_decidedly_dragging; + self } @@ -1137,6 +1145,7 @@ impl PointerState { press_origin, press_start_time, has_moved_too_much_for_a_click, + started_decidedly_dragging, last_click_time, last_last_click_time, pointer_events, @@ -1156,6 +1165,9 @@ impl PointerState { ui.label(format!( "has_moved_too_much_for_a_click: {has_moved_too_much_for_a_click}" )); + ui.label(format!( + "started_decidedly_dragging: {started_decidedly_dragging}" + )); ui.label(format!("last_click_time: {last_click_time:#?}")); ui.label(format!("last_last_click_time: {last_last_click_time:#?}")); ui.label(format!("last_move_time: {last_move_time:#?}")); diff --git a/crates/egui/src/memory.rs b/crates/egui/src/memory.rs index 213d9c8b6a0..d1881c93450 100644 --- a/crates/egui/src/memory.rs +++ b/crates/egui/src/memory.rs @@ -238,6 +238,11 @@ pub(crate) struct Interaction { pub click_id: Option, /// A widget interested in drags that has a mouse press on it. + /// + /// Note that this is set as soon as the mouse is pressed, + /// so the widget may not yet be marked as "dragged", + /// as that can only happen after the mouse has moved a bit + /// (at least if the widget is interesated in both clicks and drags). pub drag_id: Option, pub focus: Focus, @@ -698,12 +703,22 @@ impl Memory { } /// Is this specific widget being dragged? + /// + /// Usually it is better to use [`crate::Response::dragged`]. + /// + /// A widget that sense both clicks and drags is only marked as "dragged" + /// when the mouse has moved a bit, but `is_being_dragged` will return true immediately. #[inline(always)] pub fn is_being_dragged(&self, id: Id) -> bool { self.interaction().drag_id == Some(id) } /// Get the id of the widget being dragged, if any. + /// + /// Note that this is set as soon as the mouse is pressed, + /// so the widget may not yet be marked as "dragged", + /// as that can only happen after the mouse has moved a bit + /// (at least if the widget is interesated in both clicks and drags). #[inline(always)] pub fn dragged_id(&self) -> Option { self.interaction().drag_id @@ -721,6 +736,15 @@ impl Memory { self.interaction_mut().drag_id = None; } + /// Is something else being dragged? + /// + /// Returns true if we are dragging something, but not the given widget. + #[inline(always)] + pub fn dragging_something_else(&self, not_this: Id) -> bool { + let drag_id = self.interaction().drag_id; + drag_id.is_some() && drag_id != Some(not_this) + } + /// Forget window positions, sizes etc. /// Can be used to auto-layout windows. pub fn reset_areas(&mut self) { diff --git a/crates/egui/src/response.rs b/crates/egui/src/response.rs index 108a24d9340..178c28a6798 100644 --- a/crates/egui/src/response.rs +++ b/crates/egui/src/response.rs @@ -14,7 +14,7 @@ use crate::{ /// Whenever something gets added to a [`Ui`], a [`Response`] object is returned. /// [`ui.add`] returns a [`Response`], as does [`ui.button`], and all similar shortcuts. // TODO(emilk): we should be using bit sets instead of so many bools -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct Response { // CONTEXT: /// Used for optionally showing a tooltip and checking for more interactions. @@ -64,7 +64,11 @@ pub struct Response { #[doc(hidden)] pub triple_clicked: [bool; NUM_POINTER_BUTTONS], - /// The widgets is being dragged + /// The widget started being dragged this frame. + #[doc(hidden)] + pub drag_started: bool, + + /// The widgets is being dragged. #[doc(hidden)] pub dragged: bool, @@ -90,48 +94,6 @@ pub struct Response { pub changed: bool, } -impl std::fmt::Debug for Response { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let Self { - ctx: _, - layer_id, - id, - rect, - sense, - enabled, - contains_pointer, - hovered, - highlighted, - clicked, - double_clicked, - triple_clicked, - dragged, - drag_released, - is_pointer_button_down_on, - interact_pointer_pos, - changed, - } = self; - f.debug_struct("Response") - .field("layer_id", layer_id) - .field("id", id) - .field("rect", rect) - .field("sense", sense) - .field("enabled", enabled) - .field("contains_pointer", contains_pointer) - .field("hovered", hovered) - .field("highlighted", highlighted) - .field("clicked", clicked) - .field("double_clicked", double_clicked) - .field("triple_clicked", triple_clicked) - .field("dragged", dragged) - .field("drag_released", drag_released) - .field("is_pointer_button_down_on", is_pointer_button_down_on) - .field("interact_pointer_pos", interact_pointer_pos) - .field("changed", changed) - .finish() - } -} - impl Response { /// Returns true if this widget was clicked this frame by the primary button. /// @@ -295,45 +257,50 @@ impl Response { self.ctx.memory_mut(|mem| mem.surrender_focus(self.id)); } + /// Did a drag on this widgets begin this frame? + /// + /// This is only true if the widget sense drags. + /// If the widget also senses clicks, this will only become true if the pointer has moved a bit. + /// + /// This will only be true for a single frame. + #[inline] + pub fn drag_started(&self) -> bool { + self.drag_started + } + + /// Did a drag on this widgets by the button begin this frame? + /// + /// This is only true if the widget sense drags. + /// If the widget also senses clicks, this will only become true if the pointer has moved a bit. + /// + /// This will only be true for a single frame. + #[inline] + pub fn drag_started_by(&self, button: PointerButton) -> bool { + self.drag_started() && self.ctx.input(|i| i.pointer.button_down(button)) + } + /// The widgets is being dragged. /// - /// To find out which button(s), query [`crate::PointerState::button_down`] - /// (`ui.input(|i| i.pointer.button_down(…))`). + /// To find out which button(s), use [`Self::dragged_by`]. /// - /// Note that the widget must be sensing drags with [`Sense::drag`]. - /// [`crate::DragValue`] senses drags; [`crate::Label`] does not (unless you call [`crate::Label::sense`]). + /// If the widget is only sensitive to drags, this is `true` as soon as the pointer presses down on it. + /// If the widget is also sensitive to drags, this won't be true until the pointer has moved a bit, + /// or the user has pressed down for long enough. + /// See [`crate::input_state::PointerState::is_decidedly_dragging`] for details. /// + /// 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`]). /// You can use [`Self::interact`] to sense more things *after* adding a widget. #[inline(always)] pub fn dragged(&self) -> bool { self.dragged } - /// The Widget is being decidedly dragged. - /// - /// This helper function checks both the output of [`Self::dragged`] and [`crate::PointerState::is_decidedly_dragging`]. - #[inline] - pub fn decidedly_dragged(&self) -> bool { - self.dragged() && self.ctx.input(|i| i.pointer.is_decidedly_dragging()) - } - #[inline] pub fn dragged_by(&self, button: PointerButton) -> bool { self.dragged() && self.ctx.input(|i| i.pointer.button_down(button)) } - /// Did a drag on this widgets begin this frame? - #[inline] - pub fn drag_started(&self) -> bool { - self.dragged && self.ctx.input(|i| i.pointer.any_pressed()) - } - - /// Did a drag on this widgets by the button begin this frame? - #[inline] - pub fn drag_started_by(&self, button: PointerButton) -> bool { - self.drag_started() && self.ctx.input(|i| i.pointer.button_pressed(button)) - } - /// The widget was being dragged, but now it has been released. #[inline] pub fn drag_released(&self) -> bool { @@ -356,6 +323,7 @@ impl Response { } /// Where the pointer (mouse/touch) were when when this widget was clicked or dragged. + /// /// `None` if the widget is not being interacted with. #[inline] pub fn interact_pointer_pos(&self) -> Option { @@ -363,6 +331,7 @@ impl Response { } /// If it is a good idea to show a tooltip, where is pointer? + /// /// None if the pointer is outside the response area. #[inline] pub fn hover_pos(&self) -> Option { @@ -374,7 +343,11 @@ impl Response { } /// Is the pointer button currently down on this widget? - /// This is true if the pointer is pressing down or dragging a widget + /// + /// This is true if the pointer is pressing down or dragging a widget, + /// even when dragging outside the widget. + /// + /// This could also be thought of as "is this widget being interacted with?". #[inline(always)] pub fn is_pointer_button_down_on(&self) -> bool { self.is_pointer_button_down_on @@ -793,6 +766,7 @@ impl Response { self.triple_clicked[3] || other.triple_clicked[3], self.triple_clicked[4] || other.triple_clicked[4], ], + drag_started: self.drag_started || other.drag_started, dragged: self.dragged || other.dragged, drag_released: self.drag_released || other.drag_released, is_pointer_button_down_on: self.is_pointer_button_down_on