Skip to main content

kui_core/runtime/
emit.rs

1//! The frame's back half: layout, then emission into the display list.
2//!
3//! `finish_frame` runs the passes; the rest are the passes — one node's
4//! quads, the departed subtrees replayed as ghosts, position easing, the
5//! `layout` events, the default focus ring — and the quad helpers they
6//! share. Everything here reads the tree `builder` built and writes
7//! `display`; the input side of the same frame is `dispatch`.
8
9use super::*;
10
11const SCROLLBAR_W: f32 = 4.0;
12/// Thumb width while hovered or dragged.
13const SCROLLBAR_ACTIVE_W: f32 = 6.0;
14/// Grabbable gutter width (wider than the drawn thumb).
15const SCROLLBAR_HIT_W: f32 = 10.0;
16const SCROLLBAR_INSET: f32 = 2.0;
17const SCROLLBAR_MIN: f32 = 24.0;
18/// An `auto` bar (`ScrollbarMode::Auto`): how long it stays after the
19/// scroll state last changed, then how long it takes to fade. Seconds of
20/// the driver's clock.
21const SCROLLBAR_HOLD: f64 = 1.0;
22const SCROLLBAR_FADE: f64 = 0.25;
23/// The default focus ring:
24/// drawn this far outside the focused node, this thick, when focus is
25/// keyboard-visible and the node styles nothing itself.
26///
27/// The *colour* is `theme.focus_ring`: the geometry is
28/// still not a prop and still not negotiable, but a ring that cannot be
29/// seen is not a focus indicator, and the pale blue that reads on a dark
30/// page is invisible on a light one.
31const FOCUS_RING_GAP: f32 = 2.0;
32const FOCUS_RING_W: f32 = 2.0;
33
34/// The disjoint borrows one box's paint needs, split off the core field by
35/// field — a macro rather than a method so the tree, the stroke list and a
36/// ghost's points can stay borrowed beside them.
37macro_rules! painter {
38    ($core:expr) => {
39        Painter {
40            display: &mut $core.display,
41            text: &mut $core.text,
42            edit: &mut $core.edit,
43            cells: &mut $core.cells,
44            atlas: &mut $core.atlas,
45            session: &$core.session,
46            path_tex: &mut $core.path_textures,
47            frame_no: $core.frame_no,
48        }
49    };
50}
51
52impl Core {
53    /// The scrollbar thumb's colour at rest and while hovered or dragged:
54    /// the node's own where it declared one, else the theme's. A wash over
55    /// whatever it sits on rather than a fill, which is why the theme's
56    /// are two translucent colours and not one with an alpha applied.
57    fn thumb_color(&self, bar: &crate::spec::Scrollbar, active: bool) -> Color {
58        let t = self.theme();
59        if active {
60            bar.active_color.unwrap_or(t.scrollbar_active)
61        } else {
62            bar.color.unwrap_or(t.scrollbar)
63        }
64    }
65
66    /// Node `i`'s hit region, for a node that tracks the pointer. Out of
67    /// line: a box that takes no input — most of a frame — does not carry
68    /// the context-menu walk, the slider's track or the shape in
69    /// `emit_node`'s saved registers and stack frame.
70    #[inline(never)]
71    fn push_hit(
72        &mut self,
73        i: usize,
74        rect: Rect,
75        clip: Rect,
76        drop: Option<crate::input::DropOwner>,
77        hits: &mut Vec<HitRegion>,
78    ) {
79        let spec = &self.tree.specs[i];
80        let parent = self.tree.parent[i];
81        let parent_rect = if parent == NIL {
82            Rect::new(0.0, 0.0, self.viewport.w, self.viewport.h)
83        } else {
84            let p = parent as usize;
85            Rect::from_pos_size(self.tree.pos[p], self.tree.size[p])
86        };
87        // A disabled node keeps hover (a tooltip can say why) and loses
88        // every interaction: it emits nothing and takes no focus.
89        let live = !spec.disabled;
90        // The menu this region opens is its own or an ancestor's; the
91        // walk is skipped on a frame where no node offers one (T1).
92        let context_menu = if self.tree.any_context_menu {
93            self.enclosing_menu(i).map(|j| crate::input::MenuOwner {
94                key: self.tree.keys[j],
95                origin: self.tree.origins[j],
96                tag: self.tree.specs[j]
97                    .events()
98                    .on_context_menu
99                    .clone()
100                    .expect("enclosing_menu returns a node that offers one"),
101            })
102        } else {
103            None
104        };
105        // A slider that asked for its changes reads the pointer along
106        // its content box (ADR 0034, decision 4); a disabled one, or a
107        // range that is not one, reads nothing.
108        let slider = match spec.events().on_change.as_ref() {
109            Some(tag) if live && spec.access().role == Some(crate::access::Role::Slider) => {
110                crate::slider::SliderRange::of(spec.access()).map(|range| {
111                    Box::new(crate::slider::SliderTrack::new(
112                        rect,
113                        spec.layout.padding,
114                        spec.layout.dir == crate::spec::Dir::Column,
115                        range,
116                        tag.clone(),
117                    ))
118                })
119            }
120            _ => None,
121        };
122        // The shape past the rect (ADR 0026): a stroke's pieces, a
123        // fill's outline, a rounded box's corners; a plain box none.
124        let shape = match self.tree.content[i] {
125            NodeContent::Line(id) => {
126                let (run, points) = self.lines.run(id);
127                self.hit_shapes.segments(points, run.width)
128            }
129            NodeContent::Polygon(id) => {
130                let draw = self.fragments.get(id);
131                let mut pts = [Vec2::ZERO; crate::fragment::POLYGON_MAX_POINTS];
132                for (k, p) in pts.iter_mut().enumerate() {
133                    *p = Vec2::new(draw.params[k * 2] * rect.w, draw.params[k * 2 + 1] * rect.h);
134                }
135                self.hit_shapes.polygon(&pts)
136            }
137            NodeContent::Path(id) => {
138                let (run, ops) = self.paths.run(id);
139                let mut outline = Vec::new();
140                let fill = spec.style.bg.is_visible() || run.stroke_w <= 0.0;
141                if fill {
142                    crate::path::flatten(ops, &mut outline);
143                } else {
144                    crate::path::flatten_stroke(ops, &mut outline);
145                }
146                // A turned path is hit where it is drawn: the outline
147                // turned about the box's centre, which is its pivot (ADR
148                // 0041). The contour breaks are not points and stay.
149                if let Some(angle) = run.angle.filter(|a| *a != 0.0) {
150                    let c = Vec2::new(rect.w * 0.5, rect.h * 0.5);
151                    for p in outline.iter_mut().filter(|p| !p.x.is_nan()) {
152                        *p = crate::path::turned(*p, c, angle);
153                    }
154                }
155                if fill {
156                    self.hit_shapes.path(&outline, run.rule)
157                } else {
158                    // A stroke with no fill is hit as a line is, by the
159                    // pieces it paints: a contour its `Z` closed runs
160                    // back to its start, an open one does not, and the
161                    // breaks are kept so no piece runs from one contour
162                    // to the next.
163                    self.hit_shapes.segments(&outline, run.stroke_w)
164                }
165            }
166            _ if spec.style.radius != crate::display::SQUARE => {
167                crate::input::HitShape::Rounded(spec.style.radius)
168            }
169            _ => crate::input::HitShape::Rect,
170        };
171        hits.push(HitRegion {
172            key: self.tree.keys[i],
173            origin: self.tree.origins[i],
174            rect,
175            clip,
176            shape,
177            payload: spec.events().on_click.clone().filter(|_| live),
178            drag: spec.events().on_drag.clone().filter(|_| live),
179            parent_rect,
180            key_sink: spec.events().on_key.clone().filter(|_| live),
181            key_up: spec.events().key_up,
182            context_menu,
183            drop,
184            focusable: crate::access::focusable(&self.tree, i),
185            edit_origin: None,
186            select_scope: self.scope_of(i).filter(|_| live),
187            window: spec.window,
188            hover: spec.events().on_hover.clone(),
189            group: spec.interact().hover_group,
190            click_sound: spec.interact().click_sound.filter(|_| live),
191            hover_sound: spec.interact().hover_sound,
192            cursor: spec.cursor,
193            slider,
194        });
195    }
196
197    /// An editor's own hit region, whose origin is where its glyphs sit
198    /// (shifted by what a field is scrolled). Out of line for the reason
199    /// `push_hit` is.
200    #[inline(never)]
201    #[allow(clippy::too_many_arguments)]
202    fn push_edit_hit(
203        &mut self,
204        i: usize,
205        key: Key,
206        rect: Rect,
207        clip: Rect,
208        scale: f32,
209        drop: Option<crate::input::DropOwner>,
210        hits: &mut Vec<HitRegion>,
211    ) {
212        let spec = &self.tree.specs[i];
213        let pad = spec.layout.padding;
214        let content_origin = Vec2::new(rect.x + pad.l, rect.y + pad.t);
215        let inner_w = (rect.w - pad.x()).max(0.0);
216        // A single-line field scrolls its own text (F41). Resolved
217        // here, where the box is known, and read back by the hit
218        // region and the access runs so all three agree on where the
219        // glyphs are; the painter reads the same stored offset.
220        let offset = {
221            let sess = &mut *self.session.state();
222            self.edit.line_offset(key, inner_w * scale, &mut sess.fonts)
223        };
224        hits.push(HitRegion {
225            key,
226            origin: self.tree.origins[i],
227            rect,
228            clip,
229            // A field's corners round its hit too (ADR 0026).
230            shape: if spec.style.radius != crate::display::SQUARE {
231                crate::input::HitShape::Rounded(spec.style.radius)
232            } else {
233                crate::input::HitShape::Rect
234            },
235            payload: None,
236            drag: None,
237            parent_rect: rect,
238            // Shifted by what the field is scrolled: a click lands on
239            // the character under the pointer.
240            edit_origin: Some(Vec2::new(
241                content_origin.x - offset / scale,
242                content_origin.y,
243            )),
244            // An editor is its own selection scope: a press in it
245            // places a caret and drags a selection through the
246            // editor's own path, not the scope's.
247            select_scope: None,
248            key_sink: None,
249            key_up: false,
250            context_menu: None,
251            // A field inside a zone is the zone's: files dropped on
252            // it land there.
253            drop,
254            focusable: !spec.disabled,
255            window: None,
256            hover: None,
257            group: None,
258            click_sound: None,
259            hover_sound: None,
260            // The editor's own node carries any override.
261            cursor: spec.cursor,
262            slider: None,
263        });
264    }
265
266    /// The index in `regions` of the nearest scroll region around node
267    /// `i` in the tree, [`NIL`] for none. Ancestors are emitted before
268    /// their descendants — a float's layer comes after the one its
269    /// declaring node is in — so the one around is already in the list.
270    /// Off `emit_node`'s straight path: only a scroller pays for it.
271    #[inline(never)]
272    fn enclosing_scroll_region(&self, i: usize, regions: &[ScrollRegion]) -> u32 {
273        let mut a = self.tree.parent[i];
274        while a != NIL {
275            if let Some(r) = regions.iter().rposition(|r| r.node == a) {
276                return r as u32;
277            }
278            a = self.tree.parent[a as usize];
279        }
280        NIL
281    }
282
283    /// Emits one node's quads and registers its hit/scroll regions.
284    fn emit_node(
285        &mut self,
286        i: usize,
287        rect: Rect,
288        paint: Paint,
289        hits: &mut Vec<HitRegion>,
290        scroll_regions: &mut Vec<ScrollRegion>,
291    ) {
292        let Paint { clip, scale, .. } = paint;
293        // Behind a modal a node still draws, and stops taking input.
294        let interactive = self.interactive(i);
295        let spec = &self.tree.specs[i];
296        let style = spec.style;
297        // The zone this node's regions belong to — its own `on_drop` or
298        // an ancestor's — by the context menu's walk (ADR 0031, decision
299        // 2); skipped wholesale on a frame with no zone.
300        let drop = if self.tree.any_drop && interactive {
301            self.enclosing_drop(i).map(|j| crate::input::DropOwner {
302                key: self.tree.keys[j],
303                origin: self.tree.origins[j],
304                tag: self.tree.specs[j]
305                    .events()
306                    .on_drop
307                    .clone()
308                    .expect("enclosing_drop returns a node that declares one"),
309            })
310        } else {
311            None
312        };
313        // A stroke emits no hit region: it takes no input (ADR 0010,
314        // decision 7).
315        if spec.hover_tracked() && interactive {
316            self.push_hit(i, rect, clip.rect, drop.clone(), hits);
317        }
318        let spec = &self.tree.specs[i];
319        // An `on_scroll` node takes the wheel the way a container does —
320        // one list, one paint-order rule (ADR 0029, decision 4).
321        let handler = self.tree.any_scroll_handler && spec.events().on_scroll.is_some();
322        if spec.layout.scroll_x || spec.layout.scroll_y || handler {
323            // A container behind a modal keeps its scrollbar drawn and
324            // refuses the wheel and the thumb.
325            let (takes_x, takes_y) = if handler {
326                let axes = spec.events().scroll_axes;
327                (axes.takes(true), axes.takes(false))
328            } else {
329                (spec.layout.scroll_x, spec.layout.scroll_y)
330            };
331            scroll_regions.push(ScrollRegion {
332                key: self.tree.keys[i],
333                node: i as u32,
334                rect,
335                clip: clip.rect,
336                inert: !interactive,
337                handler,
338                takes_x,
339                takes_y,
340                scrolls_x: spec.layout.scroll_x,
341                scrolls_y: spec.layout.scroll_y,
342                contain: spec.interact().overscroll == crate::spec::Overscroll::Contain,
343                parent: self.enclosing_scroll_region(i, scroll_regions),
344            });
345        }
346        if let NodeContent::Edit(key) = self.tree.content[i]
347            && interactive
348        {
349            self.push_edit_hit(i, key, rect, clip.rect, scale, drop, hits);
350        }
351        // The content, resolved to what the painter needs — a text node's
352        // selection and its place, an editor's focus — then painted by the
353        // one step a ghost also paints through.
354        let leaf = match self.tree.content[i] {
355            NodeContent::Text(tid) => {
356                // The keys above it, nearest first, so a query by the
357                // `line` row (or a wrapper) finds the runs inside it.
358                let ancestry = self.text_ancestors(i);
359                self.text.place(
360                    self.tree.keys[i],
361                    &ancestry,
362                    tid,
363                    self.tree.pos[i],
364                    self.scope_of(i),
365                    true,
366                );
367                Leaf::Text {
368                    tid,
369                    sel: self.sel_range(i, tid),
370                }
371            }
372            NodeContent::Cells(cid) => Leaf::Cells {
373                cid,
374                at: self.cells_origin(i),
375                // The window's selection, when it is in this very grid.
376                sel: self
377                    .cell_selection
378                    .as_ref()
379                    .filter(|s| s.node == self.tree.keys[i] && !s.is_empty()),
380                tint: self.theme.selection,
381            },
382            NodeContent::Edit(key) => Leaf::Edit {
383                key,
384                focused: self.edit.focused() == Some(key),
385                pad: self.tree.specs[i].layout.padding,
386            },
387            NodeContent::Image(id, opts) => Leaf::Image(id, opts),
388            NodeContent::Fragment(id) => Leaf::Fragment(self.fragments.get(id)),
389            NodeContent::Polygon(id) => Leaf::Polygon(self.fragments.get(id)),
390            NodeContent::Line(id) => {
391                let (run, points) = self.lines.run(id);
392                Leaf::Line {
393                    points,
394                    width: run.width,
395                }
396            }
397            NodeContent::Path(id) => {
398                let (run, ops) = self.paths.run(id);
399                Leaf::Path {
400                    ops,
401                    rule: run.rule,
402                    stroke_w: run.stroke_w,
403                    hash: run.hash,
404                    angle: run.angle,
405                    animating: run.animating,
406                }
407            }
408            NodeContent::Container => Leaf::Container,
409        };
410        painter!(self).paint_box(rect, &style, &paint, leaf);
411        // A table's grid rules, with its box and under its cells. Out of
412        // `emit_node`: a rare path kept off its codegen (C48).
413        if self.tree.any_table
414            && let Some(c) = self.tree.specs[i].interact().rules
415            && self.tree.specs[i].layout.is_table()
416        {
417            self.emit_rules(i, rect, c, &paint);
418        }
419    }
420
421    /// The `rules` of table `i`, laid out by [`rule_lines`]
422    /// from its in-flow children as layout reads them — a row is what
423    /// `layout::is_table_row` says is one, so the grid is drawn over the
424    /// rows its columns were laid across and not through a heading or a
425    /// section beside them.
426    #[cold]
427    #[inline(never)]
428    fn emit_rules(&mut self, i: usize, rect: Rect, color: Color, paint: &Paint) {
429        let tree = &self.tree;
430        let at = |j: u32| Rect::from_pos_size(tree.pos[j as usize], tree.size[j as usize]);
431        let in_flow = |j: &u32| tree.specs[*j as usize].layout.float.is_none();
432        let children: Vec<RuledChild> = tree
433            .children(i as u32)
434            .filter(in_flow)
435            .map(|c| RuledChild {
436                rect: at(c),
437                cells: crate::layout::is_table_row(tree, c)
438                    .then(|| tree.children(c).filter(in_flow).map(at).collect()),
439            })
440            .collect();
441        let spec = &tree.specs[i];
442        let lines = rule_lines(rect, spec.layout.padding, spec.interact().rule_w, &children);
443        // The clip the table's children get: its own box when it clips or
444        // scrolls, since the rows' places carry its scroll offset and the
445        // rules drawn from them go wherever the rows do.
446        let clip_id = if spec.layout.clips() {
447            let radius = if tree.any_rounded_clip {
448                spec.style.radius
449            } else {
450                crate::display::SQUARE
451            };
452            let clip = paint.clip.intersect(rect, radius);
453            self.display.intern_clip(clip.scaled(paint.scale))
454        } else {
455            paint.clip_id
456        };
457        push_rules(&mut self.display, &lines, color, paint, clip_id);
458    }
459
460    /// [`Core::emit_rules`] for a departing table's ghost, node `i` of
461    /// `g` drawn at `rect`, its children `offset` from where they were:
462    /// the same rules from the children the ghost copied, under the same
463    /// clip its children get, faded with it by `paint`.
464    #[cold]
465    #[inline(never)]
466    fn emit_ghost_rules(
467        &mut self,
468        g: &Ghost,
469        i: usize,
470        rect: Rect,
471        offset: Vec2,
472        color: Color,
473        paint: &Paint,
474    ) {
475        let at = |node: &crate::depart::GhostNode| {
476            Rect::new(
477                node.rect.x + offset.x,
478                node.rect.y + offset.y,
479                node.rect.w,
480                node.rect.h,
481            )
482        };
483        // The nodes are in preorder, so a grandchild's parent is the most
484        // recent child. A departing `cells` grid, or a fragment whose draw
485        // was gone, is a `Container` here — a `row` one straight under a
486        // table would read as a row of no cells, where layout read it as
487        // no row.
488        let mut children: Vec<RuledChild> = Vec::new();
489        let mut last = NIL;
490        for (j, node) in g.nodes.iter().enumerate().skip(i + 1) {
491            // Past the table's subtree: a node whose parent precedes it.
492            if node.parent == NIL || (node.parent as usize) < i {
493                break;
494            }
495            if node.spec.layout.float.is_some() {
496                continue;
497            }
498            if node.parent as usize == i {
499                let row = crate::layout::row_shaped(
500                    &node.spec.layout,
501                    matches!(node.content, GhostContent::Container),
502                );
503                children.push(RuledChild {
504                    rect: at(node),
505                    cells: row.then(Vec::new),
506                });
507                last = j as u32;
508            } else if node.parent == last
509                && let Some(cells) = children.last_mut().and_then(|c| c.cells.as_mut())
510            {
511                cells.push(at(node));
512            }
513        }
514        let spec = &g.nodes[i].spec;
515        let lines = rule_lines(rect, spec.layout.padding, spec.interact().rule_w, &children);
516        let clip_id = if spec.layout.clips() {
517            let clip = paint.clip.intersect(rect, spec.style.radius);
518            self.display.intern_clip(clip.scaled(paint.scale))
519        } else {
520            paint.clip_id
521        };
522        push_rules(&mut self.display, &lines, color, paint, clip_id);
523    }
524
525    /// Runs layout and emission into `output()`, and installs this frame's
526    /// hit and scroll regions for input handling. The last step of
527    /// `Ui::finish`, which runs the extension fills, the devtools panel
528    /// and the open menu first — and crate-private for that reason:
529    /// a host that called this directly got a frame where
530    /// `open_menu` drew nothing and `KUI_DEVTOOLS` did nothing, with no
531    /// warning. A driver with a bare `Core` finishes through
532    /// `Ui::wrap(core).finish()`.
533    pub(crate) fn finish_frame(&mut self) {
534        self.layout_frame();
535        self.emit_frame();
536        self.building = false;
537        // A focus the view moved, or a focused node the frame declared
538        // `on_focus` on or dropped (backlog DX18).
539        let mut out = std::mem::take(&mut self.pending);
540        self.report_focus("program", &mut out);
541        self.pending = out;
542        self.snapshot_nodes();
543        self.devtools_after_frame();
544        self.trace_finish_frame();
545        // Between frames the host is who talks to the core: a driver that
546        // tagged the last nodes with an extension's origin by hand (rather
547        // than through `fill`, which restores it) must not leave its
548        // `set_tokens` landing in that extension's table (ADR 0027).
549        self.origin = crate::tree::OriginId::HOST;
550    }
551
552    /// The frame's first half: layout, then everything that resolves
553    /// against it before a quad is emitted — the caret and reveal nudges,
554    /// the `layout` events and the diagnostics, the declared window set,
555    /// the modal scope and the Tab step a view asked for.
556    fn layout_frame(&mut self) {
557        // Tolerate unclosed containers (an FFI caller may have bailed early).
558        self.stack.truncate(1);
559        self.counters.truncate(1);
560
561        self.tree.host_area = self.dt_area;
562        if !self.pending_scroll_labels.is_empty() {
563            self.resolve_scroll_labels();
564        }
565        {
566            let sess = &mut *self.session.state();
567            let mut measure = Measure {
568                text: &mut self.text,
569                cells: &mut self.cells,
570                fonts: &mut sess.fonts,
571                edit: &mut self.edit,
572                resources: &sess.resources,
573            };
574            layout::compute(
575                &mut self.tree,
576                &mut measure,
577                &mut self.scroll,
578                self.viewport,
579                self.scale,
580            );
581        }
582        self.scroll_caret_into_view();
583        // An explicit `reveal` after the caret nudge: the app asked for
584        // this one, so it wins the offset if both want to move it.
585        self.apply_pending_reveal();
586        // A container a view sliced its rows by (`scroll_geometry`) that
587        // came out otherwise — taller, scrolled elsewhere — owes a frame
588        // built against this layout, or the slice stays a frame behind
589        // until the next event (`ScrollStore::resliced`).
590        if self.scroll.take_resliced() {
591            self.owe_frame("resliced");
592        }
593        if self.tree.any_slide {
594            self.ease_positions();
595        }
596        // Positions are final: report the rects views asked about, and
597        // look for the misconfigurations that would otherwise fail silently.
598        if self.tree.any_layout {
599            self.emit_layout_events();
600        }
601        self.diag
602            .check(&self.tree, &self.text, &self.edit, self.frame_no);
603        // Text set for a key nothing had declared yet was held for this
604        // frame (backlog F25). What it declared has taken its seed; what
605        // is left named an editor no view draws, so drop it and say so.
606        // Drained whatever `diag.enabled` says, so the gate changes what
607        // is reported and never what is retained.
608        for seed in self.edit.take_unclaimed_seeds() {
609            self.diag.raise(match seed {
610                crate::edit::Unclaimed::Key(key) => crate::diag::edit_text_without_editor(key),
611                crate::edit::Unclaimed::Label(label) => {
612                    crate::diag::edit_text_without_editor_label(&label)
613                }
614            });
615        }
616        // The declared window set, diffed against the session's: a frame
617        // that declared a new name queues its `Open` here.
618        self.sync_windows();
619        // The frame's modal scope, and the focus it moves: emission reads
620        // it (everything outside is inert) and so does the Tab ring.
621        self.modal = if self.tree.any_modal {
622            self.modal_scope()
623        } else {
624            None
625        };
626        self.resolve_modal_focus();
627        // Then the regions: a `focus_region` asked for during the build,
628        // the region following a focus the build declared, and a region
629        // that went away handing focus back (`docs/adr/0022`).
630        self.resolve_regions();
631        // The ring exists now: laid out, and scoped to the modal if there
632        // is one, else to the region in effect. A step asked for during
633        // the build lands here, so it wins over both the modal's own focus
634        // move and a same-frame `set_focus`. Like a real Tab press, the
635        // scroll it triggers shows on the next frame.
636        if let Some(forward) = self.pending_focus_step.take() {
637            self.focus_next(forward);
638        }
639    }
640
641    /// Numbers the text nodes of the selection's scope in tree order and
642    /// resolves the two ends against those ordinals. Run once per frame,
643    /// after the scope map and before emission, because a run's range has
644    /// to be known when the run is drawn and its *offset* cannot be —
645    /// the runs after it have not been placed yet, and an end may be one
646    /// of them.
647    fn resolve_selection(&mut self) {
648        self.sel_ords.clear();
649        self.sel_ends = None;
650        let Some(sel) = self.selection else { return };
651        self.sel_ords.resize(self.tree.len(), u32::MAX);
652        let (mut anchor, mut focus) = (None, None);
653        // The built runs, in order, with the virtualised row each is in:
654        // what an end whose own node is *not* built is placed against.
655        let mut built: Vec<(u32, Option<u64>, usize)> = Vec::new();
656        let mut ord = 0u32;
657        for i in 0..self.tree.len() {
658            if self.scopes.get(i).copied().flatten() != Some(sel.scope)
659                || !matches!(self.tree.content[i], NodeContent::Text(_))
660            {
661                continue;
662            }
663            self.sel_ords[i] = ord;
664            let key = self.tree.keys[i];
665            let len = match self.tree.content[i] {
666                NodeContent::Text(tid) => self.text.content_len(tid),
667                _ => 0,
668            };
669            built.push((ord, self.rows.get(i).copied().flatten(), len));
670            if key == sel.anchor.node {
671                anchor = Some((ord, sel.anchor.byte));
672            }
673            if key == sel.focus.node {
674                focus = Some((ord, sel.focus.byte));
675            }
676            ord += 1;
677        }
678        // An end this frame did not build is placed by its row's index in
679        // the data: before everything built, after it, or at the boundary
680        // it falls on. That is what lets the built middle paint while a
681        // virtual list scrolls under a selection (ADR 0017, tier 3).
682        let place = |end: &crate::select::Endpoint| -> Option<(u32, usize)> {
683            let row = end.row?;
684            let (start, last) = (built.first()?, built.last()?);
685            // The row itself is built, on runs no end's key names: a Select
686            // All's placeholder end (`select_all_in` puts it on the scope
687            // with the row's index and `ROW_END`) after the list scrolled
688            // that row into the built window. It lands in that row's own
689            // runs — the end of its last one, or the byte into its first —
690            // rather than at the boundary an unbuilt row would take.
691            let mut in_row = built.iter().filter(|(_, r, _)| *r == Some(row));
692            if let Some(first_run) = in_row.next() {
693                return Some(if end.byte >= crate::select::ROW_END {
694                    let last_run = in_row.next_back().unwrap_or(first_run);
695                    (last_run.0, last_run.2)
696                } else {
697                    (first_run.0, end.byte.min(first_run.2))
698                });
699            }
700            // Against the built runs that *carry* a row, not the first and
701            // last of everything built: a scope can hold plain labels
702            // beside virtual rows — a header, a footer — and a label says
703            // nothing about where a row sits in the data. Comparing
704            // against one puts an end below the list at the top of it.
705            let hi = built.iter().rev().find_map(|(_, r, _)| *r)?;
706            if crate::select::unbuilt_row_is_after(row, Some(hi)) {
707                return Some((last.0, last.2));
708            }
709            // Below the first row, or inside the built range without being
710            // built — a hole, which a contiguous virtual window does not
711            // have. Either way the start is the nearest honest boundary.
712            Some((start.0, 0))
713        };
714        let anchor = anchor.or_else(|| place(&sel.anchor));
715        let focus = focus.or_else(|| place(&sel.focus));
716        if let (Some(a), Some(f)) = (anchor, focus) {
717            self.sel_ends = Some(crate::select::Ends::ordered(a, f));
718        }
719    }
720
721    /// The bytes of node `i`'s text the selection covers, with the colour
722    /// to paint under them. `None` on every node of every frame that has
723    /// no selection.
724    fn sel_range(&self, i: usize, tid: crate::tree::TextId) -> Option<((usize, usize), Color)> {
725        let ends = self.sel_ends?;
726        let ord = self.sel_ords.get(i).copied()?;
727        if ord == u32::MAX {
728            return None;
729        }
730        let range = ends.range_in(ord, self.text.content_len(tid))?;
731        Some((range, self.theme().selection))
732    }
733
734    /// The innermost selection scope node `i` is inside, if any. Empty on
735    /// every frame that declares no `selectable` at all, where the map is
736    /// not even sized.
737    #[inline]
738    fn scope_of(&self, i: usize) -> Option<Key> {
739        self.scopes.get(i).copied().flatten()
740    }
741
742    /// The keys above node `i`, nearest first, as many as a `TextPlace`
743    /// remembers — what lets a query by a `line` row or a wrapper find
744    /// the runs inside it.
745    /// The keys above text node `i`, nearest first, as many as a place
746    /// remembers; how many there are; and the depth of the nearest
747    /// `role="none"` ancestor among them, which a query from above it
748    /// does not reach. A `line` row further up than the
749    /// place can remember raises `text-beyond-line`, once per text.
750    fn text_ancestors(&mut self, i: usize) -> crate::text::Ancestry {
751        use crate::access::Role;
752        let mut ancestors = [Key::ROOT; crate::text::PLACE_ANCESTORS];
753        let mut depth = 0;
754        let mut none_at = None;
755        let mut p = self.tree.parent[i];
756        while p != NIL && depth < ancestors.len() {
757            let j = p as usize;
758            ancestors[depth] = self.tree.keys[j];
759            if none_at.is_none() && self.tree.specs[j].access().role == Some(Role::None) {
760                none_at = Some(depth);
761            }
762            depth += 1;
763            p = self.tree.parent[j];
764        }
765        // Past the reach: a `line` row still above is one the text cannot
766        // be found from, and the app should hear it.
767        if p != NIL && self.tree.any_line {
768            let mut q = p;
769            while q != NIL {
770                let j = q as usize;
771                if self.tree.specs[j].access().role == Some(Role::Line) {
772                    self.diag.raise(crate::diag::text_beyond_line(
773                        self.tree.keys[i],
774                        self.tree.keys[j],
775                        crate::text::PLACE_ANCESTORS,
776                    ));
777                    break;
778                }
779                q = self.tree.parent[j];
780            }
781        }
782        crate::text::Ancestry {
783            keys: ancestors,
784            depth,
785            none_at,
786        }
787    }
788
789    /// The frame's second half: the laid-out tree into the display list,
790    /// in paint order — the in-flow layer, then one layer per floating
791    /// subtree in the order they opened, each with the departed subtrees
792    /// that were painted among it and its own chrome (scrollbars, the
793    /// ring) at its end — and the hit and scroll regions the
794    /// next input is tested against, in the same order.
795    fn emit_frame(&mut self) {
796        let scale = self.scale;
797        let mut hits: Vec<HitRegion> = self.interaction.take_hit_buffer();
798        self.hit_shapes = self.interaction.take_shape_buffer();
799        let mut scroll_regions: Vec<ScrollRegion> = Vec::new();
800        self.display.viewport = Size::new(self.viewport.w * scale, self.viewport.h * scale);
801        self.display.scale = scale;
802        self.display.time = self.anim.time().unwrap_or(0.0) as f32;
803
804        // Read once each: what the frame declared, noted by `Tree::push`
805        // (and by `configure_root`, whose spec replaces the root's).
806        let any_clip = self.tree.any_clip;
807        let any_rounded_clip = self.tree.any_rounded_clip;
808        let any_opacity = self.tree.any_opacity;
809        let any_float = self.tree.any_float;
810        // Read once: every per-node selection check below is behind it.
811        let any_selectable = self.tree.any_selectable;
812
813        // inherited clip per node (logical): ancestors only, not the node
814        // itself. Only materialized when something actually clips.
815        // Index 0 is the clip that clips nothing, so an unclipped frame
816        // interns once and every quad on it names entry zero. Seeded
817        // rather than found, so `clip_of` has something to answer with
818        // even on a frame that emitted no quad at all.
819        let no_clip = self.display.intern_clip(Clip::NONE.scaled(scale));
820        self.clips.clear();
821        self.clip_ids.clear();
822        if any_clip {
823            self.clips.resize(self.tree.len(), Clip::NONE);
824            self.clip_ids.resize(self.tree.len(), no_clip);
825        }
826        self.opacity.clear();
827        if any_opacity {
828            self.opacity.resize(self.tree.len(), 1.0);
829        }
830        self.float_root.clear();
831        if any_float {
832            self.float_root.resize(self.tree.len(), NIL);
833        }
834        // The innermost `selectable` above each node (ADR 0017). Parents
835        // precede their children in the tree array, so one forward pass
836        // inherits it; a node declaring `selectable` inside another scope
837        // takes the text under it and is warned about, because two scopes
838        // over one run would each think they own it.
839        self.scopes.clear();
840        self.rows.clear();
841        if any_selectable {
842            self.scopes.resize(self.tree.len(), None);
843            self.rows.resize(self.tree.len(), None);
844            // The rows a virtual list built, by node, so the walk below
845            // can carry each one down to the text inside it.
846            for &(node, index) in &self.tree.indexed {
847                if let Some(slot) = self.rows.get_mut(node as usize) {
848                    *slot = Some(index);
849                }
850            }
851            for i in 0..self.tree.len() {
852                let parent = self.tree.parent[i];
853                // A floating subtree escapes the scope it floats out of,
854                // the way it escapes the clip: a popover over a card is
855                // not part of the card's paragraph, and a float is
856                // emitted in a later pass than its tree position, which
857                // would put its runs out of reading order anyway.
858                let outer = if parent == NIL || self.tree.specs[i].layout.float.is_some() {
859                    None
860                } else {
861                    self.scopes[parent as usize]
862                };
863                // An inner scope takes the text under it; the nesting
864                // itself is reported by `diag`, over the finished tree.
865                //
866                // A `cells` grid is its own scope and never joins the one
867                // around it: it selects in cells, and a selection that ran
868                // from a paragraph into a terminal screen would be two
869                // kinds of selection at once (ADR 0017, decision 4).
870                let grid = matches!(self.tree.content[i], NodeContent::Cells(_));
871                self.scopes[i] = if self.tree.specs[i].interact().selectable {
872                    Some(self.tree.keys[i])
873                } else if grid {
874                    None
875                } else {
876                    outer
877                };
878                // A row's index reaches the text inside it: the node that
879                // declared it keeps its own, everything under it inherits.
880                if self.rows[i].is_none() && parent != NIL {
881                    self.rows[i] = self.rows[parent as usize];
882                }
883            }
884        }
885        self.resolve_selection();
886
887        // Exits: what the previous frame declared and this one does not is
888        // copied out of the tree the previous frame left behind, and every
889        // departing subtree still in flight is taken out for the passes,
890        // which paint each one where its node was (see `depart`).
891        if !self.prev_tree.is_empty() || !self.depart.is_empty() {
892            self.collect_departures();
893        }
894        let mut replay = Replay::default();
895        let mut took_ghosts = false;
896        if !self.depart.is_empty()
897            && let Some(now) = self.anim.time()
898        {
899            replay = self.depart.begin_replay(now);
900            took_ghosts = true;
901        }
902        let any_ghost = !replay.is_empty();
903
904        // Pass 1: clip/float propagation + in-flow emission (preorder =
905        // paint order; parents precede children). `float_root[i]` is the
906        // nearest floating ancestor-or-self, `NIL` in flow: the layer a
907        // node paints in (ADR 0023, decision 1). Every float root goes into
908        // `roots`, in tree order, for the stack to sort.
909        let mut roots: Vec<u32> = Vec::new();
910        for i in 0..self.tree.len() {
911            let parent = self.tree.parent[i];
912            let floats_here = any_float && self.tree.specs[i].layout.float.is_some();
913            if any_float {
914                self.float_root[i] = if floats_here {
915                    roots.push(i as u32);
916                    i as u32
917                } else if parent != NIL {
918                    self.float_root[parent as usize]
919                } else {
920                    NIL
921                };
922            }
923            let rect = Rect::from_pos_size(self.tree.pos[i], self.tree.size[i]);
924            // Opacity multiplies down the tree, floats included: a tooltip
925            // inside a fading panel fades with it.
926            let opacity = if !any_opacity {
927                1.0
928            } else {
929                let inherited = if parent == NIL {
930                    1.0
931                } else {
932                    self.opacity[parent as usize]
933                };
934                let o = inherited * self.tree.specs[i].style.opacity;
935                self.opacity[i] = o;
936                o
937            };
938            // A parent-anchored float that declared `clip` belongs to the
939            // parent's content as a child does, so the parent's clip holds
940            // it: a node on a `clip` canvas panned past the canvas's edge
941            // is cut there (F90), and a graph beside a scrolled list at
942            // the list's edge like the rows it draws over, since the core
943            // sets the bit on every stroke and polygon (F78, ADR 0010
944            // decision 5). Any other float (a tooltip, a menu, a stroke
945            // anchored to the viewport) escapes. Only the clip is the
946            // parent's: the node still paints in its float layer.
947            let drawn_in_parent = floats_here
948                && parent != NIL
949                && self.tree.specs[i]
950                    .layout
951                    .float
952                    .is_some_and(|f| f.clipped_by_parent());
953            let (clip, clip_id) = if !any_clip {
954                (Clip::NONE, no_clip)
955            } else {
956                // Floating nodes escape ancestor clips.
957                let (clip, id) = if parent == NIL || (floats_here && !drawn_in_parent) {
958                    (Clip::NONE, no_clip)
959                } else {
960                    let p = parent as usize;
961                    if self.tree.specs[p].layout.clips() {
962                        // A clipper with a radius rounds what it clips, so
963                        // the children of a rounded card stay inside its
964                        // corners (see `display::Clip`).
965                        let box_rect = Rect::from_pos_size(self.tree.pos[p], self.tree.size[p]);
966                        let box_radius = if any_rounded_clip {
967                            self.tree.specs[p].style.radius
968                        } else {
969                            crate::display::SQUARE
970                        };
971                        let clip = self.clips[p].intersect(box_rect, box_radius);
972                        (clip, self.display.intern_clip(clip.scaled(scale)))
973                    } else {
974                        // The overwhelming case: the clip is the parent's,
975                        // so the entry is too, and nothing is compared.
976                        (self.clips[p], self.clip_ids[p])
977                    }
978                };
979                self.clips[i] = clip;
980                self.clip_ids[i] = id;
981                (clip, id)
982            };
983            if any_float && self.float_root[i] != NIL {
984                continue; // deferred to its layer, in the float pass
985            }
986            // A departing subtree painted just under this node last time
987            // goes first, so it stays under it.
988            if any_ghost && replay.may_precede(self.tree.keys[i]) {
989                let key = self.tree.keys[i];
990                replay.paint(At::UnderInFlow(key), |g, play| {
991                    self.emit_ghost(g, play, scale)
992                });
993            }
994            // Entirely clipped away: skip drawing and hit-testing.
995            // Rect, not rounded: a node that survives only in a corner's
996            // arc is drawn and clipped rather than culled.
997            let visible = rect.intersect(&clip.rect);
998            if visible.w <= 0.0 || visible.h <= 0.0 {
999                // Culled — but a text run inside a selection scope keeps
1000                // its place in the order and its content reachable, so a
1001                // selection can run past the edge of a scroller (ADR
1002                // 0017, tier 2). Marked undrawn: no hit region, no
1003                // `text_hit`, nothing a pointer can find.
1004                //
1005                // Behind the frame's own flag, hoisted out of the loop:
1006                // a frame that declares no scope does not pay a lookup
1007                // per culled node to find that out (C15).
1008                if any_selectable
1009                    && let Some(scope) = self.scope_of(i)
1010                    && let NodeContent::Text(tid) = self.tree.content[i]
1011                {
1012                    let ancestry = self.text_ancestors(i);
1013                    self.text.place(
1014                        self.tree.keys[i],
1015                        &ancestry,
1016                        tid,
1017                        self.tree.pos[i],
1018                        Some(scope),
1019                        false,
1020                    );
1021                }
1022                continue;
1023            }
1024            let paint = Paint {
1025                clip,
1026                clip_id,
1027                scale,
1028                opacity,
1029            };
1030            self.emit_node(i, rect, paint, &mut hits, &mut scroll_regions);
1031        }
1032
1033        if any_ghost {
1034            // The in-flow ghosts whose place is gone: the end of their
1035            // layer, still under its chrome and under every float.
1036            replay.paint(At::InFlowEnd, |g, play| self.emit_ghost(g, play, scale));
1037        }
1038
1039        // The in-flow layer's chrome — its scrollers' bars and, if the
1040        // focused node is in flow, the ring — above its content and under
1041        // every float (ADR 0023, decision 2).
1042        let mut scrollbars: Vec<ScrollbarRegion> = Vec::new();
1043        let mut chrome_from = 0usize;
1044        self.emit_layer_chrome(
1045            NIL,
1046            &scroll_regions[chrome_from..],
1047            &mut scrollbars,
1048            hits.len(),
1049            scale,
1050        );
1051        chrome_from = scroll_regions.len();
1052
1053        // Pass 2: the float layers, bottom to top in the order they opened
1054        // (ADR 0023, decision 3); each one's chrome at its end. Their hit
1055        // regions land in the same order, so `hit_at` reads the stack.
1056        if any_float {
1057            let order = self.stack_floats(&roots);
1058            for &r in &order {
1059                let root = r as usize;
1060                let root_key = self.tree.keys[root];
1061                if any_ghost && replay.may_precede(root_key) {
1062                    // A departed float that was under this one stays under
1063                    // it: a whole layer, painted before this layer starts.
1064                    replay.paint(At::UnderLayer(root_key), |g, play| {
1065                        self.emit_ghost(g, play, scale)
1066                    });
1067                }
1068                let end = self.tree.subtree_end(root);
1069                for i in root..end {
1070                    if self.float_root[i] != r {
1071                        continue; // a nested float: its own layer, later
1072                    }
1073                    if any_ghost && replay.may_precede(self.tree.keys[i]) {
1074                        let key = self.tree.keys[i];
1075                        replay.paint(At::UnderInLayer(key), |g, play| {
1076                            self.emit_ghost(g, play, scale)
1077                        });
1078                    }
1079                    let rect = Rect::from_pos_size(self.tree.pos[i], self.tree.size[i]);
1080                    let clip = if any_clip { self.clips[i] } else { Clip::NONE };
1081                    let clip_id = if any_clip { self.clip_ids[i] } else { no_clip };
1082                    let opacity = if any_opacity { self.opacity[i] } else { 1.0 };
1083                    let visible = rect.intersect(&clip.rect);
1084                    if visible.w <= 0.0 || visible.h <= 0.0 {
1085                        continue;
1086                    }
1087                    let paint = Paint {
1088                        clip,
1089                        clip_id,
1090                        scale,
1091                        opacity,
1092                    };
1093                    self.emit_node(i, rect, paint, &mut hits, &mut scroll_regions);
1094                }
1095                if any_ghost {
1096                    replay.paint(At::LayerEnd(root_key), |g, play| {
1097                        self.emit_ghost(g, play, scale)
1098                    });
1099                }
1100                // A layer with no scroller and no ring to draw has no
1101                // chrome: the common float is a leaf, and ten thousand of
1102                // them (a `line` each) pay a call apiece otherwise (C29).
1103                if scroll_regions.len() > chrome_from || self.focus_visible {
1104                    self.emit_layer_chrome(
1105                        r,
1106                        &scroll_regions[chrome_from..],
1107                        &mut scrollbars,
1108                        hits.len(),
1109                        scale,
1110                    );
1111                    chrome_from = scroll_regions.len();
1112                }
1113            }
1114            self.check_layers_over_modal(&order);
1115        } else if !self.float_stack.is_empty() {
1116            // No floats this frame: every layer closed.
1117            self.float_stack.clear();
1118        }
1119
1120        if any_ghost {
1121            // A ghost whose whole layer is gone: on top, the only place
1122            // left that is under nothing it was under.
1123            replay.paint(At::Top, |g, play| self.emit_ghost(g, play, scale));
1124        }
1125        if took_ghosts {
1126            self.depart.end_replay(replay);
1127        }
1128
1129        // Rounded span backgrounds, joined into one shape with the ones
1130        // they meet now that every text is painted (backlog F101).
1131        let joins = self.text.take_joins();
1132        if !joins.is_empty() {
1133            let sess = &mut *self.session.state();
1134            let id = sess.resources.add_fragment(crate::fragment::JOIN);
1135            if let Some(source) = sess.resources.fragment(id).cloned() {
1136                crate::join::shape(&mut self.display, &joins, id, &source, scale);
1137            }
1138        }
1139
1140        let shapes = std::mem::take(&mut self.hit_shapes);
1141        self.interaction.set_hits_shaped(hits, shapes);
1142        // A button held on a node this frame no longer declares has no one
1143        // to hear its release (backlog F105).
1144        let tree = &self.tree;
1145        self.interaction
1146            .drop_gone_buttons(|key| tree.index_of(key).is_some());
1147        // A new frame can move a hover-sound node under a still cursor.
1148        self.flush_sound_requests();
1149        // Against this window's mounts only (AR7): a popup or a second
1150        // window finishing a frame with no `<audio>` in it says nothing
1151        // about the main window's loop.
1152        let window = self.env.window.id;
1153        self.session.state().audio.reconcile(window);
1154        self.interaction.scroll_regions = scroll_regions;
1155        self.interaction.scrollbars = scrollbars;
1156        self.ime_rect = self.focused_caret_rect();
1157        self.note_sink_caret();
1158        self.atlas.end_frame();
1159        // The atlas refused a glyph for room this frame rather than drop
1160        // a slot the frame had already used (F99): the next frame starts
1161        // on an empty page and draws it, and has to come — an
1162        // input-driven app would keep the short frame until the next
1163        // event. A page that only grew is right as it is presented.
1164        if self.atlas.short() {
1165            self.owe_frame("atlas full");
1166        }
1167        // A long line's rows came out other than layout's once emission
1168        // shaped what shows (backlog RG70): the frame laid out on them is
1169        // owed, or an idle view keeps the estimate's box.
1170        if self.text.take_owed() {
1171            self.owe_frame("long line rows");
1172        }
1173    }
1174
1175    /// The exit diff: every key the previous frame declared an `exit` on
1176    /// and this frame does not becomes a departing subtree, copied out of
1177    /// `prev_tree` — the frame that still had it — and handed to the store
1178    /// with the place it painted in, so its ghost keeps it.
1179    /// A ghost whose key came back is retired here too: the live node wins.
1180    ///
1181    /// Only two kinds of key are interesting (the previous frame's
1182    /// exit-declaring roots, and the roots already departing), and both are
1183    /// few, so the walk over *this* frame's keys — the part that scales
1184    /// with the frame — is one AND against a 64-bit membership mask per
1185    /// node, and a hash lookup only for the handful that collide with it.
1186    fn collect_departures(&mut self) {
1187        let Some(now) = self.anim.time() else {
1188            // No clock: every transition snaps, and an exit that snaps is
1189            // the plain disappearance it has always been.
1190            self.depart.clear();
1191            return;
1192        };
1193        // The steady state: the view declared the same nodes in the same
1194        // order, so nothing left and nothing came back. Two flat arrays of
1195        // u64 compared is cheaper than anything that looks at the keys one
1196        // at a time, and it is the case almost every frame is.
1197        if self.prev_tree.keys == self.tree.keys {
1198            return;
1199        }
1200        let mut watch: FxHashSet<Key> = FxHashSet::default();
1201        let mut mask = 0u64;
1202        for k in self.depart.keys() {
1203            watch.insert(k);
1204            mask |= 1u64 << (k.0 & 63);
1205        }
1206        let mut candidates: Vec<usize> = Vec::new();
1207        for i in 0..self.prev_tree.len() {
1208            if crate::depart::can_depart(&self.prev_tree.specs[i]) {
1209                candidates.push(i);
1210                // The node, and its parent: an exit plays only where the
1211                // parent is still declared (backlog DX19), so the diff has
1212                // to know about that key too.
1213                let p = self.prev_tree.parent[i];
1214                let keys = [
1215                    Some(self.prev_tree.keys[i]),
1216                    (p != NIL).then(|| self.prev_tree.keys[p as usize]),
1217                ];
1218                for k in keys.into_iter().flatten() {
1219                    watch.insert(k);
1220                    mask |= 1u64 << (k.0 & 63);
1221                }
1222            }
1223        }
1224        if watch.is_empty() {
1225            return;
1226        }
1227        let mut live: FxHashSet<Key> = FxHashSet::default();
1228        for &k in &self.tree.keys {
1229            if mask & (1u64 << (k.0 & 63)) != 0 && watch.contains(&k) {
1230                live.insert(k);
1231            }
1232        }
1233        self.depart.retire_returned(&live);
1234        // The frame's removal, whole, before any of it is copied: the roots
1235        // that actually left, in tree order so a departing subtree swallows
1236        // the exits nested inside it rather than drawing them a second time
1237        // on top, and how many nodes they come to together.
1238        let mut roots: Vec<usize> = Vec::new();
1239        let mut wanted = 0usize;
1240        let mut swallowed_until = 0usize;
1241        for i in candidates {
1242            if i < swallowed_until || live.contains(&self.prev_tree.keys[i]) {
1243                continue;
1244            }
1245            // Its parent went too, and the parent declared no exit that
1246            // would have carried it (it would have swallowed it above): the
1247            // node went with its ancestor, not on its own, and plays nothing
1248            // — a tab switched away does not fade out every column that
1249            // fades when it closes (backlog DX19). CSS removes the subtree;
1250            // React's `AnimatePresence` plays the exits of its direct
1251            // children only. This is that rule.
1252            let p = self.prev_tree.parent[i];
1253            if p != NIL && !live.contains(&self.prev_tree.keys[p as usize]) {
1254                continue;
1255            }
1256            swallowed_until = self.prev_tree.subtree_end(i);
1257            wanted += swallowed_until - i;
1258            roots.push(i);
1259        }
1260        if roots.is_empty() {
1261            return;
1262        }
1263        // ADR 0012, decisions 2 and 3: the removal animates whole or not at
1264        // all, and takes the room it needs from the oldest ghosts in flight
1265        // before it is refused. Refused means every node of it vanishes at
1266        // once — what a node with no `exit` does — and one warning for the
1267        // frame, keyed by its first departing root, says how much did.
1268        if !self.depart.admit(wanted) {
1269            self.diag.raise(Warning {
1270                code: crate::diag::EXIT_BUDGET,
1271                key: self.prev_tree.keys[roots[0]],
1272                message: format!(
1273                    "this frame removed {wanted} nodes declaring `exit` and the exit store \
1274                     holds {}, so none of that removal animated: every departing node \
1275                     vanished at once, as a node with no `exit` does; `exit` is per node, \
1276                     and a list that drops many rows at once wants it on the list, not on \
1277                     every row",
1278                    crate::depart::MAX_NODES
1279                ),
1280            });
1281            return;
1282        }
1283        // The previous frame's paint order, built on the first departure:
1284        // a frame with one is a frame that changed shape and paid for a
1285        // layout, and the frames that did not never get here.
1286        let mut order: Option<PaintOrder> = None;
1287        for &i in &roots {
1288            let place = order
1289                .get_or_insert_with(|| PaintOrder::of(&self.prev_tree, &self.tree))
1290                .place(&self.prev_tree, i, &self.float_stack);
1291            // The group opacity the root inherited from ancestors that are
1292            // now gone: a subtree already half-faded departs from there.
1293            let mut base = 1.0;
1294            let mut a = self.prev_tree.parent[i];
1295            while a != NIL {
1296                base *= self.prev_tree.specs[a as usize].style.opacity;
1297                a = self.prev_tree.parent[a as usize];
1298            }
1299            self.depart.depart(
1300                &self.prev_tree,
1301                i,
1302                now,
1303                base,
1304                place,
1305                &self.text,
1306                &self.lines,
1307                &self.fragments,
1308                &self.paths,
1309            );
1310        }
1311        // Named now, while a tree still has them (backlog F111).
1312        self.trace_departures(&roots);
1313    }
1314
1315    /// One departing subtree's quads: frozen rects moved by however far
1316    /// its `exit` has got, outside every clip (its ancestors may be gone).
1317    /// A smaller `emit_node`: the parts a picture has (shadow, background,
1318    /// border, its content) and none of the parts a node has — no hit
1319    /// region, no scroll region, no access row.
1320    fn emit_ghost(&mut self, g: &Ghost, play: &Playback, scale: f32) {
1321        // Whole physical pixels, for the reason a slide's is: a ghost is
1322        // mostly text, and it moves for its whole life.
1323        let offset = play.offset.snapped(scale);
1324        self.ghost_opacity.clear();
1325        self.ghost_opacity.resize(g.nodes.len(), 1.0);
1326        self.ghost_clip.clear();
1327        self.ghost_clip.resize(g.nodes.len(), Clip::NONE);
1328        self.ghost_clip_ids.clear();
1329        self.ghost_clip_ids.resize(g.nodes.len(), NO_CLIP_ID);
1330        self.ghost_rect.clear();
1331        self.ghost_rect
1332            .resize(g.nodes.len(), Rect::new(0.0, 0.0, 0.0, 0.0));
1333        for (i, node) in g.nodes.iter().enumerate() {
1334            let mut rect = Rect::new(
1335                node.rect.x + offset.x,
1336                node.rect.y + offset.y,
1337                node.rect.w,
1338                node.rect.h,
1339            );
1340            let mut style = node.spec.style;
1341            let inherited = if node.parent == NIL {
1342                // The root carries the eased slots; an `exit` says nothing
1343                // about the subtree under it, which fades and moves with
1344                // its root and no more.
1345                if let Some(bg) = play.bg {
1346                    style.bg = bg;
1347                }
1348                if let Some(radius) = play.radius {
1349                    style.radius = radius;
1350                }
1351                if let Some((w, h)) = play.size {
1352                    // The root's own box only: the subtree inside it is a
1353                    // picture, and re-laying it out is the one thing a
1354                    // frozen ghost must not do.
1355                    rect.w = w.unwrap_or(rect.w);
1356                    rect.h = h.unwrap_or(rect.h);
1357                }
1358                style.opacity = play.opacity;
1359                play.base_opacity
1360            } else {
1361                self.ghost_opacity[node.parent as usize]
1362            };
1363            let opacity = (inherited * style.opacity).clamp(0.0, 1.0);
1364            self.ghost_opacity[i] = opacity;
1365            // The clip is the subtree's own: a scroll box or `clip` node
1366            // inside the picture still bounds what it held (the rows a
1367            // virtual list built past its edge stay past it), while the
1368            // ancestors outside the picture, which may be gone, clip
1369            // nothing. Same rule as the live pass, from the root down —
1370            // a parent-anchored float with `clip`, a stroke or polygon
1371            // included, is held by the parent's clip as a child is (F78,
1372            // F90; the ghost pass kept the old escape, RG26).
1373            let escapes = node
1374                .spec
1375                .layout
1376                .float
1377                .is_some_and(|f| !f.clipped_by_parent());
1378            let (clip, clip_id) = if node.parent == NIL || escapes {
1379                (Clip::NONE, NO_CLIP_ID)
1380            } else {
1381                let p = node.parent as usize;
1382                let inherited = self.ghost_clip[p];
1383                if g.nodes[p].spec.layout.clips() {
1384                    let clip =
1385                        inherited.intersect(self.ghost_rect[p], g.nodes[p].spec.style.radius);
1386                    (clip, self.display.intern_clip(clip.scaled(scale)))
1387                } else {
1388                    (inherited, self.ghost_clip_ids[p])
1389                }
1390            };
1391            self.ghost_clip[i] = clip;
1392            self.ghost_clip_ids[i] = clip_id;
1393            self.ghost_rect[i] = rect;
1394            let visible = rect.intersect(&clip.rect);
1395            if visible.w <= 0.0 || visible.h <= 0.0 {
1396                continue;
1397            }
1398            let leaf = match node.content {
1399                GhostContent::Container => Leaf::Container,
1400                // None once the shaped buffer has been evicted: a ghost
1401                // older than the text cache draws no text rather than
1402                // somebody else's. A departing subtree takes no input and
1403                // holds no selection; it records no place either.
1404                GhostContent::Text { cache_key, color } => {
1405                    match self.text.readd(cache_key, color) {
1406                        Some(tid) => Leaf::Text { tid, sel: None },
1407                        None => Leaf::Container,
1408                    }
1409                }
1410                // Never focused: the departing subtree gave the keyboard
1411                // up the frame it stopped being declared.
1412                GhostContent::Edit(key) => Leaf::Edit {
1413                    key,
1414                    focused: false,
1415                    pad: node.spec.layout.padding,
1416                },
1417                GhostContent::Image(id, opts) => Leaf::Image(id, opts),
1418                // The picture is frozen at departure — the parameters are
1419                // the ones the node last declared — while the box eases
1420                // and the group opacity fades it.
1421                GhostContent::Fragment(draw) => Leaf::Fragment(draw),
1422                GhostContent::Polygon(draw) => Leaf::Polygon(draw),
1423                // The points are the ghost's own copy; the colour is the
1424                // `bg` slot, which `play.bg` eases on the root.
1425                GhostContent::Line { first, len, width } => Leaf::Line {
1426                    points: &g.points[first as usize..(first + len) as usize],
1427                    width,
1428                },
1429                // The ops are the ghost's own copy, the hash the live
1430                // node's, so the masks are the slots it already had.
1431                GhostContent::Path {
1432                    first,
1433                    len,
1434                    rule,
1435                    stroke_w,
1436                    hash,
1437                    angle,
1438                } => Leaf::Path {
1439                    ops: &g.ops[first as usize..(first + len) as usize],
1440                    rule,
1441                    stroke_w,
1442                    hash,
1443                    angle,
1444                    animating: false,
1445                },
1446            };
1447            let paint = Paint {
1448                clip,
1449                clip_id,
1450                scale,
1451                opacity,
1452            };
1453            painter!(self).paint_box(rect, &style, &paint, leaf);
1454            // A departing table keeps its grid for as long as it fades.
1455            if let Some(c) = node.spec.interact().rules
1456                && node.spec.layout.is_table()
1457            {
1458                self.emit_ghost_rules(g, i, rect, offset, c, &paint);
1459            }
1460        }
1461    }
1462
1463    /// After layout: nodes that `slide` ease from last frame's position
1464    /// toward where layout put them, carrying their subtree along (hit
1465    /// regions come from the same positions, so input follows the motion).
1466    /// Nodes whose `enter` has an offset start that far away on first
1467    /// sight and ease in the same way; without `slide` that entrance is
1468    /// all their position ever eases. Preorder means a parent shifts before
1469    /// its children are visited, so nested sliders ease relative to an
1470    /// already-eased parent.
1471    fn ease_positions(&mut self) {
1472        for i in 0..self.tree.len() {
1473            let spec = &self.tree.specs[i];
1474            let Some(t) = spec.transition else {
1475                continue;
1476            };
1477            let enter = spec.anim().enter.filter(|e| e.offsets());
1478            if !spec.slide && enter.is_none() {
1479                continue;
1480            }
1481            let key = self.tree.keys[i];
1482            let target = self.tree.pos[i];
1483            let from = enter.map(|e| [target.x + e.dx, target.y + e.dy, 0.0, 0.0]);
1484            let v = self.anim.drive(
1485                key,
1486                Slot::Pos,
1487                from,
1488                [target.x, target.y, 0.0, 0.0],
1489                t,
1490                spec.slide,
1491            );
1492            // Whole physical pixels, so the text inside moves with its box
1493            // rather than wobbling in it (`Vec2::snapped`). Rounding here
1494            // and not at the end means the last frame lands exactly on the
1495            // layout position, as it did before.
1496            let d = Vec2::new(v[0] - target.x, v[1] - target.y).snapped(self.scale);
1497            if d.x == 0.0 && d.y == 0.0 {
1498                continue;
1499            }
1500            let end = self.tree.subtree_end(i);
1501            for p in &mut self.tree.pos[i..end] {
1502                p.x += d.x;
1503                p.y += d.y;
1504            }
1505        }
1506    }
1507
1508    /// After layout: every `on_layout` node whose rect differs from the one
1509    /// last reported for its key — or that was not seen last frame — posts
1510    /// `{kind="layout", x, y, w, h, parent, scale, tag}`, pending like a
1511    /// `resize`.
1512    /// A frame that leaves a node where it was posts nothing, so a view
1513    /// that stores the rect in its model and redraws does not loop.
1514    fn emit_layout_events(&mut self) {
1515        let frame_no = self.frame_no;
1516        for i in 0..self.tree.len() {
1517            let Some(tag) = &self.tree.specs[i].events().on_layout else {
1518                continue;
1519            };
1520            let key = self.tree.keys[i];
1521            let rect = Rect::from_pos_size(self.tree.pos[i], self.tree.size[i]);
1522            let changed = match self.layouts.get(&key) {
1523                Some((last, seen)) if *seen + 1 == frame_no => *last != rect,
1524                _ => true,
1525            };
1526            self.layouts.insert(key, (rect, frame_no));
1527            if !changed {
1528                continue;
1529            }
1530            let parent = self.tree.parent[i];
1531            let parent_rect = if parent == NIL {
1532                Rect::new(0.0, 0.0, self.viewport.w, self.viewport.h)
1533            } else {
1534                let p = parent as usize;
1535                Rect::from_pos_size(self.tree.pos[p], self.tree.size[p])
1536            };
1537            let rect_value = |r: Rect| {
1538                Value::map([
1539                    ("x", Value::Float(r.x as f64)),
1540                    ("y", Value::Float(r.y as f64)),
1541                    ("w", Value::Float(r.w as f64)),
1542                    ("h", Value::Float(r.h as f64)),
1543                ])
1544            };
1545            let payload = Value::map([
1546                ("kind", Value::str("layout")),
1547                ("x", Value::Float(rect.x as f64)),
1548                ("y", Value::Float(rect.y as f64)),
1549                ("w", Value::Float(rect.w as f64)),
1550                ("h", Value::Float(rect.h as f64)),
1551                ("parent", rect_value(parent_rect)),
1552                // Physical px per logical px at this node — the number a
1553                // view multiplies `w`/`h` by to know how many pixels to
1554                // render before `update_image` (ADR 0025, decision 5).
1555                // The frame's today; where a zoom would compose in.
1556                ("scale", Value::Float(self.scale as f64)),
1557            ]);
1558            self.pending
1559                .push(UiEvent::on(self.tree.origins[i], key, payload).tagged(Some(tag)));
1560        }
1561    }
1562
1563    /// The chrome of one layer, after its content: the bars of every
1564    /// scroller the layer emitted (`regions`), then the ring if the focused
1565    /// node is in this layer. `layer` is the float
1566    /// root's index, or `NIL` for the in-flow layer; `above` is the hit
1567    /// list's length now, which is what a bar records so a press can tell
1568    /// a region under it from one in a layer over it.
1569    fn emit_layer_chrome(
1570        &mut self,
1571        layer: u32,
1572        regions: &[ScrollRegion],
1573        scrollbars: &mut Vec<ScrollbarRegion>,
1574        above: usize,
1575        scale: f32,
1576    ) {
1577        let cursor = self.interaction.cursor();
1578        let above = above as u32;
1579        for r in regions {
1580            let i = r.node as usize;
1581            // The node's own bar style, if it declared one: hidden means
1582            // no thumb and no track, and the wheel still works because the
1583            // scroll region is already pushed. A handler has no offset to
1584            // draw a bar for.
1585            let style = self.tree.specs[i].interact().scrollbar;
1586            if r.handler || style.mode == crate::spec::ScrollbarMode::Hidden {
1587                continue;
1588            }
1589            let rest_w = style.width.unwrap_or(SCROLLBAR_W);
1590            let active_w = rest_w + (SCROLLBAR_ACTIVE_W - SCROLLBAR_W);
1591            // The grabbable gutter: the stock one, or enough for a wide
1592            // thumb and its inset.
1593            let hit_w = SCROLLBAR_HIT_W.max(active_w + 2.0 * SCROLLBAR_INSET);
1594            let max = self.tree.scroll_max[i];
1595            let offset = self.scroll.drawn(r.key);
1596            let clip_id = self.clip_ids.get(i).copied().unwrap_or(NO_CLIP_ID);
1597            let mut opacity = self.opacity.get(i).copied().unwrap_or(1.0);
1598            // The tracks, before either bar: an `auto` bar is held while
1599            // the pointer is on either track, and the two tracks share the
1600            // one quiet clock.
1601            let track_y = Rect::new(
1602                r.rect.x + r.rect.w - hit_w,
1603                r.rect.y + SCROLLBAR_INSET,
1604                hit_w,
1605                r.rect.h - 2.0 * SCROLLBAR_INSET,
1606            );
1607            let track_x = Rect::new(
1608                r.rect.x + SCROLLBAR_INSET,
1609                r.rect.y + r.rect.h - hit_w,
1610                r.rect.w - 2.0 * SCROLLBAR_INSET,
1611                hit_w,
1612            );
1613            if style.mode == crate::spec::ScrollbarMode::Auto
1614                && let Some(now) = self.anim.time()
1615            {
1616                let held = self.interaction.is_scrollbar_dragging(r.key, ScrollAxis::Y)
1617                    || self.interaction.is_scrollbar_dragging(r.key, ScrollAxis::X)
1618                    || cursor.is_some_and(|p| {
1619                        (max.y > 0.0 && track_y.contains(p)) || (max.x > 0.0 && track_x.contains(p))
1620                    });
1621                let idle = self.scroll.bar_idle(r.key, now, held);
1622                let shown = if idle < SCROLLBAR_HOLD {
1623                    1.0
1624                } else {
1625                    (1.0 - (idle - SCROLLBAR_HOLD) / SCROLLBAR_FADE).max(0.0)
1626                };
1627                if shown <= 0.0 {
1628                    continue; // faded out: no thumb, and no track to press
1629                }
1630                // Something to settle: the hold running out, or the fade.
1631                // Not while held — that is input's to end, and a frame a
1632                // hover would ask for every 8 ms is the idle CPU C27 fought.
1633                if !held {
1634                    self.owe_frame("scrollbar fade");
1635                }
1636                opacity *= shown as f32;
1637            }
1638            for (axis, track) in [(ScrollAxis::Y, track_y), (ScrollAxis::X, track_x)] {
1639                let (max_a, offset_a) = match axis {
1640                    ScrollAxis::Y => (max.y, offset.y),
1641                    ScrollAxis::X => (max.x, offset.x),
1642                };
1643                if max_a <= 0.0 {
1644                    continue;
1645                }
1646                let active = self.interaction.is_scrollbar_dragging(r.key, axis)
1647                    || cursor.is_some_and(|p| track.contains(p));
1648                let w = if active { active_w } else { rest_w };
1649                let (thumb, bar_len) = thumb_along(axis, r.rect, track, max_a, offset_a, w);
1650                let mut bar =
1651                    scrollbar_quad(thumb, scale, clip_id, self.thumb_color(&style, active));
1652                bar.color.a *= opacity;
1653                self.display.quads.push(bar);
1654                scrollbars.push(ScrollbarRegion {
1655                    key: r.key,
1656                    axis,
1657                    thumb,
1658                    track,
1659                    bar_len,
1660                    max: max_a,
1661                    inert: r.inert,
1662                    above,
1663                });
1664            }
1665        }
1666        self.emit_focus_ring(layer, scale);
1667    }
1668
1669    /// The layer node `i` paints in: its float root's index, `NIL` in flow
1670    /// — and `NIL` for every node of a frame that floats nothing, where the
1671    /// map is not even sized.
1672    #[inline]
1673    fn layer_of(&self, i: usize) -> u32 {
1674        self.float_root.get(i).copied().unwrap_or(NIL)
1675    }
1676
1677    /// This frame's float layers in paint order, from `roots` (the float
1678    /// roots in tree order) and the stack the last frame left: a root the
1679    /// stack knows keeps its place, one it does not is appended, in tree
1680    /// order, and a root the frame no longer declares is dropped. Writes
1681    /// the stack back for the next frame.
1682    ///
1683    /// The steady state — the same roots as last frame — is one pass over
1684    /// the stack and no allocation beyond the order itself: each entry
1685    /// remembers its root's rank in tree order, so the check and the
1686    /// answer are the same read.
1687    fn stack_floats(&mut self, roots: &[u32]) -> Vec<u32> {
1688        let keys = &self.tree.keys;
1689        let stack = &mut self.float_stack;
1690        let steady = stack.len() == roots.len()
1691            && stack
1692                .iter()
1693                .all(|&(k, rank)| keys[roots[rank as usize] as usize] == k);
1694        let order: Vec<u32> = if steady {
1695            stack
1696                .iter()
1697                .map(|&(_, rank)| roots[rank as usize])
1698                .collect()
1699        } else {
1700            // A float opened or closed: the ranks are found again, by key.
1701            let rank_of: FxHashMap<Key, u32> = roots
1702                .iter()
1703                .enumerate()
1704                .map(|(rank, &r)| (keys[r as usize], rank as u32))
1705                .collect();
1706            let mut placed = vec![false; roots.len()];
1707            let mut next: Vec<(Key, u32)> = Vec::with_capacity(roots.len());
1708            for &(k, _) in stack.iter() {
1709                if let Some(&rank) = rank_of.get(&k) {
1710                    placed[rank as usize] = true;
1711                    next.push((k, rank));
1712                }
1713            }
1714            for (rank, &r) in roots.iter().enumerate() {
1715                if !placed[rank] {
1716                    next.push((keys[r as usize], rank as u32));
1717                }
1718            }
1719            *stack = next;
1720            stack
1721                .iter()
1722                .map(|&(_, rank)| roots[rank as usize])
1723                .collect()
1724        };
1725        // A nested float is above the float it is in — by construction,
1726        // since its key derives from its parent's and so cannot have been
1727        // opened first; said here so the construction cannot drift.
1728        debug_assert!(order.iter().enumerate().all(|(pos, &r)| {
1729            let parent = self.tree.parent[r as usize];
1730            parent == NIL || {
1731                let outer = self.float_root[parent as usize];
1732                outer == NIL || order[..pos].contains(&outer)
1733            }
1734        }));
1735        order
1736    }
1737
1738    /// The `modal-behind-content` check, for the stack: a float layer
1739    /// above the modal's whose root is outside the modal's scope is inert
1740    /// and drawn over the one surface that takes input, which is the same
1741    /// defect the in-flow check names. Only a layer
1742    /// with something in it that *would* take input is the defect — a
1743    /// control the user sees and cannot press. A picture over the dialog
1744    /// (a HUD, the devtools' inspector outline) is not, and is not named.
1745    fn check_layers_over_modal(&mut self, order: &[u32]) {
1746        let Some((start, end, modal_key)) = self.modal else {
1747            return;
1748        };
1749        let modal_layer = self.layer_of(start);
1750        if modal_layer == NIL {
1751            return; // `diag::check_modal` has this case
1752        }
1753        let Some(at) = order.iter().position(|&r| r == modal_layer) else {
1754            return;
1755        };
1756        let over = order[at + 1..].iter().any(|&r| {
1757            let root = r as usize;
1758            !(start..end).contains(&root)
1759                && (root..self.tree.subtree_end(root)).any(|i| {
1760                    self.float_root[i] == r
1761                        && (self.tree.specs[i].hover_tracked()
1762                            || crate::access::focusable(&self.tree, i))
1763                })
1764        });
1765        if over {
1766            self.diag.raise(crate::diag::modal_under_layer(modal_key));
1767        }
1768    }
1769
1770    /// The default focus ring around the keyboard-visibly focused node, at
1771    /// the end of the layer the node is in — above every sibling that could
1772    /// touch it, under every layer over it — in the same display list
1773    /// every binding draws. Not for editors (the caret shows focus), key
1774    /// sinks (an app surface styles itself, through `is_focused` /
1775    /// `focus_visible`) or nodes declaring `focus_bg`.
1776    fn emit_focus_ring(&mut self, layer: u32, scale: f32) {
1777        if !self.focus_visible {
1778            return;
1779        }
1780        let Some(i) = self.focus_index() else {
1781            return;
1782        };
1783        if self.layer_of(i) != layer {
1784            return;
1785        }
1786        let spec = &self.tree.specs[i];
1787        let editor = matches!(self.tree.content[i], NodeContent::Edit(_))
1788            || spec
1789                .access()
1790                .role
1791                .is_some_and(crate::access::Role::is_editor);
1792        if editor
1793            || spec.events().on_key.is_some()
1794            || spec.interact().focus_bg.is_some()
1795            || spec.disabled
1796        {
1797            return;
1798        }
1799        let node = Rect::from_pos_size(self.tree.pos[i], self.tree.size[i]);
1800        let rect = Rect::new(
1801            node.x - FOCUS_RING_GAP,
1802            node.y - FOCUS_RING_GAP,
1803            node.w + 2.0 * FOCUS_RING_GAP,
1804            node.h + 2.0 * FOCUS_RING_GAP,
1805        );
1806        let clip = self.clips.get(i).copied().unwrap_or(Clip::NONE);
1807        // No entry means nothing clipped this frame, which is entry zero.
1808        let clip_id = self.clip_ids.get(i).copied().unwrap_or(NO_CLIP_ID);
1809        let visible = rect.intersect(&clip.rect);
1810        if visible.w <= 0.0 || visible.h <= 0.0 {
1811            return;
1812        }
1813        let mut ring = self.theme().focus_ring;
1814        ring.a *= self.opacity.get(i).copied().unwrap_or(1.0);
1815        self.display.quads.push(Quad {
1816            rect: rect.scaled(scale),
1817            color: Color::TRANSPARENT,
1818            border_color: ring,
1819            radius: spec.style.radius.map(|r| (r + FOCUS_RING_GAP) * scale),
1820            border_w: FOCUS_RING_W * scale,
1821            blur: 0.0,
1822            kind: QuadKind::Solid,
1823            clip: clip_id,
1824            uv: [0; 4],
1825        });
1826    }
1827
1828    /// See the `ime_rect` field. None when nothing with a caret is
1829    /// focused: neither a stock editor nor a sink holding a `line` that
1830    /// declares one.
1831    pub fn ime_rect(&self) -> Option<Rect> {
1832        self.ime_rect
1833    }
1834
1835    /// The stock editor `key`'s node and its caret rect in viewport
1836    /// coordinates, from the frame laid out: the editor's own caret
1837    /// (physical px inside its text box) placed at the node's content
1838    /// origin. The one place this arithmetic lives — the
1839    /// IME anchor and the scroll-into-view both read it. None when no
1840    /// node of this frame is that editor, or it has no caret.
1841    pub(crate) fn stock_caret_viewport_rect(&mut self, key: Key) -> Option<(usize, Rect)> {
1842        let i = (0..self.tree.len()).find(|&i| self.tree.content[i] == NodeContent::Edit(key))?;
1843        let caret = self.edit_with_fonts(|edit, fs| edit.caret_rect(key, fs))?;
1844        let pad = self.tree.specs[i].layout.padding;
1845        Some((
1846            i,
1847            Rect::new(
1848                self.tree.pos[i].x + pad.l + caret.x / self.scale,
1849                self.tree.pos[i].y + pad.t + caret.y / self.scale,
1850                caret.w / self.scale,
1851                caret.h / self.scale,
1852            ),
1853        ))
1854    }
1855
1856    fn focused_caret_rect(&mut self) -> Option<Rect> {
1857        if let Some(key) = self.edit.focused() {
1858            return self.stock_caret_viewport_rect(key).map(|(_, r)| r);
1859        }
1860        // A custom editor (backlog C17): the focused node's subtree holds
1861        // the `line` rows it draws, and the one carrying `caret` says
1862        // where the caret is — a byte offset into that line's runs, which
1863        // is the question `caret_rect` answers. This runs after the text
1864        // pass, so the places it reads are this frame's.
1865        let (l, caret, _) = self.sink_caret_line()?;
1866        self.text.caret_at(self.tree.keys[l], caret as usize, false)
1867    }
1868
1869    /// The `line` under the focused node that declares `caret`, the
1870    /// offset it declares, and whether it declares the caret
1871    /// `caret_solid` — a custom editor's caret, in the frame just built.
1872    /// None with a stock editor focused, or nothing declaring one.
1873    fn sink_caret_line(&self) -> Option<(usize, u32, bool)> {
1874        if !self.tree.any_line {
1875            return None;
1876        }
1877        // The editor whose caret this is: the focused sink, or the sink
1878        // enclosing a focused control inside it — the node keys and
1879        // commits already go to (`key_target`, `sink_event`). Read from
1880        // the focused node alone, focus on a pane button inside a custom
1881        // editor un-armed the blink clock and lost the IME its anchor
1882        // while the keys kept arriving (backlog AR29).
1883        let i = self.focus_index()?;
1884        let i = if self.tree.specs[i].events().on_key.is_some() {
1885            i
1886        } else {
1887            self.enclosing_sink(i)?
1888        };
1889        // The candidates are the editor's lines as the access tree reads
1890        // them — `role="none"` subtrees (a gutter) skipped, a line's own
1891        // subtree not descended into — and the *last* one declaring a
1892        // caret is the caret, as `custom_editor` reads it.
1893        let l = crate::access::lines_under(&self.tree, i)
1894            .into_iter()
1895            .rev()
1896            .find(|&l| self.tree.specs[l].access().caret.is_some())?;
1897        let access = self.tree.specs[l].access();
1898        Some((l, access.caret?, access.caret_solid))
1899    }
1900
1901    /// Remembers this frame's custom-editor caret and bumps the stamp when
1902    /// it is not last frame's: the blink clock reads both.
1903    /// Whether it is solid is kept beside it, not in it: a caret going
1904    /// from bar to block has not moved, and the clock re-arms on the
1905    /// way back from `has_caret` alone.
1906    fn note_sink_caret(&mut self) {
1907        let (now, solid) = if self.edit.focused().is_some() {
1908            (None, false)
1909        } else {
1910            match self.sink_caret_line() {
1911                Some((l, offset, solid)) => (Some((self.tree.keys[l], offset)), solid),
1912                None => (None, false),
1913            }
1914        };
1915        if now != self.sink_caret {
1916            self.sink_caret = now;
1917            self.sink_caret_stamp += 1;
1918        }
1919        self.sink_caret_solid = solid;
1920    }
1921
1922    // -- The caret's blink --------------------------------------------
1923    // The clock is the driver's (a frame twice a second is a decision
1924    // about the window, not the tree); what the core keeps is whether
1925    // there is a caret to blink, when it moved, and the phase the driver
1926    // last set — for the stock editor, which paints its own caret on the
1927    // phase, and for a custom one, which reads it (backlog C35).
1928
1929    /// Whether there is a caret to blink: a focused stock editor's, or the
1930    /// `caret` a `line` under the focused custom editor declares — unless
1931    /// that line declares it `caret_solid`, which is a caret to anchor
1932    /// the IME and read to assistive technology but not one to blink. A
1933    /// driver arms its blink clock while this is true and leaves the
1934    /// caret solid otherwise.
1935    pub fn has_caret(&self) -> bool {
1936        self.edit.focused().is_some() || (self.sink_caret.is_some() && !self.sink_caret_solid)
1937    }
1938
1939    /// Changes whenever the caret moved or focus changed — the stock
1940    /// editor's caret through typing or a click, a custom editor's
1941    /// through the `caret` row it declares — so a driver comparing it
1942    /// across frames re-arms the blink with the caret solid, the way a
1943    /// caret that just moved is never mid-blink.
1944    pub fn caret_stamp(&self) -> u64 {
1945        self.edit.caret_stamp().wrapping_add(self.sink_caret_stamp)
1946    }
1947
1948    /// The blink phase, as the driver last set it: `true` draws the
1949    /// caret. The stock editor reads it itself; a custom editor reads it
1950    /// in `view` (`Ui::caret_visible`) and skips its caret node on the
1951    /// off phase, so the two blink in step — and a window without the
1952    /// keyboard, where the driver parks it hidden, shows neither.
1953    /// Headless it stays `true`.
1954    pub fn caret_visible(&self) -> bool {
1955        self.edit.blink_visible()
1956    }
1957
1958    /// Sets the blink phase; the driver's, on its clock. A frame is the
1959    /// caller's to ask for.
1960    pub fn set_caret_visible(&mut self, visible: bool) {
1961        self.edit.set_blink_visible(visible);
1962    }
1963}
1964
1965/// Combined measurer handed to the layout pass: static text through the
1966/// shape cache, editors through the edit store (sharing one FontSystem),
1967/// images through the resource registry.
1968struct Measure<'a> {
1969    text: &'a mut TextSystem,
1970    cells: &'a mut crate::cells::CellStore,
1971    fonts: &'a mut cosmic_text::FontSystem,
1972    edit: &'a mut EditStore,
1973    resources: &'a Resources,
1974}
1975
1976impl TextMeasure for Measure<'_> {
1977    fn intrinsic(&mut self, id: crate::tree::TextId) -> Size {
1978        self.text.intrinsic(id)
1979    }
1980
1981    fn wrapped(&mut self, id: crate::tree::TextId, max_w: f32) -> Size {
1982        self.text.wrapped(id, max_w, self.fonts)
1983    }
1984
1985    fn min_content(&mut self, id: crate::tree::TextId) -> f32 {
1986        self.text.min_content(id)
1987    }
1988
1989    fn edit_intrinsic(&mut self, key: Key) -> Size {
1990        self.edit.intrinsic(key, self.fonts)
1991    }
1992
1993    fn edit_wrapped(&mut self, key: Key, max_w: f32) -> Size {
1994        self.edit.wrapped(key, max_w, self.fonts)
1995    }
1996
1997    fn baseline(&mut self, id: crate::tree::TextId) -> f32 {
1998        self.text.baseline(id)
1999    }
2000
2001    fn edit_baseline(&mut self, key: Key) -> f32 {
2002        self.edit.baseline(key)
2003    }
2004
2005    fn cells_size(&mut self, id: crate::cells::CellsId) -> Size {
2006        self.cells.size(id, self.resources, self.fonts)
2007    }
2008
2009    fn image_size(&mut self, id: crate::resources::ImageId) -> Size {
2010        self.resources
2011            .image(id)
2012            .map_or(Size::ZERO, |e| Size::new(e.width as f32, e.height as f32))
2013    }
2014}
2015
2016/// What a node inherits at emission time: the frame's scale, the clip its
2017/// ancestors imposed (logical px), and the group opacity its own `opacity`
2018/// and every ancestor's multiply out to.
2019#[derive(Clone, Copy)]
2020/// What a box holds, resolved to what painting it needs: the live pass
2021/// resolves a text node's selection and an editor's focus from the frame,
2022/// a ghost resolves nothing (no selection, never focused, its text re-added
2023/// from the cache) — and both hand the result here.
2024enum Leaf<'a> {
2025    Container,
2026    Text {
2027        tid: crate::tree::TextId,
2028        sel: Option<((usize, usize), Color)>,
2029    },
2030    Cells {
2031        cid: crate::cells::CellsId,
2032        at: Vec2,
2033        sel: Option<&'a crate::select::CellSelection>,
2034        tint: Color,
2035    },
2036    Edit {
2037        key: Key,
2038        focused: bool,
2039        /// The box's padding: the text starts inside it.
2040        pad: crate::geom::Edges,
2041    },
2042    Image(crate::resources::ImageId, crate::resources::ImageOpts),
2043    Fragment(crate::fragment::Draw),
2044    /// A polygon's draw; the fill is the node's `bg`, put through the
2045    /// fragment quad's colour rather than a box under it.
2046    Polygon(crate::fragment::Draw),
2047    Line {
2048        points: &'a [Vec2],
2049        width: f32,
2050    },
2051    /// A path's run; the fill is the node's `bg`, the stroke its border
2052    /// colour and width, each a mask quad from the atlas (ADR 0040).
2053    Path {
2054        ops: &'a [crate::path::PathOp],
2055        rule: crate::path::FillRule,
2056        stroke_w: f32,
2057        hash: u64,
2058        /// The turn in radians of a path that declared one (ADR 0041).
2059        angle: Option<f32>,
2060        animating: bool,
2061    },
2062}
2063
2064/// One box's paint: its shadow, its fill and border, its content, faded by
2065/// the group opacity — written once for the live node and the ghost, which
2066/// differ in what they *record* (hit regions, scroll regions, a text's
2067/// place) and not in what they draw. The two were the same hundred and
2068/// twenty lines until F41 had to be fixed in both.
2069struct Painter<'a> {
2070    display: &'a mut DisplayList,
2071    text: &'a mut TextSystem,
2072    edit: &'a mut EditStore,
2073    cells: &'a mut crate::cells::CellStore,
2074    atlas: &'a mut GlyphAtlas,
2075    session: &'a Session,
2076    /// The masks drawn from textures of their own (ADR 0040).
2077    path_tex: &'a mut crate::path::PathTextures,
2078    frame_no: u64,
2079}
2080
2081impl Painter<'_> {
2082    /// Inlined into its two callers: a call per node with the borrows
2083    /// packed into a struct measured +2.5% on `frame_10k_rects`.
2084    /// What a leaf draws, and a shadow, are calls (`paint_leaf`,
2085    /// `shadow_quad`): inlined as well, they made every box pay for them.
2086    #[inline(always)]
2087    fn paint_box(
2088        &mut self,
2089        rect: Rect,
2090        style: &crate::spec::VisualStyle,
2091        paint: &Paint,
2092        leaf: Leaf<'_>,
2093    ) {
2094        let Paint {
2095            clip,
2096            clip_id,
2097            scale,
2098            opacity,
2099        } = *paint;
2100        let clip_px = clip.scaled(scale);
2101        let first_quad = self.display.quads.len();
2102        if style.shadow.is_visible() {
2103            self.display
2104                .quads
2105                .push(shadow_quad(style, rect, clip_id, scale));
2106        }
2107        // A stroke's `bg` is its colour, not a box to fill (ADR 0010,
2108        // decision 7), and a polygon's is its fill (ADR 0025, decision 6)
2109        // — for the ghost of one as much as for the live one.
2110        let is_line = matches!(
2111            leaf,
2112            Leaf::Line { .. } | Leaf::Polygon(_) | Leaf::Path { .. }
2113        );
2114        if !is_line
2115            && (style.bg.is_visible() || (style.border_w > 0.0 && style.border_color.is_visible()))
2116        {
2117            // Where layout put it, or on whole pixels when it asked
2118            // (`pixelSnap`), from the same numbers a text's backgrounds are.
2119            let px = rect.scaled(scale);
2120            self.display.quads.push(Quad {
2121                rect: if style.pixel_snap { px.on_pixels() } else { px },
2122                color: style.bg,
2123                border_color: style.border_color,
2124                radius: style.radius.map(|r| r * scale),
2125                border_w: style.border_w * scale,
2126                blur: 0.0,
2127                kind: QuadKind::Solid,
2128                clip: clip_id,
2129                uv: [0; 4],
2130            });
2131        }
2132        if !matches!(leaf, Leaf::Container) {
2133            self.paint_leaf(rect, style, paint, clip_px, leaf);
2134        }
2135        if opacity < 1.0 {
2136            fade(&mut self.display.quads[first_quad..], opacity);
2137        }
2138    }
2139
2140    /// What a leaf draws inside its box: text, cells, an editor, an
2141    /// image, a fragment, a polygon's fill or a stroke. Out of line, so the
2142    /// kinds a plain box never takes do not weigh on every node's
2143    /// `emit_node` — its saved registers and its stack frame.
2144    #[inline(never)]
2145    fn paint_leaf(
2146        &mut self,
2147        rect: Rect,
2148        style: &crate::spec::VisualStyle,
2149        paint: &Paint,
2150        clip_px: crate::display::Clip,
2151        leaf: Leaf<'_>,
2152    ) {
2153        let Paint { clip_id, scale, .. } = *paint;
2154        match leaf {
2155            Leaf::Container => {}
2156            Leaf::Text { tid, sel } => {
2157                let sess = &mut *self.session.state();
2158                self.text.emit(
2159                    tid,
2160                    Vec2::new(rect.x, rect.y),
2161                    Size::new(rect.w, rect.h),
2162                    clip_px,
2163                    clip_id,
2164                    &mut self.display.clips,
2165                    &sess.resources,
2166                    &mut sess.fonts,
2167                    self.atlas,
2168                    &mut self.display.quads,
2169                    sel,
2170                );
2171            }
2172            Leaf::Cells { cid, at, sel, tint } => {
2173                let sess = &mut *self.session.state();
2174                self.cells.emit(
2175                    cid,
2176                    at,
2177                    clip_px,
2178                    clip_id,
2179                    &sess.resources,
2180                    &mut sess.fonts,
2181                    self.text.raster_mut(),
2182                    self.atlas,
2183                    &mut self.display.quads,
2184                    sel.map(|s| (s, tint)),
2185                );
2186            }
2187            Leaf::Edit { key, focused, pad } => {
2188                let origin = Vec2::new(
2189                    crate::geom::snap_px((rect.x + pad.l) * scale),
2190                    crate::geom::snap_px((rect.y + pad.t) * scale),
2191                );
2192                // A field bounds its own text horizontally — it is what
2193                // makes scrolling one legible rather than a line running
2194                // out over its neighbours (F41). Horizontally only: the
2195                // ancestors own the vertical clip, and a descender or a
2196                // caret is not what a field is trying to cut off.
2197                // Narrowing the clip makes a new one, so it needs an entry
2198                // of its own; an editor that folds to its width — a
2199                // document, or a field with `wrap` (F44) — keeps the node's.
2200                let (edit_clip, edit_clip_id) = if self.edit.folds(key) {
2201                    (clip_px, clip_id)
2202                } else {
2203                    let narrowed = clip_px.intersect(
2204                        Rect::new(
2205                            origin.x,
2206                            clip_px.rect.y,
2207                            (rect.w - pad.x()).max(0.0) * scale,
2208                            clip_px.rect.h,
2209                        ),
2210                        crate::display::SQUARE,
2211                    );
2212                    (narrowed, self.display.intern_clip(narrowed))
2213                };
2214                let sess = &mut *self.session.state();
2215                self.edit.emit(
2216                    key,
2217                    origin,
2218                    focused,
2219                    edit_clip,
2220                    edit_clip_id,
2221                    &mut sess.fonts,
2222                    self.text,
2223                    self.atlas,
2224                    &mut self.display.quads,
2225                );
2226            }
2227            Leaf::Image(id, opts) => {
2228                let sess = self.session.state();
2229                if let Some(entry) = sess.resources.image(id) {
2230                    // Atlas-backed unless the entry says otherwise — or
2231                    // unless the atlas cannot take it after all, which
2232                    // used to draw nothing (ADR 0025, decision 2).
2233                    let slot =
2234                        match entry.backing {
2235                            crate::resources::ImageBacking::Atlas => self
2236                                .atlas
2237                                .get_or_insert_image(id, entry.width, entry.height, &entry.rgba),
2238                            crate::resources::ImageBacking::Texture => None,
2239                        };
2240                    let (kind, uv) = match slot {
2241                        Some(slot) => (QuadKind::Image, [slot.x, slot.y, slot.w, slot.h]),
2242                        None => (
2243                            QuadKind::Texture,
2244                            [self.display.textures.len() as u32, 0, 0, 0],
2245                        ),
2246                    };
2247                    let mut uv = uv;
2248                    let (rect, tex_uv) = fit_image(
2249                        opts.fit,
2250                        rect,
2251                        Size::new(entry.width as f32, entry.height as f32),
2252                        [0, 0, entry.width, entry.height],
2253                    );
2254                    if kind == QuadKind::Image {
2255                        // The crop, if any, applied inside the atlas slot.
2256                        uv = [uv[0] + tex_uv[0], uv[1] + tex_uv[1], tex_uv[2], tex_uv[3]];
2257                    } else {
2258                        self.display
2259                            .textures
2260                            .push(crate::display::TextureDraw { id, uv: tex_uv });
2261                        self.display
2262                            .texture_pixels
2263                            .push(crate::display::TexturePixels {
2264                                width: entry.width,
2265                                height: entry.height,
2266                                rev: entry.rev,
2267                                rgba: entry.rgba.clone(),
2268                            });
2269                    }
2270                    self.display.quads.push(Quad {
2271                        rect: rect.scaled(scale),
2272                        // White = untinted; radius rounds like a solid.
2273                        color: Color::WHITE,
2274                        border_color: Color::TRANSPARENT,
2275                        radius: style.radius.map(|r| r * scale),
2276                        // The sampling flag rides the slot an image never
2277                        // had a border in (decision 4).
2278                        border_w: match opts.sampling {
2279                            crate::resources::Sampling::Linear => 0.0,
2280                            crate::resources::Sampling::Nearest => 1.0,
2281                        },
2282                        blur: 0.0,
2283                        kind,
2284                        clip: clip_id,
2285                        uv,
2286                    });
2287                }
2288            }
2289            Leaf::Fragment(draw) => {
2290                // On whole pixels when the node asked (`pixelSnap`), as its
2291                // background is, so a stack of fragments meets seamlessly.
2292                let px = rect.scaled(scale);
2293                push_fragment(
2294                    self.display,
2295                    self.atlas,
2296                    &self.session.state().resources,
2297                    draw,
2298                    if style.pixel_snap { px.on_pixels() } else { px },
2299                    style.radius.map(|r| r * scale),
2300                    clip_id,
2301                    Color::WHITE,
2302                );
2303            }
2304            Leaf::Polygon(draw) => {
2305                if style.bg.is_visible() {
2306                    push_fragment(
2307                        self.display,
2308                        self.atlas,
2309                        &self.session.state().resources,
2310                        draw,
2311                        rect.scaled(scale),
2312                        crate::display::SQUARE,
2313                        clip_id,
2314                        style.bg,
2315                    );
2316                }
2317            }
2318            Leaf::Line { points, width } => {
2319                push_segments(
2320                    &mut self.display.quads,
2321                    Vec2::new(rect.x, rect.y),
2322                    points,
2323                    width,
2324                    style.bg,
2325                    clip_id,
2326                    scale,
2327                );
2328            }
2329            Leaf::Path {
2330                ops,
2331                rule,
2332                stroke_w,
2333                hash,
2334                angle,
2335                animating,
2336            } => {
2337                let px = rect.scaled(scale);
2338                let mask = match angle {
2339                    // The mask covers the node's box at physical scale, a
2340                    // pixel over for the ramp, and is drawn on whole
2341                    // pixels with the box's fractional offset baked in at
2342                    // the nearest quarter — the bins a glyph is keyed on
2343                    // — so the quad never resamples it.
2344                    None => {
2345                        let bin_of = |f: f32| -> (f32, u8) {
2346                            let b = (f * 4.0).round() as u8;
2347                            if b >= 4 { (1.0, 0) } else { (0.0, b) }
2348                        };
2349                        let (fx, fy) = (px.x.floor(), px.y.floor());
2350                        let (cx, bx) = bin_of(px.x - fx);
2351                        let (cy, by) = bin_of(px.y - fy);
2352                        MaskAt {
2353                            at: Rect::new(
2354                                fx + cx,
2355                                fy + cy,
2356                                (px.w.ceil() + 1.0).max(1.0),
2357                                (px.h.ceil() + 1.0).max(1.0),
2358                            ),
2359                            bin: (bx, by),
2360                            off: Vec2::new(f32::from(bx) * 0.25, f32::from(by) * 0.25),
2361                            angle: 0.0,
2362                        }
2363                    }
2364                    // A turning path's box is the square about its pivot
2365                    // (ADR 0041): the mask is that square on an even
2366                    // number of pixels with the pivot at its centre, the
2367                    // centre on a whole pixel, so the quad turns about
2368                    // its own middle and, at no angle, is texel for texel.
2369                    Some(angle) => {
2370                        let side = 2.0 * (px.w * 0.5).ceil() + 2.0;
2371                        let (cx, cy) = ((px.x + px.w * 0.5).round(), (px.y + px.h * 0.5).round());
2372                        MaskAt {
2373                            at: Rect::new(cx - side * 0.5, cy - side * 0.5, side, side),
2374                            bin: crate::path::TURNED_BIN,
2375                            off: Vec2::new((side - px.w) * 0.5, (side - px.h) * 0.5),
2376                            angle,
2377                        }
2378                    }
2379                };
2380                if style.bg.is_visible() {
2381                    self.paint_mask(
2382                        ops,
2383                        hash,
2384                        scale,
2385                        mask,
2386                        crate::path::MaskPaint::Fill(rule),
2387                        style.bg,
2388                        clip_id,
2389                        animating,
2390                    );
2391                }
2392                if stroke_w > 0.0 && style.border_color.is_visible() {
2393                    self.paint_mask(
2394                        ops,
2395                        hash,
2396                        scale,
2397                        mask,
2398                        crate::path::MaskPaint::Stroke(stroke_w * scale),
2399                        style.border_color,
2400                        clip_id,
2401                        animating,
2402                    );
2403                }
2404            }
2405        }
2406    }
2407
2408    /// One mask quad of a path: from the atlas when the mask fits it and
2409    /// the path is still, from a texture of its own otherwise (ADR 0040,
2410    /// decisions 6–8), turned by the quad when the path turns (ADR 0041).
2411    #[allow(clippy::too_many_arguments)]
2412    fn paint_mask(
2413        &mut self,
2414        ops: &[crate::path::PathOp],
2415        hash: u64,
2416        scale: f32,
2417        mask: MaskAt,
2418        paint: crate::path::MaskPaint,
2419        color: Color,
2420        clip_id: ClipId,
2421        animating: bool,
2422    ) {
2423        let MaskAt {
2424            at,
2425            bin,
2426            off,
2427            angle,
2428        } = mask;
2429        let (w, h) = (at.w as u32, at.h as u32);
2430        let key = crate::path::mask_key(hash, scale, bin, paint);
2431        let texels = u64::from(w) * u64::from(h);
2432        let slot = if animating || texels >= crate::path::MAX_ATLAS_MASK_TEXELS {
2433            None
2434        } else {
2435            self.atlas.get_or_insert_path(key, w, h, || {
2436                crate::path::rasterize_at(ops, scale, off, w, h, paint)
2437            })
2438        };
2439        let (kind, uv) = match slot {
2440            Some(slot) => (QuadKind::GlyphMask, [slot.x, slot.y, slot.w, slot.h]),
2441            None => {
2442                let tex =
2443                    self.path_tex
2444                        .get_or_make(key, w, h, self.frame_no, self.session.id(), || {
2445                            crate::path::rasterize_at(ops, scale, off, w, h, paint)
2446                        });
2447                let index = self.display.textures.len() as u32;
2448                self.display.textures.push(crate::display::TextureDraw {
2449                    id: tex.id,
2450                    uv: [0, 0, w, h],
2451                });
2452                self.display
2453                    .texture_pixels
2454                    .push(crate::display::TexturePixels {
2455                        width: w,
2456                        height: h,
2457                        rev: 0,
2458                        rgba: tex.rgba.clone(),
2459                    });
2460                (QuadKind::Texture, [index, 0, 0, 0])
2461            }
2462        };
2463        self.display.quads.push(Quad {
2464            rect: at,
2465            color,
2466            border_color: Color::TRANSPARENT,
2467            radius: crate::display::SQUARE,
2468            border_w: 0.0,
2469            // A mask has no blur; the slot carries its turn, in radians
2470            // about the quad's centre, 0 for none (ADR 0041, decision 4).
2471            blur: angle,
2472            kind,
2473            clip: clip_id,
2474            uv,
2475        });
2476    }
2477}
2478
2479/// Where one path's masks go: the quad in physical px, on whole pixels;
2480/// the bin its masks are keyed under; where the node's box begins inside
2481/// the mask; and the quad's turn in radians.
2482#[derive(Clone, Copy)]
2483struct MaskAt {
2484    at: Rect,
2485    bin: (u8, u8),
2486    off: Vec2,
2487    angle: f32,
2488}
2489
2490#[derive(Clone, Copy)]
2491struct Paint {
2492    clip: Clip,
2493    /// `clip`, scaled and interned: what the node's quads name.
2494    clip_id: ClipId,
2495    scale: f32,
2496    /// Multiplied into the alpha of every quad the node emits.
2497    opacity: f32,
2498}
2499
2500/// As much of the previous frame's paint order as a departure needs to
2501/// keep its place (see `depart::Place`): the layer each node painted in,
2502/// and for the in-flow layer the next node at or after each index that
2503/// this frame still declares. Built once per frame that has a departure,
2504/// from the previous tree and the stack as that frame left it.
2505struct PaintOrder {
2506    /// The previous tree's float roots, by node: `NIL` in flow.
2507    float_root: Vec<u32>,
2508    /// By previous-tree index: the next live in-flow node at or after it;
2509    /// one past the end reads `NIL`.
2510    next_live: Vec<u32>,
2511    /// This frame's keys.
2512    live: FxHashSet<Key>,
2513    /// This frame's float roots, by key.
2514    roots: FxHashSet<Key>,
2515}
2516
2517impl PaintOrder {
2518    fn of(prev: &Tree, tree: &Tree) -> Self {
2519        let n = prev.len();
2520        let live: FxHashSet<Key> = tree.keys.iter().copied().collect();
2521        let roots: FxHashSet<Key> = (0..tree.len())
2522            .filter(|&i| tree.specs[i].layout.float.is_some())
2523            .map(|i| tree.keys[i])
2524            .collect();
2525        let mut float_root = vec![NIL; n];
2526        for i in 0..n {
2527            let parent = prev.parent[i];
2528            float_root[i] = if prev.specs[i].layout.float.is_some() {
2529                i as u32
2530            } else if parent != NIL {
2531                float_root[parent as usize]
2532            } else {
2533                NIL
2534            };
2535        }
2536        let mut next_live = vec![NIL; n + 1];
2537        for j in (0..n).rev() {
2538            next_live[j] = next_live[j + 1];
2539            if float_root[j] == NIL && live.contains(&prev.keys[j]) {
2540                next_live[j] = j as u32;
2541            }
2542        }
2543        Self {
2544            float_root,
2545            next_live,
2546            live,
2547            roots,
2548        }
2549    }
2550
2551    /// The place the subtree rooted at `root` of the previous frame painted
2552    /// in: its layer, and what was painted right after it there that is
2553    /// still here. `stack` is the previous frame's float stack, bottom to
2554    /// top — where a departing float finds the layer that was over it.
2555    fn place(&self, prev: &Tree, root: usize, stack: &[(Key, u32)]) -> Place {
2556        let end = prev.subtree_end(root);
2557        let layer = self.float_root[root];
2558        if layer == NIL {
2559            let after = self.next_live[end];
2560            return Place::InFlow {
2561                before: (after != NIL).then(|| prev.keys[after as usize]),
2562            };
2563        }
2564        if layer as usize != root {
2565            // Inside a float: the next live node of the same layer, found
2566            // by a scan bounded by that layer's subtree — a departure is
2567            // rare and a float is small.
2568            let layer_end = prev.subtree_end(layer as usize);
2569            let before = (end..layer_end)
2570                .find(|&j| self.float_root[j] == layer && self.live.contains(&prev.keys[j]))
2571                .map(|j| prev.keys[j]);
2572            return Place::InLayer {
2573                layer: prev.keys[layer as usize],
2574                before,
2575            };
2576        }
2577        // A float root: under the first layer above it in the stack that
2578        // is still a float this frame.
2579        let key = prev.keys[root];
2580        let at = stack.iter().position(|&(k, _)| k == key);
2581        let before = at.and_then(|at| {
2582            stack[at + 1..]
2583                .iter()
2584                .map(|&(k, _)| k)
2585                .find(|k| self.roots.contains(k))
2586        });
2587        Place::Layer { before }
2588    }
2589}
2590
2591/// One [`QuadKind::Segment`] per straight piece of a stroke: `points` are
2592/// relative to `origin` (the node's box, logical px) and `width` is
2593/// logical; everything on the quad is physical. The rect is the piece's
2594/// bounding box padded by half the width plus two logical px, so the
2595/// backend's edge ramp is never cut by the quad's own edge, and the
2596/// endpoints ride in `uv` (see [`Quad::segment_ends`]).
2597///
2598/// Kept out of line on purpose: inlined into `emit_node`,
2599/// whose size moves with every prop a hit region grows, the loop's carried
2600/// point lost its register to the stack once the drop-zone commit tipped
2601/// the allocator — a store and a reload on every segment, +10% on
2602/// `frame_1k_curves`. On its own the loop keeps every value in a register.
2603#[inline(never)]
2604fn push_segments(
2605    quads: &mut Vec<Quad>,
2606    origin: Vec2,
2607    points: &[Vec2],
2608    width: f32,
2609    color: Color,
2610    clip_id: ClipId,
2611    scale: f32,
2612) {
2613    let pad = crate::line::pad(width) * scale;
2614    let w = width.max(0.0) * scale;
2615    for pair in points.windows(2) {
2616        let a = Vec2::new(
2617            (origin.x + pair[0].x) * scale,
2618            (origin.y + pair[0].y) * scale,
2619        );
2620        let b = Vec2::new(
2621            (origin.x + pair[1].x) * scale,
2622            (origin.y + pair[1].y) * scale,
2623        );
2624        let (x0, x1) = (a.x.min(b.x) - pad, a.x.max(b.x) + pad);
2625        let (y0, y1) = (a.y.min(b.y) - pad, a.y.max(b.y) + pad);
2626        quads.push(Quad {
2627            rect: Rect::new(x0, y0, x1 - x0, y1 - y0),
2628            color,
2629            border_color: Color::TRANSPARENT,
2630            radius: crate::display::SQUARE,
2631            border_w: w,
2632            blur: 0.0,
2633            kind: QuadKind::Segment,
2634            clip: clip_id,
2635            uv: Quad::segment_uv([a.x, a.y, b.x, b.y]),
2636        });
2637    }
2638}
2639
2640/// The drop shadow behind one node, in physical pixels. The quad is the
2641/// shadow's own shape — the node's rect moved by `dx`/`dy` and grown by
2642/// `spread` — inflated by `blur` on every side, because that is how far
2643/// the blurred edge reaches; the backend insets by `blur` again to find
2644/// the shape. Radii grow with the spread so a rounded box keeps its
2645/// silhouette instead of sprouting corners. Never inlined: in
2646/// `emit_node` its arithmetic took two more saved float registers for
2647/// every node, shadow or not.
2648#[inline(never)]
2649fn shadow_quad(style: &crate::spec::VisualStyle, rect: Rect, clip_id: ClipId, scale: f32) -> Quad {
2650    let sh = style.shadow;
2651    let blur = sh.blur.max(0.0);
2652    let shape = Rect::new(
2653        rect.x + sh.dx - sh.spread,
2654        rect.y + sh.dy - sh.spread,
2655        (rect.w + 2.0 * sh.spread).max(0.0),
2656        (rect.h + 2.0 * sh.spread).max(0.0),
2657    );
2658    let rect = if style.pixel_snap {
2659        // Snapped with its box, so it stays under it; the blur around it.
2660        let s = shape.scaled(scale).on_pixels();
2661        let b = blur * scale;
2662        Rect::new(s.x - b, s.y - b, s.w + 2.0 * b, s.h + 2.0 * b)
2663    } else {
2664        Rect::new(
2665            (shape.x - blur) * scale,
2666            (shape.y - blur) * scale,
2667            (shape.w + 2.0 * blur) * scale,
2668            (shape.h + 2.0 * blur) * scale,
2669        )
2670    };
2671    Quad {
2672        rect,
2673        color: sh.color,
2674        border_color: Color::TRANSPARENT,
2675        radius: style.radius.map(|r| (r + sh.spread).max(0.0) * scale),
2676        border_w: 0.0,
2677        blur: blur * scale,
2678        kind: QuadKind::Shadow,
2679        clip: clip_id,
2680        uv: [0; 4],
2681    }
2682}
2683
2684/// Multiplies a group opacity into a run of quads. Alpha only: every quad
2685/// kind reads `color.a` as its coverage, so one multiply fades a
2686/// background, a border, a glyph and an image alike.
2687fn fade(quads: &mut [Quad], opacity: f32) {
2688    for q in quads {
2689        q.color.a *= opacity;
2690        q.border_color.a *= opacity;
2691    }
2692}
2693
2694/// The thumb of a scrollbar along `axis`, inset from the far edge of the
2695/// scroller's `rect`, `w` thick, and its length along the track: the
2696/// track's share of the content that is visible, never shorter than
2697/// `SCROLLBAR_MIN`, placed by how far the content has scrolled. One
2698/// geometry for both bars — the Y bar and the X bar were the same thirty
2699/// lines with the axes swapped.
2700fn thumb_along(
2701    axis: ScrollAxis,
2702    rect: Rect,
2703    track: Rect,
2704    max: f32,
2705    offset: f32,
2706    w: f32,
2707) -> (Rect, f32) {
2708    let t = (offset / max).clamp(0.0, 1.0);
2709    match axis {
2710        ScrollAxis::Y => {
2711            let bar = (track.h * rect.h / (rect.h + max)).max(SCROLLBAR_MIN);
2712            let thumb = Rect::new(
2713                rect.x + rect.w - w - SCROLLBAR_INSET,
2714                track.y + t * (track.h - bar),
2715                w,
2716                bar,
2717            );
2718            (thumb, bar)
2719        }
2720        ScrollAxis::X => {
2721            let bar = (track.w * rect.w / (rect.w + max)).max(SCROLLBAR_MIN);
2722            let thumb = Rect::new(
2723                track.x + t * (track.w - bar),
2724                rect.y + rect.h - w - SCROLLBAR_INSET,
2725                bar,
2726                w,
2727            );
2728            (thumb, bar)
2729        }
2730    }
2731}
2732
2733/// One in-flow child of a table as its rules read it: its box, and for a
2734/// row (`layout::is_table_row`) its in-flow cells' boxes.
2735struct RuledChild {
2736    rect: Rect,
2737    cells: Option<Vec<Rect>>,
2738}
2739
2740/// The rules of a table at `rect` over its in-flow `children`, `rule_w` thick
2741/// (1 when not positive):
2742///
2743/// - across, one down the middle of each gap between two in-flow
2744///   children, the content box wide;
2745/// - down, one down the middle of each gap between the columns of the
2746///   row with the most cells, over each run of consecutive rows — from
2747///   the run's first row's top to its last row's bottom.
2748///
2749/// A child that is no row — a heading text beside the rows, a `column`
2750/// section wrapping a heading over a row — is laid out across the
2751/// table and has no cells, and is ruled as a row spanning every column,
2752/// the way a `colspan` cell of a ruled HTML table is: a rule above and
2753/// below it as between rows, and the column rules stop at its edges
2754/// instead of crossing it. A table with no row has no grid to rule.
2755fn rule_lines(
2756    rect: Rect,
2757    pad: crate::geom::Edges,
2758    rule_w: f32,
2759    children: &[RuledChild],
2760) -> Vec<Rect> {
2761    let w = if rule_w > 0.0 { rule_w } else { 1.0 };
2762    let Some(widest) = children
2763        .iter()
2764        .filter_map(|c| c.cells.as_ref())
2765        .max_by_key(|cells| cells.len())
2766    else {
2767        return Vec::new();
2768    };
2769    let mut lines: Vec<Rect> = Vec::new();
2770    for pair in children.windows(2) {
2771        let (a, b) = (pair[0].rect, pair[1].rect);
2772        let y = (a.y + a.h + b.y) / 2.0;
2773        let x = rect.x + pad.l;
2774        lines.push(Rect::new(x, y - w / 2.0, rect.w - pad.l - pad.r, w));
2775    }
2776    let mut run = 0usize;
2777    while run < children.len() {
2778        if children[run].cells.is_none() {
2779            run += 1;
2780            continue;
2781        }
2782        let mut end = run;
2783        while end + 1 < children.len() && children[end + 1].cells.is_some() {
2784            end += 1;
2785        }
2786        let (top, last) = (children[run].rect.y, children[end].rect);
2787        let bottom = last.y + last.h;
2788        for pair in widest.windows(2) {
2789            let x = (pair[0].x + pair[0].w + pair[1].x) / 2.0;
2790            lines.push(Rect::new(x - w / 2.0, top, w, bottom - top));
2791        }
2792        run = end + 1;
2793    }
2794    lines
2795}
2796
2797/// Pushes a table's `lines` in `color`, faded by `paint`'s opacity, under
2798/// `clip_id`. On whole pixels, so a 1 px rule is one crisp pixel line at
2799/// any scale.
2800fn push_rules(
2801    display: &mut DisplayList,
2802    lines: &[Rect],
2803    color: Color,
2804    paint: &Paint,
2805    clip_id: ClipId,
2806) {
2807    let color = Color {
2808        a: color.a * paint.opacity,
2809        ..color
2810    };
2811    for line in lines {
2812        display.quads.push(Quad {
2813            rect: line.scaled(paint.scale).on_pixels(),
2814            color,
2815            border_color: Color::TRANSPARENT,
2816            radius: [0.0; 4],
2817            border_w: 0.0,
2818            blur: 0.0,
2819            kind: QuadKind::Solid,
2820            clip: clip_id,
2821            uv: [0; 4],
2822        });
2823    }
2824}
2825
2826fn scrollbar_quad(bar: Rect, scale: f32, clip_id: ClipId, color: Color) -> Quad {
2827    Quad {
2828        rect: bar.scaled(scale),
2829        color,
2830        border_color: Color::TRANSPARENT,
2831        radius: [bar.w.min(bar.h) / 2.0 * scale; 4],
2832        border_w: 0.0,
2833        blur: 0.0,
2834        kind: QuadKind::Solid,
2835        clip: clip_id,
2836        uv: [0; 4],
2837    }
2838}
2839
2840/// One `fragment` node's quad, live or ghost.
2841///
2842/// The handle is resolved first: a removed or foreign one draws nothing,
2843/// which is the documented fallback for every resource kind, and the
2844/// lookup is what records the `foreign-resource` warning. The quad carries
2845/// the node's own rect, radii and clip — a fragment rounds and clips like
2846/// a solid — and an opaque white `color`, whose alpha the group-opacity
2847/// pass then multiplies into; the shader reads that alpha and nothing else
2848/// of the colour, because a fragment returns its own.
2849#[allow(clippy::too_many_arguments)]
2850/// Resolves the `fit` row: the rect the pixels paint into (logical px)
2851/// and the texel rect of the image they come from. `fill` stretches the
2852/// whole image to the box; `contain` shrinks the painted rect to the
2853/// image's aspect, centred; `cover` keeps the box and crops the texels,
2854/// centred. A zero-sized image or box falls back to
2855/// `fill`, which paints nothing visible either way.
2856pub(crate) fn fit_image(
2857    fit: crate::resources::ImageFit,
2858    rect: Rect,
2859    image: Size,
2860    texels: [u32; 4],
2861) -> (Rect, [u32; 4]) {
2862    use crate::resources::ImageFit;
2863    if image.w <= 0.0 || image.h <= 0.0 || rect.w <= 0.0 || rect.h <= 0.0 {
2864        return (rect, texels);
2865    }
2866    let box_aspect = rect.w / rect.h;
2867    let image_aspect = image.w / image.h;
2868    match fit {
2869        ImageFit::Fill => (rect, texels),
2870        ImageFit::Contain => {
2871            let (w, h) = if image_aspect > box_aspect {
2872                (rect.w, rect.w / image_aspect)
2873            } else {
2874                (rect.h * image_aspect, rect.h)
2875            };
2876            (
2877                Rect::new(
2878                    rect.x + (rect.w - w) * 0.5,
2879                    rect.y + (rect.h - h) * 0.5,
2880                    w,
2881                    h,
2882                ),
2883                texels,
2884            )
2885        }
2886        ImageFit::Cover => {
2887            // Whole texels: a crop is a rect on the texture, and a
2888            // half-texel edge would sample the neighbour.
2889            let (w, h) = if image_aspect > box_aspect {
2890                ((image.h * box_aspect).round().max(1.0), image.h)
2891            } else {
2892                (image.w, (image.w / box_aspect).round().max(1.0))
2893            };
2894            let x = ((image.w - w) * 0.5).floor();
2895            let y = ((image.h - h) * 0.5).floor();
2896            (
2897                rect,
2898                [
2899                    texels[0] + x as u32,
2900                    texels[1] + y as u32,
2901                    w as u32,
2902                    h as u32,
2903                ],
2904            )
2905        }
2906    }
2907}
2908
2909/// One fragment quad and its side entry: the draw, the source a backend
2910/// compiles, and the colour the function reads as `in.color` — white for
2911/// a `fragment`, the fill for a `polygon`.
2912///
2913/// A draw naming an `image` resolves it here, where the window's atlas
2914/// is: an atlas-backed image goes into the atlas as an `image` node's
2915/// would and the draw carries its slot; a texture-backed one takes an
2916/// entry of the texture side list — the same entry an `image` node of it
2917/// would — and the draw carries the index, so the backend binds that
2918/// texture for this one quad as it does for a texture quad. An image
2919/// handle that is not live draws nothing, the fallback every resource
2920/// kind has, and the lookup is what records a foreign one.
2921#[allow(clippy::too_many_arguments)]
2922fn push_fragment(
2923    display: &mut DisplayList,
2924    atlas: &mut GlyphAtlas,
2925    resources: &crate::resources::Resources,
2926    draw: crate::fragment::Draw,
2927    rect: Rect,
2928    radius: [f32; 4],
2929    clip_id: ClipId,
2930    color: Color,
2931) {
2932    let Some(source) = resources.fragment(draw.id) else {
2933        return;
2934    };
2935    let image = match draw.image {
2936        None => crate::display::FragmentImage::None,
2937        Some(id) => {
2938            let Some(entry) = resources.image(id) else {
2939                return;
2940            };
2941            let slot = match entry.backing {
2942                crate::resources::ImageBacking::Atlas => {
2943                    atlas.get_or_insert_image(id, entry.width, entry.height, &entry.rgba)
2944                }
2945                crate::resources::ImageBacking::Texture => None,
2946            };
2947            match slot {
2948                Some(slot) => {
2949                    crate::display::FragmentImage::Atlas([slot.x, slot.y, slot.w, slot.h])
2950                }
2951                None => {
2952                    let index = display.textures.len() as u32;
2953                    let uv = [0, 0, entry.width, entry.height];
2954                    display
2955                        .textures
2956                        .push(crate::display::TextureDraw { id, uv });
2957                    display.texture_pixels.push(crate::display::TexturePixels {
2958                        width: entry.width,
2959                        height: entry.height,
2960                        rev: entry.rev,
2961                        rgba: entry.rgba.clone(),
2962                    });
2963                    crate::display::FragmentImage::Texture { index, uv }
2964                }
2965            }
2966        }
2967    };
2968    let index = display.fragments.len() as u32;
2969    display.fragments.push(crate::display::FragmentDraw {
2970        id: draw.id,
2971        params: draw.params,
2972        image,
2973    });
2974    display.fragment_sources.push(source.clone());
2975    display.quads.push(Quad {
2976        rect,
2977        // White on a `fragment` — the function returns its own colour and
2978        // reads this as `in.color` if it wants one — and the fill on a
2979        // `polygon`; `a` is the fill's alpha times the group opacity, which
2980        // the fade pass multiplies in after the node's quads are pushed.
2981        color,
2982        border_color: Color::TRANSPARENT,
2983        radius,
2984        border_w: 0.0,
2985        blur: 0.0,
2986        kind: QuadKind::Fragment,
2987        clip: clip_id,
2988        uv: [index, 0, 0, 0],
2989    });
2990}