frust_core/overlay.rs
1//! The overlay portal: the mechanism a widget anywhere in the tree floats a pod
2//! above the whole app with.
3//!
4//! # The contract: owner-hosted, root-painted, root-routed
5//!
6//! A floating surface (a popover, a menu, a selection toolbar, a tooltip) has to
7//! escape its owner's bounds in three different ways at once, and this module
8//! splits the three between the owner and the root:
9//!
10//! * **Owner-hosted.** The pod is a plain [`ChildPod`] the *owner* builds, lays
11//! out (against [`LayoutCtx::window_size`](crate::widget::LayoutCtx::window_size),
12//! loosely) and keeps — so it retains its own widget state and sits inside the
13//! owner's reactive context, exactly like any other child. Nothing is
14//! re-parented and no second tree exists: the pod stays a logical child of its
15//! call site.
16//! * **Root-painted.** The owner does **not** paint the pod. It hands the root a
17//! registration ([`OverlayEntry`]) from its own `paint`, through
18//! [`PaintCtx::register_overlay`](crate::widget::PaintCtx::register_overlay),
19//! and [`RenderRoot::paint`](crate::app::RenderRoot::paint) paints every
20//! registered pod *after* the main tree — which is the only way a pod escapes
21//! both its owner's paint order and every ancestor's clip. Because the entry
22//! carries an absolute [`OverlayEntry::window_rect`] the owner computed from
23//! its own [`PaintCtx::origin`](crate::widget::PaintCtx::origin), an anchor
24//! follows the owner for free: the owner re-registers every frame from
25//! wherever it now paints. `window_rect` is just a rect either way, so
26//! nothing here cares whether the owner derived it from its own bounds or
27//! from an absolute window-space point it is tracking instead (a drag
28//! ghost following the pointer, in `frust-widgets`' `OverlaySlot`) — the
29//! mechanism is the same registration, repainted on whatever cadence the
30//! owner now needs.
31//! * **Root-routed.** Painting last is not enough, because hit testing is
32//! bounds-gated: a pointer over the floated pod lands on whatever the *main*
33//! tree has at that position. So the root hit-tests the registered rects
34//! **first** (topmost band first) and, on a hit, dispatches
35//! [`InputEvent::Overlay`](crate::event::InputEvent::Overlay) as a broadcast
36//! instead of the original event. The broadcast reaches the owner wherever it
37//! sits, and the owner forwards the window-space payload into its pod. Nothing
38//! is hit-tested against the owner's own bounds, and — because a broadcast is
39//! not user input at the root — the main tree's focus session is untouched:
40//! tapping a popover does not blur the field that opened it.
41//!
42//! # Per-pass entries
43//!
44//! The registry is **per paint pass**: it is cleared when
45//! [`RenderRoot::paint`](crate::app::RenderRoot::paint) begins and drained when
46//! the main tree has finished painting. An owner keeps a surface alive by
47//! registering it again every frame; an owner that stops registering disappears
48//! from the root's routing table after the next paint, with nothing to
49//! unregister and no way to leak an entry whose owner has been unmounted. A
50//! kept-mounted exit animation is therefore just "keep registering while the
51//! animation runs".
52//!
53//! The routing table the root retains between passes
54//! ([`OverlayHit`]) deliberately carries **no pod handle** — only the key, the
55//! band, the input class, the outside-tap policy and the rect. The owner holds
56//! the pod; the root never does, so a pod can never outlive its owner because
57//! the root kept a clone of it, and no [`RefCell`] borrow is ever held across a
58//! pass boundary.
59//!
60//! # Not in v1
61//!
62//! * **No focus trap.** Focus inside a pod behaves exactly like focus anywhere
63//! else; nothing confines traversal to the pod or restores it on dismiss.
64//! * **No declined-key forwarding.** Key/IME/edit-command events stay
65//! focus-routed and are never re-offered to an overlay owner that did not take
66//! focus.
67//! * **Not visible to [`inspect`](crate::app::RenderRoot::inspect).** An overlay
68//! pod is not reachable through [`Widget::visit_children`](crate::widget::Widget::visit_children)
69//! unless its owner chooses to visit it, so devtools sees the owner, not the
70//! floated surface.
71//! * **No nesting.** The registry is drained once, after the main tree paints, so
72//! a registration made from *inside* a floated pod's own paint is not painted
73//! this pass — and, because the pass's bracket clears the slot as it closes, it
74//! is dropped rather than leaked into the next one. A surface that itself needs
75//! a floated surface (a menu opening a submenu) registers both from the one
76//! owner, in the main tree's paint.
77
78use std::cell::{Cell, RefCell};
79use std::fmt;
80use std::rc::Rc;
81
82use kurbo::Rect;
83
84use crate::event::PassBracket;
85use crate::insets::WindowInsets;
86use crate::widget::ChildPod;
87
88thread_local! {
89 /// The next value [`OverlayKey::next`] hands out.
90 ///
91 /// Thread-local and UI-thread-affine, mirroring
92 /// [`ChildPod`]'s own `NEXT_INSPECT_ID` allocator: the widget tree is
93 /// single-threaded, and an owner allocates its key while building itself
94 /// with no root in scope to ask. Two threads each running a tree therefore
95 /// hand out the same integers, which is harmless — a key is only ever
96 /// compared against the entries of the one root that painted them.
97 ///
98 /// Starts at `1` so `0` stays available as "no overlay" for a consumer that
99 /// wants a niche-free sentinel.
100 static NEXT_OVERLAY_KEY: Cell<u64> = const { Cell::new(1) };
101
102 /// The entries registered so far in the paint pass currently running on this
103 /// thread — written by
104 /// [`PaintCtx::register_overlay`](crate::widget::PaintCtx::register_overlay)
105 /// and drained by [`RenderRoot::paint`](crate::app::RenderRoot::paint).
106 ///
107 /// A side channel for the *routing* reason
108 /// [`EventCtx::set_cursor`](crate::event::EventCtx::set_cursor)'s slot is
109 /// one, not for a missing-handle reason: an entry means nothing to any
110 /// container between the owner and the root, so bubbling it pod by pod would
111 /// widen every container's paint absorb to carry a payload no container
112 /// reads. Unlike the cursor this is a `Vec` rather than a last-writer-wins
113 /// slot — any number of owners may float a surface in one pass, and
114 /// registration order is what orders them within a band.
115 ///
116 /// **Pass-scoped**: cleared when a pass opens and drained when it closes, so
117 /// an entry registered by a widget painted with no root above it (a leaf
118 /// unit test) is dropped by the next pass's clear rather than leaking into
119 /// it.
120 static OVERLAY_REGISTRY: Cell<Vec<OverlayEntry>> = const { Cell::new(Vec::new()) };
121
122 /// Whether an overlay registration pass is open on this thread — owned by
123 /// [`OverlayPaintPass`] alone (see [`PassBracket`]).
124 static OVERLAY_PASS_OPEN: Cell<bool> = const { Cell::new(false) };
125}
126
127/// An overlay owner's identity, allocated once by the owner and quoted back to
128/// it by every [`InputEvent::Overlay`](crate::event::InputEvent::Overlay) the
129/// root routes into its surface.
130///
131/// Stable for the owner's lifetime: an owner allocates one key when it is built
132/// and stores it, re-registering under the same key every paint. The root
133/// therefore never has to recognise an owner by position, and an owner that
134/// floats two surfaces simply holds two keys.
135#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
136pub struct OverlayKey(u64);
137
138impl OverlayKey {
139 /// Allocate a fresh key, distinct from every other key handed out on this
140 /// thread.
141 ///
142 /// An owner calls this **once**, when it is built, and stores the result —
143 /// calling it per frame would hand the root a new identity every paint and
144 /// break the routing it exists to enable.
145 pub fn next() -> Self {
146 OverlayKey(NEXT_OVERLAY_KEY.with(|next| {
147 let id = next.get();
148 next.set(id.wrapping_add(1));
149 id
150 }))
151 }
152
153 /// The underlying integer, for a consumer that needs to key a map by it.
154 /// Diagnostic and bookkeeping only — never derive routing from the *value*
155 /// (allocation order is not a contract).
156 pub fn value(self) -> u64 {
157 self.0
158 }
159}
160
161/// Which z-band a registered surface paints and hit-tests in.
162///
163/// Two bands, ordered `Floating` **below** `Tooltip`: a tooltip explaining a
164/// menu item must never be painted under the menu, and — because hit testing
165/// walks the bands in reverse — must never steal the pointer from it either
166/// (a tooltip is normally registered [`OverlayInput::Transparent`] anyway).
167/// Registration order breaks ties *within* a band; the band itself always wins.
168#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
169pub enum OverlayBand {
170 /// Menus, popovers, dropdowns, selection toolbars — anything the user can
171 /// interact with. Painted first, hit-tested last.
172 Floating,
173 /// Tooltips and other explanatory chrome that must sit above everything
174 /// else. Painted last, hit-tested first.
175 Tooltip,
176}
177
178/// Whether a registered surface takes pointer input at all.
179///
180/// [`OverlayInput::Transparent`] is the rule egui's `Order::Tooltip` encodes: a
181/// surface that is painted above the app but never hit-tested, so the pointer
182/// passes straight through it to whatever the main tree has underneath. A
183/// transparent entry is skipped by the root's pre-pass entirely — it receives no
184/// [`InputEvent::Overlay`](crate::event::InputEvent::Overlay), including no
185/// [`OverlayEventKind::OutsideDown`](crate::event::OverlayEventKind::OutsideDown).
186#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
187pub enum OverlayInput {
188 /// The surface hit-tests: a pointer inside its rect is routed to its owner
189 /// and never reaches the main tree.
190 Interactive,
191 /// The surface is painted but never hit-tested — pointer input passes
192 /// through as though it were not there.
193 Transparent,
194}
195
196/// What a registered surface wants to hear about a press that landed on
197/// *nothing* floated — the light-dismiss policy, a per-surface boolean
198/// everywhere this pattern exists.
199#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
200pub enum OutsideTap {
201 /// Say nothing. A surface that dismisses some other way (a menu closed by
202 /// its own item, a toolbar that follows a selection) wants this.
203 Ignore,
204 /// Deliver
205 /// [`OverlayEventKind::OutsideDown`](crate::event::OverlayEventKind::OutsideDown)
206 /// to the owner on a primary press outside every registered rect.
207 Notify {
208 /// Whether the press is **consumed** by the notification.
209 ///
210 /// `true` is the modal light-dismiss shape: the press closes the surface
211 /// and the main tree never sees it, so the tap that dismisses a menu
212 /// does not also activate the button under it. `false` is the
213 /// pass-through shape: the owner is told, *and* the press continues into
214 /// the main tree as usual.
215 consume: bool,
216 },
217}
218
219/// The shared handle an owner and the root address one floated pod through.
220///
221/// `Rc<RefCell<_>>` rather than a borrow because the two sides run in different
222/// passes: the owner keeps one clone for the layout/event side, the registration
223/// carries the other so the root can paint it after the owner's own paint has
224/// returned. The root never *retains* a clone past the paint it was registered
225/// for (see [`OverlayHit`]), so the pod's lifetime stays the owner's.
226pub type OverlayPod = Rc<RefCell<ChildPod>>;
227
228/// One floated surface, registered by its owner for the paint pass in progress.
229///
230/// Every field is a D7 rule the root reads back: `band` and registration order
231/// decide paint and hit order, `input` decides whether the surface is hit-tested
232/// at all, `outside_tap` decides what a press elsewhere delivers, and
233/// `window_rect` is both where the pod is painted and what the root hit-tests.
234#[derive(Clone)]
235pub struct OverlayEntry {
236 /// The owner's identity, quoted back on every routed
237 /// [`InputEvent::Overlay`](crate::event::InputEvent::Overlay) so the owner
238 /// recognises its own surface's input and every other widget ignores it.
239 pub key: OverlayKey,
240 /// Which z-band the surface paints and hit-tests in (`Floating` below
241 /// `Tooltip`).
242 pub band: OverlayBand,
243 /// Whether the surface takes pointer input or is painted through.
244 pub input: OverlayInput,
245 /// What a primary press outside every registered rect delivers here.
246 pub outside_tap: OutsideTap,
247 /// Where the surface sits, in **absolute logical window space** — the one
248 /// coordinate space both the root's paint origin and the root's hit test use.
249 /// An owner computes it in `paint` from
250 /// [`PaintCtx::origin`](crate::widget::PaintCtx::origin), the only absolute
251 /// anchor a widget has, which is what makes an anchored surface follow its
252 /// owner with no subscription of any kind.
253 pub window_rect: Rect,
254 /// The pod the root paints. The owner holds the other clone and lays this
255 /// out itself, against the window size; the owner must **not** paint it —
256 /// painting it as well would draw the surface twice, once clipped in place
257 /// and once floated.
258 ///
259 /// The root paints it with `window_rect.origin()` as the absolute origin, and
260 /// [`ChildPod::paint_child`](crate::widget::ChildPod::paint_child) then adds
261 /// the pod's **own** origin on top as it does for any child — so leave that at
262 /// [`Point::ZERO`](kurbo::Point::ZERO) unless you mean an offset the hit test
263 /// will not know about (routing tests `window_rect` alone).
264 pub pod: OverlayPod,
265 /// The window insets the pod was laid out under (its owner's `LayoutCtx`
266 /// view); paint installs the same value so paint-time reads agree with
267 /// layout-time reads.
268 pub insets: WindowInsets,
269}
270
271impl fmt::Debug for OverlayEntry {
272 /// Prints every routing field and elides the pod: a [`ChildPod`] is not
273 /// [`Debug`], and what a diagnostic wants from an entry is where it sits and
274 /// how it routes, not what it contains.
275 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
276 f.debug_struct("OverlayEntry")
277 .field("key", &self.key)
278 .field("band", &self.band)
279 .field("input", &self.input)
280 .field("outside_tap", &self.outside_tap)
281 .field("window_rect", &self.window_rect)
282 .field("insets", &self.insets)
283 .field("pod", &"<pod>")
284 .finish()
285 }
286}
287
288/// What the root retains from an [`OverlayEntry`] to route the *next* pass's
289/// input with: everything except the pod.
290///
291/// Dropping the pod handle is the point. The root holds these between paints, so
292/// retaining an [`OverlayPod`] here would make the root a co-owner of a pod whose
293/// owner may have been unmounted since, and would invite a borrow held across a
294/// pass boundary. Routing needs none of it: the root decides *which key* the
295/// event belongs to and broadcasts that key, and the owner — which does hold the
296/// pod — forwards it in.
297#[derive(Clone, Copy, Debug, PartialEq)]
298pub struct OverlayHit {
299 /// The owner's identity, broadcast with the routed event.
300 pub key: OverlayKey,
301 /// The band, which orders the hit test (`Tooltip` tested before `Floating`).
302 pub band: OverlayBand,
303 /// Whether this entry is hit-tested at all.
304 pub input: OverlayInput,
305 /// What a press outside every rect delivers here.
306 pub outside_tap: OutsideTap,
307 /// The absolute logical rect the hit test runs against.
308 pub window_rect: Rect,
309}
310
311impl OverlayHit {
312 /// Take the routing half of `entry`, leaving the pod behind.
313 pub(crate) fn of(entry: &OverlayEntry) -> Self {
314 Self {
315 key: entry.key,
316 band: entry.band,
317 input: entry.input,
318 outside_tap: entry.outside_tap,
319 window_rect: entry.window_rect,
320 }
321 }
322
323 /// Whether `point` (absolute logical window space) lies inside this entry.
324 ///
325 /// Half-open on the far edges, exactly like
326 /// [`ChildPod::contains`](crate::widget::ChildPod::contains), so two
327 /// surfaces sharing an edge cannot both claim the same pixel.
328 pub(crate) fn contains(&self, point: kurbo::Point) -> bool {
329 point.x >= self.window_rect.x0
330 && point.x < self.window_rect.x1
331 && point.y >= self.window_rect.y0
332 && point.y < self.window_rect.y1
333 }
334}
335
336/// The open/close bracket around one overlay registration pass.
337///
338/// [`RenderRoot::paint`](crate::app::RenderRoot::paint) holds one for the length
339/// of the pass: entering clears the registry (so nothing from a previous pass —
340/// or from a widget painted with no root above it — can survive into this one),
341/// [`OverlayPaintPass::take`] drains what this pass registered, and `Drop` hands
342/// an enclosing pass its own in-progress registrations back.
343///
344/// The mechanism is [`PassBracket`]'s, shared with the event pass's request
345/// slots; only the channel and the open flag differ.
346pub(crate) struct OverlayPaintPass(PassBracket<Vec<OverlayEntry>>);
347
348impl OverlayPaintPass {
349 /// Open an overlay registration pass, starting from "nobody has registered
350 /// anything".
351 pub(crate) fn enter() -> Self {
352 OverlayPaintPass(PassBracket::enter(&OVERLAY_REGISTRY, &OVERLAY_PASS_OPEN))
353 }
354
355 /// Take what *this* pass registered, in registration order, leaving the
356 /// registry empty.
357 pub(crate) fn take(&self) -> Vec<OverlayEntry> {
358 self.0.take()
359 }
360}
361
362/// Record `entry` in the pass currently painting on this thread.
363///
364/// The implementation behind
365/// [`PaintCtx::register_overlay`](crate::widget::PaintCtx::register_overlay),
366/// which is the API an owner calls; this is the module-private half so the slot
367/// stays owned here.
368pub(crate) fn register(entry: OverlayEntry) {
369 OVERLAY_REGISTRY.with(|registry| {
370 // Move the `Vec` out, push, move it back: a `Cell` rather than a
371 // `RefCell` because the slot is bracketed by the shared [`PassBracket`]
372 // machinery, which is written against `Cell`'s `take`/`set`. The move is
373 // three words; the allocation is reused across the whole pass.
374 let mut entries = registry.take();
375 entries.push(entry);
376 registry.set(entries);
377 });
378}
379
380/// Sort `entries` into paint order in place: `Floating` first, then `Tooltip`,
381/// preserving registration order within each band.
382///
383/// A **stable** sort, which is the whole rule — the band is the only key, so
384/// stability is what makes "later registration paints above earlier" true within
385/// a band. Reversing the result gives the hit-test order (topmost first).
386pub(crate) fn sort_into_paint_order(entries: &mut [OverlayEntry]) {
387 entries.sort_by_key(|entry| entry.band);
388}
389
390#[cfg(test)]
391mod tests {
392 use super::*;
393 use crate::layout::BoxConstraints;
394 use crate::widget::{LayoutCtx, PaintCtx, PaintScene, Widget};
395 use kurbo::{Point, Size};
396
397 /// A do-nothing leaf, so an entry can carry a real [`ChildPod`].
398 struct StubWidget;
399 impl Widget for StubWidget {
400 fn layout(&mut self, _ctx: &mut LayoutCtx, _bc: &BoxConstraints) -> Size {
401 Size::ZERO
402 }
403 fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut dyn PaintScene) {}
404 }
405
406 fn entry(key: OverlayKey, band: OverlayBand, rect: Rect) -> OverlayEntry {
407 OverlayEntry {
408 key,
409 band,
410 input: OverlayInput::Interactive,
411 outside_tap: OutsideTap::Ignore,
412 window_rect: rect,
413 pod: Rc::new(RefCell::new(ChildPod::new(Box::new(StubWidget)))),
414 insets: WindowInsets::default(),
415 }
416 }
417
418 #[test]
419 fn keys_are_distinct_and_stable() {
420 let first = OverlayKey::next();
421 let second = OverlayKey::next();
422 assert_ne!(first, second, "two owners never share an identity");
423 // An owner stores its key; copying it around must not re-allocate.
424 let stored = first;
425 assert_eq!(stored, first);
426 assert_ne!(stored, second);
427 }
428
429 #[test]
430 fn paint_order_puts_floating_below_tooltip_and_is_stable_within_a_band() {
431 let (a, b, c, d) = (
432 OverlayKey::next(),
433 OverlayKey::next(),
434 OverlayKey::next(),
435 OverlayKey::next(),
436 );
437 let rect = Rect::new(0.0, 0.0, 10.0, 10.0);
438 // Registered tooltip-first and interleaved, which is exactly what the
439 // sort has to survive: the band decides, registration order only breaks
440 // ties inside one.
441 let mut entries = vec![
442 entry(a, OverlayBand::Tooltip, rect),
443 entry(b, OverlayBand::Floating, rect),
444 entry(c, OverlayBand::Tooltip, rect),
445 entry(d, OverlayBand::Floating, rect),
446 ];
447 sort_into_paint_order(&mut entries);
448 assert_eq!(
449 entries.iter().map(|e| e.key).collect::<Vec<_>>(),
450 vec![b, d, a, c],
451 "Floating first (in registration order), then Tooltip (in registration order)"
452 );
453 }
454
455 #[test]
456 fn a_hit_rect_is_half_open_on_its_far_edges() {
457 let hit = OverlayHit::of(&entry(
458 OverlayKey::next(),
459 OverlayBand::Floating,
460 Rect::new(10.0, 20.0, 110.0, 60.0),
461 ));
462 assert!(
463 hit.contains(Point::new(10.0, 20.0)),
464 "near edges are inside"
465 );
466 assert!(hit.contains(Point::new(109.9, 59.9)));
467 // Half-open like `ChildPod::contains`, so two surfaces sharing an edge
468 // cannot both claim the same pixel.
469 assert!(!hit.contains(Point::new(110.0, 40.0)));
470 assert!(!hit.contains(Point::new(60.0, 60.0)));
471 assert!(!hit.contains(Point::new(9.9, 40.0)));
472 }
473
474 #[test]
475 fn a_hit_keeps_the_routing_fields_and_drops_the_pod() {
476 let key = OverlayKey::next();
477 let mut source = entry(key, OverlayBand::Tooltip, Rect::new(1.0, 2.0, 3.0, 4.0));
478 source.input = OverlayInput::Transparent;
479 source.outside_tap = OutsideTap::Notify { consume: true };
480 let hit = OverlayHit::of(&source);
481 assert_eq!(hit.key, key);
482 assert_eq!(hit.band, OverlayBand::Tooltip);
483 assert_eq!(hit.input, OverlayInput::Transparent);
484 assert_eq!(hit.outside_tap, OutsideTap::Notify { consume: true });
485 assert_eq!(hit.window_rect, source.window_rect);
486 // The pod stayed with its owner: the entry still holds the only two
487 // clones (the owner's and this local one), never a third in the root.
488 assert_eq!(Rc::strong_count(&source.pod), 1);
489 }
490
491 #[test]
492 fn the_registry_is_pass_scoped_and_preserves_registration_order() {
493 let (first, second) = (OverlayKey::next(), OverlayKey::next());
494 let rect = Rect::new(0.0, 0.0, 10.0, 10.0);
495 let pass = OverlayPaintPass::enter();
496 assert!(pass.take().is_empty(), "a fresh pass starts empty");
497
498 register(entry(first, OverlayBand::Floating, rect));
499 register(entry(second, OverlayBand::Floating, rect));
500 let drained = pass.take();
501 assert_eq!(
502 drained.iter().map(|e| e.key).collect::<Vec<_>>(),
503 vec![first, second],
504 "registration order is preserved"
505 );
506 assert!(pass.take().is_empty(), "and the drain empties the registry");
507 drop(pass);
508
509 // A registration made with no pass open (a leaf unit test painting a bare
510 // `PaintCtx`) is dropped by the next pass's clear, never leaked into it —
511 // which is what makes "the registry is empty at the start of every paint"
512 // true without anyone unregistering.
513 register(entry(first, OverlayBand::Floating, rect));
514 let next = OverlayPaintPass::enter();
515 assert!(
516 next.take().is_empty(),
517 "a stray registration does not survive into the next pass"
518 );
519 }
520
521 #[test]
522 fn a_nested_pass_hands_the_enclosing_passs_registrations_back() {
523 let (outer_key, inner_key) = (OverlayKey::next(), OverlayKey::next());
524 let rect = Rect::new(0.0, 0.0, 10.0, 10.0);
525 let outer = OverlayPaintPass::enter();
526 register(entry(outer_key, OverlayBand::Floating, rect));
527 {
528 let inner = OverlayPaintPass::enter();
529 assert!(inner.take().is_empty(), "the inner pass starts empty");
530 register(entry(inner_key, OverlayBand::Floating, rect));
531 assert_eq!(
532 inner.take().iter().map(|e| e.key).collect::<Vec<_>>(),
533 vec![inner_key]
534 );
535 }
536 assert_eq!(
537 outer.take().iter().map(|e| e.key).collect::<Vec<_>>(),
538 vec![outer_key],
539 "the enclosing pass's registrations are handed back intact"
540 );
541 }
542
543 #[test]
544 fn debug_prints_the_routing_fields_without_the_pod() {
545 let rendered = format!(
546 "{:?}",
547 entry(
548 OverlayKey::next(),
549 OverlayBand::Floating,
550 Rect::new(0.0, 0.0, 10.0, 10.0)
551 )
552 );
553 assert!(rendered.contains("Floating"), "{rendered}");
554 assert!(rendered.contains("Interactive"), "{rendered}");
555 assert!(rendered.contains("<pod>"), "{rendered}");
556 }
557}