Only show one tooltip per layer at a time (#4763)
Before, you could accidentally get multiple tooltips if a tooltips was interactive (e.g. had a link in it) and on the way to interact with it you would hover another widget with a tooltip. This PR ensures that each `LayerId` only has one tooltip open at a time. You can still have a tooltip for an item inside of a tooltip.
This commit is contained in:
parent
c0296fb47b
commit
d1be5a1efb
|
|
@ -17,7 +17,7 @@ use crate::*;
|
|||
/// ```
|
||||
/// # egui::__run_test_ui(|ui| {
|
||||
/// if ui.ui_contains_pointer() {
|
||||
/// egui::show_tooltip(ui.ctx(), egui::Id::new("my_tooltip"), |ui| {
|
||||
/// egui::show_tooltip(ui.ctx(), ui.layer_id(), egui::Id::new("my_tooltip"), |ui| {
|
||||
/// ui.label("Helpful text");
|
||||
/// });
|
||||
/// }
|
||||
|
|
@ -25,10 +25,11 @@ use crate::*;
|
|||
/// ```
|
||||
pub fn show_tooltip<R>(
|
||||
ctx: &Context,
|
||||
parent_layer: LayerId,
|
||||
widget_id: Id,
|
||||
add_contents: impl FnOnce(&mut Ui) -> R,
|
||||
) -> Option<R> {
|
||||
show_tooltip_at_pointer(ctx, widget_id, add_contents)
|
||||
show_tooltip_at_pointer(ctx, parent_layer, widget_id, add_contents)
|
||||
}
|
||||
|
||||
/// Show a tooltip at the current pointer position (if any).
|
||||
|
|
@ -42,7 +43,7 @@ pub fn show_tooltip<R>(
|
|||
/// ```
|
||||
/// # egui::__run_test_ui(|ui| {
|
||||
/// if ui.ui_contains_pointer() {
|
||||
/// egui::show_tooltip_at_pointer(ui.ctx(), egui::Id::new("my_tooltip"), |ui| {
|
||||
/// egui::show_tooltip_at_pointer(ui.ctx(), ui.layer_id(), egui::Id::new("my_tooltip"), |ui| {
|
||||
/// ui.label("Helpful text");
|
||||
/// });
|
||||
/// }
|
||||
|
|
@ -50,11 +51,18 @@ pub fn show_tooltip<R>(
|
|||
/// ```
|
||||
pub fn show_tooltip_at_pointer<R>(
|
||||
ctx: &Context,
|
||||
parent_layer: LayerId,
|
||||
widget_id: Id,
|
||||
add_contents: impl FnOnce(&mut Ui) -> R,
|
||||
) -> Option<R> {
|
||||
ctx.input(|i| i.pointer.hover_pos()).map(|pointer_pos| {
|
||||
show_tooltip_at(ctx, widget_id, pointer_pos + vec2(16.0, 16.0), add_contents)
|
||||
show_tooltip_at(
|
||||
ctx,
|
||||
parent_layer,
|
||||
widget_id,
|
||||
pointer_pos + vec2(16.0, 16.0),
|
||||
add_contents,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -63,14 +71,16 @@ pub fn show_tooltip_at_pointer<R>(
|
|||
/// If the tooltip does not fit under the area, it tries to place it above it instead.
|
||||
pub fn show_tooltip_for<R>(
|
||||
ctx: &Context,
|
||||
parent_layer: LayerId,
|
||||
widget_id: Id,
|
||||
widget_rect: &Rect,
|
||||
add_contents: impl FnOnce(&mut Ui) -> R,
|
||||
) -> R {
|
||||
let is_touch_screen = ctx.input(|i| i.any_touches());
|
||||
let allow_placing_below = !is_touch_screen; // There is a finger below.
|
||||
show_tooltip_at_avoid_dyn(
|
||||
show_tooltip_at_dyn(
|
||||
ctx,
|
||||
parent_layer,
|
||||
widget_id,
|
||||
allow_placing_below,
|
||||
widget_rect,
|
||||
|
|
@ -83,14 +93,16 @@ pub fn show_tooltip_for<R>(
|
|||
/// Returns `None` if the tooltip could not be placed.
|
||||
pub fn show_tooltip_at<R>(
|
||||
ctx: &Context,
|
||||
parent_layer: LayerId,
|
||||
widget_id: Id,
|
||||
suggested_position: Pos2,
|
||||
add_contents: impl FnOnce(&mut Ui) -> R,
|
||||
) -> R {
|
||||
let allow_placing_below = true;
|
||||
let rect = Rect::from_center_size(suggested_position, Vec2::ZERO);
|
||||
show_tooltip_at_avoid_dyn(
|
||||
show_tooltip_at_dyn(
|
||||
ctx,
|
||||
parent_layer,
|
||||
widget_id,
|
||||
allow_placing_below,
|
||||
&rect,
|
||||
|
|
@ -98,21 +110,32 @@ pub fn show_tooltip_at<R>(
|
|||
)
|
||||
}
|
||||
|
||||
fn show_tooltip_at_avoid_dyn<'c, R>(
|
||||
fn show_tooltip_at_dyn<'c, R>(
|
||||
ctx: &Context,
|
||||
parent_layer: LayerId,
|
||||
widget_id: Id,
|
||||
allow_placing_below: bool,
|
||||
widget_rect: &Rect,
|
||||
add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
|
||||
) -> R {
|
||||
let mut widget_rect = *widget_rect;
|
||||
if let Some(transform) = ctx.memory(|m| m.layer_transforms.get(&parent_layer).copied()) {
|
||||
widget_rect = transform * widget_rect;
|
||||
}
|
||||
|
||||
// if there are multiple tooltips open they should use the same common_id for the `tooltip_size` caching to work.
|
||||
let mut state = ctx.frame_state(|fs| {
|
||||
let mut state = ctx.frame_state_mut(|fs| {
|
||||
// Remember that this is the widget showing the tooltip:
|
||||
fs.tooltip_state
|
||||
.per_layer_tooltip_widget
|
||||
.insert(parent_layer, widget_id);
|
||||
|
||||
fs.tooltip_state
|
||||
.widget_tooltips
|
||||
.get(&widget_id)
|
||||
.copied()
|
||||
.unwrap_or(PerWidgetTooltipState {
|
||||
bounding_rect: *widget_rect,
|
||||
bounding_rect: widget_rect,
|
||||
tooltip_count: 0,
|
||||
})
|
||||
});
|
||||
|
|
@ -235,12 +258,17 @@ fn find_tooltip_position(
|
|||
/// ```
|
||||
/// # egui::__run_test_ui(|ui| {
|
||||
/// if ui.ui_contains_pointer() {
|
||||
/// egui::show_tooltip_text(ui.ctx(), egui::Id::new("my_tooltip"), "Helpful text");
|
||||
/// egui::show_tooltip_text(ui.ctx(), ui.layer_id(), egui::Id::new("my_tooltip"), "Helpful text");
|
||||
/// }
|
||||
/// # });
|
||||
/// ```
|
||||
pub fn show_tooltip_text(ctx: &Context, widget_id: Id, text: impl Into<WidgetText>) -> Option<()> {
|
||||
show_tooltip(ctx, widget_id, |ui| {
|
||||
pub fn show_tooltip_text(
|
||||
ctx: &Context,
|
||||
parent_layer: LayerId,
|
||||
widget_id: Id,
|
||||
text: impl Into<WidgetText>,
|
||||
) -> Option<()> {
|
||||
show_tooltip(ctx, parent_layer, widget_id, |ui| {
|
||||
crate::widgets::Label::new(text).ui(ui);
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,27 @@
|
|||
use crate::{id::IdSet, *};
|
||||
|
||||
/// Reset at the start of each frame.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct TooltipFrameState {
|
||||
/// If a tooltip has been shown this frame, where was it?
|
||||
/// This is used to prevent multiple tooltips to cover each other.
|
||||
pub widget_tooltips: IdMap<PerWidgetTooltipState>,
|
||||
|
||||
/// For each layer, which widget is showing a tooltip (if any)?
|
||||
///
|
||||
/// Only one widget per layer may show a tooltip.
|
||||
/// But if a tooltip contains a tooltip, you can show a tooltip on top of a tooltip.
|
||||
pub per_layer_tooltip_widget: ahash::HashMap<LayerId, Id>,
|
||||
}
|
||||
|
||||
impl TooltipFrameState {
|
||||
pub fn clear(&mut self) {
|
||||
self.widget_tooltips.clear();
|
||||
let Self {
|
||||
widget_tooltips,
|
||||
per_layer_tooltip_widget,
|
||||
} = self;
|
||||
widget_tooltips.clear();
|
||||
per_layer_tooltip_widget.clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -51,9 +65,6 @@ pub struct FrameState {
|
|||
/// How much space is used by panels.
|
||||
pub used_by_panels: Rect,
|
||||
|
||||
/// If a tooltip has been shown this frame, where was it?
|
||||
/// This is used to prevent multiple tooltips to cover each other.
|
||||
/// Reset at the start of each frame.
|
||||
pub tooltip_state: TooltipFrameState,
|
||||
|
||||
/// The current scroll area should scroll to this range (horizontal, vertical).
|
||||
|
|
|
|||
|
|
@ -545,7 +545,13 @@ impl Response {
|
|||
/// Show this UI when hovering if the widget is disabled.
|
||||
pub fn on_disabled_hover_ui(self, add_contents: impl FnOnce(&mut Ui)) -> Self {
|
||||
if !self.enabled && self.should_show_hover_ui() {
|
||||
crate::containers::show_tooltip_for(&self.ctx, self.id, &self.rect, add_contents);
|
||||
crate::containers::show_tooltip_for(
|
||||
&self.ctx,
|
||||
self.layer_id,
|
||||
self.id,
|
||||
&self.rect,
|
||||
add_contents,
|
||||
);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
|
@ -553,7 +559,12 @@ impl Response {
|
|||
/// Like `on_hover_ui`, but show the ui next to cursor.
|
||||
pub fn on_hover_ui_at_pointer(self, add_contents: impl FnOnce(&mut Ui)) -> Self {
|
||||
if self.enabled && self.should_show_hover_ui() {
|
||||
crate::containers::show_tooltip_at_pointer(&self.ctx, self.id, add_contents);
|
||||
crate::containers::show_tooltip_at_pointer(
|
||||
&self.ctx,
|
||||
self.layer_id,
|
||||
self.id,
|
||||
add_contents,
|
||||
);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
|
@ -562,14 +573,13 @@ impl Response {
|
|||
///
|
||||
/// This can be used to give attention to a widget during a tutorial.
|
||||
pub fn show_tooltip_ui(&self, add_contents: impl FnOnce(&mut Ui)) {
|
||||
let mut rect = self.rect;
|
||||
if let Some(transform) = self
|
||||
.ctx
|
||||
.memory(|m| m.layer_transforms.get(&self.layer_id).copied())
|
||||
{
|
||||
rect = transform * rect;
|
||||
}
|
||||
crate::containers::show_tooltip_for(&self.ctx, self.id, &rect, add_contents);
|
||||
crate::containers::show_tooltip_for(
|
||||
&self.ctx,
|
||||
self.layer_id,
|
||||
self.id,
|
||||
&self.rect,
|
||||
add_contents,
|
||||
);
|
||||
}
|
||||
|
||||
/// Always show this tooltip, even if disabled and the user isn't hovering it.
|
||||
|
|
@ -635,6 +645,22 @@ impl Response {
|
|||
}
|
||||
}
|
||||
|
||||
let is_other_tooltip_open = self.ctx.prev_frame_state(|fs| {
|
||||
if let Some(already_open_tooltip) = fs
|
||||
.tooltip_state
|
||||
.per_layer_tooltip_widget
|
||||
.get(&self.layer_id)
|
||||
{
|
||||
already_open_tooltip != &self.id
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
if is_other_tooltip_open {
|
||||
// We only allow one tooltip per layer. First one wins. It is up to that tooltip to close itself.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Fast early-outs:
|
||||
if self.enabled {
|
||||
if !self.hovered || !self.ctx.input(|i| i.pointer.has_pointer()) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue