1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
//! Shared internals for the overlay widgets: layering, modality, focus
//! lifecycle, and the chrome they both render.
//!
//! [`Modal`](crate::Modal) and [`Drawer`](crate::Drawer) both stack the same
//! two things over the app: a full-viewport backdrop that must swallow every
//! interaction behind it, and a content surface that must stay above that
//! backdrop. Getting this right in egui takes more care than it looks, so the
//! rules live here rather than being duplicated (and drifting) per widget.
//!
//! # The layer contract
//!
//! **One overlay is one [`Area`](egui::Area), at [`Order::Foreground`].** The
//! backdrop is painted and sensed *inside* that area rather than getting an
//! area of its own. A split across two orders (the obvious first design —
//! backdrop at [`Order::Middle`], content at [`Order::Foreground`]) leaves a
//! gap that any ordinary [`egui::Window`] slots straight into: the window
//! renders above the backdrop, so it keeps receiving clicks the overlay was
//! supposed to block, and clicking it raises it above the overlay's content.
//! Sharing one layer also makes the backdrop-below-content relationship
//! structural instead of something to re-assert every frame.
//!
//! **Pointer blocking is the backdrop's own doing.** It senses clicks across
//! the whole viewport, and egui routes a click to the topmost layer under the
//! cursor, so everything below the overlay's layer stops being reachable.
//!
//! That ordering needs no maintenance, because a window that cannot be clicked
//! never asks to be raised. Do *not* be tempted to re-assert it with a
//! per-frame `move_to_top`: egui applies that as a *stable sort* at the end of
//! the pass, lifting the layer above every layer that did not also ask. Popups,
//! menus and combo-box dropdowns are plain [`Order::Foreground`] areas, so an
//! overlay that re-raised itself each frame would sink any popup opened from
//! inside its own content.
//!
//! **Anything above [`Order::Foreground`] stays above.** Toasts and drag
//! ghosts live at [`Order::Tooltip`] and deliberately keep floating over an
//! open overlay, and remain interactive there.
//!
//! # The modal-layer claim, and the focus trap it sets
//!
//! Blocking *widgets* is not quite enough. egui also does area-level
//! hit-testing: [`egui::Area`] raises itself whenever the pointer is pressed
//! and `Memory::layer_id_at` names it. That query walks *area* rects, and an
//! overlay's area rect is only as big as its content: the full-viewport
//! backdrop is registered with [`egui::Ui::interact`], which senses a rect
//! without claiming layout space, or the card could not stay centred (an area
//! sized to the viewport has nothing left to centre within). So without help, a
//! press outside the card resolves to the window underneath and raises it above
//! the overlay: exactly the bug this design exists to fix.
//!
//! [`egui::Memory::set_modal_layer`] is what closes that gap. Its documented
//! job is confining keyboard focus, but `Memory::layer_id_at` also clamps its
//! answer to the modal layer, so a press over anything below the overlay is
//! attributed to the overlay and nothing beneath it ever asks to be raised.
//! The claim is therefore load-bearing for pointer layering, not a bonus.
//!
//! It comes with a trap. A claim takes effect the frame *after* it is made, so
//! on the frame an overlay closes, the previous frame's claim is still in
//! force. Widgets below the claimed layer are not "interested in focus", and
//! egui responds by actively surrendering their focus (see
//! `Context::create_widget`). An overlay that asks for the pre-dialog widget to
//! be focused again has it taken straight back, whenever that widget is drawn
//! *after* the overlay in the same frame. A trailing claim cannot be retracted,
//! and egui's only end-of-pass hook is a permanent plugin callback, so there is
//! nowhere to re-assert focus once per close. Overlays therefore restore it
//! twice: immediately, and again on the following frame via a pending flag.
//! The second attempt is the one that survives.
use ;
use crate::;
/// The order every overlay lives in. See the module docs for why backdrop and
/// content share it instead of straddling [`Order::Middle`].
pub const ORDER: Order = Foreground;
/// The [`LayerId`] an overlay with this area id occupies.
pub
/// Which overlays were open, tracked one pass behind.
///
/// An overlay can only be compared against the others once they have all had a
/// chance to render, so the completed set from the previous pass is what
/// [`register_open`] consults. `current` accumulates this pass's registrations
/// and rotates into `previous` the first time a new pass touches it.
/// Record that this overlay is open, and report whether it is the one on top.
///
/// Call once per pass from an open overlay, at the point it decides whether to
/// act on `Esc`.
///
/// "On top" means no other recently-open overlay sits above this one in egui's
/// layer order. Overlays that have since disappeared are ignored, so closing
/// the upper of a stack hands the answer straight to the one beneath.
///
/// One caveat, shared with egui's own [`egui::Modal`]: the comparison set is a
/// pass old, so on the single frame an overlay opens over an existing one, the
/// older overlay still believes it is on top, and an `Esc` landing in that
/// one-frame window goes to the wrong overlay. An overlay drawn before another
/// cannot know the other is about to appear above it, so short of deferring
/// every dismissal by a frame, immediate mode leaves no way to close this.
pub
/// Claim the modal layer, so presses over anything below this overlay are
/// attributed to it and nothing beneath raises itself above it.
///
/// Call only while the overlay intends to still be open *next* frame, which is
/// when egui applies the claim — see the module docs for why a claim that
/// outlives its overlay costs the background its focus.
pub
/// Persistent focus-lifecycle state for a single overlay, keyed by its
/// `id_salt`. Stored via `ctx.data_mut`.
pub
/// Render an overlay's close button under the stable id `id_salt`, so focus
/// requests targeting it survive layout changes. Returns its `Response` so the
/// caller can route focus to it and observe `clicked()`. The accesskit label is
/// set to `"Close"` explicitly — without it screen readers announce the "×"
/// glyph as "multiplication sign".
pub
/// Whether `Esc` should dismiss this overlay on this frame, consuming the key
/// press if so.
///
/// Three things have to line up. The overlay must be the topmost one, so that
/// a modal raised over a drawer takes the key and the drawer keeps its state.
/// No popup may have been open when the overlay's content ran, since egui's
/// popups close on `Esc` themselves and a single press should not collapse
/// both the dropdown and the dialog holding it. And the press is consumed, so
/// application-level `Esc` handlers don't act on a key the dialog just used.
pub