Skip to content

Commit 2654943

Browse files
emilkhacknus
authored andcommitted
Make sure all tooltips close if you open a menu in the same layer (emilk#4766)
1 parent 2107339 commit 2654943

File tree

4 files changed

+159
-103
lines changed

4 files changed

+159
-103
lines changed

crates/egui/src/containers/popup.rs

+84-51
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,29 @@ use crate::*;
66

77
// ----------------------------------------------------------------------------
88

9+
fn when_was_a_toolip_last_shown_id() -> Id {
10+
Id::new("when_was_a_toolip_last_shown")
11+
}
12+
13+
pub fn seconds_since_last_tooltip(ctx: &Context) -> f32 {
14+
let when_was_a_toolip_last_shown =
15+
ctx.data(|d| d.get_temp::<f64>(when_was_a_toolip_last_shown_id()));
16+
17+
if let Some(when_was_a_toolip_last_shown) = when_was_a_toolip_last_shown {
18+
let now = ctx.input(|i| i.time);
19+
(now - when_was_a_toolip_last_shown) as f32
20+
} else {
21+
f32::INFINITY
22+
}
23+
}
24+
25+
fn remember_that_tooltip_was_shown(ctx: &Context) {
26+
let now = ctx.input(|i| i.time);
27+
ctx.data_mut(|data| data.insert_temp::<f64>(when_was_a_toolip_last_shown_id(), now));
28+
}
29+
30+
// ----------------------------------------------------------------------------
31+
932
/// Show a tooltip at the current pointer position (if any).
1033
///
1134
/// Most of the time it is easier to use [`Response::on_hover_ui`].
@@ -123,14 +146,16 @@ fn show_tooltip_at_dyn<'c, R>(
123146
widget_rect = transform * widget_rect;
124147
}
125148

