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;
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<Pos2>,
/// 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<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?
/// 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<f64> {
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));
}

View file

@ -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,25 +351,31 @@ 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 {
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();
false
return false;
}
} else {
}
// 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
}
} else {
false
}
}
/// 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));