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