diff --git a/egui/src/input_state.rs b/egui/src/input_state.rs index e61a0c38..8f0ba00f 100644 --- a/egui/src/input_state.rs +++ b/egui/src/input_state.rs @@ -8,10 +8,14 @@ pub use crate::data::input::Key; pub use touch_state::MultiTouchInfo; use touch_state::TouchState; -/// If the pointer moves more than this, it is no longer a click (but maybe a drag) +/// If the pointer moves more than this, it won't become a click (but it is still a drag) const MAX_CLICK_DIST: f32 = 6.0; // TODO: move to settings + +/// If the pointer is down for longer than this, it won't become a click (but it is still a drag) +const MAX_CLICK_DURATION: f64 = 0.6; // TODO: move to settings + /// The new pointer press must come within this many seconds from previous pointer release -const MAX_CLICK_DELAY: f64 = 0.3; // TODO: move to settings +const MAX_DOUBLE_CLICK_DELAY: f64 = 0.3; // TODO: move to settings /// Input state that egui updates each frame. /// @@ -316,6 +320,9 @@ impl PointerEvent { /// Mouse or touch state. #[derive(Clone, Debug)] pub struct PointerState { + /// Latest known time + time: f64, + // Consider a finger tapping a touch screen. // What position should we report? // The location of the touch, or `None`, because the finger is gone? @@ -347,9 +354,13 @@ pub struct PointerState { /// `None` if no mouse button is down. press_origin: Option, - /// If the pointer button is down, will it register as a click when released? - /// Set to true on pointer button down, set to false when pointer button moves too much. - could_be_click: bool, + /// When did the current click/drag originate? + /// `None` if no mouse button is down. + press_start_time: Option, + + /// Set to `true` if the pointer has moved too much (since being pressed) + /// for it to be registered as a click. + pub(crate) has_moved_too_much_for_a_click: bool, /// When did the pointer get click last? /// Used to check for double-clicks. @@ -362,6 +373,7 @@ pub struct PointerState { impl Default for PointerState { fn default() -> Self { Self { + time: -f64::INFINITY, latest_pos: None, interact_pos: None, delta: Vec2::ZERO, @@ -369,7 +381,8 @@ impl Default for PointerState { pos_history: History::new(1000, 0.1), down: Default::default(), press_origin: None, - could_be_click: false, + press_start_time: None, + has_moved_too_much_for_a_click: false, last_click_time: std::f64::NEG_INFINITY, pointer_events: vec![], } @@ -379,6 +392,8 @@ impl Default for PointerState { impl PointerState { #[must_use] pub(crate) fn begin_frame(mut self, time: f64, new: &RawInput) -> PointerState { + self.time = time; + self.pointer_events.clear(); let old_pos = self.latest_pos; @@ -392,10 +407,9 @@ impl PointerState { self.latest_pos = Some(pos); self.interact_pos = Some(pos); - if let Some(press_origin) = &mut self.press_origin { - self.could_be_click &= press_origin.distance(pos) < MAX_CLICK_DIST; - } else { - self.could_be_click = false; + if let Some(press_origin) = self.press_origin { + self.has_moved_too_much_for_a_click |= + press_origin.distance(pos) > MAX_CLICK_DIST; } self.pointer_events.push(PointerEvent::Moved(pos)); @@ -422,13 +436,15 @@ impl PointerState { if pressed { self.press_origin = Some(pos); - self.could_be_click = true; + self.press_start_time = Some(time); + self.has_moved_too_much_for_a_click = false; self.pointer_events.push(PointerEvent::Pressed(pos)); } else { - let clicked = self.could_be_click; + let clicked = self.could_any_button_be_click(); let click = if clicked { - let double_click = (time - self.last_click_time) < MAX_CLICK_DELAY; + let double_click = + (time - self.last_click_time) < MAX_DOUBLE_CLICK_DELAY; let count = if double_click { 2 } else { 1 }; self.last_click_time = time; @@ -446,10 +462,10 @@ impl PointerState { self.pointer_events.push(PointerEvent::Released(click)); self.press_origin = None; - self.could_be_click = false; + self.press_start_time = None; } - self.down[button as usize] = pressed; + self.down[button as usize] = pressed; // must be done after the above call to `could_any_button_be_click` } Event::PointerGone => { self.latest_pos = None; @@ -507,6 +523,13 @@ impl PointerState { self.press_origin } + /// When did the current click/drag originate? + /// `None` if no mouse button is down. + #[inline(always)] + pub fn press_start_time(&self) -> Option { + self.press_start_time + } + /// Latest reported pointer position. /// When tapping a touch screen, this will be `None`. #[inline(always)] @@ -547,6 +570,7 @@ impl PointerState { /// Is the pointer currently moving? /// This is smoothed so a few frames of stillness is required before this returns `false`. + #[inline] pub fn is_moving(&self) -> bool { self.velocity != Vec2::ZERO } @@ -591,9 +615,24 @@ impl PointerState { self.down[button as usize] } + /// If the pointer button is down, will it register as a click when released? #[inline(always)] pub(crate) fn could_any_button_be_click(&self) -> bool { - self.could_be_click + if !self.any_down() { + return false; + } + + if self.has_moved_too_much_for_a_click { + return false; + } + + if let Some(press_start_time) = self.press_start_time { + if self.time - press_start_time > MAX_CLICK_DURATION { + return false; + } + } + + true } /// Is the primary button currently down? @@ -669,6 +708,7 @@ impl InputState { impl PointerState { pub fn ui(&self, ui: &mut crate::Ui) { let Self { + time: _, latest_pos, interact_pos, delta, @@ -676,7 +716,8 @@ impl PointerState { pos_history: _, down, press_origin, - could_be_click, + press_start_time, + has_moved_too_much_for_a_click, last_click_time, pointer_events, } = self; @@ -690,7 +731,11 @@ impl PointerState { )); ui.label(format!("down: {:#?}", down)); ui.label(format!("press_origin: {:?}", press_origin)); - ui.label(format!("could_be_click: {:#?}", could_be_click)); + ui.label(format!("press_start_time: {:?} s", press_start_time)); + ui.label(format!( + "has_moved_too_much_for_a_click: {}", + has_moved_too_much_for_a_click + )); ui.label(format!("last_click_time: {:#?}", last_click_time)); ui.label(format!("pointer_events: {:?}", pointer_events)); } diff --git a/egui/src/response.rs b/egui/src/response.rs index c181626c..04633a8e 100644 --- a/egui/src/response.rs +++ b/egui/src/response.rs @@ -106,6 +106,9 @@ impl std::fmt::Debug for Response { impl Response { /// Returns true if this widget was clicked this frame by the primary button. /// + /// A click is registered when the mouse or touch is released within + /// a certain amount of time and distance from when and where it was pressed. + /// /// Note that the widget must be sensing clicks with [`Sense::click`]. /// [`crate::Button`] senses clicks; [`crate::Label`] does not (unless you call [`crate::Label::sense`]). /// @@ -308,6 +311,7 @@ impl Response { /// For that, use [`Self::on_disabled_hover_ui`] instead. /// /// If you call this multiple times the tooltips will stack underneath the previous ones. + #[doc(alias = "tooltip")] pub fn on_hover_ui(self, add_contents: impl FnOnce(&mut Ui)) -> Self { if self.should_show_hover_ui() { crate::containers::show_tooltip_under( @@ -347,24 +351,30 @@ impl Response { fn should_show_hover_ui(&self) -> bool { if self.ctx.memory().everything_is_visible() { - true - } else if self.hovered && self.ctx.input().pointer.has_pointer() { - let show_tooltips_only_when_still = - self.ctx.style().interaction.show_tooltips_only_when_still; - if show_tooltips_only_when_still { - if self.ctx.input().pointer.is_still() { - true - } else { - // wait for mouse to stop - self.ctx.request_repaint(); - false - } - } else { - true - } - } else { - false + return true; } + + if !self.hovered || !self.ctx.input().pointer.has_pointer() { + return false; + } + + if self.ctx.style().interaction.show_tooltips_only_when_still { + if !self.ctx.input().pointer.is_still() { + // wait for mouse to stop + self.ctx.request_repaint(); + return false; + } + } + + // We don't want tooltips of things while we are dragging them, + // but we do want tooltips while holding down on an item on a touch screen. + if self.ctx.input().pointer.any_down() + && self.ctx.input().pointer.has_moved_too_much_for_a_click + { + return false; + } + + true } /// Show this text if the widget was hovered (i.e. a tooltip). @@ -373,6 +383,7 @@ impl Response { /// For that, use [`Self::on_disabled_hover_text`] instead. /// /// If you call this multiple times the tooltips will stack underneath the previous ones. + #[doc(alias = "tooltip")] pub fn on_hover_text(self, text: impl ToString) -> Self { self.on_hover_ui(|ui| { ui.add(crate::widgets::Label::new(text));