Skip to main content

kui_core/runtime/
emit.rs

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