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