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