Only show tooltips when mouse pointer is still (#2263)
* Only show tooltips when mouse pointer is still Revert to the old behavior by setting `style.interaction.show_tooltips_only_when_still = false`. * Area: take `impl Into<Id>` * refactor tooltips * Fix was_tooltip_open_last_frame * Bug fix * Add some spacing between tooltips
This commit is contained in:
parent
51ff32797d
commit
b1e71d308f
8 changed files with 103 additions and 68 deletions
|
@ -21,6 +21,7 @@ NOTE: [`epaint`](crates/epaint/CHANGELOG.md), [`eframe`](crates/eframe/CHANGELOG
|
||||||
|
|
||||||
### Changed 🔧
|
### Changed 🔧
|
||||||
* Panels always have a separator line, but no stroke on other sides. Their spacing has also changed slightly ([#2261](https://github.com/emilk/egui/pull/2261)).
|
* Panels always have a separator line, but no stroke on other sides. Their spacing has also changed slightly ([#2261](https://github.com/emilk/egui/pull/2261)).
|
||||||
|
* Tooltips are only shown when mouse pointer is still ([#2263](https://github.com/emilk/egui/pull/2263)).
|
||||||
|
|
||||||
### Fixed 🐛
|
### Fixed 🐛
|
||||||
* ⚠️ BREAKING: Fix text being too small ([#2069](https://github.com/emilk/egui/pull/2069)).
|
* ⚠️ BREAKING: Fix text being too small ([#2069](https://github.com/emilk/egui/pull/2069)).
|
||||||
|
|
|
@ -2,8 +2,6 @@
|
||||||
//! It has no frame or own size. It is potentially movable.
|
//! It has no frame or own size. It is potentially movable.
|
||||||
//! It is the foundation for windows and popups.
|
//! It is the foundation for windows and popups.
|
||||||
|
|
||||||
use std::{fmt::Debug, hash::Hash};
|
|
||||||
|
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
/// State that is persisted between frames.
|
/// State that is persisted between frames.
|
||||||
|
@ -56,9 +54,9 @@ pub struct Area {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Area {
|
impl Area {
|
||||||
pub fn new(id_source: impl Hash) -> Self {
|
pub fn new(id: impl Into<Id>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
id: Id::new(id_source),
|
id: id.into(),
|
||||||
movable: true,
|
movable: true,
|
||||||
interactable: true,
|
interactable: true,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
|
|
@ -6,13 +6,13 @@ use crate::*;
|
||||||
|
|
||||||
/// Same state for all tooltips.
|
/// Same state for all tooltips.
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub(crate) struct MonoState {
|
pub(crate) struct TooltipState {
|
||||||
last_id: Option<Id>,
|
last_common_id: Option<Id>,
|
||||||
last_size: Vec<Vec2>,
|
individual_ids_and_sizes: ahash::HashMap<usize, (Id, Vec2)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MonoState {
|
impl TooltipState {
|
||||||
fn load(ctx: &Context) -> Option<Self> {
|
pub fn load(ctx: &Context) -> Option<Self> {
|
||||||
ctx.data().get_temp(Id::null())
|
ctx.data().get_temp(Id::null())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,30 +20,28 @@ impl MonoState {
|
||||||
ctx.data().insert_temp(Id::null(), self);
|
ctx.data().insert_temp(Id::null(), self);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tooltip_size(&self, id: Id, index: usize) -> Option<Vec2> {
|
fn individual_tooltip_size(&self, common_id: Id, index: usize) -> Option<Vec2> {
|
||||||
if self.last_id == Some(id) {
|
if self.last_common_id == Some(common_id) {
|
||||||
self.last_size.get(index).cloned()
|
Some(self.individual_ids_and_sizes.get(&index).cloned()?.1)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_tooltip_size(&mut self, id: Id, index: usize, size: Vec2) {
|
fn set_individual_tooltip(
|
||||||
if self.last_id == Some(id) {
|
&mut self,
|
||||||
if let Some(stored_size) = self.last_size.get_mut(index) {
|
common_id: Id,
|
||||||
*stored_size = size;
|
index: usize,
|
||||||
} else {
|
individual_id: Id,
|
||||||
self.last_size
|
size: Vec2,
|
||||||
.extend((0..index - self.last_size.len()).map(|_| Vec2::ZERO));
|
) {
|
||||||
self.last_size.push(size);
|
if self.last_common_id != Some(common_id) {
|
||||||
}
|
self.last_common_id = Some(common_id);
|
||||||
return;
|
self.individual_ids_and_sizes.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
self.last_id = Some(id);
|
self.individual_ids_and_sizes
|
||||||
self.last_size.clear();
|
.insert(index, (individual_id, size));
|
||||||
self.last_size.extend((0..index).map(|_| Vec2::ZERO));
|
|
||||||
self.last_size.push(size);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -151,27 +149,30 @@ pub fn show_tooltip_at<R>(
|
||||||
|
|
||||||
fn show_tooltip_at_avoid_dyn<'c, R>(
|
fn show_tooltip_at_avoid_dyn<'c, R>(
|
||||||
ctx: &Context,
|
ctx: &Context,
|
||||||
mut id: Id,
|
individual_id: Id,
|
||||||
suggested_position: Option<Pos2>,
|
suggested_position: Option<Pos2>,
|
||||||
above: bool,
|
above: bool,
|
||||||
mut avoid_rect: Rect,
|
mut avoid_rect: Rect,
|
||||||
add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
|
add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
|
||||||
) -> Option<R> {
|
) -> Option<R> {
|
||||||
let mut tooltip_rect = Rect::NOTHING;
|
let spacing = 4.0;
|
||||||
let mut count = 0;
|
|
||||||
|
|
||||||
let stored = ctx.frame_state().tooltip_rect;
|
// if there are multiple tooltips open they should use the same common_id for the `tooltip_size` caching to work.
|
||||||
|
let mut frame_state =
|
||||||
|
ctx.frame_state()
|
||||||
|
.tooltip_state
|
||||||
|
.unwrap_or(crate::frame_state::TooltipFrameState {
|
||||||
|
common_id: individual_id,
|
||||||
|
rect: Rect::NOTHING,
|
||||||
|
count: 0,
|
||||||
|
});
|
||||||
|
|
||||||
let mut position = if let Some(stored) = stored {
|
let mut position = if frame_state.rect.is_positive() {
|
||||||
// if there are multiple tooltips open they should use the same id for the `tooltip_size` caching to work.
|
avoid_rect = avoid_rect.union(frame_state.rect);
|
||||||
id = stored.id;
|
|
||||||
tooltip_rect = stored.rect;
|
|
||||||
count = stored.count;
|
|
||||||
avoid_rect = avoid_rect.union(tooltip_rect);
|
|
||||||
if above {
|
if above {
|
||||||
tooltip_rect.left_top()
|
frame_state.rect.left_top() - spacing * Vec2::Y
|
||||||
} else {
|
} else {
|
||||||
tooltip_rect.left_bottom()
|
frame_state.rect.left_bottom() + spacing * Vec2::Y
|
||||||
}
|
}
|
||||||
} else if let Some(position) = suggested_position {
|
} else if let Some(position) = suggested_position {
|
||||||
position
|
position
|
||||||
|
@ -181,8 +182,9 @@ fn show_tooltip_at_avoid_dyn<'c, R>(
|
||||||
return None; // No good place for a tooltip :(
|
return None; // No good place for a tooltip :(
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut state = MonoState::load(ctx).unwrap_or_default();
|
let mut long_state = TooltipState::load(ctx).unwrap_or_default();
|
||||||
let expected_size = state.tooltip_size(id, count);
|
let expected_size =
|
||||||
|
long_state.individual_tooltip_size(frame_state.common_id, frame_state.count);
|
||||||
let expected_size = expected_size.unwrap_or_else(|| vec2(64.0, 32.0));
|
let expected_size = expected_size.unwrap_or_else(|| vec2(64.0, 32.0));
|
||||||
|
|
||||||
if above {
|
if above {
|
||||||
|
@ -195,31 +197,37 @@ fn show_tooltip_at_avoid_dyn<'c, R>(
|
||||||
{
|
{
|
||||||
let new_rect = Rect::from_min_size(position, expected_size);
|
let new_rect = Rect::from_min_size(position, expected_size);
|
||||||
|
|
||||||
// Note: We do not use Rect::intersects() since it returns true even if the rects only touch.
|
// Note: We use shrink so that we don't get false positives when the rects just touch
|
||||||
if new_rect.shrink(1.0).intersects(avoid_rect) {
|
if new_rect.shrink(1.0).intersects(avoid_rect) {
|
||||||
if above {
|
if above {
|
||||||
// place below instead:
|
// place below instead:
|
||||||
position = avoid_rect.left_bottom();
|
position = avoid_rect.left_bottom() + spacing * Vec2::Y;
|
||||||
} else {
|
} else {
|
||||||
// place above instead:
|
// place above instead:
|
||||||
position = Pos2::new(position.x, avoid_rect.min.y - expected_size.y);
|
position = Pos2::new(position.x, avoid_rect.min.y - expected_size.y - spacing);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let position = position.at_least(ctx.input().screen_rect().min);
|
let position = position.at_least(ctx.input().screen_rect().min);
|
||||||
|
|
||||||
|
let area_id = frame_state.common_id.with(frame_state.count);
|
||||||
|
|
||||||
let InnerResponse { inner, response } =
|
let InnerResponse { inner, response } =
|
||||||
show_tooltip_area_dyn(ctx, id.with(count), position, add_contents);
|
show_tooltip_area_dyn(ctx, area_id, position, add_contents);
|
||||||
|
|
||||||
state.set_tooltip_size(id, count, response.rect.size());
|
long_state.set_individual_tooltip(
|
||||||
state.store(ctx);
|
frame_state.common_id,
|
||||||
|
frame_state.count,
|
||||||
|
individual_id,
|
||||||
|
response.rect.size(),
|
||||||
|
);
|
||||||
|
long_state.store(ctx);
|
||||||
|
|
||||||
|
frame_state.count += 1;
|
||||||
|
frame_state.rect = frame_state.rect.union(response.rect);
|
||||||
|
ctx.frame_state().tooltip_state = Some(frame_state);
|
||||||
|
|
||||||
ctx.frame_state().tooltip_rect = Some(crate::frame_state::TooltipRect {
|
|
||||||
id,
|
|
||||||
rect: tooltip_rect.union(response.rect),
|
|
||||||
count: count + 1,
|
|
||||||
});
|
|
||||||
Some(inner)
|
Some(inner)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -247,12 +255,12 @@ pub fn show_tooltip_text(ctx: &Context, id: Id, text: impl Into<WidgetText>) ->
|
||||||
/// Show a pop-over window.
|
/// Show a pop-over window.
|
||||||
fn show_tooltip_area_dyn<'c, R>(
|
fn show_tooltip_area_dyn<'c, R>(
|
||||||
ctx: &Context,
|
ctx: &Context,
|
||||||
id: Id,
|
area_id: Id,
|
||||||
window_pos: Pos2,
|
window_pos: Pos2,
|
||||||
add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
|
add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
|
||||||
) -> InnerResponse<R> {
|
) -> InnerResponse<R> {
|
||||||
use containers::*;
|
use containers::*;
|
||||||
Area::new(id)
|
Area::new(area_id)
|
||||||
.order(Order::Tooltip)
|
.order(Order::Tooltip)
|
||||||
.fixed_pos(window_pos)
|
.fixed_pos(window_pos)
|
||||||
.interactable(false)
|
.interactable(false)
|
||||||
|
@ -267,6 +275,25 @@ fn show_tooltip_area_dyn<'c, R>(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Was this popup visible last frame?
|
||||||
|
pub fn was_tooltip_open_last_frame(ctx: &Context, tooltip_id: Id) -> bool {
|
||||||
|
if let Some(state) = TooltipState::load(ctx) {
|
||||||
|
if let Some(common_id) = state.last_common_id {
|
||||||
|
for (count, (individual_id, _size)) in &state.individual_ids_and_sizes {
|
||||||
|
if *individual_id == tooltip_id {
|
||||||
|
let area_id = common_id.with(count);
|
||||||
|
let layer_id = LayerId::new(Order::Tooltip, area_id);
|
||||||
|
if ctx.memory().areas.visible_last_frame(&layer_id) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
/// Shows a popup below another widget.
|
/// Shows a popup below another widget.
|
||||||
///
|
///
|
||||||
/// Useful for drop-down menus (combo boxes) or suggestion menus under text fields.
|
/// Useful for drop-down menus (combo boxes) or suggestion menus under text fields.
|
||||||
|
|
|
@ -39,7 +39,7 @@ impl<'open> Window<'open> {
|
||||||
/// If you need a changing title, you must call `window.id(…)` with a fixed id.
|
/// If you need a changing title, you must call `window.id(…)` with a fixed id.
|
||||||
pub fn new(title: impl Into<WidgetText>) -> Self {
|
pub fn new(title: impl Into<WidgetText>) -> Self {
|
||||||
let title = title.into().fallback_text_style(TextStyle::Heading);
|
let title = title.into().fallback_text_style(TextStyle::Heading);
|
||||||
let area = Area::new(title.text());
|
let area = Area::new(Id::new(title.text()));
|
||||||
Self {
|
Self {
|
||||||
title,
|
title,
|
||||||
open: None,
|
open: None,
|
||||||
|
|
|
@ -3,8 +3,8 @@ use std::ops::RangeInclusive;
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub(crate) struct TooltipRect {
|
pub(crate) struct TooltipFrameState {
|
||||||
pub id: Id,
|
pub common_id: Id,
|
||||||
pub rect: Rect,
|
pub rect: Rect,
|
||||||
pub count: usize,
|
pub count: usize,
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,7 @@ pub(crate) struct FrameState {
|
||||||
/// If a tooltip has been shown this frame, where was it?
|
/// If a tooltip has been shown this frame, where was it?
|
||||||
/// This is used to prevent multiple tooltips to cover each other.
|
/// This is used to prevent multiple tooltips to cover each other.
|
||||||
/// Initialized to `None` at the start of each frame.
|
/// Initialized to `None` at the start of each frame.
|
||||||
pub(crate) tooltip_rect: Option<TooltipRect>,
|
pub(crate) tooltip_state: Option<TooltipFrameState>,
|
||||||
|
|
||||||
/// Set to [`InputState::scroll_delta`] on the start of each frame.
|
/// Set to [`InputState::scroll_delta`] on the start of each frame.
|
||||||
///
|
///
|
||||||
|
@ -50,7 +50,7 @@ impl Default for FrameState {
|
||||||
available_rect: Rect::NAN,
|
available_rect: Rect::NAN,
|
||||||
unused_rect: Rect::NAN,
|
unused_rect: Rect::NAN,
|
||||||
used_by_panels: Rect::NAN,
|
used_by_panels: Rect::NAN,
|
||||||
tooltip_rect: None,
|
tooltip_state: None,
|
||||||
scroll_delta: Vec2::ZERO,
|
scroll_delta: Vec2::ZERO,
|
||||||
scroll_target: [None, None],
|
scroll_target: [None, None],
|
||||||
}
|
}
|
||||||
|
@ -64,7 +64,7 @@ impl FrameState {
|
||||||
available_rect,
|
available_rect,
|
||||||
unused_rect,
|
unused_rect,
|
||||||
used_by_panels,
|
used_by_panels,
|
||||||
tooltip_rect,
|
tooltip_state,
|
||||||
scroll_delta,
|
scroll_delta,
|
||||||
scroll_target,
|
scroll_target,
|
||||||
} = self;
|
} = self;
|
||||||
|
@ -73,7 +73,7 @@ impl FrameState {
|
||||||
*available_rect = input.screen_rect();
|
*available_rect = input.screen_rect();
|
||||||
*unused_rect = input.screen_rect();
|
*unused_rect = input.screen_rect();
|
||||||
*used_by_panels = Rect::NOTHING;
|
*used_by_panels = Rect::NOTHING;
|
||||||
*tooltip_rect = None;
|
*tooltip_state = None;
|
||||||
*scroll_delta = input.scroll_delta;
|
*scroll_delta = input.scroll_delta;
|
||||||
*scroll_target = [None, None];
|
*scroll_target = [None, None];
|
||||||
}
|
}
|
||||||
|
|
|
@ -117,7 +117,7 @@ pub(crate) fn submenu_button<R>(
|
||||||
/// wrapper for the contents of every menu.
|
/// wrapper for the contents of every menu.
|
||||||
pub(crate) fn menu_ui<'c, R>(
|
pub(crate) fn menu_ui<'c, R>(
|
||||||
ctx: &Context,
|
ctx: &Context,
|
||||||
menu_id: impl std::hash::Hash,
|
menu_id: impl Into<Id>,
|
||||||
menu_state_arc: &Arc<RwLock<MenuState>>,
|
menu_state_arc: &Arc<RwLock<MenuState>>,
|
||||||
add_contents: impl FnOnce(&mut Ui) -> R + 'c,
|
add_contents: impl FnOnce(&mut Ui) -> R + 'c,
|
||||||
) -> InnerResponse<R> {
|
) -> InnerResponse<R> {
|
||||||
|
|
|
@ -386,6 +386,11 @@ impl Response {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Was the tooltip open last frame?
|
||||||
|
pub fn is_tooltip_open(&self) -> bool {
|
||||||
|
crate::popup::was_tooltip_open_last_frame(&self.ctx, self.id.with("__tooltip"))
|
||||||
|
}
|
||||||
|
|
||||||
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() {
|
||||||
return true;
|
return true;
|
||||||
|
@ -395,12 +400,16 @@ impl Response {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.ctx.style().interaction.show_tooltips_only_when_still
|
if self.ctx.style().interaction.show_tooltips_only_when_still {
|
||||||
&& !self.ctx.input().pointer.is_still()
|
// We only show the tooltip when the mouse pointer is still,
|
||||||
{
|
// but once shown we keep showing it until the mouse leaves the parent.
|
||||||
// wait for mouse to stop
|
|
||||||
self.ctx.request_repaint();
|
let is_pointer_still = self.ctx.input().pointer.is_still();
|
||||||
return false;
|
if !is_pointer_still && !self.is_tooltip_open() {
|
||||||
|
// wait for mouse to stop
|
||||||
|
self.ctx.request_repaint();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// We don't want tooltips of things while we are dragging them,
|
// We don't want tooltips of things while we are dragging them,
|
||||||
|
|
|
@ -668,7 +668,7 @@ impl Default for Interaction {
|
||||||
Self {
|
Self {
|
||||||
resize_grab_radius_side: 5.0,
|
resize_grab_radius_side: 5.0,
|
||||||
resize_grab_radius_corner: 10.0,
|
resize_grab_radius_corner: 10.0,
|
||||||
show_tooltips_only_when_still: false,
|
show_tooltips_only_when_still: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue