kui_core/runtime/dispatch.rs
1//! Input dispatch: one event in, the UI events it resolved to out.
2//!
3//! `handle_input` routes pointer, wheel, key, text and access events
4//! against the last finished frame — its hit regions, edit buffers and
5//! focus — and stamps each result with this window. Focus motion itself
6//! lives in `focus`, the arrow-key patterns in `composites`.
7
8use super::*;
9use crate::input::Target;
10
11impl Core {
12 /// Feeds one input event; returns any UI events it resolved to,
13 /// hit-tested against the previous frame's layout.
14 pub fn handle_input(&mut self, ev: InputEvent) -> Vec<UiEvent> {
15 // The next frame's reason (backlog F111).
16 self.trace_input(&ev);
17 // What a focus move this input makes is reported as (DX18).
18 let by = match &ev {
19 InputEvent::CursorMoved(_)
20 | InputEvent::CursorLeft
21 | InputEvent::MouseDown { .. }
22 | InputEvent::MouseUp { .. }
23 | InputEvent::Scroll(_)
24 | InputEvent::ScrollGesture { .. } => "pointer",
25 InputEvent::Access(_) => "assistive",
26 InputEvent::Key(..)
27 | InputEvent::KeyDown(_)
28 | InputEvent::KeyUp(_)
29 | InputEvent::Text(_)
30 | InputEvent::Commit(_)
31 | InputEvent::Preedit(..) => "keyboard",
32 _ => "program",
33 };
34 // A move the app made since the last report — `set_focus`,
35 // `set_key_focus`, a handler's verb (ADR 0036) — with no frame
36 // finished since to report it: the program's, and before this
37 // input's own, which would otherwise carry this input's `by`
38 // (DX18, the alpha.22 regression pass).
39 let mut moved = Vec::new();
40 self.report_focus("program", &mut moved);
41 // The devtools' chords are acted on before anything is routed
42 // (ADR 0024, decision 4): the press goes no further, and what
43 // was pending still goes out.
44 let mut out = if self.devtools_intercept(&ev) {
45 std::mem::take(&mut self.pending)
46 } else {
47 self.route_input(ev)
48 };
49 if !moved.is_empty() {
50 moved.append(&mut out);
51 out = moved;
52 }
53 // Whatever the event itself made pending — the synthetic key
54 // releases a focus move forces — belongs to this batch, not to the
55 // next frame's drain.
56 out.append(&mut self.pending);
57 self.report_focus(by, &mut out);
58 // A click on a select field opens its menu and is nobody's
59 // (`widgets::select`); before the menu's own consumer, since the
60 // menu it opens is that one's from here on.
61 self.consume_select_events(&mut out);
62 // A row of the core's own context menu is not the app's click, and
63 // neither is that menu's dismissal: taken back here, acted on, and
64 // reported as one `menu` event on the node the menu was about (ADR
65 // 0017, decision 5). Here rather than inside `route_input` because
66 // several of its arms return early — the modal press among them,
67 // which is exactly the one that dismisses a menu.
68 self.consume_menu_events(&mut out);
69 // And the drawn menu bar's own nodes, on the same terms: its
70 // titles and rows post ordinary clicks, and none of them is the
71 // app's (`docs/adr/0018-a-menu-bar-the-app-declares.md`).
72 self.consume_menu_bar_events(&mut out);
73 self.outbound(&mut out);
74 out
75 }
76
77 /// The way out for every batch of events the app is about to hear,
78 /// whichever door made them — an input, or a host's own menu
79 /// answering (`activate_menu_item`), which is not an input and used
80 /// to skip this: a devtools select chosen from a native menu posted
81 /// its action to the app instead of the panel. The devtools panel's
82 /// own controls are taken back (nobody's but the core's); what is
83 /// left is translated, stamped with its window and logged on its way
84 /// out.
85 pub(crate) fn outbound(&mut self, out: &mut Vec<UiEvent>) {
86 self.devtools_consume(out);
87 self.devtools_translate(out);
88 self.stamp(out);
89 self.devtools_log(out);
90 // An event the app was handed is "the user did something": what
91 // separates a message repeated on purpose from a view announcing
92 // every frame (`announce`).
93 if !out.is_empty() {
94 self.events_answered += 1;
95 }
96 }
97
98 /// A click or drag says where it landed, in the terms of the node it
99 /// landed on — the one pass both shapes go through, run on
100 /// the `n` events at the end of `out` that `Interaction::handle` just
101 /// made from a press, and on nothing else: a click Enter, Space or a
102 /// screen reader made has no point and no count, and the cursor is
103 /// wherever the mouse happens to rest. Only a map payload can carry
104 /// the fields.
105 ///
106 /// On a `cells` grid, `cell: {row, col}` — the same arithmetic a
107 /// selection uses (`cell_row_col`), so the app never divides by a
108 /// cell size it did not choose. Inside a key sink that
109 /// draws `role="line"` rows, `line` — the ordinal among the sink's
110 /// lines, the numbering its `access` events use — `byte` — where the
111 /// point falls in that line's text, what `text_hit` would answer —
112 /// and `clicks` — the press's count, so a double click is a word
113 /// without a timer the app keeps; a point above the
114 /// first line is the first, below the last the last, and one in a
115 /// gutter is the line beside it — and `inside`, whether the point is
116 /// within that line's own box at all, so a press in the margin beside
117 /// a row is told from one on its text without the app comparing `x`
118 /// against a rect (backlog F153). The point is the event's own for a
119 /// drag and the cursor's for a click. Not opt-in, like `cell`: the
120 /// fields appear wherever the shape they describe is drawn, and a
121 /// handler that does not read them is not slower for their being
122 /// there.
123 fn attach_pointer(&mut self, out: &mut [UiEvent], n: usize) {
124 if n == 0 || self.tree.is_empty() || !(self.tree.any_text || self.tree.any_line) {
125 return;
126 }
127 let clicks = self.interaction.press_clicks();
128 let from = out.len() - n;
129 for ev in &mut out[from..] {
130 let Some(point) = self.pointer_point(ev) else {
131 continue;
132 };
133 let Some(i) = self.tree.index_of(ev.key) else {
134 continue;
135 };
136 if let Some((row, col)) = self.cell_row_col(ev.key, point)
137 && let Value::Map(entries) = &mut ev.payload
138 {
139 entries.push((
140 "cell".to_string(),
141 Value::map([
142 ("row", Value::Int(row as i64)),
143 ("col", Value::Int(col as i64)),
144 ]),
145 ));
146 }
147 if !self.tree.any_line {
148 continue;
149 }
150 // The sink: this node, or the nearest above it.
151 let mut sink = i;
152 while self.tree.specs[sink].events().on_key.is_none() {
153 let p = self.tree.parent[sink];
154 if p == crate::tree::NIL {
155 break;
156 }
157 sink = p as usize;
158 }
159 if self.tree.specs[sink].events().on_key.is_none() {
160 continue;
161 }
162 let lines = crate::access::lines_under(&self.tree, sink);
163 // Each line is read on its own upright layout: the point is
164 // pulled back through the turns that line is drawn under (ADR
165 // 0043) — the sink's, or a column's between it and its lines;
166 // `x` / `y` stay as drawn.
167 let at = |l: usize| self.unturned_at(l, point);
168 // Nearest vertically — inside one is a gap of zero — ties to
169 // the earlier line.
170 let gap = |l: usize| {
171 let (top, h) = (self.tree.pos[l].y, self.tree.size[l].h);
172 let y = at(l).y;
173 (top - y).max(y - (top + h)).max(0.0)
174 };
175 let Some((line, l)) = lines
176 .iter()
177 .enumerate()
178 .map(|(n, &l)| (n, l))
179 .min_by(|a, b| {
180 gap(a.1)
181 .partial_cmp(&gap(b.1))
182 .unwrap_or(std::cmp::Ordering::Equal)
183 })
184 else {
185 continue;
186 };
187 let byte = self
188 .text
189 .hit_at(self.tree.keys[l], at(l), self.building)
190 .map_or(0, |h| h.byte);
191 // Within the line's box, both ways: on its band, between its
192 // left and right edges.
193 let inside = {
194 let (p, x, w) = (at(l), self.tree.pos[l].x, self.tree.size[l].w);
195 gap(l) == 0.0 && p.x >= x && p.x <= x + w
196 };
197 // A `button` event's press carries its own count and its move
198 // and release none (backlog F105): `clicks` here is the last
199 // primary press's. The core's is told by its `phase` and
200 // `button` fields, not its kind alone, which an app's click
201 // payload may spell too (backlog RG75).
202 let own_count = ev.kind() == Some("button")
203 && ev.payload.get("phase").is_some()
204 && ev.payload.get("button").is_some();
205 if let Value::Map(entries) = &mut ev.payload {
206 entries.push(("line".to_string(), Value::Int(line as i64)));
207 entries.push(("byte".to_string(), Value::Int(byte as i64)));
208 entries.push(("inside".to_string(), Value::Bool(inside)));
209 if !own_count {
210 entries.push(("clicks".to_string(), Value::Int(clicks as i64)));
211 }
212 }
213 }
214 }
215
216 /// Where a pointer-made event happened: the `x` / `y` its payload
217 /// carries (a drag's), else the cursor (a click's), else nowhere —
218 /// and nowhere for a payload that is not a map, which can carry no
219 /// field anyway.
220 fn pointer_point(&self, ev: &UiEvent) -> Option<Vec2> {
221 let Value::Map(entries) = &ev.payload else {
222 return None;
223 };
224 let field = |name: &str| {
225 entries
226 .iter()
227 .find(|(k, _)| k == name)
228 .and_then(|(_, v)| match v {
229 Value::Float(f) => Some(*f as f32),
230 Value::Int(n) => Some(*n as f32),
231 _ => None,
232 })
233 };
234 match (field("x"), field("y")) {
235 (Some(x), Some(y)) => Some(Vec2::new(x, y)),
236 _ => self.interaction.cursor(),
237 }
238 }
239
240 /// One whole key going down: both channels, in the order a window
241 /// drives them. The press reaches whatever holds key
242 /// focus, and then [`KeyPress::edit_event`] asks the core for what
243 /// that key *means* — Escape dismisses a modal, Tab walks the ring,
244 /// an arrow nudges a focused slider, a printable character reaches
245 /// the focused editor.
246 ///
247 /// This is what a driver with a real keyboard does, so it is what a
248 /// headless test should do too. [`Core::handle_input`] with a bare
249 /// `KeyDown` is still the way to drive one channel on purpose.
250 pub fn press(&mut self, key: KeyPress) -> Vec<UiEvent> {
251 // Read before the move, and before the press: the key's meaning is
252 // a property of the key, not of what the first channel did with it.
253 let edit = key.edit_event();
254 let mut out = self.handle_input(InputEvent::KeyDown(key));
255 if let Some(ev) = edit {
256 out.extend(self.handle_input(ev));
257 }
258 out
259 }
260
261 /// The same key coming up. One channel, because only one has a second
262 /// half: the editing keys act on the way down. Paired with
263 /// [`Core::press`] so a held key is a press and a release, and a sink
264 /// that asked for `key_up` hears both.
265 pub fn release(&mut self, key: KeyPress) -> Vec<UiEvent> {
266 self.handle_input(InputEvent::KeyUp(key.released()))
267 }
268
269 /// Whether this core delivered `key`'s press and has not delivered
270 /// its release — the one core a `KeyUp` for it resolves in. What a
271 /// driver with more than one core asks before routing a release: a
272 /// key pressed in a window and let go while a popup borrowed its
273 /// keyboard was released in the popup, which never saw the press, and
274 /// the owner held it until it lost focus.
275 pub fn holds_key(&self, key: &KeyPress) -> bool {
276 self.keys_held.iter().any(|h| h.same_key(key))
277 }
278
279 /// Says which window every event on its way out came from.
280 ///
281 /// Most producers cannot: hit-testing and the edit buffer are below
282 /// the level at which a window exists. A `Core` is one window, though,
283 /// and the driver already told it which one (`env.window.id`, beside
284 /// `maximized` and the rest of the window facts) — so one assignment
285 /// at each of the two exits covers every event every binding will
286 /// ever see, and a multi-window driver has only to hand each core its id.
287 ///
288 /// The one producer that does know is the audio store, whose mounts
289 /// are per window and whose `ended` / `refused` events are folded
290 /// back through whichever core the driver holds — the main one, in the
291 /// runner. An event it stamped with another window keeps that stamp; a
292 /// `MAIN` one is indistinguishable from an unstamped one and takes the
293 /// draining core's, which is right wherever the draining core is the
294 /// main window, as every driver's is.
295 pub(crate) fn stamp(&self, out: &mut [UiEvent]) {
296 let id = self.env.window.id;
297 // The slot a node was filled into, from the last frame's fill
298 // ranges (`Tree::fills`): a frame with no extension recorded none,
299 // and pays one emptiness check per batch.
300 if !self.tree.fills.is_empty() {
301 for ev in out.iter_mut() {
302 if ev.slot.is_none()
303 && let Some(i) = self.tree.index_of(ev.key)
304 {
305 ev.slot = self.tree.slot_of(i);
306 }
307 }
308 }
309 if id == WindowId::MAIN {
310 // What producers already wrote. Skipped rather than written so
311 // the single-window case stays free.
312 return;
313 }
314 for ev in out {
315 if ev.window == WindowId::MAIN {
316 ev.window = id;
317 }
318 }
319 }
320
321 pub(super) fn route_input(&mut self, ev: InputEvent) -> Vec<UiEvent> {
322 let mut out = std::mem::take(&mut self.pending);
323 // Chrome commands say which window they are about, and a hit
324 // region does not know: the interaction store reads it from here.
325 self.interaction.window = self.env.window.id;
326 // The press this event is the second channel of, if it is one:
327 // a `KeyDown` leaves its modifiers for the `Key` or `Text` that
328 // follows it, and anything else is not that (AR10).
329 let pressed = self.pressed_mods.take();
330 // Whether a modifier other than Shift was down on that press — the
331 // question `route_key` asked, asked again here so the two channels
332 // agree about a chord (`docs/adr/0011`, decision 3). Without a
333 // press, the editing `Mods` are what there is.
334 let chord = |mods: Option<crate::input::Mods>| match pressed {
335 Some(m) => m.ctrl || m.alt || m.super_key,
336 None => mods.is_some_and(|m| m.word || m.doc),
337 };
338 // A paste's answer is a commit with the pasteboard's markers beside
339 // it (backlog F84), and a bare commit is one whose pasteboard marked
340 // nothing — the answer an older driver sends — so the two are one
341 // arm below.
342 let (ev, marks, paste) = match ev {
343 InputEvent::Paste { text, marks } => (InputEvent::Commit(text), marks, true),
344 ev => (ev, crate::input::ClipboardMarks::default(), false),
345 };
346 match ev {
347 // Wheel up (positive y) reveals earlier content: offset
348 // decreases. An `on_scroll` node takes it instead — the whole
349 // lines it covers on a grid, the fraction carried to the next
350 // notch on the same node (ADR 0029, decision 4). A scroller
351 // takes the axes it scrolls on and passes the rest to the
352 // region under it (backlog DX13), and a gesture keeps the
353 // targets it started with (backlog F107, `mod gesture`): a
354 // bare `Scroll` is a gesture of its own.
355 InputEvent::Scroll(delta) => self.route_scroll(delta, true, &mut out),
356 InputEvent::ScrollGesture { delta, begins } => {
357 self.route_scroll(delta, begins, &mut out)
358 }
359 InputEvent::Text(s) => {
360 if let Some(key) = self.edit.focused() {
361 if self.edit_with_fonts(|edit, fs| edit.apply_text(key, &s, fs)) {
362 self.push_edit_event(key, "changed", &mut out);
363 }
364 } else if let Some(i) = self.focused_control() {
365 // Inside a composite, printable characters search the
366 // items by name; Space extends a search already under
367 // way rather than pressing (`docs/adr/0007`, decision
368 // 9). Otherwise Space presses the focused control (a
369 // sink would have taken the press as data; an editor
370 // took the text).
371 //
372 // Unless the control claims neither and a sink above it
373 // does: the raw press already bubbled there, and the
374 // two channels have to agree about who owns the key
375 // (`docs/adr/0011`, decision 3).
376 let code = match s.chars().next() {
377 Some(c) => KeyCode::Char(c),
378 None => KeyCode::Unknown,
379 };
380 if !self.bubbles(i, code, chord(None))
381 && !self.type_ahead(i, &s, &mut out)
382 && s == " "
383 {
384 // The press shows the focus, as Enter's does
385 // (`docs/adr/0002`, decision 4a).
386 self.focus_visible = true;
387 self.click_node(self.tree.keys[i], &mut out);
388 }
389 }
390 }
391 InputEvent::Files(paths) => {
392 if let Some(ev) = self.file_ask.answer(&paths) {
393 out.push(ev);
394 }
395 }
396 // Documents the OS handed the app (backlog F124): nobody's ask,
397 // so the host's, on the root, as `system` and `fonts` are.
398 InputEvent::Open(paths) => {
399 if !paths.is_empty() {
400 out.push(UiEvent {
401 origin: OriginId::HOST,
402 window: WindowId::MAIN,
403 key: Key::ROOT,
404 payload: Value::map([
405 ("kind", Value::str("open")),
406 (
407 "paths",
408 Value::List(paths.into_iter().map(Value::Str).collect()),
409 ),
410 ]),
411 slot: None,
412 });
413 }
414 }
415 InputEvent::Commit(s) => {
416 // The paste's answer, when one was asked — a driver answers
417 // every ask, with an empty commit for an empty clipboard,
418 // which is what lets the next ask through (AR34). The same
419 // rule says whether this *is* the answer: a `Paste`, or any
420 // commit while an ask is out (backlog DX14).
421 let pasted = std::mem::replace(&mut self.awaiting_paste, false) || paste;
422 if let Some(key) = self.edit.focused() {
423 if self.edit_with_fonts(|edit, fs| edit.apply_text(key, &s, fs)) {
424 self.push_edit_event(key, "changed", &mut out);
425 }
426 } else {
427 // A custom editor: the composition's result as data,
428 // on the sink the focused node reports to (backlog
429 // C17). Never reaches the `Text` arm above, so a
430 // sink hears a commit once and a keystroke once. A
431 // marker rides along only when it is set, so a sink
432 // that never heard of them sees the payload it always
433 // did.
434 let mut fields = vec![("kind", Value::str("text")), ("text", Value::Str(s))];
435 if pasted {
436 fields.push(("pasted", Value::Bool(true)));
437 }
438 if marks.concealed {
439 fields.push(("concealed", Value::Bool(true)));
440 }
441 if marks.transient {
442 fields.push(("transient", Value::Bool(true)));
443 }
444 self.sink_event(Value::map(fields), &mut out);
445 }
446 }
447 InputEvent::Preedit(s, cursor) => {
448 if let Some(key) = self.edit.focused() {
449 self.edit_with_fonts(|edit, fs| edit.set_preedit(key, &s, cursor, fs));
450 } else {
451 let cursor = match cursor {
452 Some((a, b)) => {
453 Value::List(vec![Value::Int(a as i64), Value::Int(b as i64)])
454 }
455 None => Value::Null,
456 };
457 self.sink_event(
458 Value::map([
459 ("kind", Value::str("preedit")),
460 ("text", Value::Str(s)),
461 ("cursor", cursor),
462 ]),
463 &mut out,
464 );
465 }
466 }
467 InputEvent::Key(ek, mods) => {
468 // Inside one of the core's menus a submenu comes first: the
469 // arrows open and close one, and Escape closes the innermost
470 // before it would close the menu (backlog F128).
471 if self.submenu_key(ek) {
472 return out;
473 }
474 // A modal owns Escape: it asks to go away, and nothing
475 // else happens (see `docs/adr/0003-modal-surfaces.md`).
476 // The core closes nothing — the app stops declaring it.
477 if ek == EditKey::Escape
478 && let Some(key) = self.modal()
479 {
480 self.dismiss(key, "escape", &mut out);
481 return out;
482 }
483 // Tab walks the focus ring (Shift-Tab backwards) unless a
484 // multiline editor holds focus — there Tab stays
485 // indentation — or a key sink does: a sink is an app that
486 // owns its keyboard, Tab included (it hands focus on with
487 // `focus_next`). With nothing focused Tab enters the ring.
488 // A field declared `keep_tab` is the other exception: its
489 // Tab went to the sink above it as a press (backlog F146).
490 let sink = self.focused_sink();
491 let traverse = ek == EditKey::Tab
492 && match self.edit.focused() {
493 Some(k) => !self.edit.is_multiline(k) && !self.edit.keeps_tab(k),
494 None => !sink,
495 };
496 if traverse {
497 self.focus_next(!mods.shift);
498 } else if let Some(key) = self.edit.focused() {
499 let done = self.edit_with_fonts(|edit, fs| edit.apply_key(key, ek, mods, fs));
500 self.editor_took_selection(key);
501 if done.changed {
502 self.push_edit_event(key, "changed", &mut out);
503 }
504 if done.submit {
505 self.push_edit_event(key, "submit", &mut out);
506 }
507 // The key met the edge of the text and did nothing:
508 // the app's to act on — join a block, move to the
509 // next field (backlog F145).
510 if let Some(edge) = done.boundary {
511 out.push(UiEvent::on(
512 self.edit.origin_of(key).unwrap_or(OriginId::HOST),
513 key,
514 Value::map([
515 ("kind", Value::str("boundary")),
516 ("key", Value::Str(boundary_code(ek).name())),
517 ("edge", Value::str(edge.name())),
518 ("word", Value::Bool(mods.word)),
519 ("doc", Value::Bool(mods.doc)),
520 ]),
521 ));
522 }
523 if ek == EditKey::Escape {
524 self.push_edit_event(key, "cancel", &mut out);
525 self.move_focus(None);
526 }
527 } else if let Some(i) = self.focused_control()
528 // A key this control does not claim has already gone to
529 // the sink above it as a raw press, so it must not act
530 // here as well (`docs/adr/0011`, decision 3). With no
531 // sink above, nothing bubbled and every arm below runs
532 // as it always did — including Escape, which is how a
533 // control with no shortcut layer over it is let go of.
534 && !edit_key_code(ek).is_some_and(|c| self.bubbles(i, c, chord(Some(mods))))
535 {
536 // A control that is neither an editor nor a sink:
537 // Enter presses it, the arrows nudge a slider (the
538 // same events assistive technology produces), Escape
539 // lets go.
540 use crate::slider::SliderMove;
541 let slider =
542 self.tree.specs[i].access().role == Some(crate::access::Role::Slider);
543 let changes = slider && self.tree.specs[i].events().on_change.is_some();
544 // Each key the core acts with shows the focus first
545 // (`docs/adr/0002`, decision 4a): pointer focus is
546 // unshown, but the moment the keyboard uses it the
547 // user is owed the answer to "which node did that?" —
548 // a button pressed with Space after a click otherwise
549 // emits its event with nothing on screen naming it.
550 // Escape acts by letting go, and a ring around nothing
551 // is not a ring; a key the control does not claim went
552 // to the sink above and never arrives here at all.
553 // Shift with a horizontal motion on a node inside a
554 // `selectable` scope moves the scope's selection
555 // (backlog AR28) — the keyboard's half of what a
556 // drag does, and the one way a keyboard user selects
557 // a label. Under a sink the press already bubbled and
558 // never arrives here, like every other motion.
559 let scope = self.scopes.get(i).copied().flatten();
560 match ek {
561 EditKey::Left | EditKey::Right | EditKey::Home | EditKey::End
562 if mods.shift && scope.is_some() =>
563 {
564 self.focus_visible = true;
565 self.keyboard_select(scope.unwrap_or(Key::ROOT), ek, mods);
566 }
567 EditKey::Enter => {
568 self.focus_visible = true;
569 self.click_node(self.tree.keys[i], &mut out);
570 }
571 EditKey::Escape => self.move_focus(None),
572 EditKey::Right | EditKey::Up if slider => {
573 self.focus_visible = true;
574 self.nudge(i, SliderMove::Step(1), &mut out);
575 }
576 EditKey::Left | EditKey::Down if slider => {
577 self.focus_visible = true;
578 self.nudge(i, SliderMove::Step(-1), &mut out);
579 }
580 // A slider that asked for its changes takes the
581 // rest of the keys a range has (ADR 0034,
582 // decision 4); one that did not leaves them be.
583 EditKey::PageUp | EditKey::PageDown | EditKey::Home | EditKey::End
584 if changes =>
585 {
586 self.focus_visible = true;
587 let mv = match ek {
588 EditKey::PageUp => SliderMove::Page(1),
589 EditKey::PageDown => SliderMove::Page(-1),
590 EditKey::Home => SliderMove::Home,
591 _ => SliderMove::End,
592 };
593 self.nudge(i, mv, &mut out);
594 }
595 // Inside a composite the arrows, Home and End move
596 // focus among the items instead (see
597 // `docs/adr/0007-composite-keyboard-patterns.md`),
598 // showing the focus where they land; on anything
599 // else they do nothing, as before.
600 EditKey::Left
601 | EditKey::Right
602 | EditKey::Up
603 | EditKey::Down
604 | EditKey::Home
605 | EditKey::End => self.composite_step(i, ek, &mut out),
606 _ => {}
607 }
608 }
609 }
610 InputEvent::KeyDown(kp) => {
611 self.pressed_mods = Some(kp.mods);
612 if self.route_key(&kp, KeyPhase::Down, &mut out) {
613 // Held from here until its release, focus moving, or
614 // the window losing the keyboard. A repeat of a key
615 // already down is the same key, not a second one —
616 // matched by position, since Shift moving mid-hold
617 // changes the repeat's `code` (`KeyPress::same_key`).
618 if !self.keys_held.iter().any(|h| h.same_key(&kp)) {
619 self.keys_held.push(kp.released());
620 }
621 }
622 }
623 InputEvent::KeyUp(kp) => {
624 // Only a key whose press was delivered has a release to
625 // deliver: one pressed while an editor held focus, or
626 // already let go of synthetically, resolves nothing.
627 if let Some(i) = self.keys_held.iter().position(|h| h.same_key(&kp)) {
628 self.keys_held.remove(i);
629 self.route_key(&kp.released(), KeyPhase::Up, &mut out);
630 }
631 }
632 InputEvent::MouseDown { button, clicks } => {
633 // Only the primary button moves anything: a secondary
634 // press asks for a context menu where it landed and leaves
635 // focus, the caret and the scrollbars exactly as they were
636 // (a right-click on a selection has to keep it). Any
637 // non-primary press an `on_button` node claims is that
638 // node's instead, and captured by it (backlog F105).
639 let primary = button == MouseButton::Primary;
640 let mut owner = None;
641 // A scrollbar wins what it was painted over — its own
642 // scroller's content, not a float over it (ADR 0023): a
643 // thumb press starts a drag, a track press jumps there
644 // first. Neither blurs the focused edit.
645 if primary
646 && let Some(p) = self.interaction.cursor()
647 && let Some(Target::Bar(bar)) = self.interaction.target_at(p)
648 {
649 let (pos, thumb_start) = match bar.axis {
650 ScrollAxis::X => (p.x, bar.thumb.x),
651 ScrollAxis::Y => (p.y, bar.thumb.y),
652 };
653 let grab = if pos >= thumb_start && pos <= thumb_start + bar.bar_len {
654 pos - thumb_start
655 } else {
656 let center = bar.bar_len / 2.0;
657 let off = bar.offset_for(p, center);
658 self.set_scroll_axis(bar.key, bar.axis, off);
659 center
660 };
661 self.interaction.scrollbar_drag = Some((bar.key, bar.axis, grab));
662 return out;
663 }
664 // Click-to-focus / caret placement / start drag-selection,
665 // against the previous frame's layout.
666 if let Some(p) = self.interaction.cursor() {
667 let hit = self.interaction.hit_at(p).map(|h| {
668 // A region that does something with a press
669 // claims it: a button inside a selectable
670 // card is a button first (ADR 0017).
671 let claimed = h.payload.is_some()
672 || h.drag.is_some()
673 || h.key_sink.is_some()
674 || h.window.is_some();
675 let scope = h.select_scope.filter(|_| !claimed);
676 (h.key, h.edit_origin, h.focusable, scope, h.origin)
677 });
678 // While a modal is up, a press outside it never
679 // touches focus: one that finds no region asks the
680 // modal to go away (a modal is hit-tracked, so its own
681 // background is not "outside"), and one that finds the
682 // only live thing out there — window chrome — is the
683 // platform's business, not the app's.
684 if let Some(key) = self.modal()
685 && !hit.as_ref().is_some_and(|(k, ..)| self.within_modal(*k))
686 {
687 if hit.is_none() {
688 self.dismiss(key, "outside", &mut out);
689 }
690 let n = self
691 .interaction
692 .handle(InputEvent::MouseDown { button, clicks }, &mut out);
693 self.attach_pointer(&mut out, n);
694 return out;
695 }
696 // A press moves focus (to a focusable node, or to the
697 // key sink the press landed inside) or drops it; either
698 // way it is pointer focus, not shown.
699 if primary {
700 // A press anywhere ends the last selection; the
701 // arms below start whichever new one it begins.
702 // One selection per window (ADR 0017) — except a
703 // press inside the core's own context menu, which
704 // is *about* that selection: a Copy row that
705 // cleared what it was going to copy would be a
706 // menu that never works.
707 // The menu bar's Edit menu is about the selection
708 // for the same reason, so a press in it is spared
709 // the same way.
710 let origin = hit.as_ref().map(|(.., o)| *o);
711 let in_bar = origin == Some(OriginId::MENU_BAR);
712 // A Shift-press inside the scope the selection is
713 // in keeps its anchor and moves the live end: it
714 // extends, so it clears nothing (ADR 0029,
715 // decision 3). Anywhere else Shift is a press.
716 let shift = self.interaction.modifiers().shift;
717 let extends = shift
718 && hit.as_ref().is_some_and(|(_, _, _, scope, _)| {
719 scope.is_some()
720 && (self.selection.map(|s| s.scope) == *scope
721 || self.cell_selection.map(|c| c.node) == *scope)
722 });
723 // A press on a `keepFocus` node — a toolbar's Copy
724 // or Bold, acting on what the sink or editor has —
725 // is spared on the same terms as the menus (backlog
726 // DX10): it leaves the selection, the focus and the
727 // ring as they are. An editor inside one is its own
728 // keyboard owner and takes its caret as ever.
729 let keep = match hit {
730 Some((_, Some(_), true, _, _)) | None => false,
731 Some((key, ..)) => self.keeps_focus(key),
732 };
733 if origin != Some(OriginId::MENU) && !in_bar && !extends && !keep {
734 self.clear_selection();
735 }
736 // And the field a menu-bar menu will be about: this
737 // press is about to move focus onto the title, so
738 // the answer has to be taken before it does. Only
739 // on the way *in* — a press with a menu already
740 // open is a row or a second title, and focus is
741 // inside the bar by then, so asking again would
742 // record "no field" over the real answer.
743 if in_bar && self.menu_bar_open().is_none() {
744 self.note_menu_bar_editor();
745 }
746 match hit {
747 Some((key, Some(origin), true, _, _)) => {
748 // A Shift-press in the focused editor
749 // extends from its caret: cosmic-text's
750 // `Drag` is the action that moves the
751 // cursor and keeps (or seeds) the
752 // selection, which is the whole gesture.
753 let extend = shift && self.edit.focused() == Some(key);
754 self.move_focus(Some(key));
755 // The caret lands where the pointer is on
756 // the editor's upright layout, through any
757 // turn it is drawn under (ADR 0043).
758 let q = self.unturned(key, p);
759 let local = Vec2::new(q.x - origin.x, q.y - origin.y);
760 self.edit_with_fonts(|edit, fs| {
761 edit.click(key, local, clicks, extend, fs)
762 });
763 self.edit.dragging = Some((key, origin));
764 self.arm_follow(key, p);
765 }
766 // The press acts (its click is resolved on the
767 // release, as any) and moves nothing: not the
768 // focus, not the region Tab walks next — the
769 // keyboard's ring is still the one it is in —
770 // and no drag-select of its own.
771 Some(_) if keep => {}
772 // Inside a selection scope, with nothing else
773 // claiming the press: start a drag-select.
774 // One click places both ends together, two
775 // take the word, three the whole run.
776 Some((key, _, focusable, Some(scope), _)) => {
777 let target = self.press_focus(key, focusable);
778 self.move_focus(target);
779 self.settle_region(Some(key));
780 // The press arms the drag with what the
781 // click count says it moves by: a second
782 // click held and dragged selects word by
783 // word, a third run by run — in bytes or,
784 // for a grid, in cells; the arming knows.
785 // A Shift-press keeps the anchor instead
786 // and goes on by characters.
787 if self.arm_select_drag(scope, p, clicks, extends) {
788 self.arm_follow(scope, p);
789 }
790 }
791 // Everything else: a plain node, and a
792 // disabled editor (no caret to place).
793 Some((key, _, focusable, None, _)) => {
794 let target = self.press_focus(key, focusable);
795 self.move_focus(target);
796 // Whatever the press did to focus, Tab
797 // afterwards enters the ring under the
798 // pointer (`docs/adr/0022`, decision 3).
799 self.settle_region(Some(key));
800 }
801 None => {
802 self.move_focus(None);
803 self.settle_region(None);
804 }
805 }
806 self.focus_visible = false;
807 } else if let Some((key, ..)) = hit {
808 owner = self.button_owner(key, button);
809 }
810 }
811 let claimed_button = owner.is_some();
812 let n = match owner {
813 Some(owner) => self
814 .interaction
815 .press_button(button, clicks, owner, &mut out),
816 None => self
817 .interaction
818 .handle(InputEvent::MouseDown { button, clicks }, &mut out),
819 };
820 self.attach_pointer(&mut out, n);
821 // A right-click the app did not claim with `onContextMenu`
822 // or `onButton` gets the stock menu, where there is
823 // anything standard to put in one (ADR 0017, decision 5).
824 if button == MouseButton::Secondary
825 && let Some(p) = self.interaction.cursor()
826 {
827 let claimed =
828 claimed_button || out.iter().any(|e| e.kind() == Some("contextmenu"));
829 self.auto_menu(p, claimed);
830 }
831 }
832 InputEvent::CursorMoved(p) => {
833 if let Some((key, axis, grab)) = self.interaction.scrollbar_drag
834 && let Some(bar) = self
835 .interaction
836 .scrollbars
837 .iter()
838 .rev()
839 .find(|b| b.key == key && b.axis == axis)
840 .copied()
841 {
842 // Thumb drag: geometry is last frame's, which is fine —
843 // track length only changes with the container.
844 let off = bar.offset_for(p, grab);
845 self.set_scroll_axis(key, axis, off);
846 }
847 self.rehit(p);
848 self.follow_point(p);
849 let n = self
850 .interaction
851 .handle(InputEvent::CursorMoved(p), &mut out);
852 self.attach_pointer(&mut out, n);
853 }
854 InputEvent::MouseUp { button } => {
855 if button == MouseButton::Primary {
856 self.end_follow();
857 self.edit.dragging = None;
858 self.select_dragging = None;
859 self.interaction.scrollbar_drag = None;
860 }
861 let n = self
862 .interaction
863 .handle(InputEvent::MouseUp { button }, &mut out);
864 self.attach_pointer(&mut out, n);
865 }
866 InputEvent::ForceClick(p) => self.force_click(p, &mut out),
867 InputEvent::Access(req) => self.handle_access(req, &mut out),
868 other => {
869 self.interaction.handle(other, &mut out);
870 }
871 }
872 self.flush_sound_requests();
873 // Input moves focus, carets and scroll offsets: an access tree
874 // derived earlier this frame no longer describes it.
875 self.access_built = 0;
876 out
877 }
878
879 /// The node a non-primary press on region `key` goes to, with the tag
880 /// its event carries: see `enclosing_button`. Read off
881 /// the tree at the press, as a force click's tag is.
882 fn button_owner(&self, key: Key, button: MouseButton) -> Option<crate::input::ButtonOwner> {
883 let i = self.tree.index_of(key)?;
884 let j = self.enclosing_button(i, button)?;
885 Some(crate::input::ButtonOwner {
886 key: self.tree.keys[j],
887 origin: self.tree.origins[j],
888 tag: self.tree.specs[j].events().on_button.clone()?,
889 })
890 }
891
892 /// A force click. Over text — an editor or a
893 /// `selectable` scope — it selects the word under it and asks the host
894 /// for its definition panel, which is what the gesture means on the
895 /// one platform that has it. Anywhere else it reaches a node
896 /// declaring `on_force_click`, and over anything else it does
897 /// nothing at all.
898 ///
899 /// It moves no focus and places no caret: it happens *during* a press
900 /// that is still running, and stealing the caret out from under a
901 /// drag would be a gesture fighting itself.
902 fn force_click(&mut self, p: Vec2, out: &mut Vec<UiEvent>) {
903 let Some(region) = self.interaction.hit_at(p) else {
904 return;
905 };
906 let (key, origin) = (region.key, region.origin);
907 // Read off the tree rather than carried on the region: a force
908 // click is one event in a session, and a tag on `HitRegion` is a
909 // clone on every region of every frame (C15's rule — a node pays
910 // for props it does not declare).
911 let tag = self
912 .tree
913 .keys
914 .iter()
915 .position(|k| *k == key)
916 .and_then(|i| self.tree.specs[i].events().on_force_click.clone());
917 let editor = region.edit_origin.map(|origin| (key, origin));
918 let scope = region.select_scope;
919 // Text first: the word under the pointer, selected, and looked up.
920 // The press that deepened into this force click is still running,
921 // and its drag would overwrite the word the moment the finger
922 // moved a pixel — which is what "the panel says one word and the
923 // highlight is one character" looks like. The gesture takes the
924 // press over: no caret drag, no selection drag.
925 self.select_dragging = None;
926 self.edit.dragging = None;
927 self.drag_follow = None;
928 if let Some((key, content_origin)) = editor {
929 let q = self.unturned(key, p);
930 let local = Vec2::new(q.x - content_origin.x, q.y - content_origin.y);
931 self.move_focus(Some(key));
932 self.edit_with_fonts(|edit, fs| edit.click(key, local, 2, false, fs));
933 self.menu_editor = Some(key);
934 if let Some(action) = self.lookup_action() {
935 self.menu_actions.push(action);
936 }
937 return;
938 }
939 if let Some(scope) = scope {
940 // A force click is a double click that also asks for a
941 // definition, so it takes the same word the second click
942 // would have.
943 if !self.select_word_under(scope, p) {
944 return;
945 }
946 // A force click between words is a force click on nothing:
947 // looking up a space would put a dictionary panel over the
948 // page for no reason, which is not what the gesture does
949 // anywhere else on the platform.
950 if self.copy_selection().is_none_or(|t| t.trim().is_empty()) {
951 self.clear_selection();
952 return;
953 }
954 if let Some(action) = self.lookup_action() {
955 self.menu_actions.push(action);
956 }
957 return;
958 }
959 // Not text: the node's own event, if it asked for one.
960 let Some(tag) = tag else { return };
961 let payload = Value::map([
962 ("kind", Value::str("forceclick")),
963 ("x", Value::Float(p.x as f64)),
964 ("y", Value::Float(p.y as f64)),
965 ]);
966 out.push(UiEvent::on(origin, key, payload).tagged(Some(&tag)));
967 }
968
969 /// Resolves a request from assistive technology against the last
970 /// frame the way the pointer or keyboard equivalent would be (see
971 /// [`crate::access::AccessAction`]).
972 fn handle_access(&mut self, req: crate::access::AccessRequest, out: &mut Vec<UiEvent>) {
973 use crate::access::AccessAction;
974 let key = req.key;
975 let idx = self.tree.index_of(key);
976 // The gates every other channel obeys (AR18): a node outside the
977 // modal is inert (ADR 0003 decision 5) and a disabled one takes no
978 // action. Only `Click` resolved against the hit list before; a
979 // reader edited, nudged and scrolled the page behind a dialog,
980 // and `Focus` on an editor there routed typing to it until the
981 // next frame's containment.
982 if let Some(i) = idx
983 && !self.interactive(i)
984 {
985 // The access tree is not pruned (ADR 0003 decision 7), so a
986 // reader can name a node behind the modal, and its click is
987 // the press outside: the modal is asked to go away, and the
988 // node hears nothing (decision 6). Dropped on the floor, a
989 // select's field clicked a second time left its own menu
990 // open where the pointer closes it (backlog RG13). Every
991 // other request behind a modal does nothing — nothing a
992 // pointer does to the page behind a dialog moves its text.
993 if req.action == AccessAction::Click
994 && let Some(modal) = self.modal()
995 {
996 self.dismiss(modal, "outside", out);
997 }
998 return;
999 }
1000 // Disabled: what the access tree refuses to advertise, so a
1001 // request naming one is a reader working from a stale tree, or a
1002 // headless test.
1003 if let Some(i) = idx
1004 && self.tree.specs[i].disabled
1005 {
1006 return;
1007 }
1008 match req.action {
1009 AccessAction::Click => self.click_node(key, out),
1010 AccessAction::Focus => {
1011 // The reader's cursor lands where Tab would — on a node it
1012 // can see (decoration is not in its tree); show it.
1013 let exposed = self.access_tree().get(key).is_some();
1014 if exposed && idx.is_some_and(|i| crate::access::focusable(&self.tree, i)) {
1015 self.move_focus(Some(key));
1016 self.focus_visible = true;
1017 }
1018 }
1019 AccessAction::Blur => {
1020 if self.focus == Some(key) {
1021 self.move_focus(None);
1022 }
1023 }
1024 AccessAction::SetValue => {
1025 if self.edit.contains(key) {
1026 let value = req.value.unwrap_or_default();
1027 if self.edit.text(key).as_deref() != Some(value.as_str()) {
1028 self.set_edit_text(key, &value);
1029 self.push_edit_event(key, "changed", out);
1030 }
1031 } else if let Some(i) = idx
1032 && crate::access::is_custom_editor(&self.tree, i)
1033 {
1034 // The app owns the text: hand the request over as data.
1035 let payload = Value::map([
1036 ("kind", Value::str("access")),
1037 ("action", Value::str(req.action.name())),
1038 ("text", Value::str(req.value.unwrap_or_default())),
1039 ]);
1040 out.push(
1041 UiEvent::on(self.tree.origins[i], key, payload)
1042 .tagged(self.access_tag(i).as_ref()),
1043 );
1044 } else if let Some(i) = idx {
1045 self.set_slider(i, req.value.as_deref().unwrap_or_default(), out);
1046 }
1047 }
1048 AccessAction::Increment | AccessAction::Decrement => {
1049 let Some(i) = idx else { return };
1050 let n = if req.action == AccessAction::Increment {
1051 1
1052 } else {
1053 -1
1054 };
1055 self.nudge(i, crate::slider::SliderMove::Step(n), out);
1056 }
1057 AccessAction::SetTextSelection | AccessAction::ReplaceSelectedText => {
1058 let Some(i) = idx else { return };
1059 if self.edit.contains(key) {
1060 match req.action {
1061 AccessAction::SetTextSelection => {
1062 let (Some(anchor), Some(focus)) = (req.anchor, req.focus) else {
1063 return;
1064 };
1065 // Run positions resolve against the tree of
1066 // the last frame, which is what the request
1067 // was made from.
1068 let tree = self.access_tree();
1069 let Some(node) = tree.get(key) else { return };
1070 let (Some(a), Some(f)) =
1071 (node.line_offset(anchor), node.line_offset(focus))
1072 else {
1073 return;
1074 };
1075 self.edit.set_selection(key, a, f);
1076 self.editor_took_selection(key);
1077 }
1078 _ => {
1079 let text = req.value.unwrap_or_default();
1080 if self
1081 .edit_with_fonts(|edit, fs| edit.replace_selection(key, &text, fs))
1082 {
1083 self.push_edit_event(key, "changed", out);
1084 }
1085 }
1086 }
1087 } else if crate::access::is_custom_editor(&self.tree, i) {
1088 // The app owns the text: hand the request over as data.
1089 let mut entries = vec![
1090 ("kind".to_string(), Value::str("access")),
1091 ("action".to_string(), Value::str(req.action.name())),
1092 ];
1093 if let Some(text) = req.value {
1094 entries.push(("text".to_string(), Value::str(text)));
1095 }
1096 if let (Some(anchor), Some(focus)) = (req.anchor, req.focus) {
1097 let tree = self.access_tree();
1098 let Some(node) = tree.get(key) else { return };
1099 let (Some(a), Some(f)) =
1100 (node.line_offset(anchor), node.line_offset(focus))
1101 else {
1102 return;
1103 };
1104 let pos = |(line, offset): (usize, usize)| {
1105 Value::map([
1106 ("line", Value::Int(line as i64)),
1107 ("offset", Value::Int(offset as i64)),
1108 ])
1109 };
1110 entries.push(("anchor".to_string(), pos(a)));
1111 entries.push(("focus".to_string(), pos(f)));
1112 }
1113 let ev = self.tree.specs[i].events();
1114 let tag = ev
1115 .on_click
1116 .clone()
1117 .or_else(|| ev.on_drag.clone())
1118 .or_else(|| ev.on_key.clone());
1119 out.push(
1120 UiEvent::on(self.tree.origins[i], key, Value::Map(entries))
1121 .tagged(tag.as_ref()),
1122 );
1123 }
1124 }
1125 AccessAction::ScrollIntoView => {
1126 let Some(i) = idx else { return };
1127 let rect = Rect::from_pos_size(self.tree.pos[i], self.tree.size[i]);
1128 self.scroll_rect_into_view(i, rect, false);
1129 }
1130 AccessAction::ScrollUp
1131 | AccessAction::ScrollDown
1132 | AccessAction::ScrollLeft
1133 | AccessAction::ScrollRight => {
1134 let Some(i) = idx else { return };
1135 let size = self.tree.size[i];
1136 let delta = match req.action {
1137 AccessAction::ScrollUp => Vec2::new(0.0, -size.h * 0.8),
1138 AccessAction::ScrollDown => Vec2::new(0.0, size.h * 0.8),
1139 AccessAction::ScrollLeft => Vec2::new(-size.w * 0.8, 0.0),
1140 _ => Vec2::new(size.w * 0.8, 0.0),
1141 };
1142 self.scroll.scroll_by(key, delta);
1143 }
1144 }
1145 }
1146
1147 /// The `tag` an `access` event on node `i` carries: its click payload,
1148 /// else its drag or key tag; None when there is none (or it is null).
1149 pub(crate) fn access_tag(&self, i: usize) -> Option<Value> {
1150 let ev = self.tree.specs[i].events();
1151 ev.on_click
1152 .clone()
1153 .or_else(|| ev.on_drag.clone())
1154 .or_else(|| ev.on_key.clone())
1155 .filter(|t| *t != Value::Null)
1156 }
1157
1158 /// The key of the first node in tree order, in the last finished
1159 /// frame, whose accessible name is `name` — what a screen reader reads
1160 /// it as: its `label` row, else its own text, else a control's derived
1161 /// name (a button's text inside it). Not the key label `key_of` reads,
1162 /// which is the name the view opened the node under and a reader never
1163 /// hears. The window itself is not matched. More than one node with
1164 /// the name raises `ambiguous-name`; None when none has it. From
1165 /// inside a view, the tree is the last one derived ([`Self::access_tree`]).
1166 pub fn key_named(&mut self, name: &str) -> Option<Key> {
1167 use crate::access::Role;
1168 let (first, count, caption) = {
1169 let mut hits = self
1170 .access_tree()
1171 .nodes
1172 .iter()
1173 .skip(1)
1174 .filter(|n| n.name.as_deref() == Some(name));
1175 let head = hits.next()?;
1176 // A caption beside the control it repeats is text and a
1177 // control under one name (backlog F140).
1178 let (mut count, mut text, mut other) = (0, false, false);
1179 for n in std::iter::once(head).chain(hits) {
1180 count += 1;
1181 if n.role == Role::StaticText {
1182 text = true;
1183 } else {
1184 other = true;
1185 }
1186 }
1187 (head.key, count, text && other)
1188 };
1189 if count > 1 {
1190 self.diag.raise(if caption {
1191 crate::diag::ambiguous_caption(name, first, count)
1192 } else {
1193 crate::diag::ambiguous_name(name, first, count)
1194 });
1195 }
1196 Some(first)
1197 }
1198
1199 /// The access tree of the last finished frame (see [`crate::access`]):
1200 /// derived on the first call after a frame, then reused. A driver that
1201 /// never asks pays nothing. Asked while a frame is being built, it is
1202 /// the last tree derived, unchanged: the frame in progress is half a
1203 /// tree, and caching it would serve that half for the whole frame.
1204 pub fn access_tree(&mut self) -> &crate::access::AccessTree {
1205 if self.access_built != self.frame_no && !self.building {
1206 let src = crate::access::Sources {
1207 text: &self.text,
1208 cells: &self.cells,
1209 edit: &self.edit,
1210 scroll: &self.scroll,
1211 title: self.window_title.as_deref(),
1212 focus: self.focus,
1213 modal: self.modal(),
1214 viewport: self.viewport,
1215 scale: self.scale,
1216 clips: &self.clips,
1217 };
1218 // Deriving the tree is about 480 µs on a 10,000-node frame and
1219 // is paid on every frame a screen reader is attached; hashing
1220 // what it reads is about 105 µs, because three quarters of the
1221 // work is making the nodes rather than walking to them. So a
1222 // frame that changed nothing this tree can see — a pointer
1223 // moving across hover backgrounds, a colour transition — keeps
1224 // the one it had. See `access::inputs_hash` for the invariant
1225 // that makes it safe, and ADR 0016 decision 3 for why this is
1226 // the one thing in the frame that gets cached.
1227 let hash = crate::access::inputs_hash(&self.tree, &src);
1228 if hash.is_none() || self.access_inputs != hash {
1229 self.access = crate::access::build(&self.tree, &src);
1230 self.access_rebuilds += 1;
1231 }
1232 self.access_inputs = hash;
1233 self.access_built = self.frame_no;
1234 }
1235 &self.access
1236 }
1237
1238 /// Delivers one key event to the sink it resolves to, tagged with that
1239 /// sink's `on_key` payload; returns whether anything took it. *Which*
1240 /// sink is [`Self::key_target`]'s answer: the focused one, or — when a
1241 /// control holds focus and does not claim this key — the nearest one
1242 /// above it.
1243 ///
1244 /// A focused editor keeps every press it acts on — the editing keys,
1245 /// what it types, the clipboard chords — and a press it does not
1246 /// (⌘N, Ctrl+K, a field's Tab under `keep_tab`) goes to the nearest
1247 /// sink above it, as a chord bubbles from a control (`docs/adr/0011`,
1248 /// amended for backlog F144). A release goes where its press went:
1249 /// it only arrives here for a press a sink took.
1250 fn route_key(&mut self, kp: &KeyPress, phase: KeyPhase, out: &mut Vec<UiEvent>) -> bool {
1251 let target = match self.edit.focused() {
1252 Some(editor) => {
1253 if phase == KeyPhase::Down && self.editor_claims(editor, kp) {
1254 return false;
1255 }
1256 let Some(j) = self
1257 .tree
1258 .index_of(editor)
1259 .and_then(|i| self.enclosing_sink(i))
1260 else {
1261 return false;
1262 };
1263 self.tree.keys[j]
1264 }
1265 None => {
1266 let Some(target) =
1267 self.key_target(kp.code, kp.mods.ctrl || kp.mods.alt || kp.mods.super_key)
1268 else {
1269 return false;
1270 };
1271 target
1272 }
1273 };
1274 // A sink hears releases only by asking (`key_up`): press-only is
1275 // the keymap case, and a keymap handed both halves runs every
1276 // binding twice. The key is still tracked as held either way, so
1277 // a sink that opts in mid-hold hears the release it is owed.
1278 if phase == KeyPhase::Up
1279 && !self
1280 .sink_node(target)
1281 .is_some_and(|i| self.tree.specs[i].events().key_up)
1282 {
1283 return false;
1284 }
1285 // A modifier or lock key reaches only a sink that asked for them
1286 // (`modifier_keys`, backlog F108): to any other it is held, not
1287 // pressed, and never held as a key either — so its release has
1288 // nothing to find.
1289 if kp.code.is_modifier()
1290 && !self
1291 .sink_node(target)
1292 .is_some_and(|i| self.tree.specs[i].events().modifier_keys)
1293 {
1294 return false;
1295 }
1296 self.deliver_to_sink(target, kp.to_value(phase), out)
1297 }
1298
1299 /// The node the sink `key` names, in the last frame's tree: one that
1300 /// declares `on_key`, is not disabled, and is not shut out by a
1301 /// modal.
1302 ///
1303 /// Asked of the tree and not of the hit list, because a key is not
1304 /// pointer input. The hit list is where a *point* finds a node, and
1305 /// a node outside its scroller's clip is not under any point, so it
1306 /// has no region there (`emit_node` is never reached for it) — while
1307 /// the keyboard reaches a node by having focus, which a node keeps
1308 /// wherever it is drawn. Before F79 the delivery read the hit list
1309 /// like a click, so a focused sink scrolled out of view, or drawn
1310 /// part-way to its place by `slide` or an `enter` offset, dropped
1311 /// every key typed at it until it came back.
1312 fn sink_node(&self, key: Key) -> Option<usize> {
1313 let i = (0..self.tree.len())
1314 .rev()
1315 .find(|&i| self.tree.keys[i] == key)?;
1316 let spec = &self.tree.specs[i];
1317 (spec.events().on_key.is_some() && !spec.disabled && self.interactive(i)).then_some(i)
1318 }
1319
1320 /// Hands `payload` to the sink `target` names with the sink's tag
1321 /// merged in — the one delivery both key channels end in. False when
1322 /// the last frame declared no such sink.
1323 fn deliver_to_sink(&self, target: Key, payload: Value, out: &mut Vec<UiEvent>) -> bool {
1324 let Some(i) = self.sink_node(target) else {
1325 return false;
1326 };
1327 out.push(
1328 UiEvent::on(self.tree.origins[i], self.tree.keys[i], payload)
1329 .tagged(self.tree.specs[i].events().on_key.as_ref()),
1330 );
1331 true
1332 }
1333
1334 /// Delivers `payload` to the sink the focused node reports to — the
1335 /// focused sink itself, or the nearest one above a focused control —
1336 /// with the sink's tag merged in, the way a `key` event is. A
1337 /// composition is never a control's to claim, so unlike `route_key`
1338 /// nothing is asked about the key. False with no sink to hear it.
1339 fn sink_event(&mut self, payload: Value, out: &mut Vec<UiEvent>) -> bool {
1340 let Some(i) = self.focus_index() else {
1341 // With nothing focused, the root sink that hears every
1342 // unclaimed key (`key_target`) hears this too — a paste a
1343 // shell asked for with nothing focused would otherwise
1344 // vanish (backlog C33). Not under a modal.
1345 if self.tree.is_empty() || self.modal.is_some() {
1346 return false;
1347 }
1348 let root = &self.tree.specs[0];
1349 if root.events().on_key.is_none() || root.disabled {
1350 return false;
1351 }
1352 return self.deliver_to_sink(self.tree.keys[0], payload, out);
1353 };
1354 let target = if self.tree.specs[i].events().on_key.is_some() {
1355 self.tree.keys[i]
1356 } else {
1357 match self.enclosing_sink(i) {
1358 Some(j) => self.tree.keys[j],
1359 None => return false,
1360 }
1361 };
1362 self.deliver_to_sink(target, payload, out)
1363 }
1364
1365 /// Lets go of every key the focused sink is holding, as if the user
1366 /// had released them: each becomes a `{kind="key", phase="up"}` on the
1367 /// sink that took the press. Called when focus moves — a keymap that
1368 /// armed a mode on the way down has to hear the way up, and the node
1369 /// it moved to never saw the press — and by drivers when the window
1370 /// loses the keyboard (Cmd-Tab while a key is down otherwise leaves it
1371 /// stuck down forever).
1372 pub fn release_held_keys(&mut self) {
1373 if self.keys_held.is_empty() {
1374 return;
1375 }
1376 let mut out = Vec::new();
1377 let mut held = std::mem::take(&mut self.keys_held);
1378 while !held.is_empty() {
1379 let mut kp = held.remove(0);
1380 // A modifier key's release reports the state after it, as a
1381 // real one does (ADR 0002, decision 15): its own bit off
1382 // unless its twin is still down to be let go of next. The
1383 // press stored it on (backlog RG85).
1384 let on = held.iter().any(|h| h.code == kp.code);
1385 match kp.code {
1386 KeyCode::Shift => kp.mods.shift = on,
1387 KeyCode::Ctrl => kp.mods.ctrl = on,
1388 KeyCode::Alt => kp.mods.alt = on,
1389 KeyCode::Super => kp.mods.super_key = on,
1390 _ => {}
1391 }
1392 self.route_key(&kp, KeyPhase::Up, &mut out);
1393 }
1394 // Pending rather than returned: the writers are `set_focus` and the
1395 // driver's window-focus report, neither of which is answering an
1396 // input event. `handle_input` appends it before returning, so a
1397 // click that moved focus and the release it forced arrive together.
1398 self.pending.append(&mut out);
1399 }
1400
1401 /// The driver's report that this window gained or lost the keyboard:
1402 /// `env.focused`, plus the one rule that rides on it — a window that
1403 /// lost the keyboard lets go of every key its sink was holding, since
1404 /// the OS stops delivering key events to it and the release would
1405 /// never arrive. The rule lives here rather than in each driver so a
1406 /// Node test's `setEnv({focused: false})` and a C host's `kui_env_set`
1407 /// do what the windowed runner does, instead of each remembering to.
1408 /// The synthetic `up`s are pending, like `release_held_keys`'s, and so
1409 /// are the `release`s of the buttons `onButton` nodes held.
1410 pub fn set_focused(&mut self, focused: bool) {
1411 if self.env.focused == focused {
1412 return;
1413 }
1414 self.env.focused = focused;
1415 // The app hears it as a window event rather than diffing
1416 // `env.focused` every frame (backlog DX18).
1417 let (name, id) = (self.window_name(), self.env.window.id);
1418 self.push_window_event(if focused { "focused" } else { "blurred" }, &name, id);
1419 if !focused {
1420 self.release_held_keys();
1421 // The modifiers go with the keys: a Shift released in another
1422 // window never reaches this one, and a host that does not
1423 // resend the state on the way back (winit does; a C loop may
1424 // not) would otherwise leave every later press an extending
1425 // one. The app hears it as the `modifiers` event it is.
1426 let mut out = Vec::new();
1427 self.interaction.handle(
1428 InputEvent::Modifiers(crate::input::KeyMods::default()),
1429 &mut out,
1430 );
1431 // And the buttons an `onButton` node holds captured (backlog
1432 // F105): each owner hears its release now, with the cell it
1433 // lands in, rather than every later move as a drag.
1434 let n = self.interaction.release_buttons(&mut out);
1435 self.attach_pointer(&mut out, n);
1436 // And the primary button's hold (backlog RG75): a drag or a
1437 // slide ends where the pointer was, a caret drag, a
1438 // selection drag and a scrollbar drag stop, and the press
1439 // clicks nothing — the release, like the buttons', is not
1440 // coming here, and every move after the window came back
1441 // went on dragging with no button down.
1442 self.edit.dragging = None;
1443 self.select_dragging = None;
1444 self.interaction.scrollbar_drag = None;
1445 let n = self.interaction.release_primary(&mut out);
1446 self.attach_pointer(&mut out, n);
1447 self.pending.append(&mut out);
1448 // And a held drag's follow: the release will not come here,
1449 // and a scroller stepping toward a pointer nobody holds any
1450 // more is a window asking for frames until one does.
1451 self.drag_follow = None;
1452 }
1453 }
1454
1455 /// The sink a chord pressed now would reach, if any: the focused sink,
1456 /// the nearest one above the focused control, or the root's with
1457 /// nothing focused. What a
1458 /// driver asks before greying a menu row that spells a chord — a sink
1459 /// that would hear ⌘C may do anything with it, so the row stays lit.
1460 pub fn chord_sink(&self) -> Option<Key> {
1461 if self.edit.focused().is_some() {
1462 return None;
1463 }
1464 self.key_target(KeyCode::Char('c'), true)
1465 }
1466
1467 /// Which node hears a raw press: the focused sink, the nearest sink
1468 /// above a focused control that does not claim the key, or nothing.
1469 ///
1470 /// `chord` is whether a modifier other than Shift is down. A chord is
1471 /// never a control's key — it is what a shortcut layer is made of — so
1472 /// it bubbles whatever the focused control would have done with the
1473 /// bare key.
1474 fn key_target(&self, code: KeyCode, chord: bool) -> Option<Key> {
1475 // With nothing focused there is nothing to claim, and the sink
1476 // that hears every unclaimed key in the tree — one on the root —
1477 // hears this one too (`docs/adr/0022`, decision 8). Not under a
1478 // modal, where the root is inert like everything outside it. A
1479 // shell used to take focus on the root to get this.
1480 let Some(i) = self.focus_index() else {
1481 // Tab is still the ring's: it enters, and the sink does not
1482 // hear it — a chord on it bubbles as any chord does.
1483 if self.tree.is_empty() || self.modal.is_some() || (!chord && code == KeyCode::Tab) {
1484 return None;
1485 }
1486 let root = &self.tree.specs[0];
1487 return (root.events().on_key.is_some() && !root.disabled).then_some(self.tree.keys[0]);
1488 };
1489 // A sink that holds focus keeps everything, as it always has
1490 // (`docs/adr/0002`, decision 3).
1491 if self.tree.specs[i].events().on_key.is_some() {
1492 return Some(self.tree.keys[i]);
1493 }
1494 if !chord && self.claims(i, code) {
1495 return None;
1496 }
1497 self.enclosing_sink(i).map(|j| self.tree.keys[j])
1498 }
1499
1500 /// Whether the key `code` pressed on the focused node `i` reaches a
1501 /// sink above it instead of the node itself — the question the
1502 /// `EditKey` and `Text` channels ask, so that both agree with the raw
1503 /// press channel about who owns the key. False with no sink above, so
1504 /// a key nothing claims does exactly what it did before.
1505 fn bubbles(&self, i: usize, code: KeyCode, chord: bool) -> bool {
1506 (chord || !self.claims(i, code)) && self.enclosing_sink(i).is_some()
1507 }
1508
1509 /// Whether the focused node `i` takes `code` for itself: the keys the
1510 /// core acts on *for that node*, which are exactly the keys that never
1511 /// bubble. Static — a press is resolved
1512 /// on its way down, before the channel that would act on it arrives,
1513 /// so the question has to be answerable from the node and the key
1514 /// alone rather than from what a handler did.
1515 fn claims(&self, i: usize, code: KeyCode) -> bool {
1516 use crate::access::Role;
1517 // A space bar reported as a character is still the space bar.
1518 let code = match code {
1519 KeyCode::Char(' ') => KeyCode::Space,
1520 c => c,
1521 };
1522 // Tab belongs to the ring wherever focus is: a shell sink that
1523 // heard every Tab would be this ADR's own bug in reverse.
1524 if code == KeyCode::Tab {
1525 return true;
1526 }
1527 // Only a control the core presses itself claims anything else; a
1528 // plain box someone focused by hand claims nothing.
1529 if self.focused_control() != Some(i) {
1530 return false;
1531 }
1532 let key = self.tree.keys[i];
1533 // Enter and Space activate what there is to activate: a node with
1534 // no click payload has nothing, so its Space is free to bubble.
1535 let activates = self
1536 .interaction
1537 .hits
1538 .iter()
1539 .rev()
1540 .find(|h| h.key == key)
1541 .is_some_and(|h| h.payload.is_some() || h.window.is_some());
1542 let item = crate::composite::owner(&self.tree, i, &mut Vec::new()).is_some();
1543 let slider = self.tree.specs[i].access().role == Some(Role::Slider);
1544 let changes = slider && self.tree.specs[i].events().on_change.is_some();
1545 match code {
1546 KeyCode::Enter => activates,
1547 // Inside a composite, Space either extends a type-ahead search
1548 // or presses the item (`docs/adr/0007`, decision 9).
1549 KeyCode::Space => activates || item,
1550 KeyCode::Left | KeyCode::Right | KeyCode::Up | KeyCode::Down => slider || item,
1551 KeyCode::Home | KeyCode::End => item || changes,
1552 KeyCode::PageUp | KeyCode::PageDown => changes,
1553 // Type-ahead inside a composite; nothing anywhere else.
1554 KeyCode::Char(_) => item,
1555 _ => false,
1556 }
1557 }
1558
1559 /// Whether the focused editor `key` takes the press `kp` for itself:
1560 /// every key its editing channel acts on (`KeyPress::edit_event` — the
1561 /// arrows, Home and End, the pages, Backspace, Delete, Enter, Tab,
1562 /// Escape, and whatever types), the clipboard and undo chords a driver
1563 /// performs for it (⌘C/X/V/A, ⌘Z, ⇧⌘Z, ⌘Y — the primary modifier, as
1564 /// the runner's `edit_chord` reads it), and anything pressed without
1565 /// Control or Command, which is typing: a Mac's Option composes ø,
1566 /// and a key with no text yet may be the first half of one. Static,
1567 /// like `claims`: the press is resolved before the channel that acts
1568 /// on it arrives.
1569 fn editor_claims(&self, key: Key, kp: &KeyPress) -> bool {
1570 if kp.code == KeyCode::Tab && self.edit.keeps_tab(key) {
1571 return false;
1572 }
1573 if kp.edit_event().is_some() {
1574 return true;
1575 }
1576 let clipboard = kp.mods.primary()
1577 && matches!(kp.code, KeyCode::Char(c) if "cxvazy".contains(c.to_ascii_lowercase()));
1578 clipboard || !(kp.mods.ctrl || kp.mods.super_key)
1579 }
1580
1581 /// Whether the focused node is a key sink (it owns its keys).
1582 fn focused_sink(&self) -> bool {
1583 self.focus_index()
1584 .is_some_and(|i| self.tree.specs[i].events().on_key.is_some())
1585 }
1586
1587 /// The focused node when it is a control the core presses itself:
1588 /// not an editor, not a key sink, and still focusable.
1589 fn focused_control(&self) -> Option<usize> {
1590 let i = self.focus_index()?;
1591 let spec = &self.tree.specs[i];
1592 let editor = matches!(self.tree.content[i], NodeContent::Edit(_));
1593 (!editor && spec.events().on_key.is_none() && crate::access::focusable(&self.tree, i))
1594 .then_some(i)
1595 }
1596
1597 /// Activates node `key` the way a pointer click would — against the
1598 /// last frame's hit regions, so a disabled node emits nothing — for
1599 /// Enter, Space and an assistive-technology `click`. Focus follows
1600 /// into an editor or a sink, as a click's would.
1601 pub(crate) fn click_node(&mut self, key: Key, out: &mut Vec<UiEvent>) {
1602 let Some(h) = self.interaction.hits.iter().rev().find(|h| h.key == key) else {
1603 return;
1604 };
1605 let (origin, payload, window, sound) =
1606 (h.origin, h.payload.clone(), h.window, h.click_sound);
1607 let takes_focus = h.focusable && (h.edit_origin.is_some() || h.key_sink.is_some());
1608 // Not a press: the payload gains no `cell` and no `line` /
1609 // `byte` / `clicks` from wherever the pointer rests — only what
1610 // `Interaction::handle` made from a press does (`attach_pointer`)
1611 // — and the count the last press carried describes nothing now.
1612 self.interaction.note_synthetic_click();
1613 if let Some(sound) = sound {
1614 self.interaction.sound_requests.push(sound);
1615 }
1616 match (window, payload) {
1617 (Some(crate::window::WindowRole::Button(b)), _) => self
1618 .interaction
1619 .window_commands
1620 .push(b.command(self.env.window.id)),
1621 (None, Some(payload)) => out.push(UiEvent {
1622 origin,
1623 window: WindowId::MAIN,
1624 key,
1625 payload,
1626 slot: None,
1627 }),
1628 _ => {}
1629 }
1630 if takes_focus {
1631 self.move_focus(Some(key));
1632 }
1633 }
1634
1635 /// The modal `key` was asked to go away — Escape, or a press outside
1636 /// it. Reaches the app as `{kind="dismiss", reason, tag}` on the modal
1637 /// node; what happens next is the app's, since only it can stop
1638 /// declaring the node.
1639 fn dismiss(&mut self, key: Key, reason: &str, out: &mut Vec<UiEvent>) {
1640 let Some(i) = self.tree.index_of(key) else {
1641 return;
1642 };
1643 let payload = Value::map([
1644 ("kind", Value::str("dismiss")),
1645 ("reason", Value::str(reason)),
1646 ]);
1647 out.push(
1648 UiEvent::on(self.tree.origins[i], key, payload)
1649 .tagged(self.tree.specs[i].events().modal.as_ref()),
1650 );
1651 }
1652
1653 pub(crate) fn push_edit_event(&self, key: Key, kind: &str, out: &mut Vec<UiEvent>) {
1654 out.push(UiEvent {
1655 origin: self.edit.origin_of(key).unwrap_or(OriginId::HOST),
1656 window: WindowId::MAIN,
1657 key,
1658 payload: Value::map([("kind", kind.into())]),
1659 slot: None,
1660 });
1661 }
1662
1663 /// The window's selected text, for clipboard integration: the
1664 /// selection in a `selectable` scope when there is one, else the
1665 /// focused editor's. Only one of the two exists at a time — starting
1666 /// either clears the other
1667 /// — so this asks in that order rather than merging them.
1668 pub fn copy_selection(&self) -> Option<String> {
1669 self.note_read(|| replay::Read::Opaque);
1670 if self.selection.is_some() {
1671 return self.selection_text();
1672 }
1673 if self.cell_selection.is_some() {
1674 return self.cell_selection_text();
1675 }
1676 self.edit.copy_selection(self.edit.focused()?)
1677 }
1678
1679 /// One selection per window, in the direction
1680 /// `set_selection` does not cover: an editor's selection started by
1681 /// the keyboard — Select All, Shift+arrows, a reader's
1682 /// `setTextSelection` — takes the window's one selection with it, so
1683 /// a scope's or a grid's highlight goes and Cmd-A / Cmd-C read the
1684 /// editor and not the label dragged over before Tab.
1685 fn editor_took_selection(&mut self, key: Key) {
1686 if self.edit.has_selection(key) {
1687 self.selection = None;
1688 self.cell_selection = None;
1689 }
1690 }
1691
1692 /// Cuts the focused editor's selection, returning the removed text.
1693 /// A cut is an edit like any other, so the editor's `changed` is
1694 /// pending for the caller to route — like a `resize`, since the
1695 /// caller is not answering an input event.
1696 pub fn cut_selection(&mut self) -> Option<String> {
1697 let key = self.edit.focused()?;
1698 let text = self.cut_editor(key)?;
1699 let mut out = Vec::new();
1700 self.push_edit_event(key, "changed", &mut out);
1701 self.pending.append(&mut out);
1702 Some(text)
1703 }
1704
1705 /// Deletes editor `key`'s selection, returning what was there — the
1706 /// one mutation both cuts share; the `changed` is the caller's to
1707 /// post where its batch goes.
1708 pub(crate) fn cut_editor(&mut self, key: Key) -> Option<String> {
1709 let text = self.edit.copy_selection(key)?;
1710 self.edit_with_fonts(|edit, fs| edit.delete_selection(key, fs));
1711 Some(text)
1712 }
1713
1714 /// Where editor `key`'s caret is, in logical viewport px — zero wide,
1715 /// one line tall, where the last frame drew the editor — or `None`
1716 /// for a key with no editor in that frame. With
1717 /// [`Self::edit_caret_to`] it carries a column between an editor and
1718 /// whatever the app moves the keyboard to: a block editor's ↓ from a
1719 /// title into its first line lands under the title's caret (backlog
1720 /// F154). The rect [`Self::caret_rect`] answers for a text node, for
1721 /// an editor.
1722 pub fn edit_caret_rect(&mut self, key: Key) -> Option<Rect> {
1723 self.note_read(|| replay::Read::Opaque);
1724 let origin = self.interaction.edit_origin_of(key)?;
1725 let local = self.edit_with_fonts(|edit, fs| edit.caret_local(key, fs))?;
1726 Some(self.turned_rect(
1727 key,
1728 Rect::new(origin.x + local.x, origin.y + local.y, 0.0, local.h),
1729 ))
1730 }
1731
1732 /// Puts editor `key`'s caret at the character nearest `point` (logical
1733 /// viewport px), with no selection, as a click there would — without
1734 /// the click: focus, the window's selection and the pointer are left
1735 /// alone, so an app moving the keyboard into the editor places its
1736 /// caret first and focuses it after (backlog F154). False for a key
1737 /// with no editor in the last frame.
1738 pub fn edit_caret_to(&mut self, key: Key, point: Vec2) -> bool {
1739 let Some(origin) = self.interaction.edit_origin_of(key) else {
1740 return false;
1741 };
1742 let q = self.unturned(key, point);
1743 let local = Vec2::new(q.x - origin.x, q.y - origin.y);
1744 self.edit_with_fonts(|edit, fs| edit.click(key, local, 1, false, fs));
1745 true
1746 }
1747
1748 /// Current text of an editor by key.
1749 pub fn edit_text(&self, key: Key) -> Option<String> {
1750 let t = self.edit.text(key);
1751 self.note_read(|| replay::Read::EditText(key, t.clone()));
1752 t
1753 }
1754
1755 /// Replaces an editor's text, leaving the caret at the end.
1756 ///
1757 /// The key need not have an editor behind it yet: an `update` that
1758 /// opens a rename field runs a frame ahead of the view that declares
1759 /// it, so the text is held and seeds the editor the next frame
1760 /// declares under this key, over its `initial`. Held for that one
1761 /// frame — a key nothing declares on it drops its text and raises
1762 /// [`crate::diag::EDIT_TEXT_WITHOUT_EDITOR`].
1763 ///
1764 /// Returns whether the text reached an editor now. `false` is the
1765 /// held case: nothing on screen changed, and a driver that redraws on
1766 /// it re-lowers the tree that declares no editor, which is the frame
1767 /// the hold expires on — so a binding asks for a redraw
1768 /// only on `true`.
1769 pub fn set_edit_text(&mut self, key: Key, text: &str) -> bool {
1770 let sess = &mut *self.session.state();
1771 self.edit
1772 .set_text(key, text, &mut sess.fonts, &sess.resources)
1773 }
1774
1775 /// The same call by the name the view declares — an editor's `key`
1776 /// prop / `label` — for the app that has no key to give: the hex key
1777 /// comes from an event the node fired, and an editor a rename opens
1778 /// for the first time has fired none.
1779 ///
1780 /// A label some frame declared resolves now ([`Core::key_of`]) and
1781 /// this is [`Core::set_edit_text`] on that key. One nothing has
1782 /// declared — a first open, or a second one, since an editor closed
1783 /// in between was in no recent frame — is held for the next frame
1784 /// that declares an editor under it, and seeds it there. An editor
1785 /// retained while its key was off screen takes the text over its
1786 /// draft, which is what a `set_edit_text` by key cannot say.
1787 ///
1788 /// Held for that one frame: a label nothing declares on it drops its
1789 /// text and raises [`crate::diag::EDIT_TEXT_WITHOUT_EDITOR`].
1790 ///
1791 /// Returns whether the text reached an editor now, as
1792 /// [`Core::set_edit_text`] does; a label held is `false`.
1793 pub fn set_edit_text_by_label(&mut self, label: &str, text: &str) -> bool {
1794 match self.key_of(label) {
1795 Some(key) => self.set_edit_text(key, text),
1796 None => {
1797 self.edit.hold_label(label, text);
1798 false
1799 }
1800 }
1801 }
1802
1803 /// The pointer shape for wherever the pointer is now, derived from the
1804 /// frame's hit regions (see [`crate::cursor`]). Per-frame output like
1805 /// the window commands, but a query rather than a drain: it is a state,
1806 /// not a queue, so a driver reads it after each input and each frame and
1807 /// only touches the window when the answer changes. Headless drivers
1808 /// never read it, and the core stays device-free.
1809 pub fn cursor_shape(&self) -> crate::cursor::CursorShape {
1810 self.interaction.cursor_shape()
1811 }
1812}
1813
1814/// The raw key an editing key came from, for the keys a focused control
1815/// acts on — the one place the two input channels have to name the same
1816/// press. `None` for the editing vocabulary
1817/// with no control behaviour behind it (Backspace, PageUp, Undo): those
1818/// arms do nothing on a control either way.
1819/// The key a `boundary` event names: one of the six that can meet an
1820/// edge, spelled as a key event spells it.
1821fn boundary_code(ek: EditKey) -> KeyCode {
1822 match ek {
1823 EditKey::Up => KeyCode::Up,
1824 EditKey::Down => KeyCode::Down,
1825 EditKey::Left => KeyCode::Left,
1826 EditKey::Right => KeyCode::Right,
1827 EditKey::Backspace => KeyCode::Backspace,
1828 _ => KeyCode::Delete,
1829 }
1830}
1831
1832fn edit_key_code(ek: EditKey) -> Option<KeyCode> {
1833 Some(match ek {
1834 EditKey::Enter => KeyCode::Enter,
1835 EditKey::Escape => KeyCode::Escape,
1836 EditKey::Tab => KeyCode::Tab,
1837 EditKey::Left => KeyCode::Left,
1838 EditKey::Right => KeyCode::Right,
1839 EditKey::Up => KeyCode::Up,
1840 EditKey::Down => KeyCode::Down,
1841 EditKey::Home => KeyCode::Home,
1842 EditKey::End => KeyCode::End,
1843 _ => return None,
1844 })
1845}
1846
1847/// The access tree is cached on a hash of what
1848/// derives it, so every input that hash misses is a frame that serves a
1849/// stale reading. One case per input `access::build` reads, each mutating
1850/// only that input and asserting the tree was derived again *and* came out
1851/// different; plus the case the cache exists for, where a frame changes
1852/// something the tree cannot see and keeps the one it had.
1853#[cfg(test)]
1854mod access_cache {
1855 use crate::access::{Live, Role};
1856 use crate::*;
1857
1858 /// Builds a frame from `spec` on a keyed node with a text child, reads
1859 /// the access tree, and reports (how many derivations have happened,
1860 /// the tree's own hash).
1861 fn frame(core: &mut Core, spec: NodeSpec) -> (u64, u64) {
1862 let mut ui = core.frame(Size::new(200.0, 100.0), 1.0);
1863 ui.configure_root(NodeSpec::column());
1864 ui.text_in_keyed("node", spec, "hello", TextStyle::new(12.0));
1865 ui.finish();
1866 let hash = core.access_tree().hash;
1867 (core.access_rebuilds, hash)
1868 }
1869
1870 /// The base node: a button, so it is a semantic node with a name.
1871 fn base() -> NodeSpec {
1872 NodeSpec::column()
1873 .size(40.0, 20.0)
1874 .on_click(Value::from(1.0))
1875 .label("Save")
1876 }
1877
1878 /// Draws `first`, then `second`, and says whether the second frame
1879 /// derived the tree again and whether the tree changed.
1880 fn change(first: NodeSpec, second: NodeSpec) -> (bool, bool) {
1881 let mut core = Core::new();
1882 let (b0, h0) = frame(&mut core, first.clone());
1883 let (b1, h1) = frame(&mut core, second);
1884 (b1 > b0, h1 != h0)
1885 }
1886
1887 /// The case the cache is for: a pointer moving over a hover background
1888 /// changes `style.bg` before the node is pushed, and the access tree
1889 /// cannot see a background. The tree is kept, not rebuilt.
1890 #[test]
1891 fn a_change_the_tree_cannot_see_keeps_the_tree() {
1892 let (rebuilt, moved) = change(base().bg(Color::WHITE), base().bg(Color::BLACK));
1893 assert!(!rebuilt, "a colour the access tree never reads rebuilt it");
1894 assert!(!moved, "and the tree would have been the same anyway");
1895 }
1896
1897 /// And the same frame twice: no input moved at all.
1898 #[test]
1899 fn an_identical_frame_keeps_the_tree() {
1900 assert_eq!(change(base(), base()), (false, false));
1901 }
1902
1903 macro_rules! moves_the_tree {
1904 ($($name:ident: $first:expr => $second:expr;)*) => {$(
1905 #[test]
1906 fn $name() {
1907 let (rebuilt, moved) = change($first, $second);
1908 assert!(rebuilt, "the tree was served from the cache");
1909 assert!(moved, "it was derived again but came out the same");
1910 }
1911 )*};
1912 }
1913
1914 moves_the_tree! {
1915 label: base().label("Save") => base().label("Open");
1916 description: base() => base().description("Writes the file");
1917 role: base() => base().role(Role::Checkbox);
1918 clickable: NodeSpec::column().label("x") => NodeSpec::column().label("x").on_click(Value::from(1.0));
1919 disabled: base() => base().disabled(true);
1920 live: base() => base().live(Live::Polite);
1921 checked: base().role(Role::Checkbox) => base().role(Role::Checkbox).checked(true);
1922 selected: base().role(Role::Tab) => base().role(Role::Tab).selected(true);
1923 expanded: base() => base().expanded(true);
1924 value_now: base().role(Role::Slider) => base().role(Role::Slider).value_now(3.0);
1925 value_min: base().role(Role::Slider).value_now(3.0) => base().role(Role::Slider).value_now(3.0).value_min(1.0);
1926 value_text: base().role(Role::Slider) => base().role(Role::Slider).value_text("three");
1927 focusable: NodeSpec::column().role(Role::Group).label("g")
1928 => NodeSpec::column().role(Role::Group).label("g").focusable();
1929 rect: base() => base().width(80.0);
1930 }
1931
1932 /// The text a node is named by is not in its spec at all — it is the
1933 /// child text node's content, reached through the text system.
1934 #[test]
1935 fn the_text_a_node_reads_moves_the_tree() {
1936 let mut core = Core::new();
1937 let mut draw = |s: &str| {
1938 let mut ui = core.frame(Size::new(200.0, 100.0), 1.0);
1939 ui.configure_root(NodeSpec::column());
1940 ui.text(s, TextStyle::new(12.0));
1941 ui.finish();
1942 let h = core.access_tree().hash;
1943 (core.access_rebuilds, h)
1944 };
1945 let (b0, h0) = draw("hello");
1946 let (b1, h1) = draw("goodbye");
1947 assert!(b1 > b0 && h1 != h0, "a changed string kept its old node");
1948 }
1949
1950 /// The clip a node was emitted under is not in its spec either: a
1951 /// clipping parent narrowing cuts the rect of a node whose own box
1952 /// stayed where it was.
1953 #[test]
1954 fn a_parents_clip_moves_the_tree() {
1955 let mut core = Core::new();
1956 let mut draw = |w: f32| {
1957 let mut ui = core.frame(Size::new(200.0, 100.0), 1.0);
1958 ui.configure_root(NodeSpec::column());
1959 let clipper = NodeSpec::column().size(w, 40.0).clip();
1960 ui.with(clipper, |ui| {
1961 ui.leaf_keyed("node", base());
1962 });
1963 ui.finish();
1964 let hash = core.access_tree().hash;
1965 (core.access_rebuilds, hash)
1966 };
1967 let (b0, h0) = draw(100.0);
1968 let (b1, h1) = draw(30.0);
1969 assert!(b1 > b0 && h1 != h0, "the clip moved and the tree did not");
1970 }
1971
1972 /// Focus is the core's, not any node's spec.
1973 #[test]
1974 fn focus_moves_the_tree() {
1975 let mut core = Core::new();
1976 let (b0, h0) = frame(&mut core, base().focusable());
1977 core.set_key_focus(Some(Key::ROOT.str("node")));
1978 let (b1, h1) = frame(&mut core, base().focusable());
1979 assert!(b1 > b0 && h1 != h0, "focus moved and the tree did not");
1980 }
1981
1982 /// So is the scroll offset, which is retained across frames and only
1983 /// ever reaches the tree through the store.
1984 #[test]
1985 fn a_scroll_offset_moves_the_tree() {
1986 let mut core = Core::new();
1987 // Tall content, or the offset clamps to zero and nothing moved.
1988 let scroller = || NodeSpec::column().size(40.0, 20.0).scroll_y().label("list");
1989 let draw = |core: &mut Core| {
1990 let mut ui = core.frame(Size::new(200.0, 100.0), 1.0);
1991 ui.configure_root(NodeSpec::column());
1992 ui.with_keyed("node", scroller(), |ui| {
1993 ui.leaf_keyed("tall", NodeSpec::column().height(400.0));
1994 });
1995 ui.finish();
1996 let h = core.access_tree().hash;
1997 (core.access_rebuilds, h)
1998 };
1999 let (b0, h0) = draw(&mut core);
2000 core.set_scroll(Key::ROOT.str("node"), Vec2::new(0.0, 7.0));
2001 let (b1, h1) = draw(&mut core);
2002 assert!(b1 > b0 && h1 != h0, "the offset moved and the tree did not");
2003 }
2004
2005 /// An editor's text reaches the tree through the store, and its shaped
2006 /// runs through a version that stands in for them.
2007 #[test]
2008 fn editor_text_moves_the_tree() {
2009 let mut core = Core::new();
2010 let draw = |core: &mut Core, text: &str| {
2011 let mut ui = core.frame(Size::new(200.0, 100.0), 1.0);
2012 ui.configure_root(NodeSpec::column());
2013 let key = ui.text_edit(
2014 "name",
2015 text,
2016 &Default::default(),
2017 NodeSpec::column().size(120.0, 20.0),
2018 );
2019 ui.finish();
2020 let h = core.access_tree().hash;
2021 (core.access_rebuilds, h, key)
2022 };
2023 let (b0, h0, key) = draw(&mut core, "one");
2024 core.frame(Size::new(200.0, 100.0), 1.0).finish();
2025 core.set_edit_text(key, "typed");
2026 let (b1, h1, _) = draw(&mut core, "one");
2027 assert!(
2028 b1 > b0 && h1 != h0,
2029 "the editor's text moved and the tree did not"
2030 );
2031 }
2032
2033 /// And the viewport, which is the root node's whole rect.
2034 #[test]
2035 fn the_viewport_moves_the_tree() {
2036 let mut core = Core::new();
2037 let mut draw = |w: f32| {
2038 let mut ui = core.frame(Size::new(w, 100.0), 1.0);
2039 ui.configure_root(NodeSpec::column());
2040 ui.leaf_keyed("node", base());
2041 ui.finish();
2042 let h = core.access_tree().hash;
2043 (core.access_rebuilds, h)
2044 };
2045 let (b0, h0) = draw(200.0);
2046 let (b1, h1) = draw(300.0);
2047 assert!(
2048 b1 > b0 && h1 != h0,
2049 "the viewport moved and the tree did not"
2050 );
2051 }
2052}