Skip to content

Commit

Permalink
Delay Response::dragged and drag_started until it is decided
Browse files Browse the repository at this point in the history
  • Loading branch information
emilk committed Jan 25, 2024
1 parent 5ed021e commit 2ab6950
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 84 deletions.
4 changes: 2 additions & 2 deletions crates/egui/src/containers/panel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down
8 changes: 2 additions & 6 deletions crates/egui/src/containers/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -726,12 +726,8 @@ fn window_interaction(
id: Id,
rect: Rect,
) -> Option<WindowInteraction> {
{
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());
Expand Down
39 changes: 32 additions & 7 deletions crates/egui/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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() {
Expand All @@ -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;
Expand Down
12 changes: 12 additions & 0 deletions crates/egui/src/input_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand All @@ -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();
Expand Down Expand Up @@ -798,6 +804,8 @@ impl PointerState {
self.last_move_time = time;
}

self.started_decidedly_dragging = self.is_decidedly_dragging() && !was_decidedly_dragging;

self
}

Expand Down Expand Up @@ -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,
Expand All @@ -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:#?}"));
Expand Down
24 changes: 24 additions & 0 deletions crates/egui/src/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,11 @@ pub(crate) struct Interaction {
pub click_id: Option<Id>,

/// 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<Id>,

pub focus: Focus,
Expand Down Expand Up @@ -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<Id> {
self.interaction().drag_id
Expand All @@ -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) {
Expand Down
112 changes: 43 additions & 69 deletions crates/egui/src/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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,

Expand All @@ -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.
///
Expand Down Expand Up @@ -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 {
Expand All @@ -356,13 +323,15 @@ 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<Pos2> {
self.interact_pointer_pos
}

/// 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<Pos2> {
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 2ab6950

Please sign in to comment.