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