kui_core/runtime/builder.rs
1//! The frame builder: what a view calls between `begin_frame` and
2//! `finish_frame` to declare the tree — open, close, text, editors,
3//! images — with each spec eased against its transitions and keyframes
4//! as it is opened, and the hover / press queries a view styles by.
5
6use super::*;
7
8use crate::schema::{Identity, PropsOut};
9use crate::slots::one;
10
11/// What a node opened through [`Core::open_from`] holds: a box (left open
12/// for its children), a fragment (likewise), a `cells` grid or a stroke
13/// (leaves, closed by the door).
14pub enum Content<'a> {
15 Box,
16 /// A fragment: the function (and its image, if any) and the params.
17 Fragment(crate::fragment::FragmentRef, &'a [f32]),
18 Cells(&'a crate::cells::CellGrid<'a>),
19 Line(&'a [Vec2], Stroke),
20 /// A filled polygon; the fill is the spec's `bg`.
21 Polygon(&'a [Vec2]),
22 /// A path: its ops, the fill rule, and a stroke if it has one. The
23 /// fill is the spec's `bg`.
24 Path(
25 &'a [crate::path::PathOp],
26 crate::path::FillRule,
27 Option<Stroke>,
28 Option<crate::path::Turn>,
29 ),
30 /// The same from a `d` string, parsed here by the one parser; one
31 /// that does not parse raises `path-malformed` under the node's key.
32 PathD(
33 &'a str,
34 crate::path::FillRule,
35 Option<Stroke>,
36 Option<crate::path::Turn>,
37 ),
38 /// The same from the flat op form (`Path::to_floats`), read here; one
39 /// that is not the form raises `path-malformed` under the node's key
40 /// as a `d` that does not parse does.
41 PathFlat(
42 &'a [f32],
43 crate::path::FillRule,
44 Option<Stroke>,
45 Option<crate::path::Turn>,
46 ),
47}
48
49/// Whether a transitioning node has a transform slot to ease: it declares
50/// a turn or a scale, or its entrance or a stop names one (ADR 0043). Two
51/// box checks for the node that does none of it.
52#[inline]
53fn turns(spec: &NodeSpec) -> bool {
54 spec.interact
55 .as_deref()
56 .is_some_and(|i| i.transform.is_some())
57 || spec.anim.as_deref().is_some_and(anim_turns)
58}
59
60/// [`turns`]'s half for the animation group, out of line: inlined, its
61/// scan of the stops made `prepare_spec` too big to inline into
62/// `open_content`, and every node paid the call (ADR 0043's amendment).
63#[inline(never)]
64fn anim_turns(a: &crate::spec::AnimSpec) -> bool {
65 a.enter
66 .is_some_and(|e| e.rotate.is_some() || e.scale.is_some())
67 || a.keyframes
68 .iter()
69 .any(|k| k.rotate.is_some() || k.scale.is_some())
70}
71
72/// The transform slot's lanes a node declares: `[rotate, scale, 0, 0]`,
73/// the identity for a node that declares none (ADR 0043).
74#[inline]
75fn transform_lanes(spec: &NodeSpec) -> [f32; 4] {
76 let t = spec.transform_spec().unwrap_or_default();
77 crate::geom::Transform::lanes(t.rotate, t.scale)
78}
79
80/// A node's keyframes flattened per slot for `ease_spec`, built once per
81/// node per frame (only for nodes that declare keyframes).
82struct Tracks {
83 width: Option<Vec<(f32, [f32; 4])>>,
84 height: Option<Vec<(f32, [f32; 4])>>,
85 bg: Option<Vec<(f32, [f32; 4])>>,
86 radius: Option<Vec<(f32, [f32; 4])>>,
87 opacity: Option<Vec<(f32, [f32; 4])>>,
88}
89
90impl Tracks {
91 fn of(spec: &NodeSpec) -> Self {
92 let frames = &spec.anim().keyframes;
93 let offsets = keyframes::offsets(frames);
94 // Each slot's stops in lanes, the node's own value at the ends the
95 // stops do not reach; a sizing with no amount (`fit`) has no track.
96 let track = |slot: Slot, base: Option<[f32; 4]>| {
97 keyframes::track(frames, &offsets, base?, |k| k.lanes(slot))
98 };
99 Tracks {
100 width: track(Slot::Width, spec.layout.width.amount().map(one)),
101 height: track(Slot::Height, spec.layout.height.amount().map(one)),
102 bg: track(Slot::Bg, Some(spec.style.bg.lanes())),
103 radius: track(Slot::Radius, Some(spec.style.radius)),
104 opacity: track(Slot::Opacity, Some(one(spec.style.opacity))),
105 }
106 }
107
108 fn get(&self, slot: Slot) -> Option<&Track> {
109 match slot {
110 Slot::Width => self.width.as_deref(),
111 Slot::Height => self.height.as_deref(),
112 Slot::Bg => self.bg.as_deref(),
113 Slot::Radius => self.radius.as_deref(),
114 Slot::Opacity => self.opacity.as_deref(),
115 // Eased apart, in `ease_transform`.
116 Slot::Transform => None,
117 // Keyframing a shadow would need stops for four more numbers
118 // and a color; a shadow tweens with `transition` and no more.
119 Slot::Border | Slot::Pos | Slot::Shadow | Slot::ShadowColor => None,
120 }
121 }
122}
123
124impl Core {
125 // -- Frame builder ------------------------------------------------------
126 // Flat, non-panicking, callable through FFI. Misuse (close past the root,
127 // building outside a frame) is ignored rather than UB or panic.
128
129 /// Tags subsequently created nodes with an origin (set by the runner
130 /// before handing the frame to an extension).
131 pub fn set_origin(&mut self, origin: OriginId) {
132 self.origin = origin;
133 }
134
135 /// Replaces the implicit root's spec (e.g. to make the top level a row).
136 /// Root sizing is resolved against the viewport regardless.
137 pub fn configure_root(&mut self, mut spec: NodeSpec) {
138 if let Some(app) = self.dt_app {
139 // The host's tree is wrapped for the devtools (ADR 0024,
140 // decision 2): the spec is split between the two nodes.
141 self.devtools_configure_root(app, spec);
142 return;
143 }
144 if !self.tree.is_empty() {
145 self.ease_spec(Key::ROOT, &mut spec);
146 self.tree.note(&spec, &NodeContent::Container);
147 self.tree.specs[0] = spec;
148 }
149 }
150
151 /// Replaces a transitioning node's animatable values with this frame's
152 /// eased ones. Nodes without a transition cost one branch — inlined at
153 /// the call site, so it is a branch and not a call that returns.
154 /// A slot the node's keyframes name is sampled from its cycle instead
155 /// of tweened.
156 #[inline]
157 pub(super) fn ease_spec(&mut self, key: Key, spec: &mut NodeSpec) {
158 if let Some(t) = spec.transition {
159 // A turn the node starts declaring eases in from upright when
160 // the node was already drawn: read before `ease_transitioning`
161 // marks its other slots used this frame.
162 let declares = turns(spec);
163 let upright = declares && self.anim.turn_starts_upright(key);
164 self.ease_transitioning(key, spec, t);
165 // The turn and the scale (ADR 0043) are eased apart, after the
166 // other slots and out of line, so `ease_transitioning` is the
167 // code it was before ADR 0043 (a test for the turn inside it
168 // measured about 2% on a frame of 10,000 transitioning boxes).
169 // A turn the node stops declaring eases back to upright on the
170 // tween it had.
171 if declares || self.anim.turn_live(key) {
172 self.ease_transform(key, spec, t, upright);
173 }
174 }
175 }
176
177 #[inline(never)]
178 fn ease_transitioning(&mut self, key: Key, spec: &mut NodeSpec, t: crate::anim::Transition) {
179 // One lookup for the whole node. Every slot below used to reach
180 // `AnimStore` by key on its own, which was seven to nine hashes and
181 // probes of the same entry per transitioning node, per frame.
182 let mut anim = self.anim.node(key);
183 let tracks = (!spec.anim().keyframes.is_empty()).then(|| Tracks::of(spec));
184 let track = |slot: Slot| tracks.as_ref().and_then(|k| k.get(slot));
185 let enter = spec.anim().enter.unwrap_or_default();
186 let iterations = spec.anim().iterations;
187 // Each slot: sampled from its track when keyframed, else tweened
188 // toward its declared value from where the entrance says it starts.
189 let mut ease = |slot: Slot, target: [f32; 4]| match track(slot) {
190 Some(track) => anim.sample(track, t, iterations).unwrap_or(target),
191 None => anim.drive(slot, enter.lanes(slot), target, t, true),
192 };
193 let mut sizing = |slot: Slot, s: Sizing| match s.amount() {
194 Some(v) => s.with_amount(ease(slot, one(v))[0]),
195 None => s,
196 };
197 spec.layout.width = sizing(Slot::Width, spec.layout.width);
198 spec.layout.height = sizing(Slot::Height, spec.layout.height);
199 let mut color = |slot: Slot, c: Color| Color::from_lanes(ease(slot, c.lanes()));
200 spec.style.bg = color(Slot::Bg, spec.style.bg);
201 spec.style.border_color = color(Slot::Border, spec.style.border_color);
202 spec.style.shadow.color = color(Slot::ShadowColor, spec.style.shadow.color);
203 let sh = spec.style.shadow;
204 let geom = ease(Slot::Shadow, [sh.dx, sh.dy, sh.blur, sh.spread]);
205 spec.style.shadow.dx = geom[0];
206 spec.style.shadow.dy = geom[1];
207 spec.style.shadow.blur = geom[2].max(0.0);
208 spec.style.shadow.spread = geom[3];
209 spec.style.opacity = ease(Slot::Opacity, one(spec.style.opacity))[0].clamp(0.0, 1.0);
210 spec.style.radius = ease(Slot::Radius, spec.style.radius);
211 }
212
213 /// The transform slot of a transitioning node that turns
214 /// ([`turns`]): sampled from its stops when they name `rotate` or
215 /// `scale`, else tweened from where its entrance says. The eased
216 /// lanes land on the spec's transform, allocated for an entrance or a
217 /// cycle that turns a node declaring none.
218 #[cold]
219 #[inline(never)]
220 fn ease_transform(
221 &mut self,
222 key: Key,
223 spec: &mut NodeSpec,
224 t: crate::anim::Transition,
225 upright: bool,
226 ) {
227 let declared = spec.interact().transform.is_some();
228 let base = transform_lanes(spec);
229 let frames = &spec.anim().keyframes;
230 // A stop's lanes are filled from the node's own transform, so a
231 // stop naming only `rotate` keeps the scale.
232 let track = if frames.is_empty() {
233 None
234 } else {
235 keyframes::track(frames, &keyframes::offsets(frames), base, |k| {
236 k.transform_lanes(base)
237 })
238 };
239 let v = match track {
240 Some(track) => self
241 .anim
242 .sample_cycle(key, &track, t, spec.anim().iterations)
243 .unwrap_or(base),
244 None => {
245 // Already drawn upright, the turn eases in from there; a
246 // node new this frame enters as its `enter` says.
247 const UPRIGHT: [f32; 4] = [0.0, 1.0, 0.0, 0.0];
248 let from = if upright {
249 Some(UPRIGHT)
250 } else {
251 spec.anim().enter.and_then(|e| e.transform_lanes(base))
252 };
253 let v = self.anim.drive_turn(key, from, base, t);
254 if !declared && v == UPRIGHT {
255 self.anim.forget_settled_turn(key, UPRIGHT);
256 }
257 v
258 }
259 };
260 if declared || v != base {
261 let tr = spec.transform_mut();
262 tr.rotate = v[0];
263 tr.scale = v[1];
264 }
265 }
266
267 /// The root node's key — for hover/press queries or `set_key_focus` when
268 /// the root itself declares the interaction (e.g. a root-level key sink).
269 pub fn root_key(&self) -> Key {
270 self.tree.keys.first().copied().unwrap_or(Key(0))
271 }
272
273 pub(crate) fn current(&self) -> u32 {
274 self.stack.last().copied().unwrap_or(0)
275 }
276
277 /// The key a child of the current node is derived from: the node's own
278 /// key, except inside a slot fill at the depth the fill began, where it
279 /// is the fill's namespace (`Core::fill`). One
280 /// compare on the auto-key path; `ns_depth` is `usize::MAX` outside a
281 /// fill.
282 #[inline]
283 pub(crate) fn parent_key(&self) -> Key {
284 if self.stack.len() == self.ns_depth {
285 self.ns_key
286 } else {
287 // Outside a frame — or in the devtools' own window, where the
288 // root is deferred (ADR 0024, decision 6) — there is no node
289 // to derive from, and the open that follows is a no-op.
290 self.tree
291 .keys
292 .get(self.current() as usize)
293 .copied()
294 .unwrap_or(Key::ROOT)
295 }
296 }
297
298 #[inline]
299 pub(crate) fn auto_key(&mut self) -> Key {
300 let parent = self.parent_key();
301 let i = self.counters.last().copied().unwrap_or(0);
302 if let Some(c) = self.counters.last_mut() {
303 *c += 1;
304 }
305 parent.index(i)
306 }
307
308 /// The key a child labeled `label` would get — usable before creating it,
309 /// e.g. to check hover state for styling.
310 pub fn child_key(&self, label: &str) -> Key {
311 self.parent_key().str(label)
312 }
313
314 /// The key the `i`th child gets from auto-keying — what `open_indexed`
315 /// opens with, usable before the node exists.
316 pub fn child_key_indexed(&self, i: u64) -> Key {
317 self.parent_key().index(i)
318 }
319
320 pub fn is_hovered(&self, key: Key) -> bool {
321 let v = self.interaction.is_hovered(key);
322 self.note_read(|| replay::Read::Hover(key, v));
323 v
324 }
325
326 /// Whether files dragged in from the OS are over `key`.
327 pub fn is_drop_target(&self, key: Key) -> bool {
328 let v = self.interaction.is_drop_target(key);
329 self.note_read(|| replay::Read::Drop(key, v));
330 v
331 }
332
333 /// The zone the dragged files are over, if any — what a driver
334 /// answers the OS with.
335 pub fn drop_target(&self) -> Option<Key> {
336 self.interaction.drop_target()
337 }
338
339 pub fn is_pressed(&self, key: Key) -> bool {
340 let v = self.interaction.is_pressed(key);
341 self.note_read(|| replay::Read::Pressed(key, v));
342 v
343 }
344
345 /// Whether any member of hover group `group` (see
346 /// `NodeSpec::hover_group`) is hovered.
347 pub fn is_group_hovered(&self, group: u64) -> bool {
348 let v = self.interaction.is_group_hovered(group);
349 self.note_read(|| replay::Read::GroupHover(group, v));
350 v
351 }
352
353 /// Whether hover group `group` is pressed (press started on a member,
354 /// pointer still over one).
355 pub fn is_group_pressed(&self, group: u64) -> bool {
356 let v = self.interaction.is_group_pressed(group);
357 self.note_read(|| replay::Read::GroupPressed(group, v));
358 v
359 }
360
361 /// Events raised outside `handle_input`: the `resize` a changed
362 /// viewport produced at `begin_frame`, and `on_hover` enter/leave
363 /// caused by a finished frame changing what sits under a still cursor.
364 /// Frame drivers route these after `finish_frame`; they also ride along
365 /// with the next `handle_input` result, so a driver that never calls
366 /// this merely sees them a little later.
367 pub fn take_pending_events(&mut self) -> Vec<UiEvent> {
368 let mut out = std::mem::take(&mut self.pending);
369 out.append(&mut self.interaction.take_pending());
370 self.devtools_consume(&mut out);
371 self.devtools_translate(&mut out);
372 self.stamp(&mut out);
373 self.devtools_log(&out);
374 out
375 }
376
377 /// Swaps in the hover / pressed / focus background the spec declares
378 /// for the node's (or its group's) current state: pressed wins over
379 /// keyboard-visible focus wins over hover. A disabled node keeps its
380 /// plain `bg`. Runs before easing so a `transition` tweens between
381 /// the states.
382 #[inline]
383 fn resolve_hover_style(&self, key: Key, spec: &mut NodeSpec) {
384 // Runs for every node of every frame, and almost every node declares
385 // none of this — so the early-out is one null check on the boxed
386 // group rather than three `Option`s read out of the spec, and it is
387 // inlined so the check is a branch rather than a call (C15).
388 if spec.interact.is_some() {
389 self.resolve_declared_hover_style(key, spec);
390 }
391 }
392
393 #[inline(never)]
394 fn resolve_declared_hover_style(&self, key: Key, spec: &mut NodeSpec) {
395 let Some(interact) = spec.interact.as_deref() else {
396 return;
397 };
398 let (hover_bg, pressed_bg, focus_bg, drop_bg, group) = (
399 interact.hover_bg,
400 interact.pressed_bg,
401 interact.focus_bg,
402 interact.drop_bg,
403 interact.hover_group,
404 );
405 if spec.disabled
406 || (hover_bg.is_none()
407 && pressed_bg.is_none()
408 && focus_bg.is_none()
409 && drop_bg.is_none())
410 {
411 return;
412 }
413 // Dragged files over the zone win over every pointer state: a
414 // press cannot be held while the OS holds a drag (ADR 0031,
415 // decision 3).
416 if let Some(c) = drop_bg
417 && self.interaction.is_drop_target(key)
418 {
419 spec.style.bg = c;
420 return;
421 }
422 let pressed = self.interaction.is_pressed(key)
423 || group.is_some_and(|g| self.interaction.is_group_pressed(g));
424 let hovered = pressed
425 || self.interaction.is_hovered(key)
426 || group.is_some_and(|g| self.interaction.is_group_hovered(g));
427 let focused = self.focus_visible && self.focus == Some(key);
428 if pressed && let Some(c) = pressed_bg {
429 spec.style.bg = c;
430 } else if focused && let Some(c) = focus_bg {
431 spec.style.bg = c;
432 } else if hovered && let Some(c) = hover_bg {
433 spec.style.bg = c;
434 }
435 }
436
437 /// Physical modifier state as of the last `InputEvent::Modifiers`.
438 pub fn modifiers(&self) -> crate::input::KeyMods {
439 let m = self.interaction.modifiers();
440 self.note_read(|| replay::Read::Mods(m));
441 m
442 }
443
444 /// Where the pointer is, in this window's logical viewport
445 /// coordinates, as of the last `CursorMoved` — `None` once it has
446 /// left the window. What a view that follows the pointer reads (the
447 /// devtools' picker outlines the node under it); a control that wants
448 /// to *react* to the pointer declares `hoverable` or `on_hover` and
449 /// lets the core do the hit test.
450 pub fn cursor(&self) -> Option<Vec2> {
451 let p = self.interaction.cursor().map(|p| p.minus(self.dt_shift()));
452 self.note_read(|| replay::Read::Cursor(p));
453 p
454 }
455
456 // The open chain is inlined end to end (`Ui::open` → here →
457 // `open_with_key` → `Tree::push`): a `NodeSpec` is 224 bytes and moved
458 // by value at every step, and each step that is a real call is a copy
459 // of all of them. Inlined, the spec the view built travels by pointer
460 // and is copied once, into the tree (C15).
461 #[inline]
462 pub fn open(&mut self, spec: NodeSpec) -> Key {
463 let key = self.auto_key();
464 self.open_with_key(key, spec);
465 key
466 }
467
468 #[inline]
469 pub fn open_keyed(&mut self, label: &str, spec: NodeSpec) -> Key {
470 let key = self.child_key(label);
471 if self.tree.is_empty() {
472 // No frame to open into (the same no-op as `open_with_key`),
473 // and so no node for the label to name.
474 return key;
475 }
476 self.open_with_key(key, spec);
477 self.note_label(key, label);
478 key
479 }
480
481 /// The inverse of [`Self::key_of`]: the label `key` was opened under
482 /// — in the frame being built so far, else in the last one — or
483 /// `None` for an auto-keyed node or a key no frame has declared. What
484 /// a reader holding a key from an event or from `focus()` turns back
485 /// into the name the view gave it.
486 pub fn label_of(&self, key: Key) -> Option<&str> {
487 self.key_labels
488 .label_of(key)
489 .or_else(|| self.key_labels_last.label_of(key))
490 }
491
492 /// The key of the node opened under the key label `label`
493 /// (`open_keyed`; a `key` prop in JSX or a Lua table — not the `label`
494 /// row, the accessible name, which [`Self::key_named`] reads) in the
495 /// last finished frame — or, while
496 /// a frame is being built, in it so far and then in the last one. The
497 /// door for a caller that holds only strings: keys are hashes of the
498 /// path from the root, and that path runs through auto-keyed
499 /// ancestors nothing outside the build can spell, so "focus the node
500 /// I just declared" is this and not `child_key`. None when no node
501 /// declared the label. Labels are unique among siblings, not across a
502 /// tree, so two nodes may share one under different parents. A guest
503 /// asking from inside its fill is answered from the nodes
504 /// it opened and no one else's — it cannot know what the host or
505 /// another guest called theirs, and its env is a reading of its own
506 /// view; the host, whose frame it is, from its own first and from
507 /// everyone's when it opened none. Within that, the first in tree
508 /// order wins and an `ambiguous-key` warning says so.
509 pub fn key_of(&mut self, label: &str) -> Option<Key> {
510 self.find_label(label, true)
511 }
512
513 /// The one label lookup: the first node in tree order
514 /// opened under `label` in the frame being built, and — with
515 /// `fall_back` and a build under way — in the last frame when this
516 /// one has not declared it yet; an `ambiguous-key` warning when more
517 /// than one did. `key_of` falls back; `resolve_regions` runs at the
518 /// frame's end, when this frame's labels are the whole story.
519 pub(crate) fn find_label(&mut self, label: &str, fall_back: bool) -> Option<Key> {
520 let (first, count) = {
521 let mut hits = self.key_labels.find_for(label, self.origin);
522 if hits.0.is_none() && fall_back && self.building {
523 hits = self.key_labels_last.find_for(label, self.origin);
524 }
525 (hits.0?, hits.1)
526 };
527 if count > 1 {
528 self.diag
529 .raise(crate::diag::ambiguous_key(label, first, count));
530 }
531 Some(first)
532 }
533
534 /// `open_keyed` in the sibling-index namespace: the key auto-keying
535 /// would have given the `i`th child. A list that builds only rows
536 /// 900..930 opens each with its *data* index, so row 900 keeps the key
537 /// it has when the whole list is built — hover, focus, edit buffers and
538 /// tweens follow the row instead of the slot it happens to occupy.
539 #[inline]
540 pub fn open_indexed(&mut self, i: u64, spec: NodeSpec) -> Key {
541 let key = self.child_key_indexed(i);
542 let at = self.tree.len() as u32;
543 self.open_with_key(key, spec);
544 // Remembered for the node that was actually pushed, so a selection
545 // inside a virtual row can be ordered by the row's place in the
546 // *data* when the row itself is not built (ADR 0017, tier 3).
547 if self.tree.len() as u32 > at {
548 self.tree.indexed.push((at, i));
549 if self.keeping() {
550 self.keep_op(replay::Op::Indexed(i));
551 }
552 }
553 key
554 }
555
556 /// Declares how many indexed rows the *open* node's virtual list has,
557 /// built or not (`rowCount`): what Select All inside a `selectable`
558 /// virtual list spans, since the built rows are all the core can see.
559 /// `widgets::uniform_list` and `widgets::list`
560 /// call it on their container; a list composed by hand calls it
561 /// inside the container's `with`. Nothing, outside any node.
562 pub fn row_count(&mut self, n: u64) {
563 if self.tree.is_empty() || self.stack.is_empty() {
564 return;
565 }
566 let at = self.current();
567 self.tree.row_counts.push((at, n));
568 if self.keeping() {
569 self.keep_op(replay::Op::RowCountOpen(n));
570 }
571 }
572
573 /// Opens a node under a key the caller built; see `Ui::open_key`.
574 #[inline]
575 pub fn open_key(&mut self, key: Key, spec: NodeSpec) -> Key {
576 self.open_with_key(key, spec);
577 key
578 }
579
580 #[inline]
581 pub(crate) fn open_with_key(&mut self, key: Key, spec: NodeSpec) {
582 // The spec as declared, before hover, accent and easing touch
583 // it (ADR 0045): a replay resolves those for its own frame.
584 if self.keeping() {
585 self.keep_op(replay::Op::Open {
586 key,
587 spec: Box::new(spec.clone()),
588 });
589 }
590 self.open_content(key, spec, NodeContent::Container);
591 }
592
593 /// `open_with_key` with the node named `label` for `key_of`, the way
594 /// `open_keyed` names its node — for a key the caller fixed rather
595 /// than derived (a devtools tab's body).
596 pub(crate) fn open_with_key_named(&mut self, key: Key, label: &str, spec: NodeSpec) {
597 if self.tree.is_empty() {
598 return;
599 }
600 self.open_with_key(key, spec);
601 self.note_label(key, label);
602 }
603
604 /// `open_with_key` for a node that is a box in every way but what it
605 /// paints: the caller supplies the content and closes the node. What
606 /// the node asks of the frame is noted by `Tree::push`, the same for a
607 /// box, a `fragment` and every leaf.
608 fn open_content(&mut self, key: Key, mut spec: NodeSpec, content: NodeContent) {
609 if self.tree.is_empty() {
610 return;
611 }
612 self.prepare_spec(key, &mut spec);
613 // `NodeSpec::tooltip`: the hint floats on this node's `close`, the
614 // way a parsed `tooltip` prop's does. One pointer check for a node
615 // that declares no access group.
616 let tip = match spec.access.as_deref() {
617 Some(a) if a.tooltip => a.description.clone(),
618 _ => None,
619 };
620 let parent = self.current();
621 let idx = self.tree.push(parent, key, self.origin, spec, content);
622 self.stack.push(idx);
623 self.counters.push(0);
624 if let Some(tip) = tip {
625 self.spec_hint(key, &tip);
626 }
627 }
628
629 /// The hint of a node that declared [`NodeSpec::tooltip`], recorded
630 /// only while it is hovered — `close` would drop it otherwise.
631 #[cold]
632 #[inline(never)]
633 fn spec_hint(&mut self, key: Key, tip: &str) {
634 if self.is_hovered(key) {
635 self.hint(key, tip);
636 }
637 }
638
639 /// Pushes a leaf — a node that is never left open for children: a
640 /// `cells` grid, an editor, an image, a stroke, a fill, a path — under
641 /// the current node. The one door every leaf goes through, so a leaf
642 /// whose spec asks for its tooltip drawn (`AccessSpec::tooltip`) has it
643 /// floated here once rather than by six doors. One pointer check for a
644 /// leaf that declares no access group.
645 #[inline]
646 fn push_leaf(&mut self, key: Key, spec: NodeSpec, content: NodeContent) {
647 let parent = self.current();
648 let idx = self.tree.push(parent, key, self.origin, spec, content);
649 // Read off the pushed spec rather than before the push, so nothing
650 // is held across `Tree::push` on the path every leaf takes.
651 if self.tree.specs[idx as usize]
652 .access
653 .as_deref()
654 .is_some_and(|a| a.tooltip)
655 {
656 self.leaf_hint(key, idx);
657 }
658 }
659
660 /// The hint of the leaf just pushed, floated while it is hovered: a
661 /// leaf holds no children, so the hint cannot be its last child the
662 /// way `close` floats a box's, and it floats beside the leaf anchored
663 /// to it instead (`widgets::leaf_hint`, backlog RG113). The string is
664 /// the leaf's description, which is what `apply_tooltip` set it to.
665 #[cold]
666 #[inline(never)]
667 fn leaf_hint(&mut self, key: Key, idx: u32) {
668 if !self.is_hovered(key) {
669 return;
670 }
671 let Some(tip) = self
672 .tree
673 .specs
674 .get(idx as usize)
675 .and_then(|s| s.access.as_deref())
676 .and_then(|a| a.description.clone())
677 else {
678 return;
679 };
680 self.pause_keeping();
681 crate::widgets::leaf_hint(&mut Ui::wrap(self), key, &tip);
682 self.resume_keeping();
683 }
684
685 /// What every node's spec goes through between the door and the tree,
686 /// in this order — one pipeline for a box, a leaf, a stroke and a
687 /// fill alike (AR16: five doors ran five subsets of it, and a wedge's
688 /// `hover_bg` never painted). The one paint the environment decides
689 /// (`accent`): the theme's accent, which is the OS's where the host
690 /// reported one, the app's where it pinned one, and kui's otherwise;
691 /// before the hover resolution, so a node that
692 /// declares both still hovers to what it declared. Then the hover /
693 /// pressed / focus background for the node's state, then the eased
694 /// values a transition, entrance or keyframes put over the declared
695 /// ones.
696 #[inline]
697 fn prepare_spec(&mut self, key: Key, spec: &mut NodeSpec) {
698 if spec.accent && self.has_accent_raw() {
699 spec.style.bg = self.theme.accent;
700 }
701 self.resolve_hover_style(key, spec);
702 self.ease_spec(key, spec);
703 }
704
705 /// The layout of a node placed by its own geometry — a stroke, a fill:
706 /// never in layout, a float at `rect` in the
707 /// parent's box space sized exactly to it, the declared float's
708 /// *anchor* kept and every sizing, clamp and scroll row overridden,
709 /// since the box is the shape's own and not a size the view chose or
710 /// a tween may lag.
711 fn float_box_for(spec: &mut NodeSpec, rect: Rect) {
712 let anchor = spec
713 .layout
714 .float
715 .map_or(crate::spec::FloatAnchor::Parent, |f| f.anchor);
716 spec.layout.float = Some(crate::spec::FloatConfig {
717 anchor,
718 offset: crate::spec::Vec2Offset {
719 x: rect.x,
720 y: rect.y,
721 },
722 // A stroke drawn in its parent's box is the parent's content,
723 // so the parent's clip holds it as it holds a child (F78; ADR
724 // 0010 decision 5, as amended by F90): the bit a declared float
725 // opts into, set here for every stroke. A viewport-anchored
726 // one escapes regardless (`FloatConfig::clipped_by_parent`).
727 clip: true,
728 ..crate::spec::FloatConfig::default()
729 });
730 spec.layout.width = Sizing::Fixed(rect.w);
731 spec.layout.height = Sizing::Fixed(rect.h);
732 spec.layout.min_w = crate::spec::Min::AUTO;
733 spec.layout.max_w = f32::INFINITY;
734 spec.layout.min_h = crate::spec::Min::AUTO;
735 spec.layout.max_h = f32::INFINITY;
736 spec.layout.clip = false;
737 spec.layout.scroll_x = false;
738 spec.layout.scroll_y = false;
739 }
740
741 #[inline]
742 pub fn close(&mut self) {
743 // The tooltip prop's third effect, for the node being closed: its
744 // hint floats below it as its last child while it is hovered. One
745 // length check per close for a frame that declared no hints.
746 if let Some((depth, _, _)) = self.hints.last()
747 && *depth == self.stack.len()
748 {
749 let (_, key, hint) = self.hints.pop().unwrap();
750 if self.is_hovered(key) {
751 // The core's own nodes, not the fill's (ADR 0045).
752 self.pause_keeping();
753 crate::widgets::hover_hint(&mut Ui::wrap(self), &hint);
754 self.resume_keeping();
755 }
756 }
757 if self.stack.len() > 1 {
758 if self.keeping() {
759 self.keep_op(replay::Op::Close);
760 }
761 self.stack.pop();
762 self.counters.pop();
763 }
764 }
765
766 /// Records the hover hint of the node just opened (the top of the
767 /// stack): `close` floats `widgets::hover_hint` below it while it is
768 /// hovered. The one place that decides *when* a tooltip shows, so a
769 /// binding that parsed the string cannot show it some other way.
770 pub fn hint(&mut self, key: Key, text: impl Into<String>) {
771 let text = text.into();
772 if self.keeping() {
773 self.keep_op(replay::Op::Hint {
774 key,
775 text: text.as_str().into(),
776 });
777 }
778 self.hints.push((self.stack.len(), key, text));
779 }
780
781 /// Names the node under `key` `label` for `key_of`: what every keyed
782 /// door does after its push, and what a kept fill journals.
783 pub(crate) fn note_label(&mut self, key: Key, label: &str) {
784 self.key_labels.push(key, label, self.origin);
785 if self.keeping() {
786 self.keep_op(replay::Op::Label {
787 key,
788 label: label.into(),
789 });
790 }
791 }
792
793 /// Opens a node the way a parsed prop list says — under the data
794 /// index, the label or the next auto key; taking keyboard focus when
795 /// `keyFocus` asked; floating its `tooltip` on `close` while hovered —
796 /// with whatever the node holds. The one door for every binding that
797 /// lowers props, so the identity match, the focus edge and the hint
798 /// are not re-derived per binding per element (they were, eight, five
799 /// and four times). A box or a fragment is left open for its children,
800 /// and its hint floats as its last child on `close`; a `cells` grid, a
801 /// `line`, a `polygon` and a `path` are leaves, and theirs floats
802 /// beside the leaf, anchored to it (`PropsOut::for_leaf`, backlog
803 /// RG113). Returns the key.
804 pub fn open_from(&mut self, props: PropsOut, content: Content<'_>) -> Key {
805 let leaf = !matches!(content, Content::Box | Content::Fragment(..));
806 let props = if leaf { props.for_leaf() } else { props };
807 let PropsOut {
808 spec,
809 key: label,
810 index,
811 row_count,
812 key_focus,
813 tooltip,
814 ..
815 } = props;
816 let identity = match (index, &label) {
817 (Some(i), _) => Identity::Index(i),
818 (None, Some(label)) => Identity::Label(label),
819 (None, None) => Identity::Auto,
820 };
821 let key = match identity {
822 Identity::Auto => self.auto_key(),
823 Identity::Label(l) => self.child_key(l),
824 Identity::Index(i) => self.child_key_indexed(i),
825 };
826 let at = self.tree.len() as u32;
827 match content {
828 Content::Box => self.open_with_key(key, spec),
829 Content::Fragment(frag, params) => self.fragment_with_key(key, frag, params, spec),
830 Content::Cells(grid) => self.cells_at(key, grid, spec),
831 Content::Line(points, stroke) => self.line_with_key(key, points, &stroke, spec),
832 Content::Polygon(points) => self.polygon_with_key(key, points, spec),
833 Content::Path(ops, rule, stroke, turn) => {
834 self.path_with_key(key, ops, rule, stroke, turn, spec)
835 }
836 Content::PathD(d, rule, stroke, turn) => {
837 self.path_node_d(key, d, rule, stroke, turn, spec)
838 }
839 Content::PathFlat(floats, rule, stroke, turn) => {
840 self.path_node_flat(key, floats, rule, stroke, turn, spec)
841 }
842 }
843 // Bookkeeping for the node that was actually pushed: the label
844 // `key_of` resolves through, or the data index a selection inside
845 // a virtual row is ordered by when the row is not built (ADR 0017).
846 if self.tree.len() as u32 > at {
847 match identity {
848 Identity::Label(l) => self.note_label(key, l),
849 Identity::Index(i) => {
850 self.tree.indexed.push((at, i));
851 if self.keeping() {
852 self.keep_op(replay::Op::Indexed(i));
853 }
854 }
855 Identity::Auto => {}
856 }
857 if let Some(n) = row_count {
858 self.tree.row_counts.push((at, n));
859 if self.keeping() {
860 self.keep_op(replay::Op::RowCount(n));
861 }
862 }
863 }
864 if key_focus {
865 self.set_key_focus(Some(key));
866 }
867 // A leaf's hint was asked of its door by `for_leaf`, above.
868 if let Some(hint) = tooltip
869 && !leaf
870 {
871 self.hint(key, hint);
872 }
873 key
874 }
875
876 /// The root the way a parsed prop list says: its title, whether it
877 /// wants the window on top, its keyboard secure, its Option keys as
878 /// Alt or its input method off, the windows it declares, its spec, and
879 /// keyboard focus on it when asked — what a binding's root op does,
880 /// once.
881 pub fn configure_root_from(&mut self, props: PropsOut) {
882 if let Some(title) = &props.title {
883 self.set_window_title(title);
884 }
885 if props.always_on_top {
886 self.set_always_on_top(true);
887 }
888 if props.secure_input {
889 self.set_secure_input(true);
890 }
891 if props.option_as_alt != crate::OptionAsAlt::None {
892 self.set_option_as_alt(props.option_as_alt);
893 }
894 if props.ime_off {
895 self.set_ime_off(true);
896 }
897 for (name, cfg) in &props.windows {
898 self.declare_window(name, *cfg);
899 }
900 self.configure_root(props.spec);
901 if props.key_focus {
902 let root = self.root_key();
903 self.set_key_focus(Some(root));
904 }
905 }
906
907 pub fn text_node(&mut self, content: &str, style: TextStyle) {
908 if self.tree.is_empty() {
909 return;
910 }
911 let key = self.auto_key();
912 if self.keeping() {
913 self.keep_op(replay::Op::Text {
914 key,
915 content: content.into(),
916 style,
917 });
918 }
919 self.text_with_key(key, content, style);
920 }
921
922 /// [`Self::text_node`] under a key the caller derived.
923 pub(crate) fn text_with_key(&mut self, key: Key, content: &str, style: TextStyle) {
924 if self.tree.is_empty() {
925 return;
926 }
927 // A style that named no colour takes the theme's foreground here,
928 // at the one door text comes through, so the shaping cache, the
929 // display list and every binding downstream see a real colour
930 // (ADR 0019).
931 let style = style.or_fg(self.theme.fg);
932 let tid = {
933 let sess = &mut *self.session.state();
934 self.text
935 .add(content, &style, &sess.resources, &mut sess.fonts)
936 };
937 let parent = self.current();
938 self.tree.push(
939 parent,
940 key,
941 self.origin,
942 NodeSpec::default(),
943 NodeContent::Text(tid),
944 );
945 }
946
947 /// A cell grid as one leaf node, sized `cols × cell_w` by `rows ×
948 /// cell_h`. `spec` is the node's:
949 /// an `on_key` makes it the terminal's sink, an `on_click` / `on_drag`
950 /// carry `cell: {row, col}` on their events.
951 pub fn cells(&mut self, grid: &crate::cells::CellGrid<'_>, spec: NodeSpec) {
952 let key = self.auto_key();
953 self.cells_at(key, grid, spec);
954 }
955
956 /// [`Self::cells`] under a declared key.
957 pub fn cells_keyed(&mut self, label: &str, grid: &crate::cells::CellGrid<'_>, spec: NodeSpec) {
958 if self.tree.is_empty() {
959 return;
960 }
961 let key = self.child_key(label);
962 self.cells_at(key, grid, spec);
963 // Like every other keyed door: the label after the node, so a
964 // frame with no root records no name (AR16).
965 self.note_label(key, label);
966 }
967
968 /// [`Self::cells`] under a data index; see [`Self::open_indexed`].
969 pub fn cells_indexed(&mut self, i: u64, grid: &crate::cells::CellGrid<'_>, spec: NodeSpec) {
970 let key = self.child_key_indexed(i);
971 self.cells_at(key, grid, spec);
972 }
973
974 fn cells_at(&mut self, key: Key, grid: &crate::cells::CellGrid<'_>, mut spec: NodeSpec) {
975 if self.tree.is_empty() {
976 return;
977 }
978 // The node's box is a box like any leaf's: its `hoverBg` lights
979 // and its `transition` tweens the bg, the opacity, the size. The
980 // cells inside it are a picture the app redraws, and nothing here
981 // touches them (AR5).
982 // A grid is a picture the app redraws, not a spec to journal.
983 self.taint_kept("it drew a cells grid");
984 self.prepare_spec(key, &mut spec);
985 let cid = self.cells.add(key, grid);
986 self.push_leaf(key, spec, NodeContent::Cells(cid));
987 }
988
989 /// An editable text node. State (buffer, cursor, selection) is retained
990 /// by key across frames; edits arrive via `handle_input` and come back to
991 /// the host as "changed"/"submit" events. Read with `edit_text`.
992 pub fn text_edit(
993 &mut self,
994 label: &str,
995 initial: &str,
996 opts: &EditOptions,
997 mut spec: NodeSpec,
998 ) -> Key {
999 if self.tree.is_empty() {
1000 return Key::ROOT;
1001 }
1002 let key = self.child_key(label);
1003 if self.keeping() {
1004 self.keep_op(replay::Op::Edit {
1005 label: label.into(),
1006 initial: initial.into(),
1007 opts: Box::new(opts.clone()),
1008 spec: Box::new(spec.clone()),
1009 });
1010 }
1011 self.prepare_spec(key, &mut spec);
1012 // The same stamp the two text funnels make: an editor that named
1013 // no text colour and no selection tint takes the theme's, so a
1014 // field and a label beside it agree on both (ADR 0019).
1015 let opts = &EditOptions {
1016 style: opts.style.or_fg(self.theme.fg),
1017 accent: Some(opts.accent.unwrap_or(self.theme.selection)),
1018 ..opts.clone()
1019 };
1020 let edge = {
1021 let origin = self.origin;
1022 let scale = self.scale;
1023 let sess = &mut *self.session.state();
1024 // A `set_edit_text` by label, held for the frame that would
1025 // declare the name (backlog F32): claimed here, where the
1026 // label and its key are both in hand, and before `declare`,
1027 // which is what turns it into this key's seed.
1028 self.edit
1029 .claim_label(key, label, &mut sess.fonts, &sess.resources);
1030 self.edit.declare(
1031 key,
1032 initial,
1033 opts,
1034 origin,
1035 scale,
1036 &mut sess.fonts,
1037 &sess.resources,
1038 )
1039 };
1040 // Autofocus takes the keyboard only while nothing holds it — never
1041 // from a control Tab landed on — and only on the frame the editor
1042 // starts being declared (`docs/adr/0022`, decision 9): asked every
1043 // frame, it would take focus straight back from every blur, and an
1044 // app with an autofocus field could never have nothing focused.
1045 if opts.autofocus && edge && self.focus.is_none() && !spec.disabled {
1046 self.move_focus(Some(key));
1047 }
1048 self.push_leaf(key, spec, NodeContent::Edit(key));
1049 // A leaf keyed by its label, like `open_keyed`: `key_of` must find
1050 // the editor an app wants to focus by name. Not journaled: the
1051 // `Edit` op names its label, and a replay comes back through here.
1052 self.key_labels.push(key, label, self.origin);
1053 key
1054 }
1055
1056 /// A registered image (see `Resources::add_image`). Fit sizing takes
1057 /// the image's pixel dimensions as logical px; a Fit height against a
1058 /// resolved width preserves the aspect ratio. `style.radius` rounds the
1059 /// corners. Linear sampling, stretched to the box: [`Self::image_node_with`]
1060 /// takes the two rows that say otherwise.
1061 pub fn image_node(&mut self, id: crate::resources::ImageId, spec: NodeSpec) {
1062 self.image_node_with(id, crate::resources::ImageOpts::default(), spec);
1063 }
1064
1065 /// [`Self::image_node`] with its `sampling` and `fit` rows: how texels are
1066 /// read between pixels, and how the pixels
1067 /// meet a box of another aspect. The box — its layout, hit region and
1068 /// access rect — is the same in every mode.
1069 pub fn image_node_with(
1070 &mut self,
1071 id: crate::resources::ImageId,
1072 opts: crate::resources::ImageOpts,
1073 spec: NodeSpec,
1074 ) {
1075 if self.tree.is_empty() {
1076 return;
1077 }
1078 let key = self.auto_key();
1079 if self.keeping() {
1080 self.keep_op(replay::Op::Image {
1081 key,
1082 id,
1083 opts,
1084 spec: Box::new(spec.clone()),
1085 });
1086 }
1087 self.image_with_key(key, id, opts, spec);
1088 }
1089
1090 /// [`Self::image_node_with`] under a key the caller derived.
1091 pub(crate) fn image_with_key(
1092 &mut self,
1093 key: Key,
1094 id: crate::resources::ImageId,
1095 opts: crate::resources::ImageOpts,
1096 mut spec: NodeSpec,
1097 ) {
1098 if self.tree.is_empty() {
1099 return;
1100 }
1101 self.prepare_spec(key, &mut spec);
1102 self.push_leaf(key, spec, NodeContent::Image(id, opts));
1103 }
1104
1105 /// A box a registered WGSL function paints.
1106 ///
1107 /// An ordinary node in every other respect: it lays out where it is
1108 /// declared, sizes from `spec`, rounds by `radius`, clips, fades with
1109 /// its subtree's opacity, takes input like any box, and may hold
1110 /// children — which paint over it, so a gradient card is a `fragment`
1111 /// with a title and buttons inside it.
1112 ///
1113 /// It has **no intrinsic size**: unlike an image there is nothing to
1114 /// measure, so a fragment with no `width` / `height` / `fill` is zero
1115 /// by zero and draws nothing. Size it.
1116 ///
1117 /// `params` is up to sixteen numbers, positional, zero-padded, read by
1118 /// the shader as four `vec4<f32>`; more than sixteen are dropped with
1119 /// a `fragment-params-truncated` warning. A handle that is not live in
1120 /// this session draws nothing, as every resource kind does — and so
1121 /// does one whose `image` (`FragmentId::with_image`) is not, which is
1122 /// the removal order: the image goes, the fragment reading it draws
1123 /// the fallback, and the handle it kept is a `foreign-resource` miss
1124 /// like any other.
1125 pub fn fragment_node(
1126 &mut self,
1127 frag: impl Into<crate::fragment::FragmentRef>,
1128 params: &[f32],
1129 spec: NodeSpec,
1130 ) -> Key {
1131 let key = self.open_fragment(frag, params, spec);
1132 self.close();
1133 key
1134 }
1135
1136 /// Opens a fragment as a parent: its children paint over it, which is
1137 /// what a gradient card with a title and buttons in it is. Balance it
1138 /// with [`Self::close`], or use `Ui::fragment_with`.
1139 pub fn open_fragment(
1140 &mut self,
1141 frag: impl Into<crate::fragment::FragmentRef>,
1142 params: &[f32],
1143 spec: NodeSpec,
1144 ) -> Key {
1145 if self.tree.is_empty() {
1146 return Key::ROOT;
1147 }
1148 let key = self.auto_key();
1149 self.fragment_with_key(key, frag.into(), params, spec);
1150 key
1151 }
1152
1153 /// [`Self::fragment_node`] under a label key, for a fragment that
1154 /// transitions or exits and needs a stable identity across frames.
1155 pub fn fragment_node_keyed(
1156 &mut self,
1157 label: &str,
1158 frag: impl Into<crate::fragment::FragmentRef>,
1159 params: &[f32],
1160 spec: NodeSpec,
1161 ) -> Key {
1162 let key = self.open_fragment_keyed(label, frag, params, spec);
1163 self.close();
1164 key
1165 }
1166
1167 /// [`Self::open_fragment`] under a label key.
1168 pub fn open_fragment_keyed(
1169 &mut self,
1170 label: &str,
1171 frag: impl Into<crate::fragment::FragmentRef>,
1172 params: &[f32],
1173 spec: NodeSpec,
1174 ) -> Key {
1175 if self.tree.is_empty() {
1176 return Key::ROOT;
1177 }
1178 let key = self.child_key(label);
1179 self.fragment_with_key(key, frag.into(), params, spec);
1180 self.note_label(key, label);
1181 key
1182 }
1183
1184 /// [`Self::open_fragment`] under a data index; see [`Self::open_indexed`].
1185 pub fn open_fragment_indexed(
1186 &mut self,
1187 i: u64,
1188 frag: impl Into<crate::fragment::FragmentRef>,
1189 params: &[f32],
1190 spec: NodeSpec,
1191 ) -> Key {
1192 if self.tree.is_empty() {
1193 return Key::ROOT;
1194 }
1195 let key = self.child_key_indexed(i);
1196 self.fragment_with_key(key, frag.into(), params, spec);
1197 key
1198 }
1199
1200 pub(crate) fn fragment_with_key(
1201 &mut self,
1202 key: Key,
1203 frag: crate::fragment::FragmentRef,
1204 params: &[f32],
1205 spec: NodeSpec,
1206 ) {
1207 if self.keeping() {
1208 self.keep_op(replay::Op::Fragment {
1209 key,
1210 frag,
1211 params: params.to_vec(),
1212 spec: Box::new(spec.clone()),
1213 });
1214 }
1215 let (params, dropped) = crate::fragment::params_of(params);
1216 if dropped > 0 {
1217 self.diag.raise(Warning {
1218 code: crate::diag::FRAGMENT_PARAMS_TRUNCATED,
1219 key,
1220 message: format!(
1221 "a fragment takes sixteen params and {} were declared, so the last {dropped} were dropped; pack what the shader needs into the sixteen it has",
1222 params.len() + dropped
1223 ),
1224 });
1225 }
1226 let draw = self.fragments.push(crate::fragment::Draw {
1227 id: frag.id,
1228 image: frag.image,
1229 params,
1230 });
1231 self.open_content(key, spec, NodeContent::Fragment(draw));
1232 }
1233
1234 /// A stroke through `points` in the parent's box space: one round-capped
1235 /// segment for two points, a polyline for more, a smooth curve through
1236 /// them with [`Stroke::curve`].
1237 ///
1238 /// Never in layout. The node is a float sized to the stroke's padded
1239 /// bounding box, so it takes no room in a row or column, and `spec`'s
1240 /// sizing, clamps, padding, gap and alignment are ignored. What `spec`
1241 /// carries that matters: `transition` (the colour eases — it rides in
1242 /// the `bg` slot — and `slide`, `enter` and `exit` offsets move the
1243 /// float), `opacity`, `on_layout` (reports the bounding box), a
1244 /// declared `float` whose *anchor* is kept (`FloatAnchor::Viewport`
1245 /// reads the points in viewport space), and `role` / `label`, which are
1246 /// honoured like any node's; without them a line has no access row —
1247 /// unless it takes input, when it derives one as a box would. Input
1248 /// is hit by *shape*: a press within half the stroke's
1249 /// width of any piece (at least `MIN_STROKE_GRAB` wide) hits it, and
1250 /// a press elsewhere in its box falls through to what is under it.
1251 /// Fewer than two points draw nothing.
1252 ///
1253 /// Consecutive segments overlap at their round caps, which is the
1254 /// join: exact for an opaque stroke, and a translucent one
1255 /// double-blends there, the way a faded subtree shows its seams.
1256 pub fn line_node(&mut self, points: &[Vec2], stroke: Stroke, spec: NodeSpec) {
1257 if self.tree.is_empty() {
1258 return;
1259 }
1260 let key = self.auto_key();
1261 self.line_with_key(key, points, &stroke, spec);
1262 }
1263
1264 /// [`Self::line_node`] under a label key, for a stroke that transitions
1265 /// or exits and needs a stable identity across frames.
1266 pub fn line_node_keyed(
1267 &mut self,
1268 label: &str,
1269 points: &[Vec2],
1270 stroke: Stroke,
1271 spec: NodeSpec,
1272 ) {
1273 if self.tree.is_empty() {
1274 return;
1275 }
1276 let key = self.child_key(label);
1277 self.line_with_key(key, points, &stroke, spec);
1278 // Like every other keyed door: the label `key_of` resolves through.
1279 self.note_label(key, label);
1280 }
1281
1282 /// [`Self::line_node`] under a data index; see [`Self::open_indexed`].
1283 pub fn line_node_indexed(&mut self, i: u64, points: &[Vec2], stroke: Stroke, spec: NodeSpec) {
1284 if self.tree.is_empty() {
1285 return;
1286 }
1287 let key = self.child_key_indexed(i);
1288 self.line_with_key(key, points, &stroke, spec);
1289 }
1290
1291 /// The stroke is lent from here down: at 44 bytes with its dash it
1292 /// is passed in memory, and a copy at each call of the chain was
1293 /// most of what a solid line cost over alpha.36's (backlog C52).
1294 pub(crate) fn line_with_key(
1295 &mut self,
1296 key: Key,
1297 points: &[Vec2],
1298 stroke: &Stroke,
1299 mut spec: NodeSpec,
1300 ) {
1301 let declared = self.keeping().then(|| spec.clone());
1302 let Some((id, rect)) = self.lines.push(points, stroke) else {
1303 return;
1304 };
1305 if let Some(spec) = declared {
1306 self.keep_op(replay::Op::Line {
1307 key,
1308 points: points.to_vec(),
1309 stroke: *stroke,
1310 spec: Box::new(spec),
1311 });
1312 }
1313 // The stroke colour rides in the slot backgrounds tween through, so
1314 // `transition`, `enter` and `exit` reach it with no slot of its own;
1315 // nothing else of the box vocabulary applies to a stroke.
1316 spec.style.bg = stroke.color;
1317 spec.style.border_w = 0.0;
1318 spec.style.border_color = Color::TRANSPARENT;
1319 spec.style.shadow = crate::spec::Shadow::default();
1320 // The same pipeline as a box's, so a stroke's `hover_bg` is the
1321 // colour it takes under the pointer and `accent` is honoured.
1322 self.prepare_spec(key, &mut spec);
1323 // The box is the stroke's own, and the points are stored relative
1324 // to it.
1325 Self::float_box_for(&mut spec, rect);
1326 self.push_leaf(key, spec, NodeContent::Line(id));
1327 }
1328
1329 /// A filled polygon through `points` in the parent's box space:
1330 /// up to
1331 /// eight vertices, the fill in `spec`'s `bg`, painted by the stock
1332 /// polygon fragment the core registers itself.
1333 ///
1334 /// Placed exactly as a line is: never in
1335 /// layout, a float sized to the points' bounding box inflated by a
1336 /// logical pixel for the edge ramp, so it takes no room in a row or
1337 /// column and `spec`'s sizing, clamps, padding, gap and alignment are
1338 /// ignored. `transition` eases the fill through the `bg` slot, and
1339 /// `slide`, `enter` and `exit` move the float; a declared `float`
1340 /// keeps its *anchor*; `role` and `label` are honoured, and without
1341 /// them a polygon has no access row unless it takes input, when it
1342 /// derives one as a box would (a clickable wedge is a button). Input
1343 /// is hit by *shape*: a press inside the outline hits it,
1344 /// one in its box but outside the outline falls through to what is
1345 /// under. Fewer than three points draw nothing; a ninth and later are
1346 /// dropped with `polygon-points-truncated`. The outline may be
1347 /// concave; a self-intersecting one fills even-odd, its overlaps
1348 /// unfilled.
1349 pub fn polygon_node(&mut self, points: &[Vec2], spec: NodeSpec) {
1350 if self.tree.is_empty() {
1351 return;
1352 }
1353 let key = self.auto_key();
1354 self.polygon_with_key(key, points, spec);
1355 }
1356
1357 /// [`Self::polygon_node`] under a label key.
1358 pub fn polygon_node_keyed(&mut self, label: &str, points: &[Vec2], spec: NodeSpec) {
1359 if self.tree.is_empty() {
1360 return;
1361 }
1362 let key = self.child_key(label);
1363 self.polygon_with_key(key, points, spec);
1364 self.note_label(key, label);
1365 }
1366
1367 /// [`Self::polygon_node`] under a data index; see [`Self::open_indexed`].
1368 pub fn polygon_node_indexed(&mut self, i: u64, points: &[Vec2], spec: NodeSpec) {
1369 if self.tree.is_empty() {
1370 return;
1371 }
1372 let key = self.child_key_indexed(i);
1373 self.polygon_with_key(key, points, spec);
1374 }
1375
1376 /// The stock polygon fragment's handle, registered on first use and
1377 /// again after `remove_fragment` forgot it.
1378 fn stock_polygon(&mut self) -> Option<crate::resources::FragmentId> {
1379 if let Some(id) = self.stock_polygon {
1380 return Some(id);
1381 }
1382 let id = self.add_fragment(crate::fragment::POLYGON);
1383 self.stock_polygon = id;
1384 id
1385 }
1386
1387 pub(crate) fn polygon_with_key(&mut self, key: Key, points: &[Vec2], mut spec: NodeSpec) {
1388 if points.len() < 3 {
1389 return;
1390 }
1391 if self.keeping() {
1392 self.keep_op(replay::Op::Polygon {
1393 key,
1394 points: points.to_vec(),
1395 spec: Box::new(spec.clone()),
1396 });
1397 }
1398 if points.len() > crate::fragment::POLYGON_MAX_POINTS {
1399 self.diag.raise(Warning {
1400 code: crate::diag::POLYGON_POINTS_TRUNCATED,
1401 key,
1402 message: format!(
1403 "a polygon takes {} points and {} were declared, so the last {} were \
1404 dropped; split it in two",
1405 crate::fragment::POLYGON_MAX_POINTS,
1406 points.len(),
1407 points.len() - crate::fragment::POLYGON_MAX_POINTS
1408 ),
1409 });
1410 }
1411 let points = &points[..points.len().min(crate::fragment::POLYGON_MAX_POINTS)];
1412 let Some(id) = self.stock_polygon() else {
1413 return;
1414 };
1415 // The box: the points' bounds, a logical pixel out on every side
1416 // so the one-pixel edge ramp is never cut by the quad's own edge.
1417 let (mut x0, mut y0, mut x1, mut y1) = (f32::MAX, f32::MAX, f32::MIN, f32::MIN);
1418 for p in points {
1419 x0 = x0.min(p.x);
1420 y0 = y0.min(p.y);
1421 x1 = x1.max(p.x);
1422 y1 = y1.max(p.y);
1423 }
1424 let rect = Rect::new(x0 - 1.0, y0 - 1.0, x1 - x0 + 2.0, y1 - y0 + 2.0);
1425 // The vertices, normalised to that box; the last repeated to pad,
1426 // which the stock source reads as a zero-length edge and skips.
1427 let mut params = [0.0f32; 16];
1428 let last = points[points.len() - 1];
1429 for i in 0..crate::fragment::POLYGON_MAX_POINTS {
1430 let p = points.get(i).copied().unwrap_or(last);
1431 params[i * 2] = (p.x - rect.x) / rect.w;
1432 params[i * 2 + 1] = (p.y - rect.y) / rect.h;
1433 }
1434 let draw = self.fragments.push(crate::fragment::Draw {
1435 id,
1436 image: None,
1437 params,
1438 });
1439 // The fill rides in `bg`, which `transition`, `enter` and `exit`
1440 // already ease — and which `hover_bg` and `accent` swap, through
1441 // the same pipeline as a box's; nothing else of the box vocabulary
1442 // applies.
1443 spec.style.border_w = 0.0;
1444 spec.style.border_color = Color::TRANSPARENT;
1445 spec.style.shadow = crate::spec::Shadow::default();
1446 self.prepare_spec(key, &mut spec);
1447 Self::float_box_for(&mut spec, rect);
1448 self.push_leaf(key, spec, NodeContent::Polygon(draw));
1449 }
1450
1451 /// A path — any outline, SVG's `d` — filled with `spec`'s `bg` by the
1452 /// path's rule and stroked by its stroke if it has one
1453 /// (`docs/adr/0040-a-path-is-a-mask-in-the-atlas.md`).
1454 ///
1455 /// Placed exactly as a line is: never in layout, a float sized to the
1456 /// outline's bounding box two logical pixels out (and half the stroke's
1457 /// width further), so it takes no room in a row or column and
1458 /// `spec`'s sizing, clamps, padding, gap and alignment are ignored.
1459 /// `transition` eases the fill through the `bg` slot, and `slide`,
1460 /// `enter` and `exit` move the float; a declared `float` keeps its
1461 /// *anchor*; `role` and `label` are honoured, and without them a path
1462 /// has no access row unless it takes input, when it derives one as a
1463 /// box would. Input is hit by *shape*: a press inside the outline by
1464 /// the fill rule hits it, one in its box past the outline falls
1465 /// through to what is under. A path with no outline draws nothing.
1466 ///
1467 /// The outline is rasterized once per shape, scale and quarter-pixel
1468 /// position into the glyph atlas and drawn as a glyph-mask quad; the
1469 /// fill bleeds half a pixel so two paths sharing an edge meet without
1470 /// the background showing through. A path whose ops change twice within
1471 /// a few frames, or whose mask is a quarter of the biggest atlas page or
1472 /// more, draws from a texture of its own instead.
1473 pub fn path_node(&mut self, path: &crate::path::Path, spec: NodeSpec) {
1474 if self.tree.is_empty() {
1475 return;
1476 }
1477 let key = self.auto_key();
1478 self.path_with_key(
1479 key,
1480 path.ops(),
1481 path.rule(),
1482 path.stroke(),
1483 path.turn(),
1484 spec,
1485 );
1486 }
1487
1488 /// [`Self::path_node`] under a label key.
1489 pub fn path_node_keyed(&mut self, label: &str, path: &crate::path::Path, spec: NodeSpec) {
1490 if self.tree.is_empty() {
1491 return;
1492 }
1493 let key = self.child_key(label);
1494 self.path_with_key(
1495 key,
1496 path.ops(),
1497 path.rule(),
1498 path.stroke(),
1499 path.turn(),
1500 spec,
1501 );
1502 self.note_label(key, label);
1503 }
1504
1505 /// [`Self::path_node`] under a data index; see [`Self::open_indexed`].
1506 pub fn path_node_indexed(&mut self, i: u64, path: &crate::path::Path, spec: NodeSpec) {
1507 if self.tree.is_empty() {
1508 return;
1509 }
1510 let key = self.child_key_indexed(i);
1511 self.path_with_key(
1512 key,
1513 path.ops(),
1514 path.rule(),
1515 path.stroke(),
1516 path.turn(),
1517 spec,
1518 );
1519 }
1520
1521 /// SVG path data to a [`crate::path::Path`], through the one parser
1522 /// every binding's `d` goes through (`Path::parse`, reached here as
1523 /// the door the C API's `kui_path_parse` is). `Err` names the byte.
1524 pub fn parse_path(&self, d: &str) -> Result<crate::path::Path, crate::path::PathError> {
1525 crate::path::Path::parse(d)
1526 }
1527
1528 /// [`Self::path_node`] from SVG path data, parsed by the one parser
1529 /// every binding goes through; data that does not parse raises
1530 /// `path-malformed` under the node's key and draws nothing.
1531 pub fn path_d_node(
1532 &mut self,
1533 d: &str,
1534 rule: crate::path::FillRule,
1535 stroke: Option<Stroke>,
1536 turn: Option<crate::path::Turn>,
1537 spec: NodeSpec,
1538 ) {
1539 if self.tree.is_empty() {
1540 return;
1541 }
1542 let key = self.auto_key();
1543 self.path_node_d(key, d, rule, stroke, turn, spec);
1544 }
1545
1546 /// [`Self::path_d_node`] under a label key.
1547 pub fn path_d_node_keyed(
1548 &mut self,
1549 label: &str,
1550 d: &str,
1551 rule: crate::path::FillRule,
1552 stroke: Option<Stroke>,
1553 turn: Option<crate::path::Turn>,
1554 spec: NodeSpec,
1555 ) {
1556 if self.tree.is_empty() {
1557 return;
1558 }
1559 let key = self.child_key(label);
1560 self.path_node_d(key, d, rule, stroke, turn, spec);
1561 self.note_label(key, label);
1562 }
1563
1564 /// [`Self::path_d_node`] under a key the caller derived.
1565 pub fn path_node_d(
1566 &mut self,
1567 key: Key,
1568 d: &str,
1569 rule: crate::path::FillRule,
1570 stroke: Option<Stroke>,
1571 turn: Option<crate::path::Turn>,
1572 spec: NodeSpec,
1573 ) {
1574 // The string a key declared last frame is the string it declares
1575 // this frame, nearly always: its ops are kept, and the parse is
1576 // paid when the string changes.
1577 let hash = crate::key::hash_bulk(d.as_bytes());
1578 let kept = self
1579 .path_parsed
1580 .remove(&key)
1581 .filter(|p| p.hash == hash && p.len == d.len());
1582 let ops = match kept {
1583 Some(p) => p.ops,
1584 None => match crate::path::Path::parse(d) {
1585 Ok(path) => path.into_ops(),
1586 Err(e) => {
1587 self.diag.raise(Warning {
1588 code: crate::diag::PATH_MALFORMED,
1589 key,
1590 message: format!("the path's `d` did not parse: expected {e}"),
1591 });
1592 return;
1593 }
1594 },
1595 };
1596 self.path_with_key(key, &ops, rule, stroke, turn, spec);
1597 self.path_parsed.insert(
1598 key,
1599 crate::path::Parsed {
1600 hash,
1601 len: d.len(),
1602 seen: self.frame_no,
1603 ops,
1604 },
1605 );
1606 }
1607
1608 /// [`Self::path_node`] from the flat op form — a code, then its
1609 /// operands, per op, as `Path::to_floats` writes it and a binding's
1610 /// wire carries it. Floats that are not the form (a code that is not
1611 /// one, an op cut short) raise `path-malformed` under the node's key
1612 /// and draw nothing: one answer in every binding, where it was an
1613 /// error in one and silence in two (RG112).
1614 pub fn path_flat_node(
1615 &mut self,
1616 floats: &[f32],
1617 rule: crate::path::FillRule,
1618 stroke: Option<Stroke>,
1619 turn: Option<crate::path::Turn>,
1620 spec: NodeSpec,
1621 ) {
1622 if self.tree.is_empty() {
1623 return;
1624 }
1625 let key = self.auto_key();
1626 self.path_node_flat(key, floats, rule, stroke, turn, spec);
1627 }
1628
1629 /// [`Self::path_flat_node`] under a label key.
1630 pub fn path_flat_node_keyed(
1631 &mut self,
1632 label: &str,
1633 floats: &[f32],
1634 rule: crate::path::FillRule,
1635 stroke: Option<Stroke>,
1636 turn: Option<crate::path::Turn>,
1637 spec: NodeSpec,
1638 ) {
1639 if self.tree.is_empty() {
1640 return;
1641 }
1642 let key = self.child_key(label);
1643 self.path_node_flat(key, floats, rule, stroke, turn, spec);
1644 self.note_label(key, label);
1645 }
1646
1647 /// [`Self::path_flat_node`] under a key the caller derived.
1648 pub fn path_node_flat(
1649 &mut self,
1650 key: Key,
1651 floats: &[f32],
1652 rule: crate::path::FillRule,
1653 stroke: Option<Stroke>,
1654 turn: Option<crate::path::Turn>,
1655 spec: NodeSpec,
1656 ) {
1657 match crate::path::Path::from_floats(floats) {
1658 Ok(path) => self.path_with_key(key, path.ops(), rule, stroke, turn, spec),
1659 Err(e) => self.diag.raise(Warning {
1660 code: crate::diag::PATH_MALFORMED,
1661 key,
1662 message: format!(
1663 "the path's ops are not the flat form: expected {} at float {}",
1664 e.what, e.at
1665 ),
1666 }),
1667 }
1668 }
1669
1670 pub(crate) fn path_with_key(
1671 &mut self,
1672 key: Key,
1673 ops: &[crate::path::PathOp],
1674 rule: crate::path::FillRule,
1675 stroke: Option<Stroke>,
1676 turn: Option<crate::path::Turn>,
1677 mut spec: NodeSpec,
1678 ) {
1679 let declared = self.keeping().then(|| spec.clone());
1680 // A number that is not one - `1e99` in `d` is an infinity, a
1681 // chart's 0/0 a NaN - has no outline to draw: the bounds would
1682 // drop it and the rasterizer would not.
1683 // The same for its turn, which the box does not depend on and so
1684 // could not catch.
1685 let turn_ok = turn.is_none_or(|t| {
1686 t.turns.is_finite() && t.pivot.is_none_or(|p| p.x.is_finite() && p.y.is_finite())
1687 });
1688 if !turn_ok || !ops.iter().all(crate::path::PathOp::is_finite) {
1689 self.diag.raise(Warning {
1690 code: crate::diag::PATH_MALFORMED,
1691 key,
1692 message: "the path holds a number that is not finite (a NaN or an \
1693 infinity), among its coordinates or in its turn"
1694 .into(),
1695 });
1696 return;
1697 }
1698 let stroke_w = stroke.map_or(0.0, |s| s.width.max(0.0));
1699 let dash = stroke.and_then(|s| s.dash.cut(stroke_w));
1700 let Some((id, rect)) = self.paths.push(ops, rule, stroke_w, dash, turn) else {
1701 return;
1702 };
1703 if let Some(spec) = declared {
1704 self.keep_op(replay::Op::Path {
1705 key,
1706 ops: ops.to_vec(),
1707 rule,
1708 stroke,
1709 turn,
1710 spec: Box::new(spec),
1711 });
1712 }
1713 // The mask is the box at the frame's scale; past what a texture
1714 // can hold it draws nothing, and says so once per key. A box
1715 // that is not finite - a coordinate past what an f32 holds, a NaN
1716 // among the ops or in the turn - is past it too: `max` drops a
1717 // NaN, so the side alone would let one through.
1718 let side = (rect.w.max(rect.h) * self.scale).ceil();
1719 let finite = [rect.x, rect.y, rect.w, rect.h]
1720 .iter()
1721 .all(|v| v.is_finite());
1722 if !finite || side + 2.0 > crate::path::MAX_MASK_SIDE as f32 {
1723 self.diag.raise(Warning {
1724 code: crate::diag::PATH_TOO_LARGE,
1725 key,
1726 message: format!(
1727 "the path's mask would be {side} px on a side at this scale, and a \
1728 texture holds {} at most; draw it smaller, or as several paths",
1729 crate::path::MAX_MASK_SIDE
1730 ),
1731 });
1732 return;
1733 }
1734 // A key whose ops changed twice within a few frames is animating:
1735 // its masks go to a texture of their own rather than churning the
1736 // atlas (ADR 0040, decision 8). One change is a new shape and a
1737 // new slot.
1738 // A pattern that moves - a marquee's marching ants - is a shape
1739 // that moves: each offset is a mask of its own.
1740 let hash = self.paths.run(id).0.hash ^ dash.map_or(0, |d| d.hash());
1741 let now = self.frame_no;
1742 let motion = match self.path_motion.get(&key) {
1743 Some(&m) if m.hash != hash => crate::path::Motion {
1744 hash,
1745 seen: now,
1746 changed: now,
1747 animating: m.animating
1748 || (m.changed != 0 && now - m.changed <= crate::path::ANIMATING_WINDOW),
1749 },
1750 // Still for long enough, it is a shape again and goes back
1751 // to the atlas: the latch is for what moves, and a key from
1752 // the tree position whose siblings came and went would
1753 // otherwise hold a texture of its own for good (RG112).
1754 Some(&m) => crate::path::Motion {
1755 seen: now,
1756 animating: m.animating && now - m.changed <= crate::path::SETTLED_AFTER,
1757 ..m
1758 },
1759 None => crate::path::Motion {
1760 hash,
1761 seen: now,
1762 changed: 0,
1763 animating: false,
1764 },
1765 };
1766 if motion.animating {
1767 self.paths.set_animating(id);
1768 }
1769 self.path_motion.insert(key, motion);
1770 // The fill rides in `bg`, which `transition`, `enter` and `exit`
1771 // ease and `hover_bg` and `accent` swap; the stroke rides in the
1772 // border slots, which is what a border is to a box.
1773 spec.style.border_w = stroke_w;
1774 spec.style.border_color = stroke.map_or(Color::TRANSPARENT, |s| s.color);
1775 spec.style.shadow = crate::spec::Shadow::default();
1776 self.prepare_spec(key, &mut spec);
1777 Self::float_box_for(&mut spec, rect);
1778 self.push_leaf(key, spec, NodeContent::Path(id));
1779 }
1780
1781 /// A paragraph of styled spans, shaped and wrapped as one flow.
1782 pub fn rich_text_node(&mut self, spans: &[Span<'_>], base: TextStyle) {
1783 if self.tree.is_empty() {
1784 return;
1785 }
1786 let key = self.auto_key();
1787 if self.keeping() {
1788 self.keep_op(replay::Op::Rich {
1789 key,
1790 spans: spans
1791 .iter()
1792 .map(|s| (s.text.into(), Span { text: "", ..*s }))
1793 .collect(),
1794 base,
1795 });
1796 }
1797 self.rich_text_with_key(key, spans, base);
1798 }
1799
1800 /// [`Self::rich_text_node`] under a key the caller derived.
1801 pub(crate) fn rich_text_with_key(&mut self, key: Key, spans: &[Span<'_>], base: TextStyle) {
1802 if self.tree.is_empty() {
1803 return;
1804 }
1805 // The paragraph's own colour, which each span falls back to.
1806 let base = base.or_fg(self.theme.fg);
1807 let tid = {
1808 let sess = &mut *self.session.state();
1809 self.text
1810 .add_rich(spans, &base, &sess.resources, &mut sess.fonts)
1811 };
1812 let parent = self.current();
1813 self.tree.push(
1814 parent,
1815 key,
1816 self.origin,
1817 NodeSpec::default(),
1818 NodeContent::Text(tid),
1819 );
1820 }
1821}