126-
// if there are multiple tooltips open they should use the same common_id for the `tooltip_size` caching to work.
149+
remember_that_tooltip_was_shown(ctx);
150+
127151
let mut state = ctx.frame_state_mut(|fs| {
128152
// Remember that this is the widget showing the tooltip:
129-
fs.tooltip_state
130-
.per_layer_tooltip_widget
131-
.insert(parent_layer, widget_id);
153+
fs.layers
154+
.entry(parent_layer)
155+
.or_default()
156+
.widget_with_tooltip = Some(widget_id);
132157

133-
fs.tooltip_state
158+
fs.tooltips
134159
.widget_tooltips
135160
.get(&widget_id)
136161
.copied()
@@ -174,15 +199,15 @@ fn show_tooltip_at_dyn<'c, R>(
174199

175200
state.tooltip_count += 1;
176201
state.bounding_rect = state.bounding_rect.union(response.rect);
177-
ctx.frame_state_mut(|fs| fs.tooltip_state.widget_tooltips.insert(widget_id, state));
202+
ctx.frame_state_mut(|fs| fs.tooltips.widget_tooltips.insert(widget_id, state));
178203

179204
inner
180205
}
181206

182207
/// What is the id of the next tooltip for this widget?
183208
pub fn next_tooltip_id(ctx: &Context, widget_id: Id) -> Id {
184209
let tooltip_count = ctx.frame_state(|fs| {
185-
fs.tooltip_state
210+
fs.tooltips
186211
.widget_tooltips
187212
.get(&widget_id)
188213
.map_or(0, |state| state.tooltip_count)
@@ -351,53 +376,61 @@ pub fn popup_above_or_below_widget<R>(
351376
close_behavior: PopupCloseBehavior,
352377
add_contents: impl FnOnce(&mut Ui) -> R,
353378
) -> Option<R> {
354-
if parent_ui.memory(|mem| mem.is_popup_open(popup_id)) {
355-
let (mut pos, pivot) = match above_or_below {
356-
AboveOrBelow::Above => (widget_response.rect.left_top(), Align2::LEFT_BOTTOM),
357-
AboveOrBelow::Below => (widget_response.rect.left_bottom(), Align2::LEFT_TOP),
358-
};
359-
if let Some(transform) = parent_ui
360-
.ctx()
361-
.memory(|m| m.layer_transforms.get(&parent_ui.layer_id()).copied())
362-
{
363-
pos = transform * pos;
364-
}
379+
if !parent_ui.memory(|mem| mem.is_popup_open(popup_id)) {
380+
return None;
381+
}
382+
383+
let (mut pos, pivot) = match above_or_below {
384+
AboveOrBelow::Above => (widget_response.rect.left_top(), Align2::LEFT_BOTTOM),
385+
AboveOrBelow::Below => (widget_response.rect.left_bottom(), Align2::LEFT_TOP),
386+
};
387+
if let Some(transform) = parent_ui
388+
.ctx()
389+
.memory(|m| m.layer_transforms.get(&parent_ui.layer_id()).copied())
390+
{
391+
pos = transform * pos;
392+
}
393+
394+
let frame = Frame::popup(parent_ui.style());
395+
let frame_margin = frame.total_margin();
396+
let inner_width = widget_response.rect.width() - frame_margin.sum().x;
397+
398+
parent_ui.ctx().frame_state_mut(|fs| {
399+
fs.layers
400+
.entry(parent_ui.layer_id())
401+
.or_default()
402+
.open_popups
403+
.insert(popup_id)
404+
});
365405

366-
let frame = Frame::popup(parent_ui.style());
367-
let frame_margin = frame.total_margin();
368-
let inner_width = widget_response.rect.width() - frame_margin.sum().x;
369-
370-
let response = Area::new(popup_id)
371-
.kind(UiKind::Popup)
372-
.order(Order::Foreground)
373-
.fixed_pos(pos)
374-
.default_width(inner_width)
375-
.pivot(pivot)
376-
.show(parent_ui.ctx(), |ui| {
377-
frame
378-
.show(ui, |ui| {
379-
ui.with_layout(Layout::top_down_justified(Align::LEFT), |ui| {
380-
ui.set_min_width(inner_width);
381-
add_contents(ui)
382-
})
383-
.inner
406+
let response = Area::new(popup_id)
407+
.kind(UiKind::Popup)
408+
.order(Order::Foreground)
409+
.fixed_pos(pos)
410+
.default_width(inner_width)
411+
.pivot(pivot)
412+
.show(parent_ui.ctx(), |ui| {
413+
frame
414+
.show(ui, |ui| {
415+
ui.with_layout(Layout::top_down_justified(Align::LEFT), |ui| {
416+
ui.set_min_width(inner_width);
417+
add_contents(ui)
384418
})
385419
.inner
386-
});
387-
388-
let should_close = match close_behavior {
389-
PopupCloseBehavior::CloseOnClick => widget_response.clicked_elsewhere(),
390-
PopupCloseBehavior::CloseOnClickOutside => {
391-
widget_response.clicked_elsewhere() && response.response.clicked_elsewhere()
392-
}
393-
PopupCloseBehavior::IgnoreClicks => false,
394-
};
395-
396-
if parent_ui.input(|i| i.key_pressed(Key::Escape)) || should_close {
397-
parent_ui.memory_mut(|mem| mem.close_popup());
420+
})
421+
.inner
422+
});
423+
424+
let should_close = match close_behavior {
425+
PopupCloseBehavior::CloseOnClick => widget_response.clicked_elsewhere(),
426+
PopupCloseBehavior::CloseOnClickOutside => {
427+
widget_response.clicked_elsewhere() && response.response.clicked_elsewhere()
398428
}
399-
Some(response.inner)
400-
} else {
401-
None
429+
PopupCloseBehavior::IgnoreClicks => false,
430+
};
431+
432+
if parent_ui.input(|i| i.key_pressed(Key::Escape)) || should_close {
433+
parent_ui.memory_mut(|mem| mem.close_popup());
402434
}
435+
Some(response.inner)
403436
}

crates/egui/src/frame_state.rs

+30-16
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use ahash::{HashMap, HashSet};
2+
13
use crate::{id::IdSet, *};
24

35
/// Reset at the start of each frame.
@@ -6,22 +8,12 @@ pub struct TooltipFrameState {
68
/// If a tooltip has been shown this frame, where was it?
79
/// This is used to prevent multiple tooltips to cover each other.
810
pub widget_tooltips: IdMap<PerWidgetTooltipState>,
9-
10-
/// For each layer, which widget is showing a tooltip (if any)?
11-
///
12-
/// Only one widget per layer may show a tooltip.
13-
/// But if a tooltip contains a tooltip, you can show a tooltip on top of a tooltip.
14-
pub per_layer_tooltip_widget: ahash::HashMap<LayerId, Id>,
1511
}
1612

1713
impl TooltipFrameState {
1814
pub fn clear(&mut self) {
19-
let Self {
20-
widget_tooltips,
21-
per_layer_tooltip_widget,
22-
} = self;
15+
let Self { widget_tooltips } = self;
2316
widget_tooltips.clear();
24-
per_layer_tooltip_widget.clear();
2517
}
2618
}
2719

@@ -34,6 +26,20 @@ pub struct PerWidgetTooltipState {
3426
pub tooltip_count: usize,
3527
}
3628

29+
#[derive(Clone, Debug, Default)]
30+
pub struct PerLayerState {
31+
/// Is there any open popup (menus, combo-boxes, etc)?
32+
///
33+
/// Does NOT include tooltips.
34+
pub open_popups: HashSet<Id>,
35+
36+
/// Which widget is showing a tooltip (if any)?
37+
///
38+
/// Only one widget per layer may show a tooltip.
39+
/// But if a tooltip contains a tooltip, you can show a tooltip on top of a tooltip.
40+
pub widget_with_tooltip: Option<Id>,
41+
}
42+
3743
#[cfg(feature = "accesskit")]
3844
#[derive(Clone)]
3945
pub struct AccessKitFrameState {
@@ -53,6 +59,13 @@ pub struct FrameState {
5359
/// All widgets produced this frame.
5460
pub widgets: WidgetRects,
5561

62+
/// Per-layer state.
63+
///
64+
/// Not all layers registers themselves there though.
65+
pub layers: HashMap<LayerId, PerLayerState>,
66+
67+
pub tooltips: TooltipFrameState,
68+
5669
/// Starts off as the `screen_rect`, shrinks as panels are added.
5770
/// The [`CentralPanel`] does not change this.
5871
/// This is the area available to Window's.
@@ -65,8 +78,6 @@ pub struct FrameState {
6578
/// How much space is used by panels.
6679
pub used_by_panels: Rect,
6780

68-
pub tooltip_state: TooltipFrameState,
69-
7081
/// The current scroll area should scroll to this range (horizontal, vertical).
7182
pub scroll_target: [Option<(Rangef, Option<Align>)>; 2],
7283

@@ -96,10 +107,11 @@ impl Default for FrameState {
96107
Self {
97108
used_ids: Default::default(),
98109
widgets: Default::default(),
110+
layers: Default::default(),
111+
tooltips: Default::default(),
99112
available_rect: Rect::NAN,
100113
unused_rect: Rect::NAN,
101114
used_by_panels: Rect::NAN,
102-
tooltip_state: Default::default(),
103115
scroll_target: [None, None],
104116
scroll_delta: Vec2::default(),
105117
#[cfg(feature = "accesskit")]
@@ -118,10 +130,11 @@ impl FrameState {
118130
let Self {
119131
used_ids,
120132
widgets,
133+
tooltips,
134+
layers,
121135
available_rect,
122136
unused_rect,
123137
used_by_panels,
124-
tooltip_state,
125138
scroll_target,
126139
scroll_delta,
127140
#[cfg(feature = "accesskit")]
@@ -134,10 +147,11 @@ impl FrameState {
134147

135148
used_ids.clear();
136149
widgets.clear();
150+
tooltips.clear();
151+
layers.clear();
137152
*available_rect = screen_rect;
138153
*unused_rect = screen_rect;
139154
*used_by_panels = Rect::NOTHING;
140-
tooltip_state.clear();
141155
*scroll_target = [None, None];
142156
*scroll_delta = Vec2::default();
143157

crates/egui/src/menu.rs

+25-7
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ pub(crate) fn submenu_button<R>(
135135
/// wrapper for the contents of every menu.
136136
fn menu_popup<'c, R>(
137137
ctx: &Context,
138+
parent_layer: LayerId,
138139
menu_state_arc: &Arc<RwLock<MenuState>>,
139140
menu_id: Id,
140141
add_contents: impl FnOnce(&mut Ui) -> R + 'c,
@@ -145,7 +146,17 @@ fn menu_popup<'c, R>(
145146
menu_state.rect.min
146147
};
147148

148-
let area = Area::new(menu_id.with("__menu"))
149+
let area_id = menu_id.with("__menu");
150+
151+
ctx.frame_state_mut(|fs| {
152+
fs.layers
153+
.entry(parent_layer)
154+
.or_default()
155+
.open_popups
156+
.insert(area_id)
157+
});
158+
159+
let area = Area::new(area_id)
149160
.kind(UiKind::Menu)
150161
.order(Order::Foreground)
151162
.fixed_pos(pos)
@@ -320,7 +331,13 @@ impl MenuRoot {
320331
add_contents: impl FnOnce(&mut Ui) -> R,
321332
) -> (MenuResponse, Option<InnerResponse<R>>) {
322333
if self.id == button.id {
323-
let inner_response = menu_popup(&button.ctx, &self.menu_state, self.id, add_contents);
334+
let inner_response = menu_popup(
335+
&button.ctx,
336+
button.layer_id,
337+
&self.menu_state,
338+
self.id,
339+
add_contents,
340+
);
324341
let menu_state = self.menu_state.read();
325342

326343
let escape_pressed = button.ctx.input(|i| i.key_pressed(Key::Escape));
@@ -580,10 +597,10 @@ impl SubMenu {
580597
self.parent_state
581598
.write()
582599
.submenu_button_interaction(ui, sub_id, &response);
583-
let inner = self
584-
.parent_state
585-
.write()
586-
.show_submenu(ui.ctx(), sub_id, add_contents);
600+
let inner =
601+
self.parent_state
602+
.write()
603+
.show_submenu(ui.ctx(), ui.layer_id(), sub_id, add_contents);
587604
InnerResponse::new(inner, response)
588605
}
589606
}
@@ -624,11 +641,12 @@ impl MenuState {
624641
fn show_submenu<R>(
625642
&mut self,
626643
ctx: &Context,
644+
parent_layer: LayerId,
627645
id: Id,
628646
add_contents: impl FnOnce(&mut Ui) -> R,
629647
) -> Option<R> {
630648
let (sub_response, response) = self.submenu(id).map(|sub| {
631-
let inner_response = menu_popup(ctx, sub, id, add_contents);
649+
let inner_response = menu_popup(ctx, parent_layer, sub, id, add_contents);
632650
(sub.read().response, inner_response.inner)
633651
})?;
634652
self.cascade_close_response(sub_response);

0 commit comments

Comments
 (0)