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