Skip to main content

kui_core/
ui.rs

1//! The Rust frame builder: a thin safe façade over `Core`'s flat builder
2//! methods (which are also the FFI surface). It never captures user state,
3//! so `view(&state)` and `update(&mut state)` can't conflict.
4
5use crate::edit::EditOptions;
6use crate::env::Env;
7use crate::geom::{Rect, Size, Vec2};
8use crate::key::Key;
9use crate::line::Stroke;
10use crate::runtime::Core;
11use crate::slot::{Fill, Slot};
12use crate::spec::{NodeSpec, TextStyle};
13use crate::text::Span;
14use crate::tree::OriginId;
15use crate::value::Value;
16use crate::window::{WindowCommand, WindowConfig, WindowId};
17
18pub struct Ui<'a> {
19    core: &'a mut Core,
20    /// What fills the slots this frame declares (`Core::frame_with`);
21    /// `None` for a frame begun without one, and inside a fill — an
22    /// extension's `slot` declares and fills nothing.
23    filler: Option<&'a mut dyn Fill>,
24}
25
26impl<'a> Ui<'a> {
27    pub(crate) fn new(core: &'a mut Core) -> Self {
28        Self { core, filler: None }
29    }
30
31    /// [`Self::wrap`] with something to fill the slots the frame declares
32    /// — for a frontend that drives `Core` directly and keeps an
33    /// extension list of its own (the C context, a headless Node one), so
34    /// its `slot` and `finish` are this type's rather than a copy of them.
35    pub fn with_filler(core: &'a mut Core, filler: &'a mut dyn Fill) -> Self {
36        Self {
37            core,
38            filler: Some(filler),
39        }
40    }
41
42    /// Escape hatch to the underlying core (e.g. for FFI view callbacks).
43    pub fn core(&mut self) -> &mut Core {
44        self.core
45    }
46
47    /// The inverse escape hatch: wraps a borrowed core mid-frame so foreign
48    /// frontends that drive `Core` directly (FFI, Node) can call `widgets::*`.
49    pub fn wrap(core: &'a mut Core) -> Self {
50        Self { core, filler: None }
51    }
52
53    /// Declares a slot here, with no parameters: whatever fills it draws
54    /// now, as children of the node this view is inside, at this
55    /// position among its siblings. `name` is the full name,
56    /// `namespace/slot` — the namespace the host gave the extension when
57    /// it loaded it, and the slot in the extension's own vocabulary
58    /// (`"fs/panel"`). `"ns/root"` is the fill that follows the host's
59    /// view for an extension listing no slots, and declaring it moves
60    /// that fill here. See
61    /// `docs/adr/0014-slots-an-extension-fills-in-place.md`.
62    pub fn slot(&mut self, name: &str) {
63        self.slot_with(name, &crate::slot::NULL_PARAMS);
64    }
65
66    /// `slot` with parameters the extension reads this frame
67    /// (`Slot::params`); a `Value` because it is the type that already
68    /// crosses to an extension. Nothing is retained — pass what is true
69    /// this frame, every frame.
70    ///
71    /// Whether the slot was declared: false for a name this frame already
72    /// declared (which warns, `duplicate-slot`) or outside a frame. True
73    /// whether or not anything filled it — a host with nothing loaded
74    /// still gets a placed, empty node to lay out around.
75    pub fn slot_with(&mut self, name: &str, params: &Value) -> bool {
76        let Some(key) = self.core.begin_slot(name) else {
77            return false;
78        };
79        if let Some(filler) = self.filler.as_deref_mut() {
80            filler.fill(name, key, params, &mut Ui::new(self.core));
81        }
82        true
83    }
84
85    /// Whether the full name `name` was declared this frame so far.
86    pub fn slot_declared(&self, name: &str) -> bool {
87        self.core.slot_declared(name)
88    }
89
90    /// Declares a devtools tab an extension fills (ADR 0032, decision
91    /// 1): `name` is the tab's identity, `label` what the strip shows,
92    /// `slot` the full slot name (`"ts/panel"`) the extension names.
93    /// While the tab is the one on show, the panel declares the slot in
94    /// the tab's body and the fill is drawn there; otherwise the slot is
95    /// not declared and the extension is not asked, though its naming
96    /// the slot raises no `unknown-slot`. Made every frame, panel on or
97    /// off. A name declared twice in a frame warns `duplicate-tab`.
98    pub fn devtools_tab(&mut self, name: &str, label: &str, slot: &str) {
99        self.core.devtools_tab_declare(name, label, Some(slot));
100    }
101
102    /// Declares a devtools tab the host draws itself, and draws it
103    /// **only when it is shown** (ADR 0032, decisions 1 and 3): `f` runs
104    /// when the panel is on, docked in this window, and `name` is the
105    /// tab on show — otherwise this declares and returns, and the tab
106    /// costs nothing. What `f` builds is the host's: its keys, labels
107    /// and origin, its events reaching the host untouched — laid out
108    /// and painted as a layer over the panel's tab body, clipped to it,
109    /// in the dock's focus region. The panel's facts are read through
110    /// `devtools_selected` and its siblings.
111    pub fn devtools_tab_with(&mut self, name: &str, label: &str, f: impl FnOnce(&mut Ui<'_>)) {
112        if !self.core.devtools_tab_declare(name, label, None) {
113            return;
114        }
115        if !self.core.devtools_tab_shown(name) {
116            return;
117        }
118        self.core.devtools_tab_open(name);
119        f(self);
120        self.core.close();
121    }
122
123    /// Whether the host form of tab `name` is shown this frame — what
124    /// `devtools_tab_with` asks before running its closure, for a caller
125    /// that builds the content some other way.
126    pub fn devtools_tab_shown(&self, name: &str) -> bool {
127        self.core.devtools_tab_shown(name)
128    }
129
130    /// The bare declaration either form makes (ADR 0032, decision 1):
131    /// `slot` for the extension form, `None` for a host form whose
132    /// content is built some other way or not at all this frame. Whether
133    /// the declaration stood — false for a name already declared this
134    /// frame (`duplicate-tab`) or outside a frame.
135    pub fn devtools_tab_declare(&mut self, name: &str, label: &str, slot: Option<&str>) -> bool {
136        self.core.devtools_tab_declare(name, label, slot)
137    }
138
139    /// The host form for a binding that decided the laziness on its own
140    /// side (a Node encoder or a Lua converter that already read which
141    /// tab is on show, ADR 0032 decision 3): declares the tab and builds
142    /// `f` as its content **whether or not** the tab is shown here — a
143    /// content whose body the panel did not build anchors to nothing
144    /// and paints nothing, and a name already declared this frame
145    /// (`duplicate-tab`) builds into a node of no size, so the caller's
146    /// stream stays in step either way. `devtools_tab_with` is the door
147    /// for a caller that can skip the work.
148    pub fn devtools_tab_declared(&mut self, name: &str, label: &str, f: impl FnOnce(&mut Ui<'_>)) {
149        if self.core.devtools_tab_declare(name, label, None) {
150            self.core.devtools_tab_open(name);
151        } else {
152            self.core.open(
153                NodeSpec::column()
154                    .width(crate::spec::Sizing::Fixed(0.0))
155                    .height(crate::spec::Sizing::Fixed(0.0))
156                    .clip(),
157            );
158        }
159        f(self);
160        self.core.close();
161    }
162
163    /// The panel's selected node (`Core::devtools_selected`).
164    pub fn devtools_selected(&self) -> Option<Key> {
165        self.core.devtools_selected()
166    }
167
168    /// The panel's hovered tree row (`Core::devtools_hovered`).
169    pub fn devtools_hovered(&self) -> Option<Key> {
170        self.core.devtools_hovered()
171    }
172
173    /// The node the picker is over (`Core::devtools_picked`).
174    pub fn devtools_picked(&self) -> Option<Key> {
175        self.core.devtools_picked()
176    }
177
178    /// Whether the panel's picker is up (`Core::devtools_picking`).
179    pub fn devtools_picking(&self) -> bool {
180        self.core.devtools_picking()
181    }
182
183    /// The tab the panel is on, by name (`Core::devtools_current_tab`).
184    pub fn devtools_current_tab(&self) -> String {
185        self.core.devtools_current_tab()
186    }
187
188    /// Loads `ext` under `namespace` into the list filling this frame's
189    /// slots, and answers with the origin it got. This is how an
190    /// extension hosts an extension of its own: the guest asks mid-frame,
191    /// when it knows what it wants, and the plugin lands in the same list
192    /// as the host's own — one namespace map, one origin per extension,
193    /// however deep the loading went (`crate::slot`).
194    ///
195    /// Fails when the namespace is taken, or empty with an extension that
196    /// names itself nothing, exactly as
197    /// `Extensions::push_as` does, and when this frame was begun without
198    /// a filler (`Core::frame`) or with one that is not a list.
199    pub fn add_extension(
200        &mut self,
201        namespace: &str,
202        ext: Box<dyn crate::runtime::Extension>,
203    ) -> Result<OriginId, String> {
204        match self.filler.as_deref_mut() {
205            Some(filler) => filler.add(namespace, ext),
206            None => Err(format!(
207                "cannot load `{namespace}`: this frame declares no slots to fill"
208            )),
209        }
210    }
211
212    /// Runs `f` as the fill of `slot` under `origin`: nodes it opens are
213    /// tagged with the origin, keyed under the slot's key, and closed
214    /// for it if it leaves any open. What a `Fill` implementation calls
215    /// per extension; see `Core::fill`.
216    pub fn fill(&mut self, origin: OriginId, slot: &Slot<'_>, f: impl FnOnce(&mut Ui<'_>)) {
217        self.core.fill(slot, origin, f);
218    }
219
220    /// `fill`, with `filler` answering the slots the fill declares — an
221    /// extension hosting extensions of its own. `Extensions::fill_one`
222    /// passes itself, which is what makes one namespace map do for every
223    /// level; see `crate::slot`.
224    pub fn fill_within(
225        &mut self,
226        origin: OriginId,
227        slot: &Slot<'_>,
228        filler: &mut dyn Fill,
229        f: impl FnOnce(&mut Ui<'_>),
230    ) {
231        self.core.fill_within(slot, origin, Some(filler), f);
232    }
233
234    /// The origin the nodes opened right now are tagged with:
235    /// `OriginId::HOST` in the host's own view, the filling extension's
236    /// inside a fill. What records who declared a slot, and so where the
237    /// replies of whatever fills it go.
238    pub fn origin(&self) -> OriginId {
239        self.core.origin()
240    }
241
242    /// The viewport this frame lays out into: the window, less the
243    /// devtools' dock while the panel is docked (`Core::viewport`).
244    pub fn viewport(&self) -> Size {
245        self.core.viewport()
246    }
247
248    /// The env reading's inputs, the frame's facts included
249    /// (`Core::env_facts`): what a binding's `env` table is filled from.
250    pub fn env_facts(&self) -> crate::schema::EnvFacts {
251        self.core.env_facts()
252    }
253
254    /// Host facts pushed by the frame driver (refresh rate, focus).
255    pub fn env(&self) -> Env {
256        self.core.env
257    }
258
259    /// This frame's palette: the named colours the stock widgets paint
260    /// with, derived from `env.system` unless the app pinned something
261    /// else (`docs/adr/0019-a-theme-derived-from-appearance-and-accent.md`).
262    ///
263    /// By value, because it is [`Copy`] and a view that took a reference
264    /// could not then touch `ui` — which is the whole of what a view
265    /// does. `let t = ui.theme();` at the top of a widget is the idiom.
266    pub fn theme(&self) -> crate::theme::Theme {
267        *self.core.theme()
268    }
269
270    /// Whether anyone chose the theme's accent, or it is kui's fallback
271    /// blue — see [`Core::has_accent`](crate::runtime::Core::has_accent).
272    pub fn has_accent(&self) -> bool {
273        self.core.has_accent()
274    }
275
276    /// The sizes the stock widgets are built from
277    /// (`crate::metrics::Metrics`, backlog T2), by value like the theme
278    /// and for the same reason. What a view reads to make its own
279    /// controls agree with the stock ones on a radius and a padding.
280    pub fn metrics(&self) -> crate::metrics::Metrics {
281        *self.core.metrics()
282    }
283
284    /// Declare the tokens this origin references by name
285    /// (`docs/adr/0027-tokens-beside-the-theme.md`); see
286    /// [`Core::set_tokens`](crate::runtime::Core::set_tokens). Inside a
287    /// fill the table is the extension's own.
288    pub fn set_tokens(&mut self, tokens: crate::tokens::Tokens) {
289        self.core.set_tokens(tokens);
290    }
291
292    /// What a `$name` resolves to this frame — the running origin's
293    /// table over the host's, the roles in front of both — for a view
294    /// that reads a token by name rather than holding its id.
295    pub fn tokens(&self) -> crate::tokens::TokenLookup<'_> {
296        self.core.token_lookup()
297    }
298
299    /// A colour token by name, this frame's half. A name that resolves
300    /// to nothing — or to a length — raises `unknown-token` and answers
301    /// `None`, so the view leaves the slot at the row's default the way
302    /// a `$name` in a prop does in every binding (`.bg(ui.token_color(
303    /// "peach").unwrap_or(t.surface))`), rather than painting an explicit
304    /// transparent that would hide the node a typo was on (AR14).
305    pub fn token_color(&mut self, name: &str) -> Option<crate::color::Color> {
306        match self.core.token_lookup().color(name) {
307            Ok(c) => Some(c),
308            Err(e) => {
309                self.core.warn_unknown_token(&e);
310                None
311            }
312        }
313    }
314
315    /// A length token by name; unknown warns and answers `None`, as above.
316    pub fn token_length(&mut self, name: &str) -> Option<f32> {
317        match self.core.token_lookup().length(name) {
318            Ok(v) => Some(v),
319            Err(e) => {
320                self.core.warn_unknown_token(&e);
321                None
322            }
323        }
324    }
325
326    /// Declares this frame's window title (declare every frame you care;
327    /// the driver diffs and applies changes).
328    pub fn window_title(&mut self, title: &str) {
329        self.core.set_window_title(title);
330    }
331
332    /// Declares that this frame wants the window kept above every other
333    /// app's; see [`crate::Core::set_always_on_top`]. Declare it every
334    /// frame you want it — a frame that does not lowers the window again,
335    /// which is what makes a pin button a toggle — and read whether the
336    /// platform agreed from `env().window.always_on_top`.
337    pub fn always_on_top(&mut self, on_top: bool) {
338        self.core.set_always_on_top(on_top);
339    }
340
341    /// Declares that this frame wants secure keyboard entry while the
342    /// window has the keyboard — a password prompt (backlog F85); see
343    /// [`crate::Core::set_secure_input`]. Declare it every frame the
344    /// prompt is up: a frame that does not turns it off.
345    pub fn secure_input(&mut self, on: bool) {
346        self.core.set_secure_input(on);
347    }
348
349    /// Declares which Option keys act as Alt in this window on macOS, so
350    /// ⌥u arrives as `<A-u>` rather than composing an accent (backlog
351    /// F113); see [`crate::Core::set_option_as_alt`]. Declare it every
352    /// frame: a frame that does not gives the Option keys back to the
353    /// layout.
354    pub fn option_as_alt(&mut self, option_as_alt: crate::OptionAsAlt) {
355        self.core.set_option_as_alt(option_as_alt);
356    }
357
358    /// Declares that a window named `name` exists this frame; see
359    /// `Core::declare_window`. It opens on the first frame that declares
360    /// it (`config` is read then and never again), stays open while any
361    /// window's frame keeps declaring it, and closes when none does.
362    /// `view` is then called for it too, with [`Ui::window_name`] saying
363    /// which window is being drawn.
364    pub fn window(&mut self, name: &str, config: WindowConfig) {
365        self.core.declare_window(name, config);
366    }
367
368    /// The name of the window this frame is drawing: `"main"` for the one
369    /// the launcher opened, else the name the declaration that opened it
370    /// used. `env().window.id` is the same window as a number.
371    pub fn window_name(&self) -> std::rc::Rc<str> {
372        self.core.window_name()
373    }
374
375    pub fn set_origin(&mut self, origin: OriginId) {
376        self.core.set_origin(origin);
377    }
378
379    pub fn configure_root(&mut self, spec: NodeSpec) {
380        self.core.configure_root(spec);
381    }
382
383    pub fn child_key(&self, label: &str) -> Key {
384        self.core.child_key(label)
385    }
386
387    /// The key the `i`th child gets from auto-keying; see `open_indexed`.
388    pub fn child_key_indexed(&self, i: u64) -> Key {
389        self.core.child_key_indexed(i)
390    }
391
392    pub fn is_hovered(&self, key: Key) -> bool {
393        self.core.is_hovered(key)
394    }
395
396    pub fn is_pressed(&self, key: Key) -> bool {
397        self.core.is_pressed(key)
398    }
399
400    /// Whether files dragged in from the OS are over `key` (ADR 0031) —
401    /// for drop-dependent *layout*; a colour swap is `drop_bg`.
402    pub fn is_drop_target(&self, key: Key) -> bool {
403        self.core.is_drop_target(key)
404    }
405
406    /// The zone the dragged files are over, if any.
407    pub fn drop_target(&self) -> Option<Key> {
408        self.core.drop_target()
409    }
410
411    /// Whether any member of a hover group (`NodeSpec::hover_group`) is
412    /// hovered; the id comes from `NodeSpec::hover_group_id`.
413    pub fn is_group_hovered(&self, group: u64) -> bool {
414        self.core.is_group_hovered(group)
415    }
416
417    pub fn is_group_pressed(&self, group: u64) -> bool {
418        self.core.is_group_pressed(group)
419    }
420
421    /// Physical modifier state (the host also receives it as a
422    /// `{kind="modifiers"}` event whenever it changes).
423    pub fn modifiers(&self) -> crate::input::KeyMods {
424        self.core.modifiers()
425    }
426
427    /// The caret's blink phase — `true` draws it; see
428    /// `Core::caret_visible`. A custom editor draws its caret node on the
429    /// on phase and skips it on the off, keeping the `caret` row on its
430    /// `line` either way (that row is what the clock is armed on).
431    pub fn caret_visible(&self) -> bool {
432        self.core.caret_visible()
433    }
434
435    /// Asks for one more frame after this one; see `Core::request_frame`.
436    #[track_caller]
437    pub fn request_frame(&mut self) {
438        self.core.request_frame();
439    }
440
441    /// Asks for one more frame on kui's own behalf — a stock widget's, not
442    /// the app's — named `why` in a trace (backlog RG82).
443    #[track_caller]
444    pub(crate) fn owe_frame(&mut self, why: &'static str) {
445        self.core.owe_frame(why);
446    }
447
448    /// Measures text the way layout would, without adding a node; see
449    /// `Core::measure_text`. Sizing a column to its widest label, or
450    /// choosing a tier that fits, is arithmetic on these numbers instead
451    /// of hand-tuned constants. The metrics do not scale linearly:
452    /// `measured × zoom` is not `measure(size × zoom)`, because shaping
453    /// rounds per size, so anything that zooms measures at the size it
454    /// draws.
455    pub fn measure_text(
456        &mut self,
457        content: &str,
458        style: &TextStyle,
459        max_w: Option<f32>,
460    ) -> crate::text::TextMetrics {
461        self.core.measure_text(content, style, max_w)
462    }
463
464    /// Where a point lands in the text node `key` drew, as a byte offset
465    /// and a visual line; see `Core::text_hit`. During a build it answers
466    /// from the last frame, which is the layout a click was made against.
467    pub fn text_hit(&self, key: Key, point: Vec2) -> Option<crate::text::TextHit> {
468        self.core.text_hit(key, point)
469    }
470
471    /// The window's selected text — a `selectable` scope's, or the
472    /// focused editor's; see `Core::copy_selection`.
473    pub fn selection_text(&self) -> Option<String> {
474        self.core.copy_selection()
475    }
476
477    /// The text selection's two ends as the drag made them — anchor, then
478    /// focus — each a virtualised row's data index (or none) and a byte;
479    /// see `Core::selection_ends`.
480    pub fn selection_ends(&self) -> Option<(crate::select::RangeEnd, crate::select::RangeEnd)> {
481        self.core.selection_ends()
482    }
483
484    /// The window's selection when it lives in a `cells` grid — its ends
485    /// as absolute lines and columns; see `Core::cell_selection`.
486    ///
487    /// Offered where the text selection offers only [`Self::selection_text`]
488    /// because a grid's ends mean something to the app: they are the
489    /// session's own line numbers, not byte offsets into runs the app never
490    /// laid out (ADR 0017, decision 4).
491    pub fn cell_selection(&self) -> Option<crate::select::CellSelection> {
492        self.core.cell_selection()
493    }
494
495    /// Opens a context menu; see `Core::open_menu`.
496    pub fn open_menu(&mut self, menu: crate::menu::Menu) {
497        self.core.open_menu(menu);
498    }
499
500    /// Closes whatever menu is open; see `Core::close_menu`.
501    pub fn close_menu(&mut self) -> bool {
502        self.core.close_menu()
503    }
504
505    /// Asks for the selection as text; see `Core::request_copy`.
506    pub fn request_copy(&mut self) -> crate::select::CopyRequest {
507        self.core.request_copy()
508    }
509
510    /// Answers a `selectionrange` ask; see `Core::answer_selection_range`.
511    pub fn answer_selection_range(&mut self, text: &str) -> bool {
512        self.core.answer_selection_range(text)
513    }
514
515    /// The selection as HTML; see `Core::selection_html`.
516    pub fn selection_html(&self) -> Option<String> {
517        self.core.selection_html()
518    }
519
520    /// Puts text on the system clipboard; see `Core::set_clipboard`.
521    pub fn set_clipboard(&mut self, text: impl Into<String>, html: Option<String>) {
522        self.core.set_clipboard(text, html);
523    }
524
525    /// Puts a secret on the system clipboard marked concealed and
526    /// transient, the way a password manager does (backlog F84); see
527    /// `Core::set_clipboard_secret`.
528    pub fn set_clipboard_secret(&mut self, text: impl Into<String>) {
529        self.core.set_clipboard_secret(text);
530    }
531
532    /// Asks for the clipboard's text, delivered as a `text` event on the
533    /// focused sink or as typing into the focused editor; see
534    /// `Core::request_paste`.
535    pub fn request_paste(&mut self) {
536        self.core.request_paste();
537    }
538
539    /// Whether a paste asked for is still unanswered; see
540    /// `Core::awaiting_paste`.
541    pub fn awaiting_paste(&self) -> bool {
542        self.core.awaiting_paste()
543    }
544
545    /// Asks the host for a file dialog; the answer is a `files` event to
546    /// whoever's view asked. False when one is already outstanding. See
547    /// `Core::request_files` (backlog C51).
548    #[track_caller]
549    pub fn request_files(&mut self, dialog: crate::dialog::FileDialog) -> bool {
550        self.core.request_files(dialog)
551    }
552
553    /// `Core::awaiting_files`.
554    pub fn awaiting_files(&self) -> bool {
555        self.core.awaiting_files()
556    }
557
558    /// Selects everything in the scope `key` declared; see
559    /// `Core::select_all_in`.
560    pub fn select_all_in(&mut self, key: Key) -> bool {
561        self.core.select_all_in(key)
562    }
563
564    /// Drops the window's selection; see `Core::clear_selection`.
565    pub fn clear_selection(&mut self) -> bool {
566        self.core.clear_selection()
567    }
568
569    /// The caret rect for a byte offset in the text node `key` drew; see
570    /// `Core::caret_rect`.
571    pub fn caret_rect(&self, key: Key, byte: usize) -> Option<Rect> {
572        self.core.caret_rect(key, byte)
573    }
574
575    /// `measure_text` for a rich-text paragraph.
576    pub fn measure_rich_text(
577        &mut self,
578        spans: &[Span<'_>],
579        base: &TextStyle,
580        max_w: Option<f32>,
581    ) -> crate::text::TextMetrics {
582        self.core.measure_rich_text(spans, base, max_w)
583    }
584
585    /// Opens a node under the next auto key; its children follow until
586    /// [`Self::close`]. [`Self::with`] is the scoped form.
587    #[inline]
588    pub fn open(&mut self, spec: NodeSpec) -> Key {
589        self.core.open(spec)
590    }
591
592    /// [`Self::open`] under a label key: stable across frames whatever
593    /// the siblings before it, and what `key_of(label)` finds.
594    #[inline]
595    pub fn open_keyed(&mut self, label: &str, spec: NodeSpec) -> Key {
596        self.core.open_keyed(label, spec)
597    }
598
599    /// Opens a node under a key the caller built rather than a label:
600    /// `ui.child_key("gap").index(id)` is a label and an index with no
601    /// string formatted and no clash with the sibling-index keys
602    /// `open_indexed` gives, and a key kept from an earlier `child_key` —
603    /// read for `is_hovered` first — is opened as it is instead of spelled
604    /// twice. Build it from this parent's `child_key`: two nodes under one
605    /// key in a frame are a `duplicate-key` warning, as two labels are.
606    /// It has no label, so `key_of` does not find it.
607    #[inline]
608    pub fn open_key(&mut self, key: Key, spec: NodeSpec) -> Key {
609        self.core.open_key(key, spec)
610    }
611
612    /// Scoped [`Self::open_key`].
613    pub fn with_key(&mut self, key: Key, spec: NodeSpec, f: impl FnOnce(&mut Ui<'_>)) -> Key {
614        self.open_key(key, spec);
615        f(self);
616        self.close();
617        key
618    }
619
620    /// [`Self::leaf`] under a key the caller built; see [`Self::open_key`].
621    #[inline]
622    pub fn leaf_key(&mut self, key: Key, spec: NodeSpec) -> Key {
623        self.open_key(key, spec);
624        self.close();
625        key
626    }
627
628    /// `open` under a key the caller chose — the panel's own nodes, whose
629    /// keys another node anchors to by name before they exist.
630    #[inline]
631    pub(crate) fn open_with_key(&mut self, key: Key, label: &str, spec: NodeSpec) {
632        self.core.open_with_key_named(key, label, spec)
633    }
634
635    /// `open_keyed` by sibling index: the key auto-keying would have given
636    /// the `i`th child. A virtualizing list opens each row with its data
637    /// index, so a row keeps its identity when the built range slides past
638    /// it. See `Core::open_indexed`.
639    #[inline]
640    pub fn open_indexed(&mut self, i: u64, spec: NodeSpec) -> Key {
641        self.core.open_indexed(i, spec)
642    }
643
644    /// Closes the node the last `open*` opened.
645    #[inline]
646    pub fn close(&mut self) {
647        self.core.close();
648    }
649
650    /// Scoped open/close.
651    pub fn with(&mut self, spec: NodeSpec, f: impl FnOnce(&mut Ui<'_>)) -> Key {
652        let key = self.open(spec);
653        f(self);
654        self.close();
655        key
656    }
657
658    /// Scoped [`Self::open_keyed`].
659    pub fn with_keyed(&mut self, label: &str, spec: NodeSpec, f: impl FnOnce(&mut Ui<'_>)) -> Key {
660        let key = self.open_keyed(label, spec);
661        f(self);
662        self.close();
663        key
664    }
665
666    /// A node with no children: a spacer, a rule, a swatch, a hit area —
667    /// `with(spec, |_| {})` without the empty closure.
668    #[inline]
669    pub fn leaf(&mut self, spec: NodeSpec) -> Key {
670        let key = self.open(spec);
671        self.close();
672        key
673    }
674
675    /// [`Self::leaf`] under a label key.
676    #[inline]
677    pub fn leaf_keyed(&mut self, label: &str, spec: NodeSpec) -> Key {
678        let key = self.open_keyed(label, spec);
679        self.close();
680        key
681    }
682
683    /// [`Self::leaf`] under a data index; see [`Self::open_indexed`].
684    #[inline]
685    pub fn leaf_indexed(&mut self, i: u64, spec: NodeSpec) -> Key {
686        let key = self.open_indexed(i, spec);
687        self.close();
688        key
689    }
690
691    /// Declares how many indexed rows the open node's virtual list has,
692    /// built or not; see [`crate::Core::row_count`].
693    pub fn row_count(&mut self, n: u64) {
694        self.core.row_count(n);
695    }
696
697    /// Scoped `open_indexed`: the `i`th child's auto-key, given to a node
698    /// that is not in the `i`th slot.
699    pub fn with_indexed(&mut self, i: u64, spec: NodeSpec, f: impl FnOnce(&mut Ui<'_>)) -> Key {
700        let key = self.open_indexed(i, spec);
701        f(self);
702        self.close();
703        key
704    }
705
706    /// A paragraph of plain text in one style. A text has no box of its
707    /// own — no padding, background, key or click; [`Self::text_in`] puts
708    /// it in one.
709    pub fn text(&mut self, content: &str, style: TextStyle) {
710        self.core.text_node(content, style);
711    }
712
713    /// A box of `spec` holding one text: `with(spec, |ui| ui.text(…))` for
714    /// the label, the cell, the badge that needs a width, a background, a
715    /// click or a role. Returns the box's key.
716    #[inline]
717    pub fn text_in(&mut self, spec: NodeSpec, content: &str, style: TextStyle) -> Key {
718        let key = self.open(spec);
719        self.text(content, style);
720        self.close();
721        key
722    }
723
724    /// [`Self::text_in`] under a label key.
725    #[inline]
726    pub fn text_in_keyed(
727        &mut self,
728        label: &str,
729        spec: NodeSpec,
730        content: &str,
731        style: TextStyle,
732    ) -> Key {
733        let key = self.open_keyed(label, spec);
734        self.text(content, style);
735        self.close();
736        key
737    }
738
739    /// [`Self::text_in`] under a data index; see [`Self::open_indexed`].
740    #[inline]
741    pub fn text_in_indexed(
742        &mut self,
743        i: u64,
744        spec: NodeSpec,
745        content: &str,
746        style: TextStyle,
747    ) -> Key {
748        let key = self.open_indexed(i, spec);
749        self.text(content, style);
750        self.close();
751        key
752    }
753
754    /// A cell grid — a terminal's screen — as one node; see
755    /// `crate::cells` (backlog C20). The spec is the node's own.
756    pub fn cells(&mut self, grid: &crate::cells::CellGrid<'_>, spec: NodeSpec) {
757        self.core.cells(grid, spec);
758    }
759
760    /// [`Self::cells`] under a data index; see [`Self::open_indexed`].
761    pub fn cells_indexed(&mut self, i: u64, grid: &crate::cells::CellGrid<'_>, spec: NodeSpec) {
762        self.core.cells_indexed(i, grid, spec);
763    }
764
765    /// [`Self::cells`] under a declared key.
766    pub fn cells_keyed(&mut self, label: &str, grid: &crate::cells::CellGrid<'_>, spec: NodeSpec) {
767        self.core.cells_keyed(label, grid, spec);
768    }
769
770    /// A paragraph of styled spans, shaped and wrapped as one flow.
771    pub fn rich_text(&mut self, spans: &[Span<'_>], base: TextStyle) {
772        self.core.rich_text_node(spans, base);
773    }
774
775    /// A registered image; see `Core::image_node` for sizing semantics.
776    pub fn image(&mut self, id: crate::resources::ImageId, spec: NodeSpec) {
777        self.core.image_node(id, spec);
778    }
779
780    /// [`Self::image`] with its `sampling` and `fit` rows; see
781    /// `Core::image_node_with`.
782    pub fn image_with(
783        &mut self,
784        id: crate::resources::ImageId,
785        opts: crate::resources::ImageOpts,
786        spec: NodeSpec,
787    ) {
788        self.core.image_node_with(id, opts, spec);
789    }
790
791    /// A box a registered WGSL function paints; see `Core::fragment_node`
792    /// for what it is, and `Core::add_fragment` for where the handle comes
793    /// from. It has no intrinsic size, so give it one.
794    ///
795    /// `frag` is the handle, or `handle.with_image(img)` for a function
796    /// that reads a registered image through `kui_sample(uv)` — a
797    /// waveform, a heatmap, an image effect (ADR 0025, decision 7).
798    pub fn fragment(
799        &mut self,
800        frag: impl Into<crate::fragment::FragmentRef>,
801        params: &[f32],
802        spec: NodeSpec,
803    ) -> Key {
804        self.core.fragment_node(frag, params, spec)
805    }
806
807    /// A fragment holding children, which paint over it: a gradient card
808    /// with a title and buttons on top of it.
809    pub fn fragment_with(
810        &mut self,
811        frag: impl Into<crate::fragment::FragmentRef>,
812        params: &[f32],
813        spec: NodeSpec,
814        f: impl FnOnce(&mut Ui<'_>),
815    ) -> Key {
816        let key = self.core.open_fragment(frag, params, spec);
817        f(self);
818        self.core.close();
819        key
820    }
821
822    /// [`Self::fragment`] under a label key, for one that transitions or
823    /// exits and needs a stable identity across frames.
824    pub fn fragment_keyed(
825        &mut self,
826        label: &str,
827        frag: impl Into<crate::fragment::FragmentRef>,
828        params: &[f32],
829        spec: NodeSpec,
830    ) -> Key {
831        self.core.fragment_node_keyed(label, frag, params, spec)
832    }
833
834    /// [`Self::fragment_with`] under a label key.
835    pub fn fragment_with_keyed(
836        &mut self,
837        label: &str,
838        frag: impl Into<crate::fragment::FragmentRef>,
839        params: &[f32],
840        spec: NodeSpec,
841        f: impl FnOnce(&mut Ui<'_>),
842    ) -> Key {
843        let key = self.core.open_fragment_keyed(label, frag, params, spec);
844        f(self);
845        self.core.close();
846        key
847    }
848
849    /// [`Self::fragment`] under a data index; see [`Self::open_indexed`].
850    pub fn fragment_indexed(
851        &mut self,
852        i: u64,
853        frag: impl Into<crate::fragment::FragmentRef>,
854        params: &[f32],
855        spec: NodeSpec,
856    ) -> Key {
857        let key = self.core.open_fragment_indexed(i, frag, params, spec);
858        self.core.close();
859        key
860    }
861
862    /// [`Self::fragment_with`] under a data index.
863    pub fn fragment_with_indexed(
864        &mut self,
865        i: u64,
866        frag: impl Into<crate::fragment::FragmentRef>,
867        params: &[f32],
868        spec: NodeSpec,
869        f: impl FnOnce(&mut Ui<'_>),
870    ) -> Key {
871        let key = self.core.open_fragment_indexed(i, frag, params, spec);
872        f(self);
873        self.core.close();
874        key
875    }
876
877    /// A round-capped segment from `from` to `to`, in the parent's box
878    /// space; see `Core::line_node` for what it is and is not.
879    pub fn line(&mut self, from: Vec2, to: Vec2, stroke: Stroke, spec: NodeSpec) {
880        self.core.line_node(&[from, to], stroke, spec);
881    }
882
883    /// [`Self::line`] under a label key.
884    pub fn line_keyed(
885        &mut self,
886        label: &str,
887        from: Vec2,
888        to: Vec2,
889        stroke: Stroke,
890        spec: NodeSpec,
891    ) {
892        self.core.line_node_keyed(label, &[from, to], stroke, spec);
893    }
894
895    /// [`Self::line`] under a data index; see [`Self::open_indexed`].
896    pub fn line_indexed(&mut self, i: u64, from: Vec2, to: Vec2, stroke: Stroke, spec: NodeSpec) {
897        self.core.line_node_indexed(i, &[from, to], stroke, spec);
898    }
899
900    /// A stroke through `points`: a polyline, or a smooth curve through
901    /// them with [`Stroke::curve`]; see `Core::line_node`.
902    pub fn polyline(&mut self, points: &[Vec2], stroke: Stroke, spec: NodeSpec) {
903        self.core.line_node(points, stroke, spec);
904    }
905
906    /// [`Self::polyline`] under a data index; see [`Self::open_indexed`].
907    pub fn polyline_indexed(&mut self, i: u64, points: &[Vec2], stroke: Stroke, spec: NodeSpec) {
908        self.core.line_node_indexed(i, points, stroke, spec);
909    }
910
911    /// [`Self::polyline`] under a label key.
912    pub fn polyline_keyed(&mut self, label: &str, points: &[Vec2], stroke: Stroke, spec: NodeSpec) {
913        self.core.line_node_keyed(label, points, stroke, spec);
914    }
915
916    /// A filled polygon through `points` in the parent's box space, the
917    /// fill in `spec`'s `bg`; see `Core::polygon_node` for what it is and
918    /// is not (ADR 0025, decision 6).
919    pub fn polygon(&mut self, points: &[Vec2], spec: NodeSpec) {
920        self.core.polygon_node(points, spec);
921    }
922
923    /// [`Self::polygon`] under a label key.
924    pub fn polygon_keyed(&mut self, label: &str, points: &[Vec2], spec: NodeSpec) {
925        self.core.polygon_node_keyed(label, points, spec);
926    }
927
928    /// [`Self::polygon`] under a data index; see [`Self::open_indexed`].
929    pub fn polygon_indexed(&mut self, i: u64, points: &[Vec2], spec: NodeSpec) {
930        self.core.polygon_node_indexed(i, points, spec);
931    }
932
933    /// An `audio` node: a playback retained for as long as the view keeps
934    /// declaring it; see `Core::audio_node`.
935    pub fn audio(&mut self, spec: crate::audio::AudioSpec) -> Key {
936        self.core.audio_node(spec)
937    }
938
939    /// `audio` with a label-derived key; see `Core::audio_node_keyed`.
940    pub fn audio_keyed(&mut self, label: &str, spec: crate::audio::AudioSpec) -> Key {
941        self.core.audio_node_keyed(label, spec)
942    }
943
944    /// Starts a playback from a view; see `Core::play`. Views run every
945    /// frame, so gate it on state that changes once (or use `audio`).
946    pub fn play(
947        &mut self,
948        sound: crate::resources::SoundId,
949        opts: crate::audio::PlayOptions,
950    ) -> crate::audio::PlaybackId {
951        self.core.play(sound, opts)
952    }
953
954    /// An editable text node; state retained by key. See `Core::text_edit`.
955    pub fn text_edit(
956        &mut self,
957        label: &str,
958        initial: &str,
959        opts: &EditOptions,
960        spec: NodeSpec,
961    ) -> Key {
962        self.core.text_edit(label, initial, opts, spec)
963    }
964
965    /// Whether `key` holds keyboard focus — any node: an editor, a key
966    /// sink, a button Tab landed on (see `Core::focus`).
967    pub fn is_focused(&self, key: Key) -> bool {
968        self.core.is_focused(key)
969    }
970
971    /// The node holding keyboard focus, if any.
972    pub fn focused(&self) -> Option<Key> {
973        self.core.focus()
974    }
975
976    /// Whether focus got where it is by keyboard or assistive technology
977    /// (a Tab press, a reader's request) rather than a click — when a
978    /// view that styles its own focus should show it.
979    pub fn focus_visible(&self) -> bool {
980        self.core.focus_visible()
981    }
982
983    /// The key of the node opened under `label` — in this frame so far,
984    /// then in the last finished one. For a caller that holds only the
985    /// label and cannot spell the path (`child_key` is the same question
986    /// asked from the parent); see `Core::key_of`.
987    pub fn key_of(&mut self, label: &str) -> Option<Key> {
988        self.core.key_of(label)
989    }
990
991    /// Moves keyboard focus to `key` now (an editor, an `on_key` sink, a
992    /// control, a `focusable` node); see `Core::set_focus`.
993    pub fn focus(&mut self, key: Key) {
994        self.core.set_focus(Some(key));
995    }
996
997    /// Drops keyboard focus.
998    pub fn blur(&mut self) {
999        self.core.set_focus(None);
1000    }
1001
1002    /// Moves focus to the next focusable node in tree order, wrapping —
1003    /// what Tab does. A key sink that binds Tab itself calls this to hand
1004    /// the keyboard on.
1005    ///
1006    /// Deferred, unlike the rest of this handle: a `Ui` only exists while a
1007    /// frame is being built, and `begin_frame` cleared the tree the Tab
1008    /// ring is made of, so stepping now would walk an empty ring. The step
1009    /// is applied at `finish`, against the frame this call is part of — so
1010    /// a view that declares three rows and asks to step lands on one of
1011    /// them, without waiting a frame for them to exist. Outside a frame
1012    /// (a driver handling a key press) `Core::focus_next` steps at once.
1013    #[track_caller]
1014    pub fn focus_next(&mut self) {
1015        self.core.request_focus_step(true);
1016    }
1017
1018    /// Shift-Tab: the previous focusable node. Deferred to `finish` for the
1019    /// reason [`Ui::focus_next`] gives.
1020    #[track_caller]
1021    pub fn focus_prev(&mut self) {
1022        self.core.request_focus_step(false);
1023    }
1024
1025    /// Enters a focus region — the node `key` names, declared
1026    /// `focus_region` — or the main ring for `None`
1027    /// (`docs/adr/0022-focus-regions.md`): focus lands on what that ring
1028    /// last held if the node is still there, else its `initial_focus`,
1029    /// else its first stop, and shows. Deferred to `finish` like
1030    /// [`Ui::focus_next`], so a view may name the region it is declaring
1031    /// right now — the dock this frame toggles on. A key the frame does
1032    /// not declare as a region raises `focus-region-without-node`.
1033    #[track_caller]
1034    pub fn focus_region(&mut self, key: Option<Key>) {
1035        self.core.focus_region(key);
1036    }
1037
1038    /// The focus region in effect — the node whose ring Tab walks — or
1039    /// `None` for the main ring. What a chord that toggles between a dock
1040    /// and the app reads to know which way it is going.
1041    pub fn region(&self) -> Option<Key> {
1042        self.core.region()
1043    }
1044
1045    pub fn edit_text(&self, key: Key) -> Option<String> {
1046        self.core.edit_text(key)
1047    }
1048
1049    /// Replaces an editor's text, caret at the end (`Core::set_edit_text`).
1050    /// Returns whether it reached an editor now, or was held for the
1051    /// frame that declares the key.
1052    pub fn set_edit_text(&mut self, key: Key, text: &str) -> bool {
1053        self.core.set_edit_text(key, text)
1054    }
1055
1056    /// The same by the label the view declares, for a caller with no key
1057    /// yet — an `update` opening a field the editor has not fired an
1058    /// event from (`Core::set_edit_text_by_label`, backlog F32).
1059    pub fn set_edit_text_by_label(&mut self, label: &str, text: &str) -> bool {
1060        self.core.set_edit_text_by_label(label, text)
1061    }
1062
1063    /// Says something once, with no node behind it (`Core::announce`).
1064    /// Takes effect at once, unlike `focus_next`: the queue is not made of
1065    /// a finished tree.
1066    ///
1067    /// A view runs every frame, so a call made from here needs a guard the
1068    /// app clears — the core reports the unguarded case as
1069    /// `announcement-repeated`. A region whose message is on screen is the
1070    /// `live` prop instead
1071    /// (`docs/adr/0008-live-regions-and-announcements.md`).
1072    pub fn announce(&mut self, text: &str, live: crate::access::Live) {
1073        self.core.announce(text, live);
1074    }
1075
1076    /// Scrolls whatever contains `key` so the node shows — "scroll to the
1077    /// selected row", without the container geometry the app cannot see.
1078    /// Resolved when this frame finishes laying out, so a row the view is
1079    /// declaring right now reveals fine; a key the frame does not declare,
1080    /// or one nothing scrollable contains, is a no-op. See `Core::reveal`.
1081    #[track_caller]
1082    pub fn reveal(&mut self, key: Key) {
1083        self.core.reveal(key);
1084    }
1085
1086    /// The handle for an installed or loaded font family by name — what
1087    /// `family = "Name"` resolves to in the declarative bindings (ADR
1088    /// 0037) — so a Rust view names a face without reaching for the core.
1089    /// `None` when no face matches. Idempotent.
1090    pub fn system_font(&mut self, name: &str) -> Option<crate::resources::FontId> {
1091        self.core.add_system_font(name)
1092    }
1093
1094    /// [`Self::reveal`] by label, resolved when this frame finishes; see
1095    /// `Core::reveal_label`.
1096    #[track_caller]
1097    pub fn reveal_label(&mut self, label: &str) {
1098        self.core.reveal_label(label);
1099    }
1100
1101    /// `set_scroll` by label, resolved before this frame lays out; see
1102    /// `Core::set_scroll_label`.
1103    #[track_caller]
1104    pub fn set_scroll_label(&mut self, label: &str, offset: Vec2) {
1105        self.core.set_scroll_label(label, offset);
1106    }
1107
1108    /// A scroll container's retained offset, clamped as of the last
1109    /// layout — the number to stash in a model and hand back to
1110    /// `set_scroll` later. Zero for a node that never scrolled.
1111    pub fn scroll_offset(&self, key: Key) -> Vec2 {
1112        self.core.scroll_offset(key)
1113    }
1114
1115    /// Sets that offset, the way the wheel would: `Vec2::ZERO` jumps to
1116    /// the top, a large value to the end (the next layout clamps it).
1117    #[track_caller]
1118    pub fn set_scroll(&mut self, key: Key, offset: Vec2) {
1119        self.core.set_scroll(key, offset);
1120    }
1121
1122    /// Moves a container's scroll state by the content that moved under
1123    /// it — `drawn` for the drawn place and an eased leg's start, `target`
1124    /// for the offset — with no ease asked or ended: a variable-height
1125    /// list's height correction (RG18), which the Node and Lua ports ask
1126    /// for through their own door (backlog C46). See `Core::shift_scroll`.
1127    pub fn shift_scroll(&mut self, key: Key, drawn: Vec2, target: Vec2) {
1128        self.core.shift_scroll(key, drawn, target);
1129    }
1130
1131    /// What the last layout resolved for a scroll container — its box, its
1132    /// content size and the clamped offset — so a view can build only the
1133    /// rows that fit and two spacers instead of ten thousand rows. `None`
1134    /// until a layout has resolved `key` as a container. It describes the
1135    /// previous frame; see `Core::scroll_geometry`, or
1136    /// `widgets::uniform_list` for the uniform-row case.
1137    pub fn scroll_geometry(&self, key: Key) -> Option<crate::scroll::ScrollGeometry> {
1138        self.core.scroll_geometry(key)
1139    }
1140
1141    /// The rect the last frame laid an `on_layout` node out at; see
1142    /// `Core::layout_of` (backlog C26 step 2).
1143    pub fn layout_of(&self, key: Key) -> Option<Rect> {
1144        self.core.layout_of(key)
1145    }
1146
1147    /// Declares this node focused: it takes keyboard focus when the
1148    /// declaration *starts* (the first frame it is made), and a Tab press
1149    /// afterwards is not clobbered by the view repeating it. An `on_key`
1150    /// sink then gets presses in `on_event` as `{kind="key", code, ctrl,
1151    /// alt, shift, super, text, repeat, tag}`. To move focus at any time,
1152    /// `focus(key)`.
1153    pub fn take_key_focus(&mut self, key: Key) {
1154        self.core.set_key_focus(Some(key));
1155    }
1156
1157    /// The node holding keyboard focus (the same as `focused`).
1158    pub fn key_focus(&self) -> Option<Key> {
1159        self.core.key_focus()
1160    }
1161
1162    /// Asks the frame driver to apply a window command (close from a
1163    /// keymap, minimize from a command line).
1164    pub fn window_command(&mut self, cmd: WindowCommand) {
1165        self.core.push_window_command(cmd);
1166    }
1167
1168    /// Asks the driver to resize a window (logical px) — a request applied
1169    /// on the driver's next pump and ignored headlessly, not a declaration:
1170    /// the user owns a window's size once it exists. `ui.env().window.id`
1171    /// is the window this view is drawing. See `Core::set_window_size`.
1172    pub fn set_window_size(&mut self, window: WindowId, size: Size) {
1173        self.core.set_window_size(window, size);
1174    }
1175
1176    /// Asks the driver to give a window keyboard focus; queued the same
1177    /// way. See `Core::focus_window`.
1178    pub fn focus_window(&mut self, window: WindowId) {
1179        self.core.focus_window(window);
1180    }
1181
1182    /// Runs layout and emission; results land in `Core::output()`. A frame
1183    /// begun with a filler lets it finish first: the `"root"` fill, unless
1184    /// the view declared it, and the `unknown-slot` check. An open context
1185    /// menu is drawn after both, which is what makes it the frame's modal
1186    /// scope and its topmost float (ADR 0017, decision 5).
1187    pub fn finish(self) {
1188        let Ui { core, filler } = self;
1189        // Not in the devtools' own window: nothing the host or an
1190        // extension declares there is built (ADR 0024, decision 6).
1191        if let Some(filler) = filler {
1192            if !core.devtools_window() {
1193                // A declared tab's extension fill first — a layer
1194                // anchored to a body the panel builds below, so the
1195                // filler's own `unknown-slot` check sees the slot
1196                // declared (ADR 0032, decision 5) — then the root fills.
1197                core.devtools_fill_mount(filler);
1198                filler.finish(&mut Ui::new(core));
1199            }
1200            // The panel, after the fills and before the menu: the menu is
1201            // the last thing declared and so the topmost float.
1202            core.devtools_finish();
1203            if core.devtools_window() {
1204                // The panel's own window: the extension form's fill lands
1205                // here too, when the pane gave the frame a filler.
1206                core.devtools_fill_mount(filler);
1207            }
1208        } else {
1209            core.devtools_finish();
1210        }
1211        Core::build_menu(&mut Ui::new(core));
1212        core.finish_frame();
1213    }
1214}