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:
parent
19eed94499
commit
a76b81647c
2 changed files with 91 additions and 35 deletions
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,25 +351,31 @@ 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 !self.hovered || !self.ctx.input().pointer.has_pointer() {
|
||||||
if show_tooltips_only_when_still {
|
return false;
|
||||||
if self.ctx.input().pointer.is_still() {
|
}
|
||||||
true
|
|
||||||
} else {
|
if self.ctx.style().interaction.show_tooltips_only_when_still {
|
||||||
|
if !self.ctx.input().pointer.is_still() {
|
||||||
// wait for mouse to stop
|
// wait for mouse to stop
|
||||||
self.ctx.request_repaint();
|
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
|
true
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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));
|
||||||
|
|
Loading…
Reference in a new issue