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
//! Overlay input routing: topmost-z-first event dispatch, the
//! outside-press contract, and pointer-press cancellation across
//! layer trees. `#[path]` sibling of overlays.rs (file-size split) —
//! same `impl Overlays`, different file.
//!
//! OWNER: OVERLAY.
use crate::base::{Point, Rect};
use crate::ui::UiTree;
use super::{OverlayContent, Overlays};
impl Overlays {
/// Route an input event through overlay trees, topmost-z first.
/// Returns Some(consumed) when an overlay owned the event (a MODAL
/// overlay owns EVERYTHING while visible); None = fall through to
/// the root tree.
pub(crate) fn dispatch(&self, event: &crate::ui::UiEvent) -> Option<bool> {
use crate::ui::UiEvent;
// Snapshot (tree handle, modal, bounds, z) without holding the
// borrow while user handlers run.
let mut targets: Vec<(UiTree, bool, Rect, i32, u64)> = {
let store = self.store.borrow();
store
.meta
.iter()
.zip(&store.layers)
.filter(|(_, l)| l.visible())
.filter_map(|(m, l)| match &m.content {
OverlayContent::Tree { tree, modal, .. } => {
Some((tree.handle(), *modal, l.bounds(), l.z(), m.id))
}
_ => None,
})
.collect()
};
targets.sort_by_key(|(_, _, _, z, _)| std::cmp::Reverse(*z));
let mut fell_through_press = false;
for (tree, modal, bounds, _, id) in targets.iter() {
let mut tree = tree.handle();
let modal = *modal;
let bounds = *bounds;
match event {
UiEvent::Mouse(m) => {
if modal || bounds.contains(m.pos) {
// A press OUTSIDE a modal's bounds is swallowed
// AND reported to its dismiss hook (menus close;
// the press never acts below — deliberate).
if modal
&& !bounds.contains(m.pos)
&& matches!(m.kind, crate::ui::MouseKind::Down(_))
{
self.fire_outside_press(*id);
return Some(true);
}
// Overlay trees live in layer-local coordinates.
let mut local = *m;
local.pos = Point::new(m.pos.x - bounds.x, m.pos.y - bounds.y);
let consumed = tree.dispatch(&UiEvent::Mouse(local));
if modal {
return Some(true); // modals swallow even misses
}
// The panel is OPAQUE: pointer events over it are
// its own even when no handler consumed them —
// click-through to covered content would act on
// things the user cannot see.
return Some(consumed);
}
if matches!(m.kind, crate::ui::MouseKind::Down(_)) {
fell_through_press = true;
}
}
UiEvent::Key(_) | UiEvent::Paste(_) => {
if modal {
return Some(tree.dispatch(event));
}
// NON-MODAL KEY RULE (cycle 5): the topmost overlay
// tree HOLDING FOCUS owns keys — same opacity logic
// as the pointer rule (a focused popup's Escape must
// not also scroll the app). No focused overlay =
// keys fall to the root.
if tree.focused().is_some() {
return Some(tree.dispatch(event));
}
}
_ => {}
}
}
// A press that landed on the ROOT (outside every overlay) steals
// key focus back from non-modal overlays: one focus story across
// trees — click where you want your keys to go.
if fell_through_press {
for (tree, modal, _, _, _) in targets.iter() {
if !modal {
tree.handle().set_focus(None);
}
}
}
None
}
/// Take-out/run/put-back a modal's outside-press callback — user
/// code never runs under the store borrow, and the callback may
/// remove the layer it belongs to (a menu closing itself).
fn fire_outside_press(&self, id: u64) {
let taken = {
let mut store = self.store.borrow_mut();
store
.index_of(id)
.and_then(|i| match &mut store.meta[i].content {
OverlayContent::Tree { on_outside, .. } => on_outside.take(),
_ => None,
})
};
let Some(mut f) = taken else { return };
f();
let mut store = self.store.borrow_mut();
if let Some(i) = store.index_of(id) {
if let OverlayContent::Tree { on_outside, .. } = &mut store.meta[i].content {
*on_outside = Some(f);
}
}
}
/// Cancel in-progress pointer presses in every overlay tree (0285:
/// the selection layer claimed a passed-through gesture — see
/// `UiTree::cancel_pointer_press`). Snapshot handles first — widget
/// un-press handlers must never run under the store borrow. Covers
/// hidden layers too: a stale capture is worth dropping wherever it
/// lives.
pub(crate) fn cancel_pointer_press(&self) {
let trees: Vec<UiTree> = {
let store = self.store.borrow();
store
.meta
.iter()
.filter_map(|m| match &m.content {
OverlayContent::Tree { tree, .. } => Some(tree.handle()),
_ => None,
})
.collect()
};
for mut tree in trees {
tree.cancel_pointer_press();
}
}
}