Hide tooltips while dragging a widget

Also: don't register as click if the
pointer has been pressed for too long.
This commit is contained in:
Emil Ernerfeldt 2021-09-08 23:26:21 +02:00
parent 19eed94499
commit a76b81647c
2 changed files with 91 additions and 35 deletions

View file

@ -8,10 +8,14 @@ pub use crate::data::input::Key;
pub use touch_state::MultiTouchInfo; pub use touch_state::MultiTouchInfo;
use touch_state::TouchState; 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 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 /// 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. /// Input state that egui updates each frame.
/// ///
@ -316,6 +320,9 @@ impl PointerEvent {
/// Mouse or touch state. /// Mouse or touch state.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct PointerState { pub struct PointerState {
/// Latest known time
time: f64,
// Consider a finger tapping a touch screen. // Consider a finger tapping a touch screen.
// What position should we report? // What position should we report?
// The location of the touch, or `None`, because the finger is gone? // 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. /// `None` if no mouse button is down.
press_origin: Option<Pos2>, press_origin: Option<Pos2>,
/// If the pointer button is down, will it register as a click when released? /// When did the current click/drag originate?
/// Set to true on pointer button down, set to false when pointer button moves too much. /// `None` if no mouse button is down.
could_be_click: bool, press_start_time: Option<f64>,
/// 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? /// When did the pointer get click last?
/// Used to check for double-clicks. /// Used to check for double-clicks.
@ -362,6 +373,7 @@ pub struct PointerState {
impl Default for PointerState { impl Default for PointerState {
fn default() -> Self { fn default() -> Self {
Self { Self {
time: -f64::INFINITY,
latest_pos: None, latest_pos: None,
interact_pos: None, interact_pos: None,
delta: Vec2::ZERO, delta: Vec2::ZERO,
@ -369,7 +381,8 @@ impl Default for PointerState {
pos_history: History::new(1000, 0.1), pos_history: History::new(1000, 0.1),
down: Default::default(), down: Default::default(),
press_origin: None, 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, last_click_time: std::f64::NEG_INFINITY,
pointer_events: vec![], pointer_events: vec![],
} }
@ -379,6 +392,8 @@ impl Default for PointerState {
impl PointerState { impl PointerState {
#[must_use] #[must_use]
pub(crate) fn begin_frame(mut self, time: f64, new: &RawInput) -> PointerState { pub(crate) fn begin_frame(mut self, time: f64, new: &RawInput) -> PointerState {
self.time = time;
self.pointer_events.clear(); self.pointer_events.clear();
let old_pos = self.latest_pos; let old_pos = self.latest_pos;
@ -392,10 +407,9 @@ impl PointerState {
self.latest_pos = Some(pos); self.latest_pos = Some(pos);
self.interact_pos = Some(pos); self.interact_pos = Some(pos);
if let Some(press_origin) = &mut self.press_origin { if let Some(press_origin) = self.press_origin {
self.could_be_click &= press_origin.distance(pos) < MAX_CLICK_DIST; self.has_moved_too_much_for_a_click |=
} else { press_origin.distance(pos) > MAX_CLICK_DIST;
self.could_be_click = false;
} }
self.pointer_events.push(PointerEvent::Moved(pos)); self.pointer_events.push(PointerEvent::Moved(pos));
@ -422,13 +436,15 @@ impl PointerState {
if pressed { if pressed {
self.press_origin = Some(pos); 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)); self.pointer_events.push(PointerEvent::Pressed(pos));
} else { } else {
let clicked = self.could_be_click; let clicked = self.could_any_button_be_click();
let click = if clicked { 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 }; let count = if double_click { 2 } else { 1 };
self.last_click_time = time; self.last_click_time = time;
@ -446,10 +462,10 @@ impl PointerState {
self.pointer_events.push(PointerEvent::Released(click)); self.pointer_events.push(PointerEvent::Released(click));
self.press_origin = None; 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 => { Event::PointerGone => {
self.latest_pos = None; self.latest_pos = None;
@ -507,6 +523,13 @@ impl PointerState {
self.press_origin 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<f64> {
self.press_start_time
}
/// Latest reported pointer position. /// Latest reported pointer position.
/// When tapping a touch screen, this will be `None`. /// When tapping a touch screen, this will be `None`.
#[inline(always)] #[inline(always)]
@ -547,6 +570,7 @@ impl PointerState {
/// Is the pointer currently moving? /// Is the pointer currently moving?
/// This is smoothed so a few frames of stillness is required before this returns `false`. /// This is smoothed so a few frames of stillness is required before this returns `false`.
#[inline]
pub fn is_moving(&self) -> bool { pub fn is_moving(&self) -> bool {
self.velocity != Vec2::ZERO self.velocity != Vec2::ZERO
} }
@ -591,9 +615,24 @@ impl PointerState {
self.down[button as usize] self.down[button as usize]
} }
/// If the pointer button is down, will it register as a click when released?
#[inline(always)] #[inline(always)]
pub(crate) fn could_any_button_be_click(&self) -> bool { 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? /// Is the primary button currently down?
@ -669,6 +708,7 @@ impl InputState {
impl PointerState { impl PointerState {
pub fn ui(&self, ui: &mut crate::Ui) { pub fn ui(&self, ui: &mut crate::Ui) {
let Self { let Self {
time: _,
latest_pos, latest_pos,
interact_pos, interact_pos,
delta, delta,
@ -676,7 +716,8 @@ impl PointerState {
pos_history: _, pos_history: _,
down, down,
press_origin, press_origin,
could_be_click, press_start_time,
has_moved_too_much_for_a_click,
last_click_time, last_click_time,
pointer_events, pointer_events,
} = self; } = self;
@ -690,7 +731,11 @@ impl PointerState {
)); ));
ui.label(format!("down: {:#?}", down)); ui.label(format!("down: {:#?}", down));
ui.label(format!("press_origin: {:?}", press_origin)); 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!("last_click_time: {:#?}", last_click_time));
ui.label(format!("pointer_events: {:?}", pointer_events)); ui.label(format!("pointer_events: {:?}", pointer_events));
} }

View file

@ -106,6 +106,9 @@ impl std::fmt::Debug for Response {
impl Response { impl Response {
/// Returns true if this widget was clicked this frame by the primary button. /// 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`]. /// 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`]). /// [`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. /// For that, use [`Self::on_disabled_hover_ui`] instead.
/// ///
/// If you call this multiple times the tooltips will stack underneath the previous ones. /// 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 { pub fn on_hover_ui(self, add_contents: impl FnOnce(&mut Ui)) -> Self {
if self.should_show_hover_ui() { if self.should_show_hover_ui() {
crate::containers::show_tooltip_under( crate::containers::show_tooltip_under(
@ -347,24 +351,30 @@ impl Response {
fn should_show_hover_ui(&self) -> bool { fn should_show_hover_ui(&self) -> bool {
if self.ctx.memory().everything_is_visible() { if self.ctx.memory().everything_is_visible() {
true return 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
} }
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). /// 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. /// For that, use [`Self::on_disabled_hover_text`] instead.
/// ///
/// If you call this multiple times the tooltips will stack underneath the previous ones. /// 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 { pub fn on_hover_text(self, text: impl ToString) -> Self {
self.on_hover_ui(|ui| { self.on_hover_ui(|ui| {
ui.add(crate::widgets::Label::new(text)); ui.add(crate::widgets::Label::new(text));