Skip to main content

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