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