Hide tooltips when scrolling (#4784)

* Closes #4781
This commit is contained in:
Emil Ernerfeldt 2024-07-05 09:39:12 +02:00 committed by GitHub
parent 143119943d
commit 977d83a08f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 99 additions and 13 deletions

View File

@ -1442,6 +1442,16 @@ impl Context {
self.request_repaint_after_for(duration, self.viewport_id());
}
/// Repaint after this many seconds.
///
/// See [`Self::request_repaint_after`] for details.
#[track_caller]
pub fn request_repaint_after_secs(&self, seconds: f32) {
if let Ok(duration) = std::time::Duration::try_from_secs_f32(seconds) {
self.request_repaint_after(duration);
}
}
/// Request repaint after at most the specified duration elapses.
///
/// The backend can chose to repaint sooner, for instance if some other code called

View File

@ -44,6 +44,12 @@ pub struct InputState {
/// (We keep a separate [`TouchState`] for each encountered touch device.)
touch_states: BTreeMap<TouchDeviceId, TouchState>,
// ----------------------------------------------
// Scrolling:
//
/// Time of the last scroll event.
last_scroll_time: f64,
/// Used for smoothing the scroll delta.
unprocessed_scroll_delta: Vec2,
@ -87,6 +93,7 @@ pub struct InputState {
/// * `zoom > 1`: pinch spread
zoom_factor_delta: f32,
// ----------------------------------------------
/// Position and size of the egui area.
pub screen_rect: Rect,
@ -161,11 +168,14 @@ impl Default for InputState {
raw: Default::default(),
pointer: Default::default(),
touch_states: Default::default(),
last_scroll_time: f64::NEG_INFINITY,
unprocessed_scroll_delta: Vec2::ZERO,
unprocessed_scroll_delta_for_zoom: 0.0,
raw_scroll_delta: Vec2::ZERO,
smooth_scroll_delta: Vec2::ZERO,
zoom_factor_delta: 1.0,
screen_rect: Rect::from_min_size(Default::default(), vec2(10_000.0, 10_000.0)),
pixels_per_point: 1.0,
max_texture_side: 2048,
@ -320,14 +330,24 @@ impl InputState {
}
}
let is_scrolling = raw_scroll_delta != Vec2::ZERO || smooth_scroll_delta != Vec2::ZERO;
let last_scroll_time = if is_scrolling {
time
} else {
self.last_scroll_time
};
Self {
pointer,
touch_states: self.touch_states,
last_scroll_time,
unprocessed_scroll_delta,
unprocessed_scroll_delta_for_zoom,
raw_scroll_delta,
smooth_scroll_delta,
zoom_factor_delta,
screen_rect,
pixels_per_point,
max_texture_side: new.max_texture_side.unwrap_or(self.max_texture_side),
@ -393,6 +413,12 @@ impl InputState {
)
}
/// How long has it been (in seconds) since the use last scrolled?
#[inline(always)]
pub fn time_since_last_scroll(&self) -> f32 {
(self.time - self.last_scroll_time) as f32
}
/// The [`crate::Context`] will call this at the end of each frame to see if we need a repaint.
///
/// Returns how long to wait for a repaint.
@ -1218,6 +1244,7 @@ impl InputState {
pointer,
touch_states,
last_scroll_time,
unprocessed_scroll_delta,
unprocessed_scroll_delta_for_zoom,
raw_scroll_delta,
@ -1257,6 +1284,10 @@ impl InputState {
});
}
ui.label(format!(
"Time since last scroll: {:.1} s",
time - last_scroll_time
));
if cfg!(debug_assertions) {
ui.label(format!(
"unprocessed_scroll_delta: {unprocessed_scroll_delta:?} points"
@ -1270,6 +1301,7 @@ impl InputState {
"smooth_scroll_delta: {smooth_scroll_delta:?} points"
));
ui.label(format!("zoom_factor_delta: {zoom_factor_delta:4.2}x"));
ui.label(format!("screen_rect: {screen_rect:?} points"));
ui.label(format!(
"{pixels_per_point} physical pixels for each logical point"

View File

@ -611,6 +611,21 @@ impl Response {
return false;
}
let style = self.ctx.style();
let tooltip_delay = style.interaction.tooltip_delay;
let tooltip_grace_time = style.interaction.tooltip_grace_time;
let time_since_last_scroll = self.ctx.input(|i| i.time_since_last_scroll());
if time_since_last_scroll < tooltip_delay {
// See https://github.com/emilk/egui/issues/4781
// Note that this means we cannot have `ScrollArea`s in a tooltip.
self.ctx
.request_repaint_after_secs(tooltip_delay - time_since_last_scroll);
return false;
}
let is_our_tooltip_open = self.is_tooltip_open();
if is_our_tooltip_open {
@ -680,9 +695,6 @@ impl Response {
return false;
}
let tooltip_delay = self.ctx.style().interaction.tooltip_delay;
let tooltip_grace_time = self.ctx.style().interaction.tooltip_grace_time;
// There is a tooltip_delay before showing the first tooltip,
// but once one tooltips is show, moving the mouse cursor to
// another widget should show the tooltip for that widget right away.
@ -692,23 +704,27 @@ impl Response {
crate::popup::seconds_since_last_tooltip(&self.ctx) < tooltip_grace_time;
if !tooltip_was_recently_shown && !is_our_tooltip_open {
if self.ctx.style().interaction.show_tooltips_only_when_still {
if style.interaction.show_tooltips_only_when_still {
// We only show the tooltip when the mouse pointer is still.
if !self.ctx.input(|i| i.pointer.is_still()) {
if !self
.ctx
.input(|i| i.pointer.is_still() && i.smooth_scroll_delta == Vec2::ZERO)
{
// wait for mouse to stop
self.ctx.request_repaint();
return false;
}
}
let time_til_tooltip =
tooltip_delay - self.ctx.input(|i| i.pointer.time_since_last_movement());
let time_since_last_interaction = self.ctx.input(|i| {
i.time_since_last_scroll()
.max(i.pointer.time_since_last_movement())
});
let time_til_tooltip = tooltip_delay - time_since_last_interaction;
if 0.0 < time_til_tooltip {
// Wait until the mouse has been still for a while
if let Ok(duration) = std::time::Duration::try_from_secs_f32(time_til_tooltip) {
self.ctx.request_repaint_after(duration);
}
self.ctx.request_repaint_after_secs(time_til_tooltip);
return false;
}
}

View File

@ -99,8 +99,7 @@ pub fn paint_text_cursor(
total_duration - time_in_cycle
};
ui.ctx()
.request_repaint_after(std::time::Duration::from_secs_f32(wake_in));
ui.ctx().request_repaint_after_secs(wake_in);
} else {
paint_cursor_end(painter, ui.visuals(), primary_cursor_rect);
}

View File

@ -19,7 +19,8 @@ impl crate::Demo for Tooltips {
use crate::View as _;
let window = egui::Window::new("Tooltips")
.constrain(false) // So we can test how tooltips behave close to the screen edge
.resizable(false)
.resizable(true)
.default_size([450.0, 300.0])
.scroll(false)
.open(open);
window.show(ctx, |ui| self.ui(ui));
@ -34,6 +35,34 @@ impl crate::View for Tooltips {
ui.add(crate::egui_github_link_file_line!());
});
egui::SidePanel::right("scroll_test").show_inside(ui, |ui| {
ui.label(
"The scroll area below has many labels with interactive tooltips. \
The purpose is to test that the tooltips close when you scroll.",
)
.on_hover_text("Try hovering a label below, then scroll!");
egui::ScrollArea::vertical()
.auto_shrink(false)
.show(ui, |ui| {
for i in 0..1000 {
ui.label(format!("This is line {i}")).on_hover_ui(|ui| {
ui.style_mut().interaction.selectable_labels = true;
ui.label(
"This tooltip is interactive, because the text in it is selectable.",
);
});
}
});
});
egui::CentralPanel::default().show_inside(ui, |ui| {
self.misc_tests(ui);
});
}
}
impl Tooltips {
fn misc_tests(&mut self, ui: &mut egui::Ui) {
ui.label("All labels in this demo have tooltips.")
.on_hover_text("Yes, even this one.");