1use std::cell::{Cell, RefCell};
24use std::fmt;
25use std::rc::Rc;
26
27use kurbo::Rect;
28
29use crate::event::PassBracket;
30use crate::insets::WindowInsets;
31use crate::widget::ChildPod;
32
33thread_local! {
34 static NEXT_OVERLAY_KEY: Cell<u64> = const { Cell::new(1) };
36
37 static OVERLAY_REGISTRY: Cell<Vec<OverlayEntry>> = const { Cell::new(Vec::new()) };
39
40 static OVERLAY_PASS_OPEN: Cell<bool> = const { Cell::new(false) };
41}
42
43#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
45pub struct OverlayKey(u64);
46
47impl OverlayKey {
48 pub fn next() -> Self {
50 OverlayKey(NEXT_OVERLAY_KEY.with(|next| {
51 let id = next.get();
52 next.set(id.wrapping_add(1));
53 id
54 }))
55 }
56
57 pub fn value(self) -> u64 {
59 self.0
60 }
61}
62
63#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
65pub enum OverlayBand {
66 Floating,
68 Tooltip,
70}
71
72#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
74pub enum OverlayInput {
75 Interactive,
77 Transparent,
79}
80
81#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
83pub enum OutsideTap {
84 Ignore,
86 Notify {
88 consume: bool,
90 },
91}
92
93#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
95pub enum OverlayKeys {
96 #[default]
97 Pass,
98 Modal,
100}
101
102pub type OverlayPod = Rc<RefCell<ChildPod>>;
104
105#[derive(Clone)]
107pub struct OverlayEntry {
108 pub key: OverlayKey,
109 pub band: OverlayBand,
110 pub input: OverlayInput,
111 pub outside_tap: OutsideTap,
112 pub keys: OverlayKeys,
113 pub window_rect: Rect,
116 pub pod: OverlayPod,
118 pub insets: WindowInsets,
120}
121
122impl fmt::Debug for OverlayEntry {
123 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
124 f.debug_struct("OverlayEntry")
125 .field("key", &self.key)
126 .field("band", &self.band)
127 .field("input", &self.input)
128 .field("outside_tap", &self.outside_tap)
129 .field("keys", &self.keys)
130 .field("window_rect", &self.window_rect)
131 .field("insets", &self.insets)
132 .field("pod", &"<pod>")
133 .finish()
134 }
135}
136
137#[derive(Clone, Copy, Debug, PartialEq)]
139pub struct OverlayHit {
140 pub key: OverlayKey,
141 pub band: OverlayBand,
142 pub input: OverlayInput,
143 pub outside_tap: OutsideTap,
144 pub keys: OverlayKeys,
145 pub window_rect: Rect,
146}
147
148impl OverlayHit {
149 pub(crate) fn of(entry: &OverlayEntry) -> Self {
150 Self {
151 key: entry.key,
152 band: entry.band,
153 input: entry.input,
154 outside_tap: entry.outside_tap,
155 keys: entry.keys,
156 window_rect: entry.window_rect,
157 }
158 }
159
160 pub(crate) fn is_modal(&self) -> bool {
162 self.input == OverlayInput::Interactive
163 && (self.keys == OverlayKeys::Modal
164 || self.outside_tap == OutsideTap::Notify { consume: true })
165 }
166
167 pub(crate) fn contains(&self, point: kurbo::Point) -> bool {
169 point.x >= self.window_rect.x0
170 && point.x < self.window_rect.x1
171 && point.y >= self.window_rect.y0
172 && point.y < self.window_rect.y1
173 }
174}
175
176pub(crate) struct OverlayPaintPass(PassBracket<Vec<OverlayEntry>>);
178
179impl OverlayPaintPass {
180 pub(crate) fn enter() -> Self {
181 OverlayPaintPass(PassBracket::enter(&OVERLAY_REGISTRY, &OVERLAY_PASS_OPEN))
182 }
183
184 pub(crate) fn take(&self) -> Vec<OverlayEntry> {
185 self.0.take()
186 }
187}
188
189pub(crate) fn register(entry: OverlayEntry) {
191 OVERLAY_REGISTRY.with(|registry| {
192 let mut entries = registry.take();
193 entries.push(entry);
194 registry.set(entries);
195 });
196}
197
198pub(crate) fn sort_into_paint_order(entries: &mut [OverlayEntry]) {
200 entries.sort_by_key(|entry| entry.band);
201}
202
203#[cfg(test)]
204mod tests {
205 use super::*;
206 use crate::layout::BoxConstraints;
207 use crate::widget::{LayoutCtx, PaintCtx, PaintScene, Widget};
208 use kurbo::{Point, Size};
209
210 struct StubWidget;
212 impl Widget for StubWidget {
213 fn layout(&mut self, _ctx: &mut LayoutCtx, _bc: &BoxConstraints) -> Size {
214 Size::ZERO
215 }
216 fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut dyn PaintScene) {}
217 }
218
219 fn entry(key: OverlayKey, band: OverlayBand, rect: Rect) -> OverlayEntry {
220 OverlayEntry {
221 key,
222 band,
223 input: OverlayInput::Interactive,
224 outside_tap: OutsideTap::Ignore,
225 keys: OverlayKeys::Pass,
226 window_rect: rect,
227 pod: Rc::new(RefCell::new(ChildPod::new(Box::new(StubWidget)))),
228 insets: WindowInsets::default(),
229 }
230 }
231
232 #[test]
233 fn keys_are_distinct_and_stable() {
234 let first = OverlayKey::next();
235 let second = OverlayKey::next();
236 assert_ne!(first, second, "two owners never share an identity");
237 let stored = first;
239 assert_eq!(stored, first);
240 assert_ne!(stored, second);
241 }
242
243 #[test]
244 fn paint_order_puts_floating_below_tooltip_and_is_stable_within_a_band() {
245 let (a, b, c, d) = (
246 OverlayKey::next(),
247 OverlayKey::next(),
248 OverlayKey::next(),
249 OverlayKey::next(),
250 );
251 let rect = Rect::new(0.0, 0.0, 10.0, 10.0);
252 let mut entries = vec![
256 entry(a, OverlayBand::Tooltip, rect),
257 entry(b, OverlayBand::Floating, rect),
258 entry(c, OverlayBand::Tooltip, rect),
259 entry(d, OverlayBand::Floating, rect),
260 ];
261 sort_into_paint_order(&mut entries);
262 assert_eq!(
263 entries.iter().map(|e| e.key).collect::<Vec<_>>(),
264 vec![b, d, a, c],
265 "Floating first (in registration order), then Tooltip (in registration order)"
266 );
267 }
268
269 #[test]
270 fn a_hit_rect_is_half_open_on_its_far_edges() {
271 let hit = OverlayHit::of(&entry(
272 OverlayKey::next(),
273 OverlayBand::Floating,
274 Rect::new(10.0, 20.0, 110.0, 60.0),
275 ));
276 assert!(
277 hit.contains(Point::new(10.0, 20.0)),
278 "near edges are inside"
279 );
280 assert!(hit.contains(Point::new(109.9, 59.9)));
281 assert!(!hit.contains(Point::new(110.0, 40.0)));
284 assert!(!hit.contains(Point::new(60.0, 60.0)));
285 assert!(!hit.contains(Point::new(9.9, 40.0)));
286 }
287
288 #[test]
289 fn a_hit_keeps_the_routing_fields_and_drops_the_pod() {
290 let key = OverlayKey::next();
291 let mut source = entry(key, OverlayBand::Tooltip, Rect::new(1.0, 2.0, 3.0, 4.0));
292 source.input = OverlayInput::Transparent;
293 source.outside_tap = OutsideTap::Notify { consume: true };
294 let hit = OverlayHit::of(&source);
295 assert_eq!(hit.key, key);
296 assert_eq!(hit.band, OverlayBand::Tooltip);
297 assert_eq!(hit.input, OverlayInput::Transparent);
298 assert_eq!(hit.outside_tap, OutsideTap::Notify { consume: true });
299 assert_eq!(hit.window_rect, source.window_rect);
300 assert_eq!(Rc::strong_count(&source.pod), 1);
303 }
304
305 #[test]
306 fn a_hit_is_modal_when_interactive_and_modal_or_consuming_outside_taps() {
307 let mut source = entry(OverlayKey::next(), OverlayBand::Floating, Rect::ZERO);
308 assert!(!OverlayHit::of(&source).is_modal());
309 source.keys = OverlayKeys::Modal;
310 assert!(OverlayHit::of(&source).is_modal());
311 source.keys = OverlayKeys::Pass;
312 source.outside_tap = OutsideTap::Notify { consume: true };
313 assert!(OverlayHit::of(&source).is_modal());
314 source.input = OverlayInput::Transparent;
315 assert!(
316 !OverlayHit::of(&source).is_modal(),
317 "a transparent pod never takes keys"
318 );
319 }
320
321 #[test]
322 fn the_registry_is_pass_scoped_and_preserves_registration_order() {
323 let (first, second) = (OverlayKey::next(), OverlayKey::next());
324 let rect = Rect::new(0.0, 0.0, 10.0, 10.0);
325 let pass = OverlayPaintPass::enter();
326 assert!(pass.take().is_empty(), "a fresh pass starts empty");
327
328 register(entry(first, OverlayBand::Floating, rect));
329 register(entry(second, OverlayBand::Floating, rect));
330 let drained = pass.take();
331 assert_eq!(
332 drained.iter().map(|e| e.key).collect::<Vec<_>>(),
333 vec![first, second],
334 "registration order is preserved"
335 );
336 assert!(pass.take().is_empty(), "and the drain empties the registry");
337 drop(pass);
338
339 register(entry(first, OverlayBand::Floating, rect));
344 let next = OverlayPaintPass::enter();
345 assert!(
346 next.take().is_empty(),
347 "a stray registration does not survive into the next pass"
348 );
349 }
350
351 #[test]
352 fn a_nested_pass_hands_the_enclosing_passs_registrations_back() {
353 let (outer_key, inner_key) = (OverlayKey::next(), OverlayKey::next());
354 let rect = Rect::new(0.0, 0.0, 10.0, 10.0);
355 let outer = OverlayPaintPass::enter();
356 register(entry(outer_key, OverlayBand::Floating, rect));
357 {
358 let inner = OverlayPaintPass::enter();
359 assert!(inner.take().is_empty(), "the inner pass starts empty");
360 register(entry(inner_key, OverlayBand::Floating, rect));
361 assert_eq!(
362 inner.take().iter().map(|e| e.key).collect::<Vec<_>>(),
363 vec![inner_key]
364 );
365 }
366 assert_eq!(
367 outer.take().iter().map(|e| e.key).collect::<Vec<_>>(),
368 vec![outer_key],
369 "the enclosing pass's registrations are handed back intact"
370 );
371 }
372
373 #[test]
374 fn debug_prints_the_routing_fields_without_the_pod() {
375 let rendered = format!(
376 "{:?}",
377 entry(
378 OverlayKey::next(),
379 OverlayBand::Floating,
380 Rect::new(0.0, 0.0, 10.0, 10.0)
381 )
382 );
383 assert!(rendered.contains("Floating"), "{rendered}");
384 assert!(rendered.contains("Interactive"), "{rendered}");
385 assert!(rendered.contains("<pod>"), "{rendered}");
386 }
387}