kui_core/runtime/focus.rs
1//! Keyboard focus and the Tab ring (`docs/adr/0002-keyboard-focus-as-data.md`),
2//! the modal scope that narrows both (`docs/adr/0003-modal-surfaces.md`),
3//! and the focus regions that split the ring into rings
4//! (`docs/adr/0022-focus-regions.md`): the one focus, who is in the ring,
5//! what moving it does to the edit store and the held keys, the focus a
6//! modal displaces and gives back, and the region a press or a call enters.
7
8use super::*;
9
10impl Core {
11 /// Moves keyboard focus to the next / previous focusable node in tree
12 /// order (from the last laid-out frame; see `access::focusable`),
13 /// wrapping around; with no current focus, enters the first (or last,
14 /// going backwards). What Tab does. The landing node scrolls into
15 /// view, and the focus shows (ring or `focus_bg`), as keyboard focus
16 /// should.
17 pub fn focus_next(&mut self, forward: bool) {
18 let ring = self.focus_ring();
19 if ring.is_empty() {
20 return;
21 }
22 let at = self
23 .focus
24 .and_then(|cur| ring.iter().position(|(_, k)| *k == cur));
25 let (i, _) = match at {
26 Some(p) if forward => ring[(p + 1) % ring.len()],
27 Some(p) => ring[(p + ring.len() - 1) % ring.len()],
28 None if forward => ring[0],
29 None => ring[ring.len() - 1],
30 };
31 self.land_focus(i);
32 }
33
34 /// Puts keyboard focus on node `i` of the last frame the way a Tab
35 /// step does: shown, and scrolled into view.
36 pub(crate) fn land_focus(&mut self, i: usize) {
37 self.move_focus(Some(self.tree.keys[i]));
38 self.focus_visible = true;
39 let rect = Rect::from_pos_size(self.tree.pos[i], self.tree.size[i]);
40 self.scroll_rect_into_view(i, rect, false);
41 }
42
43 /// Where a ring is entered with nothing remembered: its
44 /// `initial_focus`, else its first stop — the rule a modal and a
45 /// region both enter by.
46 fn ring_entry(&self, ring: &[(usize, Key)]) -> Option<Key> {
47 ring.iter()
48 .find(|(i, _)| self.tree.specs[*i].initial_focus)
49 .or_else(|| ring.first())
50 .map(|(_, k)| *k)
51 }
52
53 /// The Tab ring: (tree index, key) of every focusable node of the
54 /// last frame in tree order, `role="none"` subtrees skipped whole —
55 /// and, when a modal is in effect, of its subtree only (see
56 /// `docs/adr/0003-modal-surfaces.md`); otherwise of the region in
57 /// effect, with every region nested in the range skipped whole
58 /// (`docs/adr/0022-focus-regions.md`, decisions 1 and 2).
59 ///
60 /// A composite contributes **one** stop instead of one per item
61 /// (`docs/adr/0007-composite-keyboard-patterns.md`): the walk enters
62 /// the container as usual — a focusable node inside it but outside
63 /// every item, a "+" at the end of a tab bar, keeps its own stop — and
64 /// then takes the whole of each item's subtree in one step, emitting
65 /// the stop only at the item [`Self::composite_entry`] picked. So the
66 /// stop sits where that item sits in tree order, and Tab out and back
67 /// lands on it again.
68 ///
69 /// A float anchored to a node by key (`FloatAnchor::Node`) is a stop
70 /// of the ring its *anchor* is in, not the one its tree position
71 /// says: skipped whole by the range walk, and walked after the range
72 /// when its anchor's region is the ring's — so a devtools tab's
73 /// content comes after the panel's own stops, and never into the
74 /// app's ring (ADR 0032, decision 2).
75 fn focus_ring(&self) -> Vec<(usize, Key)> {
76 let mut out = Vec::new();
77 let (start, end) = self.ring_range();
78 self.ring_walk(start, end, &mut out);
79 if self.tree.any_node_float && self.modal.is_none() {
80 let mut i = 0;
81 while i < self.tree.len() {
82 if self.anchored_elsewhere(i) {
83 let sub_end = self.tree.subtree_end(i);
84 if self.region_of(i) == self.region {
85 self.ring_walk(i, sub_end, &mut out);
86 }
87 i = sub_end;
88 } else {
89 i += 1;
90 }
91 }
92 }
93 out
94 }
95
96 /// Whether node `i` is a float anchored to a node by key — shown
97 /// somewhere other than where it sits in the tree.
98 fn anchored_elsewhere(&self, i: usize) -> bool {
99 self.tree.any_node_float
100 && matches!(
101 self.tree.specs[i].layout.float,
102 Some(crate::spec::FloatConfig {
103 anchor: crate::spec::FloatAnchor::Node(_),
104 ..
105 })
106 )
107 }
108
109 /// The stops of the tree range `start..end`, appended to `out`: what
110 /// `focus_ring` walks once for the ring's range and once per float
111 /// anchored into it.
112 fn ring_walk(&self, start: usize, end: usize, out: &mut Vec<(usize, Key)>) {
113 use crate::access::Role;
114 let mut i = start;
115 // The composites enclosing `i`, innermost last: how far each runs,
116 // which role its items carry, and the one item that is its stop.
117 let mut open: Vec<(usize, Role, Option<usize>)> = Vec::new();
118 let mut items = Vec::new();
119 while i < end {
120 while open.last().is_some_and(|(e, _, _)| i >= *e) {
121 open.pop();
122 }
123 let role = self.tree.specs[i].access().role;
124 if role == Some(Role::None) {
125 i = self.tree.subtree_end(i);
126 continue;
127 }
128 // A float anchored to a node is walked with its anchor's
129 // ring (`focus_ring`), not where it sits — the range's own
130 // root excepted, which is that walk.
131 if i != start && self.anchored_elsewhere(i) {
132 i = self.tree.subtree_end(i);
133 continue;
134 }
135 // A region inside the range is a ring of its own, not a part
136 // of this one — the range's own root excepted, which is the
137 // region being walked.
138 if self.tree.any_region && i != start && self.tree.specs[i].interact().focus_region {
139 i = self.tree.subtree_end(i);
140 continue;
141 }
142 if let Some(&(_, item, chosen)) = open.last()
143 && role == Some(item)
144 {
145 if chosen == Some(i) {
146 out.push((i, self.tree.keys[i]));
147 }
148 i = self.tree.subtree_end(i);
149 continue;
150 }
151 if let Some(item) = role.and_then(crate::composite::item_role) {
152 crate::composite::items(&self.tree, i, item, &mut items);
153 if crate::composite::is_composite(&self.tree, i, &items) {
154 let chosen = self.composite_entry(&items);
155 open.push((self.tree.subtree_end(i), item, chosen));
156 }
157 }
158 // The root is never a stop. It encloses everything, so a sink
159 // on it hears every key nothing below claims (ADR 0011) and a
160 // view may focus it outright — but a Tab stop is a thing the
161 // user acts on, and the whole window is not one. Without this
162 // a root sink drew the ring around the window.
163 if i != 0 && crate::access::focusable(&self.tree, i) {
164 out.push((i, self.tree.keys[i]));
165 }
166 i += 1;
167 }
168 }
169
170 /// The tree range the ring is made of: the modal's subtree when one is
171 /// in effect (a modal wins, `docs/adr/0022`, decision 6), else the
172 /// region in effect's, else the whole tree. A region the frame no
173 /// longer has falls back to the whole tree; `resolve_regions` is what
174 /// moves the region off it, and runs before any ring is asked for.
175 fn ring_range(&self) -> (usize, usize) {
176 if let Some((start, end, _)) = self.modal {
177 return (start, end);
178 }
179 if let Some(i) = self.region_index() {
180 return (i, self.tree.subtree_end(i));
181 }
182 (0, self.tree.len())
183 }
184
185 /// The region in effect's index in the last frame, if the frame
186 /// declared it as one.
187 fn region_index(&self) -> Option<usize> {
188 if !self.tree.any_region {
189 return None;
190 }
191 let key = self.region?;
192 self.tree
193 .keys
194 .iter()
195 .position(|k| *k == key)
196 .filter(|&i| self.tree.specs[i].interact().focus_region)
197 }
198
199 /// The region enclosing node `i` — the nearest ancestor-or-self
200 /// declaring `focus_region` — or `None` for the main ring. A float
201 /// anchored to a node by key belongs where it is *shown*: the walk
202 /// continues from its anchor, not its parent, so a devtools tab's
203 /// content is the dock's to Tab through and never the app's (ADR
204 /// 0032, decision 2).
205 pub(crate) fn region_of(&self, i: usize) -> Option<Key> {
206 if !self.tree.any_region {
207 return None;
208 }
209 let mut n = i as u32;
210 while n != crate::tree::NIL {
211 let j = n as usize;
212 if self.tree.specs[j].interact().focus_region {
213 return Some(self.tree.keys[j]);
214 }
215 n = self.tree.region_parent(j);
216 }
217 None
218 }
219
220 /// The focus region in effect: the node whose subtree Tab walks, or
221 /// `None` for the main ring (`docs/adr/0022-focus-regions.md`).
222 pub fn region(&self) -> Option<Key> {
223 self.region
224 }
225
226 /// Asks to enter a focus region at the end of the frame being built
227 /// (`None` is the main ring): focus lands on what that region last
228 /// held if the node is still there, else its ring's `initial_focus`,
229 /// else the ring's first stop, and shows. Deferred like
230 /// `request_focus_step`, and for one more reason: the caller that
231 /// toggles a dock on and enters it in one `update` names a node the
232 /// last frame did not build. A key the frame does not declare as a
233 /// region raises `focus-region-without-node` and moves nothing.
234 #[track_caller]
235 pub fn focus_region(&mut self, key: Option<Key>) {
236 self.pending_region = Some(match key {
237 Some(k) => RegionTarget::Key(k),
238 None => RegionTarget::Main,
239 });
240 self.owe_frame("focus_region");
241 }
242
243 /// `focus_region` by the label the region's node declares — the
244 /// spelling a caller has for a node that does not exist yet.
245 #[track_caller]
246 pub fn focus_region_by_label(&mut self, label: &str) {
247 self.pending_region = Some(RegionTarget::Label(label.to_string()));
248 self.owe_frame("focus_region_by_label");
249 }
250
251 /// Settles the region on the one enclosing `key`, for a press that
252 /// focused nothing (dead space, a sink keeping the keyboard): Tab
253 /// afterwards enters the ring under the pointer, not the one focus
254 /// left (`docs/adr/0022`, decision 3). A press on nothing at all is a
255 /// press on the main ring.
256 pub(crate) fn settle_region(&mut self, key: Option<Key>) {
257 if !self.tree.any_region {
258 return;
259 }
260 self.region = key
261 .and_then(|k| self.tree.index_of(k))
262 .and_then(|i| self.region_of(i));
263 // The press may have focused a sink outside this region (ADR 0011
264 // gives dead space to the enclosing sink); the settle holds over
265 // that focus until it moves.
266 self.region_held = self
267 .focus_index()
268 .is_some_and(|i| self.region_of(i) != self.region);
269 }
270
271 /// Remembers the focus under the region holding it — the one enclosing
272 /// the focused node when the frame has it, else the region in effect
273 /// (a node the frame no longer has is the departed region's) — so
274 /// entering that region again lands there. A blur remembers nothing:
275 /// the last node the region held is still the best place to land.
276 fn remember_region_focus(&mut self) {
277 let Some(focus) = self.focus else { return };
278 let region = match self.focus_index() {
279 Some(i) => self.region_of(i),
280 None => self.region,
281 };
282 match self.region_focus.iter_mut().find(|(r, _)| *r == region) {
283 Some(slot) => slot.1 = Some(focus),
284 None => self.region_focus.push((region, Some(focus))),
285 }
286 }
287
288 /// The focus `region` last held, if that node is in this frame and
289 /// still inside the region.
290 fn remembered_region_focus(&self, region: Option<Key>) -> Option<Key> {
291 let saved = self.region_focus.iter().find(|(r, _)| *r == region)?.1?;
292 let i = self.tree.index_of(saved)?;
293 (self.region_of(i) == region).then_some(saved)
294 }
295
296 /// Where entering `region` lands with nothing remembered: the ring's
297 /// `initial_focus`, else its first stop — the rule a modal enters by.
298 fn region_entry(&mut self, region: Option<Key>) -> Option<Key> {
299 let before = self.region;
300 self.region = region;
301 let ring = self.focus_ring();
302 self.region = before;
303 self.ring_entry(&ring)
304 }
305
306 /// The regions' half of the frame's focus bookkeeping, run after the
307 /// modal's and before a pending Tab step
308 /// (`docs/adr/0022-focus-regions.md`):
309 ///
310 /// - a `focus_region` asked for during the frame enters its target
311 /// (decision 4), remembering what the region being left holds;
312 /// - a region in effect that this frame stopped declaring hands focus
313 /// back to main's (decision 5);
314 /// - and the region follows a focus the build moved by declaration —
315 /// `set_focus` learns the region only when the key is in the tree,
316 /// which during a build it may not be yet (decision 3).
317 pub(crate) fn resolve_regions(&mut self) {
318 let Some(target) = self.pending_region.take() else {
319 if self.tree.any_region || self.region.is_some() {
320 self.follow_region();
321 }
322 return;
323 };
324 let region = match target {
325 RegionTarget::Main => Some(None),
326 RegionTarget::Key(k) => self
327 .tree
328 .keys
329 .iter()
330 .position(|x| *x == k)
331 .filter(|&i| self.tree.any_region && self.tree.specs[i].interact().focus_region)
332 .map(|_| Some(k)),
333 RegionTarget::Label(ref label) => {
334 // The first in tree order, as `key_of` resolves a label,
335 // with the same warning when the name is not unique — the
336 // one lookup (AR45), without the fall-back to last frame:
337 // this runs at the frame's end, when its labels are whole.
338 self.find_label(label, false)
339 .and_then(|k| self.tree.index_of(k))
340 .filter(|&i| self.tree.any_region && self.tree.specs[i].interact().focus_region)
341 .map(|i| Some(self.tree.keys[i]))
342 }
343 };
344 let Some(region) = region else {
345 self.diag
346 .raise(crate::diag::focus_region_without_node(&target));
347 self.follow_region();
348 return;
349 };
350 // Under a modal the call still records where the user meant to be;
351 // the ring stays the modal's until it closes, and the restore then
352 // lands focus where the modal was opened from, which decision 3
353 // follows. Nothing to enter now.
354 // A call is a move on purpose, whatever a press settled before it.
355 self.region_held = false;
356 if self.modal.is_some() {
357 self.remember_region_focus();
358 self.region = region;
359 return;
360 }
361 self.remember_region_focus();
362 let landing = self
363 .remembered_region_focus(region)
364 .or_else(|| self.region_entry(region));
365 self.region = region;
366 match landing.and_then(|k| self.tree.index_of(k)) {
367 Some(i) => self.land_focus(i),
368 None => {
369 self.move_focus(landing);
370 self.focus_visible = true;
371 }
372 }
373 }
374
375 /// The region follows focus, and a region that went away hands focus
376 /// back (decisions 3 and 5).
377 fn follow_region(&mut self) {
378 if let Some(i) = self.focus_index() {
379 // Focus moved during the build to a node that was not in the
380 // tree yet (a declaration), so `set_focus` could not place it;
381 // the region it left keeps the memory it had — this focus was
382 // never that region's to remember. A region a press settled
383 // away from a focus that has not moved since stands.
384 if !self.region_held {
385 self.region = self.region_of(i);
386 }
387 self.remember_region_focus();
388 return;
389 }
390 // Nothing focused, or a focus on a node the frame no longer has:
391 // a region that is gone falls back to main's remembered focus. Its
392 // own memory is kept — a dock toggled off and on again lands where
393 // the user was, and a node that has not come back is checked for
394 // on the way in.
395 if self.region.is_some() && self.region_index().is_none() {
396 // The focus it had on the way out, node or no node: what the
397 // region is entered on if the node comes back with it.
398 self.remember_region_focus();
399 self.region = None;
400 let back = self.remembered_region_focus(None);
401 self.move_focus(back);
402 }
403 }
404
405 /// Which item of a composite is its Tab stop, in precedence order
406 /// (`docs/adr/0007`, decision 4): the item that currently holds focus,
407 /// else one declaring `initial_focus`, else the one declaring
408 /// `selected`, else the first. **No new retained state** — the roving
409 /// tabindex a browser keeps per composite is, in every pattern kui has,
410 /// the item the app already marks `selected`, and the same fact that
411 /// tells a reader which one is current tells the keyboard where to
412 /// enter. Only focusable items are candidates, so a disabled one is
413 /// skipped here as it is everywhere else.
414 fn composite_entry(&self, items: &[usize]) -> Option<usize> {
415 let live = |&&j: &&usize| crate::access::focusable(&self.tree, j);
416 let pick = |f: &dyn Fn(usize) -> bool| items.iter().filter(live).copied().find(|&j| f(j));
417 pick(&|j| Some(self.tree.keys[j]) == self.focus)
418 .or_else(|| pick(&|j| self.tree.specs[j].initial_focus))
419 .or_else(|| pick(&|j| self.tree.specs[j].access().selected))
420 .or_else(|| items.iter().filter(live).copied().next())
421 }
422
423 /// The frame's modal scope: the tree range of the last node declaring
424 /// `modal`, and its key. The last one wins, so a confirm declared
425 /// inside (or after) a dialog is the one in effect and the dialog
426 /// under it is as inert as the app under the dialog.
427 pub(crate) fn modal_scope(&self) -> Option<(usize, usize, Key)> {
428 let i = (0..self.tree.len())
429 .rev()
430 .find(|&i| self.tree.specs[i].events().modal.is_some())?;
431 Some((i, self.tree.subtree_end(i), self.tree.keys[i]))
432 }
433
434 /// The key of the modal in effect this frame, if any.
435 pub fn modal(&self) -> Option<Key> {
436 self.modal.map(|(_, _, key)| key)
437 }
438
439 /// Whether node `i` takes input: everything does, until a modal is in
440 /// effect — then its subtree does, and so does window chrome (the
441 /// window's own controls belong to the platform, not to the dialog).
442 pub(crate) fn interactive(&self, i: usize) -> bool {
443 match self.modal {
444 Some((start, end, _)) => {
445 (start..end).contains(&i) || self.tree.specs[i].window.is_some()
446 }
447 None => true,
448 }
449 }
450
451 /// Whether node `key` is inside the frame's modal scope (nothing is
452 /// outside one when there is no modal).
453 pub(crate) fn within_modal(&self, key: Key) -> bool {
454 let Some((start, end, _)) = self.modal else {
455 return true;
456 };
457 self.tree.keys[start..end].contains(&key)
458 }
459
460 /// Focus follows the modal: a modal that appears remembers the focus
461 /// it displaces and takes focus into itself; one that stops being
462 /// declared gives that focus back. Run once per frame, after the
463 /// scope is known.
464 pub(crate) fn resolve_modal_focus(&mut self) {
465 // This frame's modals in tree order, each carrying the focus it
466 // displaced when it first appeared.
467 let mut now: Vec<(Key, Option<Key>)> = Vec::new();
468 if self.tree.any_modal {
469 for i in 0..self.tree.len() {
470 if self.tree.specs[i].events().modal.is_none() {
471 continue;
472 }
473 let key = self.tree.keys[i];
474 let saved = self
475 .modal_focus
476 .iter()
477 .find(|(k, _)| *k == key)
478 .map_or(self.focus, |(_, f)| *f);
479 now.push((key, saved));
480 }
481 }
482 // The outermost modal that went away hands its focus back, so a
483 // dialog and the confirm inside it closing together land where
484 // the dialog was opened from.
485 let closed = self
486 .modal_focus
487 .iter()
488 .find(|(k, _)| !now.iter().any(|(n, _)| n == k))
489 .map(|(_, saved)| saved.filter(|key| self.tree.keys.contains(key)));
490 // A `keyFocus` *edge* on this same frame — a node declared focused
491 // now and not last frame — is the app saying where focus lands on
492 // the way out, and it wins over the restore (ADR 0003, decision
493 // 4): a rename editor opened on a node created in the same frame
494 // displaced the node the user was on *before*, and the restore
495 // is the default for an app that says nothing, not a rule for one
496 // that did. A sink redeclaring itself every frame is no edge, so
497 // the restore still lands where it always has under one.
498 // The same for an imperative move since the last frame — a
499 // handler closing the dialog and naming where focus lands
500 // (AR17): three doors, one precedence.
501 let edge = self
502 .declared_focus
503 .iter()
504 .any(|k| !self.declared_focus_last.contains(k))
505 || self.focus_asked;
506 if let Some(saved) = closed
507 && !edge
508 {
509 // Exactly what it displaced, nothing included: leaving focus
510 // on the dismissed modal's own button would be a focus on a
511 // node that is not there any more.
512 self.move_focus(saved);
513 }
514 self.modal_focus = now;
515 // Containment: focus outside the scope enters it, or is dropped
516 // when it holds none. Not `focus_visible`: the app showed the
517 // modal, nobody pressed a key. The entry is the first node in the
518 // ring declaring `initial_focus` — a destructive confirm opening
519 // on its Cancel — and the ring's first when none does. Only
520 // *entry* reads it: focus already inside the scope never reaches
521 // here, so a Tab press stands and a nested confirm closing leaves
522 // focus where it handed it back.
523 if self.modal.is_some() && !self.focus.is_some_and(|k| self.within_modal(k)) {
524 let ring = self.focus_ring();
525 let entry = self.ring_entry(&ring);
526 self.move_focus(entry);
527 }
528 // Read: the stamp covers the moves since the last frame's end —
529 // a handler's between frames and the view's during the build —
530 // and a redraw of the same tree starts clean.
531 self.focus_asked = false;
532 }
533
534 /// Whether a primary press on node `key` leaves the keyboard alone:
535 /// `keepFocus` on the node or an ancestor (backlog DX10). Such a press
536 /// acts and touches nothing the keyboard has — not the focus, not the
537 /// ring Tab walks next (ADR 0022's press settling the region is a
538 /// press *taking* the keyboard), and not the selection, which is what
539 /// a toolbar's Copy or Bold acts on (the alpha.22 regression pass: it
540 /// cleared it, as ADR 0017 spares the core's menu and menu bar from
541 /// doing). One walk per press.
542 pub(crate) fn keeps_focus(&self, key: Key) -> bool {
543 let Some(i) = self.tree.index_of(key) else {
544 return false;
545 };
546 let mut n = i as u32;
547 while n != crate::tree::NIL {
548 if self.tree.specs[n as usize].events().keep_focus {
549 return true;
550 }
551 n = self.tree.parent[n as usize];
552 }
553 false
554 }
555
556 /// Where a primary press on node `key` puts the keyboard: the node
557 /// itself when it is focusable, and nothing when it is not — except
558 /// on window chrome, which leaves focus alone, and inside a key sink,
559 /// which keeps the keyboard through any press landing in its subtree.
560 ///
561 /// A sink is an app that owns its keyboard (`docs/adr/0002`, decision
562 /// 3, the same reason Tab stays with it). The panes, buttons and dead
563 /// space it draws are that app's surface, and a derived button among
564 /// them is focusable enough to be a Tab stop without being entitled to
565 /// take the keys away from the app drawing it — otherwise the first
566 /// click anywhere in a multiplexer kills every chord, permanently,
567 /// because `take_key_focus` is edge-triggered and will not ask twice.
568 /// An editor or a nested sink is its own keyboard owner and still
569 /// takes focus (the editor through the caret arm above). A press on a
570 /// `keepFocus` node never gets here: see [`Self::keeps_focus`].
571 pub(crate) fn press_focus(&self, key: Key, focusable: bool) -> Option<Key> {
572 let Some(i) = self.tree.index_of(key) else {
573 return focusable.then_some(key);
574 };
575 // Window chrome belongs to the platform, not to the app (decision
576 // 2, the same reason it is never a Tab stop): dragging a window by
577 // its titlebar, or pressing minimize, is not the app being asked
578 // to give up its keyboard.
579 if self.tree.specs[i].window.is_some() {
580 return self.focus;
581 }
582 if self.tree.specs[i].events().on_key.is_some() {
583 return Some(key);
584 }
585 match self.enclosing_sink(i) {
586 Some(j) => Some(self.tree.keys[j]),
587 None => focusable.then_some(key),
588 }
589 }
590
591 /// The nearest key sink strictly above node `i`: the walk
592 /// [`Self::press_focus`] makes for a press, made for a key as well
593 /// (`docs/adr/0011-keys-bubble-to-the-enclosing-sink.md`, decision 1).
594 ///
595 /// A disabled node is not a sink at all (`docs/adr/0002`, decision 6),
596 /// so it neither answers here nor hides a live sink further up. The
597 /// walk stops at the modal boundary rather than climbing through it:
598 /// the app around a dialog is inert (`docs/adr/0003`), and a shell that
599 /// kept hearing shortcuts while its own dialog was up would be running
600 /// commands against a surface the user cannot see the state of.
601 pub(crate) fn enclosing_sink(&self, i: usize) -> Option<usize> {
602 let mut n = self.tree.parent[i];
603 while n != crate::tree::NIL {
604 let j = n as usize;
605 if !self.interactive(j) {
606 return None;
607 }
608 if self.tree.specs[j].events().on_key.is_some() && !self.tree.specs[j].disabled {
609 return Some(j);
610 }
611 n = self.tree.parent[j];
612 }
613 None
614 }
615
616 /// The node whose context menu a secondary press on node `i` opens:
617 /// `i` itself when it declares a live `on_context_menu`, else the
618 /// nearest enclosing node that does — the same walk and the same
619 /// modal boundary as `enclosing_sink`, because an unclaimed press is
620 /// unclaimed in the sense ADR 0011 gave keys (backlog T1). A disabled
621 /// node's own menu is skipped like a disabled sink's, so the press
622 /// reaches the container's.
623 pub(crate) fn enclosing_menu(&self, i: usize) -> Option<usize> {
624 let offers = |j: usize| {
625 self.tree.specs[j].events().on_context_menu.is_some() && !self.tree.specs[j].disabled
626 };
627 if offers(i) {
628 return Some(i);
629 }
630 let mut n = self.tree.parent[i];
631 while n != crate::tree::NIL {
632 let j = n as usize;
633 if !self.interactive(j) {
634 return None;
635 }
636 if offers(j) {
637 return Some(j);
638 }
639 n = self.tree.parent[j];
640 }
641 None
642 }
643
644 /// The node a non-primary press of `button` on node `i` goes to
645 /// (backlog F105): `i` itself when its live `on_button` claims the
646 /// button, else the nearest enclosing node whose does — the walk
647 /// `enclosing_menu` takes, stopping at the modal boundary and skipping
648 /// a disabled node's own. For the secondary button a nearer live
649 /// `on_context_menu` ends the walk with nothing, the nested
650 /// declaration winning over its ancestor's either way; a node that
651 /// declares both is the button's, which is what claiming it says.
652 pub(crate) fn enclosing_button(&self, i: usize, button: MouseButton) -> Option<usize> {
653 let mut n = i as u32;
654 while n != crate::tree::NIL {
655 let j = n as usize;
656 if j != i && !self.interactive(j) {
657 return None;
658 }
659 let spec = &self.tree.specs[j];
660 if !spec.disabled {
661 let ev = spec.events();
662 if ev.on_button.is_some() && ev.buttons.contains(button) {
663 return Some(j);
664 }
665 if button == MouseButton::Secondary && ev.on_context_menu.is_some() {
666 return None;
667 }
668 }
669 n = self.tree.parent[j];
670 }
671 None
672 }
673
674 /// The zone node `i` belongs to: itself when it declares `on_drop`,
675 /// else the nearest enclosing declaration (ADR 0031, decision 2) —
676 /// the same walk as `enclosing_menu`, stopping at the modal boundary
677 /// and skipping a disabled node's own.
678 pub(crate) fn enclosing_drop(&self, i: usize) -> Option<usize> {
679 let declares = |j: usize| {
680 self.tree.specs[j].events().on_drop.is_some() && !self.tree.specs[j].disabled
681 };
682 if declares(i) {
683 return Some(i);
684 }
685 let mut n = self.tree.parent[i];
686 while n != crate::tree::NIL {
687 let j = n as usize;
688 if !self.interactive(j) {
689 return None;
690 }
691 if declares(j) {
692 return Some(j);
693 }
694 n = self.tree.parent[j];
695 }
696 None
697 }
698
699 /// The focused node's index in the last frame, if it is there.
700 pub(crate) fn focus_index(&self) -> Option<usize> {
701 self.tree.index_of(self.focus?)
702 }
703
704 /// Whether `key` holds keyboard focus — any node (see `focus`).
705 pub fn is_focused(&self, key: Key) -> bool {
706 self.focus == Some(key)
707 }
708
709 /// The node holding keyboard focus: an editor, an `on_key` sink, or
710 /// a control Tab (or assistive technology, or `set_focus`) put it on.
711 pub fn focus(&self) -> Option<Key> {
712 self.focus
713 }
714
715 /// Whether focus got where it is by keyboard or assistive technology
716 /// rather than a click — when it shows (the default ring, or the
717 /// node's `focus_bg`).
718 pub fn focus_visible(&self) -> bool {
719 self.focus_visible
720 }
721
722 /// Moves keyboard focus (None blurs) — the app's door: `Ui::focus`,
723 /// Node's `focus`, `kui_focus`, Lua's `env.set_focus`. Any node can be
724 /// focused this way; only focusable ones (see `access::focusable`)
725 /// are reached by Tab. A move made here is the app saying where focus
726 /// goes, and it stands at the frame's end against a closing modal's
727 /// restore, the way a `keyFocus` edge does (ADR 0003 decision 4,
728 /// backlog AR17): the restore is the default for an app that said
729 /// nothing, and this is an app that did. The core's own moves — a
730 /// press, a Tab, an autofocus, the restore itself — go through
731 /// [`Self::move_focus`] and say nothing.
732 pub fn set_focus(&mut self, key: Option<Key>) {
733 if self.focus != key {
734 self.focus_asked = true;
735 }
736 self.move_focus(key);
737 }
738
739 /// Emits `focus` events for the `on_focus` nodes the focus left and
740 /// entered since the last report, `by` what moved it: the input being
741 /// handled, or `"program"` at a frame's end (backlog DX18). A node
742 /// hears focus anywhere in its subtree; leaving is reported innermost
743 /// first, entering outermost first, as the focus crosses them.
744 pub(crate) fn report_focus(&mut self, by: &'static str, out: &mut Vec<UiEvent>) {
745 // The `on_focus` nodes on the focused node's path, outermost first,
746 // by index: the tags are cloned only when the set changed, so a
747 // frame where the focus stayed put costs the walk and nothing else.
748 let mut path = Vec::new();
749 if let Some(mut i) = self.focus.and_then(|k| self.tree.index_of(k)) {
750 loop {
751 if self.tree.specs[i].events().on_focus.is_some() {
752 path.push(i);
753 }
754 let p = self.tree.parent[i];
755 if p == crate::tree::NIL {
756 break;
757 }
758 i = p as usize;
759 }
760 path.reverse();
761 }
762 if path.len() == self.focus_reported.len()
763 && path
764 .iter()
765 .zip(&self.focus_reported)
766 .all(|(&i, r)| self.tree.keys[i] == r.0)
767 {
768 return;
769 }
770 let now: Vec<_> = path
771 .iter()
772 .map(|&i| {
773 let tag = self.tree.specs[i]
774 .events()
775 .on_focus
776 .clone()
777 .unwrap_or_default();
778 (self.tree.keys[i], self.tree.origins[i], tag)
779 })
780 .collect();
781 let event = |(key, origin, tag): &(Key, crate::tree::OriginId, Value), phase: &str| {
782 let payload = Value::map([
783 ("kind", Value::str("focus")),
784 ("phase", Value::str(phase)),
785 ("by", Value::str(by)),
786 ]);
787 UiEvent::on(*origin, *key, payload).tagged(Some(tag))
788 };
789 for r in self.focus_reported.iter().rev() {
790 if !now.iter().any(|n| n.0 == r.0) {
791 out.push(event(r, "out"));
792 }
793 }
794 for n in &now {
795 if !self.focus_reported.iter().any(|r| r.0 == n.0) {
796 out.push(event(n, "in"));
797 }
798 }
799 self.focus_reported = now;
800 }
801
802 /// The one writer of `focus`: the edit store mirrors it for editor
803 /// keys, a landing editor scrolls its caret into view, the region
804 /// follows.
805 pub(crate) fn move_focus(&mut self, key: Option<Key>) {
806 let edit_key = key.filter(|k| self.edit.contains(*k));
807 if self.focus != key {
808 // The keys the leaving sink is holding come up first, while it
809 // is still the focus that routing resolves against.
810 self.release_held_keys();
811 if let Some(k) = edit_key {
812 self.edit.caret_moved = Some(k);
813 }
814 }
815 // The region follows focus (`docs/adr/0022`, decision 3) — when
816 // the key is in the tree; a build declaring focus on a node it has
817 // not pushed yet is caught up by `resolve_regions`. The region
818 // being left remembers what it held first, so entering it again
819 // lands there.
820 if key != self.focus {
821 self.region_held = false;
822 }
823 if self.tree.any_region
824 && let Some(k) = key
825 && let Some(i) = self.tree.index_of(k)
826 {
827 let region = self.region_of(i);
828 if region != self.region {
829 self.remember_region_focus();
830 self.region = region;
831 }
832 }
833 self.focus = key;
834 self.edit.set_focus(edit_key);
835 }
836
837 /// Asks for a Tab step (`forward`) / Shift-Tab step at the end of the
838 /// frame being built. `focus_next` moves focus now, against the last
839 /// finished tree — which is what a driver handling a key press between
840 /// frames wants, and exactly what a *view* cannot use, since its own
841 /// tree does not exist yet. A view asks with this instead and the step
842 /// lands on the frame it is declaring.
843 #[track_caller]
844 pub fn request_focus_step(&mut self, forward: bool) {
845 self.pending_focus_step = Some(forward);
846 self.owe_frame("request_focus_step");
847 }
848
849 /// Declares a node focused this frame (None blurs at once). The
850 /// declaration is edge-triggered: the node takes focus on the first
851 /// frame it is declared and keeps being declared without effect
852 /// afterwards, so a view that repeats it every frame (an app that
853 /// owns its keyboard, a `keyFocus` prop) does not clobber the focus a
854 /// Tab press or a click moved. Programmatic focus keeps the modality
855 /// of the last input (it shows after keyboard use, not after a
856 /// click). To move focus at any time, `set_focus`.
857 pub fn set_key_focus(&mut self, key: Option<Key>) {
858 let Some(k) = key else {
859 self.move_focus(None);
860 return;
861 };
862 if !self.declared_focus.contains(&k) {
863 self.declared_focus.push(k);
864 }
865 if !self.declared_focus_last.contains(&k) {
866 self.move_focus(Some(k));
867 }
868 }
869
870 /// The node holding keyboard focus (the same as `focus`; kept from
871 /// when only key sinks and editors could).
872 pub fn key_focus(&self) -> Option<Key> {
873 self.focus
874 }
875
876 /// The keys that declared key focus while the current frame was built
877 /// (`set_key_focus`). The scene corpus's coverage derivation reads it:
878 /// `keyFocus` leaves no mark on the tree, and the focus it takes is
879 /// indistinguishable from the focus a click takes.
880 #[cfg(feature = "conformance")]
881 pub(crate) fn declared_focus(&self) -> &[Key] {
882 &self.declared_focus
883 }
884}