diff --git a/CHANGELOG.md b/CHANGELOG.md
index b50edd81..67baa18d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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
diff --git a/egui/src/containers/popup.rs b/egui/src/containers/popup.rs
index e0beffd4..70ca2319 100644
--- a/egui/src/containers/popup.rs
+++ b/egui/src/containers/popup.rs
@@ -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,
+ 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) {
- show_tooltip(ctx, |ui| {
+pub fn show_tooltip_text(ctx: &CtxRef, id: Id, text: impl Into) {
+ show_tooltip(ctx, id, |ui| {
ui.add(crate::widgets::Label::new(text));
})
}
diff --git a/egui/src/frame_state.rs b/egui/src/frame_state.rs
index 3d7c0b91..84c4bf52 100644
--- a/egui/src/frame_state.rs
+++ b/egui/src/frame_state.rs
@@ -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,
+ pub(crate) tooltip_rect: Option<(Id, Rect)>,
/// Cleared by the first `ScrollArea` that makes use of it.
pub(crate) scroll_delta: Vec2,
diff --git a/egui/src/id.rs b/egui/src/id.rs
index e077431f..7915954a 100644
--- a/egui/src/id.rs
+++ b/egui/src/id.rs
@@ -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;
diff --git a/egui/src/memory.rs b/egui/src/memory.rs
index 5789b32c..650f88c7 100644
--- a/egui/src/memory.rs
+++ b/egui/src/memory.rs
@@ -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,
+ /// 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 {
+ 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
diff --git a/egui/src/response.rs b/egui/src/response.rs
index a1765b47..4fa00520 100644
--- a/egui/src/response.rs
+++ b/egui/src/response.rs
@@ -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
}
diff --git a/egui/src/style.rs b/egui/src/style.rs
index 8e25a32e..3b80e719 100644
--- a/egui/src/style.rs
+++ b/egui/src/style.rs
@@ -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));
}