Improve the positioning of tooltips
This commit is contained in:
parent
d5bb85b245
commit
4354f7582f
7 changed files with 91 additions and 27 deletions
|
@ -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 `egui::plot::Plot` to plot some 2D data
|
||||||
* Add `Ui::hyperlink_to(label, url)`.
|
* Add `Ui::hyperlink_to(label, url)`.
|
||||||
|
|
||||||
|
### Changed 🔧
|
||||||
|
|
||||||
|
* Improve the positioning of tooltips.
|
||||||
|
|
||||||
|
|
||||||
## 0.9.0 - 2021-02-07 - Light Mode and much more
|
## 0.9.0 - 2021-02-07 - Light Mode and much more
|
||||||
|
|
||||||
<img src="media/0.9.0-disabled.gif" width="50%">
|
<img src="media/0.9.0-disabled.gif" width="50%">
|
||||||
|
|
|
@ -11,21 +11,47 @@ use crate::*;
|
||||||
/// ```
|
/// ```
|
||||||
/// # let mut ui = egui::Ui::__test();
|
/// # let mut ui = egui::Ui::__test();
|
||||||
/// if ui.ui_contains_pointer() {
|
/// 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");
|
/// ui.label("Helpful text");
|
||||||
/// });
|
/// });
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn show_tooltip(ctx: &CtxRef, add_contents: impl FnOnce(&mut Ui)) {
|
pub fn show_tooltip(ctx: &CtxRef, id: Id, add_contents: impl FnOnce(&mut Ui)) {
|
||||||
let tooltip_rect = ctx.frame_state().tooltip_rect;
|
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()
|
tooltip_rect.left_bottom()
|
||||||
} else if let Some(pointer_pos) = ctx.input().pointer.tooltip_pos() {
|
} else if let Some(position) = suggested_position {
|
||||||
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());
|
|
||||||
position
|
position
|
||||||
} else if ctx.memory().everything_is_visible() {
|
} else if ctx.memory().everything_is_visible() {
|
||||||
Pos2::default()
|
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 :(
|
return; // No good place for a tooltip :(
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: default size
|
let expected_size = ctx.memory().tooltip_size(id);
|
||||||
let id = Id::tooltip();
|
let expected_size = expected_size.unwrap_or_else(|| vec2(64.0, 32.0));
|
||||||
let response = show_tooltip_area(ctx, id, window_pos, add_contents);
|
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);
|
let response = show_tooltip_area(ctx, id, position, add_contents);
|
||||||
ctx.frame_state().tooltip_rect = Some(tooltip_rect.union(response.rect));
|
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).
|
/// 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();
|
/// # let mut ui = egui::Ui::__test();
|
||||||
/// if ui.ui_contains_pointer() {
|
/// 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>) {
|
pub fn show_tooltip_text(ctx: &CtxRef, id: Id, text: impl Into<String>) {
|
||||||
show_tooltip(ctx, |ui| {
|
show_tooltip(ctx, id, |ui| {
|
||||||
ui.add(crate::widgets::Label::new(text));
|
ui.add(crate::widgets::Label::new(text));
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,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<Rect>,
|
pub(crate) tooltip_rect: Option<(Id, Rect)>,
|
||||||
|
|
||||||
/// Cleared by the first `ScrollArea` that makes use of it.
|
/// Cleared by the first `ScrollArea` that makes use of it.
|
||||||
pub(crate) scroll_delta: Vec2,
|
pub(crate) scroll_delta: Vec2,
|
||||||
|
|
|
@ -32,14 +32,11 @@ use std::hash::Hash;
|
||||||
pub struct Id(u64);
|
pub struct Id(u64);
|
||||||
|
|
||||||
impl Id {
|
impl Id {
|
||||||
pub fn background() -> Self {
|
pub(crate) fn background() -> Self {
|
||||||
Self(0)
|
Self(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tooltip() -> Self {
|
/// Generate a new `Id` by hashing some source (e.g. a string or integer).
|
||||||
Self(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new(source: impl Hash) -> Id {
|
pub fn new(source: impl Hash) -> Id {
|
||||||
// NOTE: AHasher is NOT suitable for this!
|
// NOTE: AHasher is NOT suitable for this!
|
||||||
use std::collections::hash_map::DefaultHasher;
|
use std::collections::hash_map::DefaultHasher;
|
||||||
|
@ -49,6 +46,7 @@ impl Id {
|
||||||
Id(hasher.finish())
|
Id(hasher.finish())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generate a new `Id` by hashing the parent `Id` and the given argument.
|
||||||
pub fn with(self, child: impl Hash) -> Id {
|
pub fn with(self, child: impl Hash) -> Id {
|
||||||
// NOTE: AHasher is NOT suitable for this!
|
// NOTE: AHasher is NOT suitable for this!
|
||||||
use std::collections::hash_map::DefaultHasher;
|
use std::collections::hash_map::DefaultHasher;
|
||||||
|
|
|
@ -2,7 +2,7 @@ use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
area, collapsing_header, menu, resize, scroll_area, util::Cache, widgets::text_edit, window,
|
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};
|
use epaint::color::{Color32, Hsva};
|
||||||
|
|
||||||
|
@ -51,6 +51,10 @@ pub struct Memory {
|
||||||
#[cfg_attr(feature = "persistence", serde(skip))]
|
#[cfg_attr(feature = "persistence", serde(skip))]
|
||||||
popup: Option<Id>,
|
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))]
|
#[cfg_attr(feature = "persistence", serde(skip))]
|
||||||
everything_is_visible: bool,
|
everything_is_visible: bool,
|
||||||
}
|
}
|
||||||
|
@ -254,6 +258,29 @@ impl Memory {
|
||||||
pub fn reset_areas(&mut self) {
|
pub fn reset_areas(&mut self) {
|
||||||
self.areas = Default::default();
|
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
|
/// ## Popups
|
||||||
|
|
|
@ -193,7 +193,12 @@ impl Response {
|
||||||
if (self.hovered() && self.ctx.input().pointer.tooltip_pos().is_some())
|
if (self.hovered() && self.ctx.input().pointer.tooltip_pos().is_some())
|
||||||
|| self.ctx.memory().everything_is_visible()
|
|| 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
|
self
|
||||||
}
|
}
|
||||||
|
|
|
@ -293,7 +293,7 @@ impl Default for Spacing {
|
||||||
text_edit_width: 280.0,
|
text_edit_width: 280.0,
|
||||||
icon_width: 16.0,
|
icon_width: 16.0,
|
||||||
icon_spacing: 0.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(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_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(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));
|
ui.vertical_centered(|ui| reset_button(ui, self));
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue