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