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