Improve the positioning of tooltips

This commit is contained in:
Emil Ernerfeldt 2021-02-20 10:33:33 +01:00
parent d5bb85b245
commit 4354f7582f
7 changed files with 91 additions and 27 deletions

View file

@ -14,6 +14,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
* Add `egui::plot::Plot` to plot some 2D data
* Add `Ui::hyperlink_to(label, url)`.
### Changed 🔧
* Improve the positioning of tooltips.
## 0.9.0 - 2021-02-07 - Light Mode and much more
<img src="media/0.9.0-disabled.gif" width="50%">

View file

@ -11,21 +11,47 @@ use crate::*;
/// ```
/// # let mut ui = egui::Ui::__test();
/// if ui.ui_contains_pointer() {
/// egui::show_tooltip(ui.ctx(), |ui| {
/// egui::show_tooltip(ui.ctx(), egui::Id::new("my_tooltip"), |ui| {
/// ui.label("Helpful text");
/// });
/// }
/// ```
pub fn show_tooltip(ctx: &CtxRef, add_contents: impl FnOnce(&mut Ui)) {
let tooltip_rect = ctx.frame_state().tooltip_rect;
pub fn show_tooltip(ctx: &CtxRef, id: Id, add_contents: impl FnOnce(&mut Ui)) {
show_tooltip_at_pointer(ctx, id, add_contents)
}
let window_pos = if let Some(tooltip_rect) = tooltip_rect {
pub fn show_tooltip_at_pointer(ctx: &CtxRef, id: Id, add_contents: impl FnOnce(&mut Ui)) {
let suggested_pos = ctx
.input()
.pointer
.tooltip_pos()
.map(|pointer_pos| pointer_pos + vec2(16.0, 16.0));
show_tooltip_at(ctx, id, suggested_pos, add_contents)
}
pub fn show_tooltip_under(ctx: &CtxRef, id: Id, rect: &Rect, add_contents: impl FnOnce(&mut Ui)) {
show_tooltip_at(
ctx,
id,
Some(rect.left_bottom() + vec2(-2.0, 4.0)),
add_contents,
)
}
pub fn show_tooltip_at(
ctx: &CtxRef,
mut id: Id,
suggested_position: Option<Pos2>,
add_contents: impl FnOnce(&mut Ui),
) {
let mut tooltip_rect = Rect::NOTHING;
let position = if let Some((stored_id, stored_tooltip_rect)) = ctx.frame_state().tooltip_rect {
// if there are multiple tooltips open they should use the same id for the `tooltip_size` caching to work.
id = stored_id;
tooltip_rect = stored_tooltip_rect;
tooltip_rect.left_bottom()
} else if let Some(pointer_pos) = ctx.input().pointer.tooltip_pos() {
let expected_size = vec2(ctx.style().spacing.tooltip_width, 32.0);
let position = pointer_pos + vec2(16.0, 16.0);
let position = position.min(ctx.input().screen_rect().right_bottom() - expected_size);
let position = position.max(ctx.input().screen_rect().left_top());
} else if let Some(position) = suggested_position {
position
} else if ctx.memory().everything_is_visible() {
Pos2::default()
@ -33,12 +59,15 @@ pub fn show_tooltip(ctx: &CtxRef, add_contents: impl FnOnce(&mut Ui)) {
return; // No good place for a tooltip :(
};
// TODO: default size
let id = Id::tooltip();
let response = show_tooltip_area(ctx, id, window_pos, add_contents);
let expected_size = ctx.memory().tooltip_size(id);
let expected_size = expected_size.unwrap_or_else(|| vec2(64.0, 32.0));
let position = position.min(ctx.input().screen_rect().right_bottom() - expected_size);
let position = position.max(ctx.input().screen_rect().left_top());
let tooltip_rect = tooltip_rect.unwrap_or(Rect::NOTHING);
ctx.frame_state().tooltip_rect = Some(tooltip_rect.union(response.rect));
let response = show_tooltip_area(ctx, id, position, add_contents);
ctx.memory().set_tooltip_size(id, response.rect.size());
ctx.frame_state().tooltip_rect = Some((id, tooltip_rect.union(response.rect)));
}
/// Show some text at the current pointer position (if any).
@ -50,11 +79,11 @@ pub fn show_tooltip(ctx: &CtxRef, add_contents: impl FnOnce(&mut Ui)) {
/// ```
/// # let mut ui = egui::Ui::__test();
/// if ui.ui_contains_pointer() {
/// egui::show_tooltip_text(ui.ctx(), "Helpful text");
/// egui::show_tooltip_text(ui.ctx(), egui::Id::new("my_tooltip"), "Helpful text");
/// }
/// ```
pub fn show_tooltip_text(ctx: &CtxRef, text: impl Into<String>) {
show_tooltip(ctx, |ui| {
pub fn show_tooltip_text(ctx: &CtxRef, id: Id, text: impl Into<String>) {
show_tooltip(ctx, id, |ui| {
ui.add(crate::widgets::Label::new(text));
})
}

View file

@ -24,7 +24,7 @@ pub(crate) struct FrameState {
/// If a tooltip has been shown this frame, where was it?
/// This is used to prevent multiple tooltips to cover each other.
/// Initialized to `None` at the start of each frame.
pub(crate) tooltip_rect: Option<Rect>,
pub(crate) tooltip_rect: Option<(Id, Rect)>,
/// Cleared by the first `ScrollArea` that makes use of it.
pub(crate) scroll_delta: Vec2,

View file

@ -32,14 +32,11 @@ use std::hash::Hash;
pub struct Id(u64);
impl Id {
pub fn background() -> Self {
pub(crate) fn background() -> Self {
Self(0)
}
pub fn tooltip() -> Self {
Self(1)
}
/// Generate a new `Id` by hashing some source (e.g. a string or integer).
pub fn new(source: impl Hash) -> Id {
// NOTE: AHasher is NOT suitable for this!
use std::collections::hash_map::DefaultHasher;
@ -49,6 +46,7 @@ impl Id {
Id(hasher.finish())
}
/// Generate a new `Id` by hashing the parent `Id` and the given argument.
pub fn with(self, child: impl Hash) -> Id {
// NOTE: AHasher is NOT suitable for this!
use std::collections::hash_map::DefaultHasher;

View file

@ -2,7 +2,7 @@ use std::collections::{HashMap, HashSet};
use crate::{
area, collapsing_header, menu, resize, scroll_area, util::Cache, widgets::text_edit, window,
Id, LayerId, Pos2, Rect, Style,
Id, LayerId, Pos2, Rect, Style, Vec2,
};
use epaint::color::{Color32, Hsva};
@ -51,6 +51,10 @@ pub struct Memory {
#[cfg_attr(feature = "persistence", serde(skip))]
popup: Option<Id>,
/// Used to clamp the tooltip to the screen.
#[cfg_attr(feature = "persistence", serde(skip))]
tooltip_size: Option<(Id, Vec2)>,
#[cfg_attr(feature = "persistence", serde(skip))]
everything_is_visible: bool,
}
@ -254,6 +258,29 @@ impl Memory {
pub fn reset_areas(&mut self) {
self.areas = Default::default();
}
pub(crate) fn tooltip_size(&self, id: Id) -> Option<Vec2> {
if let Some((stored_id, stored_size)) = self.tooltip_size {
if stored_id == id {
Some(stored_size)
} else {
None
}
} else {
None
}
}
pub(crate) fn set_tooltip_size(&mut self, id: Id, size: Vec2) {
if let Some((stored_id, stored_size)) = &mut self.tooltip_size {
if *stored_id == id {
*stored_size = stored_size.max(size);
return;
}
}
self.tooltip_size = Some((id, size));
}
}
/// ## Popups

View file

@ -193,7 +193,12 @@ impl Response {
if (self.hovered() && self.ctx.input().pointer.tooltip_pos().is_some())
|| self.ctx.memory().everything_is_visible()
{
crate::containers::show_tooltip(&self.ctx, add_contents);
crate::containers::show_tooltip_under(
&self.ctx,
self.id.with("__tooltip"),
&self.rect,
add_contents,
);
}
self
}

View file

@ -293,7 +293,7 @@ impl Default for Spacing {
text_edit_width: 280.0,
icon_width: 16.0,
icon_spacing: 0.0,
tooltip_width: 400.0,
tooltip_width: 600.0,
}
}
}
@ -505,7 +505,7 @@ impl Spacing {
ui.add(Slider::f32(text_edit_width, 0.0..=1000.0).text("text_edit_width"));
ui.add(Slider::f32(icon_width, 0.0..=60.0).text("icon_width"));
ui.add(Slider::f32(icon_spacing, 0.0..=10.0).text("icon_spacing"));
ui.add(Slider::f32(tooltip_width, 0.0..=10.0).text("tooltip_width"));
ui.add(Slider::f32(tooltip_width, 0.0..=1000.0).text("tooltip_width"));
ui.vertical_centered(|ui| reset_button(ui, self));
}