Skip to main content

kui_core/
runtime.rs

1//! The `Core`: one window. It owns everything that survives across frames
2//! and belongs to this window alone (interaction state, scroll and edit
3//! stores, focus) plus the reusable per-frame tree — and the frame-builder
4//! state itself. Builder state living here (not in a borrowing wrapper) is
5//! what lets flat C bindings drive a frame through one opaque pointer; the
6//! Rust `Ui` is a thin safe façade.
7//!
8//! What must not be duplicated per window — the resource registry, the
9//! shaping caches, the glyph atlas and the audio store — lives in the
10//! [`crate::session::Session`] a core is constructed against.
11//! [`Core::new`] makes a private one, so a single-window app never sees it.
12
13use std::rc::Rc;
14
15use rustc_hash::{FxHashMap, FxHashSet};
16
17use crate::anim::{AnimStore, Slot, Track};
18use crate::atlas::GlyphAtlas;
19use crate::color::Color;
20use crate::depart::{At, DepartStore, Ghost, GhostContent, Place, Playback, Replay};
21use crate::diag::{Diagnostics, Warning};
22use crate::display::{Clip, ClipId, DisplayList, NO_CLIP_ID, Quad, QuadKind};
23use crate::edit::{EditOptions, EditStore};
24use crate::env::{Env, SystemEnv};
25use crate::geom::{Rect, Size, Vec2};
26use crate::input::{
27    EditKey, HitRegion, InputEvent, Interaction, KeyCode, KeyMods, KeyPhase, KeyPress, MouseButton,
28    ScrollAxis, ScrollRegion, ScrollbarRegion, UiEvent,
29};
30use crate::key::{Key, LabelIndex};
31use crate::keyframes;
32use crate::layout::{self, TextMeasure};
33use crate::line::Stroke;
34use crate::resources::{FontId, Resources};
35use crate::scroll::ScrollStore;
36use crate::session::{
37    MAIN_WINDOW_NAME, Session, SharedAudio, SharedResources, WindowChange, WindowDecl,
38};
39use crate::spec::{NodeSpec, Sizing, TextStyle};
40use crate::stats::FrameStats;
41use crate::text::TextHit;
42use crate::text::{Span, TextMetrics, TextSystem};
43use crate::theme::{Theme, ThemeSource};
44use crate::tree::{NIL, NodeContent, OriginId, Tree};
45use crate::ui::Ui;
46use crate::value::Value;
47use crate::window::{WindowConfig, WindowId};
48
49// `impl Core` continues in these, one concern per file (each opens with
50// what it holds). Children of this module, so the fields stay private.
51mod builder;
52pub use builder::Content;
53pub mod cause;
54mod composites;
55pub mod devtools;
56mod dispatch;
57mod emit;
58mod fills;
59mod focus;
60pub mod follow;
61mod gesture;
62pub mod inspect;
63mod menu_api;
64mod menubar_api;
65mod resources_api;
66mod scrolling;
67mod select_api;
68mod windows;
69
70/// Wheel line-delta to logical px.
71const SCROLL_LINE_PX: f32 = 40.0;
72
73/// What a `Core::focus_region` call asked to enter, held until the frame
74/// finishes (`docs/adr/0022-focus-regions.md`, decision 4): the main ring,
75/// a region by key, or one by the label its node declares — the spelling
76/// a caller has for a node the last frame did not build.
77#[derive(Clone, Debug, PartialEq)]
78pub(crate) enum RegionTarget {
79    Main,
80    Key(Key),
81    Label(String),
82}
83
84pub struct Core {
85    /// The caches and registries this window shares with the rest of its
86    /// session. Everything a frame needs from it is borrowed inside a
87    /// method and dropped before it returns; see `session`'s module doc.
88    session: Session,
89    /// This window's shaped-text cache and rasterizer. Not the session's:
90    /// its cache entries are stamped with `atlas`'s epoch, and `TextId`
91    /// indexes its per-frame list.
92    pub text: TextSystem,
93    /// The frame's cell grids and their glyph tables (backlog C20).
94    pub cells: crate::cells::CellStore,
95    /// This window's glyph atlas — the CPU side of its renderer's texture,
96    /// handed out by `output`.
97    pub atlas: GlyphAtlas,
98    /// The session's resource registry. A font, image or sound registered
99    /// through it is registered for every window in the session.
100    pub resources: SharedResources,
101    /// The session's audio store: one device for the process, so playback
102    /// bookkeeping and the command queue are shared, not per window.
103    pub audio: SharedAudio,
104    /// The family names of the session's fonts as of this window's last
105    /// frame, so `font_family` can lend one out. Refreshed from
106    /// `SessionState::fonts_rev`.
107    font_names: FxHashMap<FontId, std::rc::Rc<str>>,
108    fonts_rev: u64,
109    /// The session's `weights_rev` this core's shaped text is of
110    /// (`sync_weights`).
111    weights_rev: u64,
112    /// The session's `images_rev` this core last checked its atlas
113    /// against (`sync_dropped`).
114    images_rev: u64,
115    pub interaction: Interaction,
116    pub scroll: ScrollStore,
117    pub edit: EditStore,
118    /// Transition tweens, keyed by node; see `anim`. Fed by `set_time`.
119    pub anim: AnimStore,
120    /// Subtrees the view stopped declaring, played out and then dropped;
121    /// see `depart`. Empty unless something declares `exit`.
122    pub depart: DepartStore,
123    /// Frame timing pushed by the frame driver; see `widgets::latency_graph`.
124    pub stats: FrameStats,
125    /// Host facts pushed by the frame driver (refresh rate, focus).
126    pub env: Env,
127    /// Where this window's palette comes from; see [`crate::theme`].
128    /// `Derived` unless the app said otherwise, so an app that never
129    /// mentions themes still follows the OS.
130    theme_source: ThemeSource,
131    /// `theme_source` resolved against `env.system`, re-resolved at the
132    /// start of every frame. Read by the stock widgets, by the core's own
133    /// chrome (ring, scrollbar, selection) and by any view that asks.
134    theme: Theme,
135    /// The sizes the stock widgets are built from (backlog T2): the
136    /// palette's other axis, set by the app or [`Metrics::default`].
137    metrics: crate::metrics::Metrics,
138    /// The named colours and lengths each origin declared (ADR 0027): the
139    /// host's under `OriginId::HOST`, an extension's under its own, so a
140    /// guest's declaration never replaces the host's palette. Read through
141    /// [`Core::token_lookup`], which puts the running origin's table over
142    /// the host's.
143    tokens: rustc_hash::FxHashMap<OriginId, crate::tokens::Tokens>,
144    /// Window title declared this frame (immediate-mode: cleared each
145    /// `begin_frame`; the driver diffs and applies). None = leave as-is.
146    window_title: Option<String>,
147    /// Whether this frame asked for the window to stay above every other
148    /// app's (backlog C30). The title's shape — cleared each `begin_frame`,
149    /// the driver diffs and applies on change — but a bool with a default
150    /// rather than an option, so a frame that stops asking is what lowers
151    /// the window again: the pin button an app draws for this is a
152    /// toggle, and the fact follows it.
153    always_on_top: bool,
154    /// Whether this frame asked for secure keyboard entry while its window
155    /// has the keyboard (backlog F85). `always_on_top`'s shape: cleared each
156    /// `begin_frame`, so a frame that stops asking is what turns it off.
157    secure_input: bool,
158    /// Which Option keys this frame asked to act as Alt on macOS (backlog
159    /// F113). `always_on_top`'s shape: cleared each `begin_frame`, so a
160    /// frame that stops declaring it gives the Option keys back to the
161    /// layout's composition.
162    option_as_alt: crate::input::OptionAsAlt,
163    /// Keyboard focus: the one node key input goes to — an editor (the
164    /// edit store mirrors it), an `on_key` sink, a control Tab landed on
165    /// (see `docs/adr/0002-keyboard-focus-as-data.md`). `set_focus` is
166    /// the only writer.
167    focus: Option<Key>,
168    /// Focus got there by keyboard or assistive technology, so it shows:
169    /// the default ring, or the node's `focus_bg`. A mouse press clears it.
170    focus_visible: bool,
171    /// Nodes that declared focus this frame and last (`set_key_focus`):
172    /// a declaration takes focus only when it starts, so a repeated one
173    /// does not clobber a Tab press.
174    declared_focus: Vec<Key>,
175    declared_focus_last: Vec<Key>,
176    /// Whether the app moved focus through `set_focus` since the last
177    /// frame began — the edge a closing modal's restore yields to, like a
178    /// `keyFocus` edge (AR17). Cleared by `begin_frame`.
179    focus_asked: bool,
180    /// The `.str`-keyed nodes this frame and last, with their labels
181    /// (`open_keyed`): what `key_of` resolves a name through. The same
182    /// swap-and-clear pair as the focus declarations, so a frame that
183    /// keys nothing costs two clears.
184    key_labels: LabelIndex,
185    key_labels_last: LabelIndex,
186    /// The slots this frame declared (`begin_slot`), by name: what the
187    /// `"root"` fill and the `unknown-slot` check read, and what makes a
188    /// second declaration of one name a `duplicate-slot`. Cleared each
189    /// frame; never compared to the last one, since a slot is a position
190    /// and not a declaration the core diffs.
191    slot_labels: LabelIndex,
192    /// The key namespace of the fill in progress (ADR 0014 decision 4):
193    /// while the node stack is exactly `ns_depth` deep, a child's key is
194    /// derived from `ns_key` instead of from the node it is opened under,
195    /// so an extension's nodes are keyed by the slot and the extension
196    /// rather than by whatever the host built around them. `usize::MAX`
197    /// when no fill is in progress — one compare on the auto-key path.
198    ns_depth: usize,
199    ns_key: Key,
200    /// Between `begin_frame` and `finish_frame`: `key_labels` is partial
201    /// and `key_labels_last` is the last whole frame, and `key_of` reads
202    /// both; outside a build `key_labels` is the whole last frame and is
203    /// the only one read.
204    building: bool,
205    /// Windows this frame and last declared (`declare_window`): the same
206    /// edge-triggered shape as the focus pair, one level up. The session's
207    /// registry diffs the union across every core at `finish_frame`; the
208    /// pair here is what makes a frame that changed nothing cost nothing.
209    declared_windows: Vec<WindowDecl>,
210    declared_windows_last: Vec<WindowDecl>,
211    /// The presses delivered to the current focus and not yet released,
212    /// in press order. A `KeyUp` is routed only when its press is in here
213    /// (so a sink never sees a release it did not see the press of), and
214    /// focus leaving synthesizes the missing releases from it.
215    keys_held: Vec<KeyPress>,
216    /// The modifiers of the `KeyDown` the next input event is the second
217    /// channel of (`KeyPress::edit_event`), so the `Key` and `Text` arms
218    /// ask the same chord question the raw press did (AR10, ADR 0011
219    /// decision 3). Set by a `KeyDown`, read and cleared by whatever
220    /// comes next: a `Key` or `Text` with no press before it — a test
221    /// driving one channel, a host's editing-key door — has no chord to
222    /// agree with and falls back to what its own `Mods` say.
223    pressed_mods: Option<KeyMods>,
224    pub(crate) tree: Tree,
225    /// Whether the host asked for `finish_frame` to copy the frame into
226    /// `inspected` (see `runtime/inspect.rs`, `set_inspect`); off unless
227    /// it did. The panel's own need is `dt_inspect`, derived each frame
228    /// and kept apart (backlog AR38), so neither ask can turn the other
229    /// off.
230    inspect: bool,
231    /// The devtools panel's need for the snapshot this frame: its tree
232    /// tab is showing, or it is picking.
233    dt_inspect: bool,
234    inspected: Vec<inspect::NodeInfo>,
235    /// The devtools' hold on this window's frame (`docs/adr/0024`): the
236    /// tree index of the app container the host's tree is wrapped in
237    /// while the panel is docked, whether this core draws the panel's own
238    /// window, and — while the panel's theme override is in force — the
239    /// source the host had before it, beside the override applied, so a
240    /// source the host sets *under* the override is told from it.
241    dt_app: Option<usize>,
242    /// The dock the frame was wrapped for at `begin_frame`, `None` when
243    /// it was not: a panel turned on or re-docked mid-frame (an app's
244    /// `set_devtools` from its `view`) is built next frame, which is
245    /// asked for, rather than into a root laid out for something else.
246    dt_dock: Option<devtools::Dock>,
247    dt_window: bool,
248    dt_theme: Option<(ThemeSource, ThemeSource)>,
249    /// The menu mode the host had before the panel's override went on —
250    /// drawn or native, the bar with it — for the override's `platform`
251    /// choice to put back.
252    dt_menus: Option<(bool, bool)>,
253    /// The panel was built at `begin_frame` (a left dock precedes the
254    /// app's container in tree order), on this tab, so `finish` must not
255    /// build again — and a door that moved the panel, turned it off or
256    /// changed its tab since is the next frame's, which `finish` asks
257    /// for (backlog RG4).
258    dt_built: Option<devtools::Shown>,
259    /// The devtools tabs declared this frame, in order (ADR 0032): what
260    /// the panel's strip lists, moved into the session's state at the
261    /// end of the main window's frame. Empty on a frame nobody declares
262    /// one, which is what every other frame pays.
263    dt_tabs: Vec<devtools::TabDecl>,
264    /// The host's viewport in window coordinates: the whole window, or
265    /// what the dock leaves of it while the panel is docked (ADR 0024).
266    /// What `Core::viewport` reports, what a `resize` is measured on,
267    /// what the host's viewport floats resolve against, and the origin
268    /// every coordinate the host is handed or hands in is relative to.
269    dt_area: Rect,
270    /// The previous frame's tree, kept only while a frame declares `exit`
271    /// — `begin_frame` swaps the two buffers instead of clearing one, so
272    /// the frame that notices a node gone still has the node. Empty (and
273    /// untouched) for every frame that declares no exit.
274    prev_tree: Tree,
275    /// The frame's strokes, indexed by the `line` nodes' `LineId`s; the
276    /// previous frame's kept alongside on the same terms as `prev_tree`.
277    pub(crate) lines: crate::line::LineStore,
278    pub(crate) fragments: crate::fragment::FragmentList,
279    /// The stock polygon fragment's handle, once a `polygon` node has
280    /// asked for it this session (ADR 0025, decision 6). Forgotten by
281    /// `remove_fragment` if a host removes it, so the next node registers
282    /// it again rather than drawing nothing — and not re-checked per node,
283    /// which was a session lock per polygon and cost more than the six
284    /// segment quads a closed stroke of the same outline emits.
285    pub(crate) stock_polygon: Option<crate::resources::FragmentId>,
286    /// The hit shapes the frame being emitted builds beside its regions
287    /// (ADR 0026), handed to `interaction` with them at the end of
288    /// emission; the previous frame's buffers, cleared, in between.
289    pub(crate) hit_shapes: crate::input::HitShapes,
290    pub(crate) display: DisplayList,
291    pub(crate) viewport: Size,
292    pub(crate) scale: f32,
293    // Frame-builder state.
294    stack: Vec<u32>,
295    counters: Vec<u64>,
296    origin: OriginId,
297    /// Per-node inherited clip (logical), rebuilt each finish_frame.
298    /// The access tree cuts each node's rect to it (F93), so what a
299    /// reader finds is what a pointer can hit.
300    clips: Vec<Clip>,
301    /// The `DisplayList::clips` index each of those became, so a node
302    /// whose clip is its parent's names the entry the parent already
303    /// interned instead of asking again. Parallel to `clips`.
304    clip_ids: Vec<ClipId>,
305    /// Per-node inherited group opacity (the product down the ancestors),
306    /// rebuilt each finish_frame and only materialized when something
307    /// actually fades.
308    opacity: Vec<f32>,
309    /// Per node, the layer it paints in: the index of its nearest floating
310    /// ancestor-or-self, `NIL` in flow (ADR 0023). Only filled on a frame
311    /// that floats something.
312    float_root: Vec<u32>,
313    /// The float layers as the last frame painted them, bottom to top:
314    /// each root's key and its rank among that frame's float roots in
315    /// tree order. A root the next frame keeps stays where it is, one it
316    /// opens goes on top, one it closes leaves — so the stack is the
317    /// order the layers opened in (ADR 0023, decision 3). Bounded by the
318    /// frame's own float count; nothing to evict.
319    float_stack: Vec<(Key, u32)>,
320    /// The hover hints of the nodes open right now that declared a
321    /// `tooltip` (`Core::hint`), each with the stack depth it was opened
322    /// at, so `close` knows whose turn it is. Only nodes that declared one
323    /// are here: a frame without a tooltip pays one length check per close.
324    hints: Vec<(usize, Key, String)>,
325    /// The context menu this window has open, the keys the stock renderer
326    /// gave its rows (so their clicks can be told from the app's), and
327    /// what choosing one left for the host to do. See
328    /// `docs/adr/0017-selection-as-a-scope.md`, decision 5.
329    menu: Option<crate::menu::Menu>,
330    menu_actions: Vec<crate::menu::MenuAction>,
331    /// The editor that held focus when the menu opened, since the menu's
332    /// own rows take focus from it — what Cut, Copy and Select All act on.
333    menu_editor: Option<Key>,
334    /// Whether the host draws menus itself (`set_native_menus`).
335    native_menus: bool,
336    /// The application menu this frame has in force, and a count bumped
337    /// whenever it changes, so a driver diffs against one integer rather
338    /// than against a tree (`docs/adr/0018-a-menu-bar-the-app-declares.md`).
339    /// `None` is a declaration nobody has made; an empty `MenuBar` is one
340    /// that took the bar away.
341    menu_bar: Option<crate::menu::MenuBar>,
342    menu_bar_rev: u64,
343    /// Who declared it, and so who hears the events its items post — the
344    /// host, or the extension whose view declared the bar.
345    menu_bar_origin: OriginId,
346    /// Which of the drawn bar's menus is open, and the bar's root this
347    /// frame — where a menu-bar event lands. Its titles and rows are known
348    /// by their origin (`OriginId::MENU_BAR`), not by key.
349    menu_bar_open: Option<usize>,
350    menu_bar_root: Option<Key>,
351    /// The select fields this frame built, each with its menu's rows
352    /// (`widgets::select`); a click on one opens that menu.
353    selects: Vec<(Key, Vec<crate::menu::MenuItem>)>,
354    /// Whether the platform owns the menu bar (`set_native_menu_bar`), in
355    /// which case the drawn one draws nothing and the driver hands the
356    /// declaration over instead.
357    native_menu_bar: bool,
358    /// Whether the host can show a definition panel
359    /// (`set_lookup_available`).
360    lookup_available: bool,
361    /// The window's text selection outside an editor, and what the
362    /// frame resolved it to: `sel_ords` numbers the text nodes of the
363    /// selection's scope in emission order (`u32::MAX` for a node
364    /// outside it), and `sel_ends` is the pair of ends in reading order,
365    /// `None` when this frame builds neither end. See
366    /// `docs/adr/0017-selection-as-a-scope.md`.
367    selection: Option<crate::select::Selection>,
368    /// The window's selection when it is in a `cells` grid instead of in
369    /// text. One selection per window: starting either clears the other,
370    /// which `Core::set_selection` / `set_cell_selection` enforce in one
371    /// place each (ADR 0017, decisions 1 and 4).
372    cell_selection: Option<crate::select::CellSelection>,
373    /// Whether a `selectionrange` ask is outstanding (ADR 0017, tier 3).
374    awaiting_selection: bool,
375    /// Whether a paste ask is outstanding — queued, or taken by the
376    /// driver and not yet answered with a `Commit` (backlog AR34). A
377    /// second ask while one is out is dropped, so a view that asks every
378    /// frame until the answer lands asks once.
379    awaiting_paste: bool,
380    /// The one file-dialog ask (backlog C51): queued, taken by the host,
381    /// or none.
382    file_ask: crate::dialog::FileAsk,
383    /// The drag a press is running through a selection scope, if any: set
384    /// on the press inside a scope, cleared on release. The counterpart of
385    /// `EditStore::dragging` for text nobody is editing.
386    select_dragging: Option<crate::select::SelectDrag>,
387    /// The held drag — a caret drag or a drag-select — following its
388    /// scroller: the pointer's last position, the scroller found for it,
389    /// and what that scroller was at when the live end was last placed
390    /// (ADR 0029). Set with either drag, cleared with both.
391    drag_follow: Option<follow::DragFollow>,
392    /// The frame clock's reading at the last frame, for the edge drag's
393    /// rate (`follow::follow_drag`); `None` before a clock is set.
394    last_frame_time: Option<f64>,
395    /// The fraction of a line the last delta over an `on_scroll` grid —
396    /// a wheel notch or an edge step — did not cover, with the grid it
397    /// was over: the next delta on the same grid adds to it (ADR 0029,
398    /// decision 4).
399    line_carry: Option<(Key, f32)>,
400    /// The targets the scroll gesture under way latched, per axis
401    /// (backlog F107, `mod gesture`).
402    scroll_latch: gesture::ScrollLatch,
403    sel_ords: Vec<u32>,
404    sel_ends: Option<crate::select::Ends>,
405    /// Per-node innermost enclosing selection scope — the key of the
406    /// nearest ancestor (or the node itself) declaring `selectable`, and
407    /// `None` outside every scope. Filled only on a frame that declares
408    /// one at all (`Tree::any_selectable`), which is what keeps ADR 0017
409    /// off the frames of apps that never select anything.
410    scopes: Vec<Option<Key>>,
411    /// Per-node enclosing virtualised row index, filled beside `scopes`:
412    /// what places an endpoint whose own node is no longer built.
413    rows: Vec<Option<u64>>,
414    /// The frame's modal scope: the tree range `[i, subtree_end(i))` of the
415    /// last node declaring `modal`, and its key. Everything outside it is
416    /// inert and out of the Tab ring (see
417    /// `docs/adr/0003-modal-surfaces.md`). Recomputed by `finish_frame`.
418    modal: Option<(usize, usize, Key)>,
419    /// The modals declared by the last finished frame, in tree order, each
420    /// with the focus it displaced: a modal that stops being declared gives
421    /// that focus back.
422    modal_focus: Vec<(Key, Option<Key>)>,
423    /// Per-node inherited opacity while a departing subtree is replayed
424    /// (`depart`), reused across ghosts and frames.
425    ghost_opacity: Vec<f32>,
426    /// Per-node inherited clip and painted rect while a departing subtree
427    /// is replayed: the clips its own clippers establish, since a ghost
428    /// draws outside every clip its ancestors held (`depart`).
429    ghost_clip: Vec<Clip>,
430    /// The interned index of each of those, as `clip_ids` is for `clips`.
431    ghost_clip_ids: Vec<ClipId>,
432    ghost_rect: Vec<Rect>,
433    /// A view asked for one more frame (`request_frame`); cleared by
434    /// `begin_frame`, reported through `animating`.
435    frame_requested: bool,
436    /// Why frames run: the reasons, and — traced — who held an owed one
437    /// and whether a frame changed anything (`runtime/cause.rs`, backlog
438    /// F111).
439    trace: cause::Trace,
440    /// The focused editor's caret rect (logical, viewport coords) as of the
441    /// last finish_frame — where drivers should anchor the OS IME window.
442    ime_rect: Option<Rect>,
443    /// A custom editor's caret as of the last frame: the `line` under the
444    /// focused sink that declares `caret`, and the offset it declares
445    /// (backlog C35). What the blink clock is armed on when no stock
446    /// editor is focused; `None` with nothing to blink.
447    sink_caret: Option<(Key, u32)>,
448    /// Whether that line declared its caret `caret_solid` — a block caret
449    /// in a modal editor's normal mode: still the IME's anchor and the
450    /// access tree's caret, but not a caret to blink, so `has_caret`
451    /// leaves it out and an idle app draws no frame for it.
452    sink_caret_solid: bool,
453    /// Bumped whenever `sink_caret` changes between frames — the caret
454    /// moved, or focus came to or left a custom editor — so the driver
455    /// re-arms the blink solid, the way `EditStore::caret_stamp` does for
456    /// the stock editor. The two are summed in `caret_stamp`.
457    sink_caret_stamp: u64,
458    /// Events raised by the frame driver's own reports rather than by input
459    /// — a changed viewport becoming a `resize`. Drained alongside the
460    /// interaction's pending queue.
461    pending: Vec<UiEvent>,
462    /// Whether any frame has begun yet: the first one establishes the
463    /// viewport instead of resizing it.
464    framed: bool,
465    /// The OS settings the last frame was begun with. A driver pushes
466    /// them into `env` whenever it learns of a change, and the difference
467    /// between two frames is what becomes a `system` event — the same
468    /// bookkeeping `viewport` does for `resize` (backlog F40).
469    system_seen: SystemEnv,
470    /// The session's `system_fonts_rev` the last frame was begun with; the
471    /// difference is what becomes a `fonts` event.
472    system_fonts_seen: u64,
473    /// Frames begun so far; stamps the per-key stores below.
474    frame_no: u64,
475    /// The rect last reported for each `on_layout` node and the frame it
476    /// was seen: a different rect, or a node not seen last frame, posts a
477    /// `layout` event (see `emit_layout_events`).
478    layouts: FxHashMap<Key, (Rect, u64)>,
479    /// One-off announcements queued since the last drain (see
480    /// [`Self::announce`] and
481    /// `docs/adr/0008-live-regions-and-announcements.md`). The fourth of
482    /// the four drained channels, and the same shape as the other three:
483    /// the core appends, a driver drains, a headless test asserts on what
484    /// it drained.
485    announcements: Vec<crate::access::Announcement>,
486    /// The last announcement's text, the frame it was queued on and the
487    /// [`Self::events_answered`] reading then: the same text on two
488    /// consecutive frames with no event handed to the app between them is
489    /// what an unguarded `ui.announce(...)` in a view looks like, and
490    /// `announcement-repeated` says so. The event count is what tells a
491    /// window that redraws only on input apart from one shouting every
492    /// frame — two Copy presses in a row are two consecutive frames there.
493    last_announcement: Option<(String, u64, u64)>,
494    /// How many times `handle_input` handed the app at least one event.
495    events_answered: u64,
496    /// The `reveal(key)`s waiting for a layout to resolve against, in the
497    /// order asked: the next `finish_frame` scrolls each node's scrolling
498    /// ancestor to show it, then clears them. Within one container the
499    /// last ask wins; asks aimed at different containers all land (F82).
500    pending_reveal: Vec<Key>,
501    /// `reveal_label` and `set_scroll_label` asks, with the origin that
502    /// asked: resolved when the frame finishes, where a label named before
503    /// its node is declared — later in the same build, or by the next
504    /// frame — has a node to find (backlog DX15).
505    pending_reveal_labels: Vec<(String, crate::tree::OriginId)>,
506    /// The `on_focus` nodes the focus was last reported inside, outermost
507    /// first, with each one's origin and tag — what `report_focus` diffs
508    /// the focus against (backlog DX18).
509    focus_reported: Vec<(Key, crate::tree::OriginId, Value)>,
510    pending_scroll_labels: Vec<(String, crate::tree::OriginId, Vec2)>,
511    /// Type-ahead inside a composite (`docs/adr/0007`, decision 9): the
512    /// characters typed so far, and the frame clock reading of the last
513    /// keystroke. The buffer is cleared at the start of the first frame
514    /// more than [`TYPE_AHEAD_SECS`] after it, so input routing stays
515    /// timeless — the aging happens where time already lives. With no
516    /// clock set `type_ahead_at` is None and every keystroke starts a
517    /// fresh search, which is the useful half of type-ahead.
518    type_ahead: String,
519    type_ahead_at: Option<f64>,
520    /// Reused by the composite walks (the ring, arrow motion), so a frame
521    /// with composites in it pays one allocation rather than one each.
522    items_scratch: Vec<usize>,
523    /// A `focus_next` / `focus_prev` waiting for a frame to walk: `true`
524    /// forward. The Tab ring is the finished tree's, and a request made
525    /// while a frame is being built has no tree to walk yet (`begin_frame`
526    /// cleared it), so the step is held until `finish_frame` — after the
527    /// modal scope is resolved, which is what scopes the ring. Last writer
528    /// wins, and an applied step beats a `set_focus` from the same frame.
529    pending_focus_step: Option<bool>,
530    /// The focus region in effect — the key of the node whose subtree Tab
531    /// walks — or `None` for the main ring (the tree minus every region).
532    /// Follows focus: `set_focus` moves it to the region enclosing the
533    /// focused node, a press settles it on the region under the pointer,
534    /// and it is kept across a blur so Tab re-enters where the user was.
535    region: Option<Key>,
536    /// Whether a press settled `region` somewhere other than the focused
537    /// node's own region — dead space in the dock, focus on the root sink
538    /// the press bubbled to — so the region stops following that focus
539    /// until it moves (decision 3's second sentence). Cleared by any
540    /// `set_focus` that changes the focus.
541    region_held: bool,
542    /// The focus each region last held, main (`None`) included: what
543    /// `focus_region` lands on when entering it again, and what main gets
544    /// back when the region in effect stops being declared.
545    region_focus: Vec<(Option<Key>, Option<Key>)>,
546    /// A `focus_region` waiting for the frame to finish, for the reason
547    /// `pending_focus_step` waits: the ring it enters is the finished
548    /// tree's, and the caller may name a node the last frame did not have.
549    /// Last writer wins.
550    pending_region: Option<RegionTarget>,
551    /// Silent-misconfiguration detection; see `diag`.
552    diag: Diagnostics,
553    /// The access tree of the last finished frame, built on demand (see
554    /// `access_tree`) and stamped with the frame it was built from.
555    access: crate::access::AccessTree,
556    access_built: u64,
557    /// The hash of the inputs `self.access` was derived from, so a frame
558    /// whose access-relevant state is unchanged keeps it (ADR 0016,
559    /// decision 3). `None` when the last frame could not be hashed, which
560    /// forces the next derivation.
561    access_inputs: Option<u64>,
562    /// How many times the access tree has actually been derived, as against
563    /// asked for. Tests read it to tell a cache hit from a miss; nothing in
564    /// the library acts on it.
565    access_rebuilds: u64,
566}
567
568/// How long a type-ahead search buffer survives without a keystroke
569/// (`docs/adr/0007-composite-keyboard-patterns.md`, decision 9). Aged at
570/// the start of a frame, so a core with no clock never ages one.
571const TYPE_AHEAD_SECS: f64 = 1.0;
572
573/// One step along a list of `len` items from `at`, wrapping or clamping.
574/// None = the step ran off the end of a list that clamps.
575fn step(at: usize, delta: isize, len: usize, wrap: bool) -> Option<usize> {
576    let n = len as isize;
577    let p = at as isize + delta;
578    if (0..n).contains(&p) {
579        return Some(p as usize);
580    }
581    wrap.then(|| (((p % n) + n) % n) as usize)
582}
583
584/// What a frame left owed, by kind: [`Core::owed`]. `any()` is what
585/// [`Core::animating`] answers; `beyond_cycles()` is the same with a
586/// keyframe cycle — which never ends — left out (backlog F64).
587#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
588pub struct Owed {
589    /// A finite transition — a leg or a spring — still mid-flight.
590    pub transition: bool,
591    /// A keyframe cycle running; it always is, while its node is drawn.
592    pub cycle: bool,
593    /// An exit animation (a ghost) still departing.
594    pub depart: bool,
595    /// A frame a view asked for: `request_frame`, or an `animate` row.
596    pub requested: bool,
597    /// A held drag scrolling its container (ADR 0029).
598    pub autoscroll: bool,
599    /// A scroll container easing a programmatic offset change — a
600    /// `reveal` or a `set_scroll` on a container with a `transition`
601    /// (F80).
602    pub scroll: bool,
603}
604
605impl Owed {
606    /// Anything at all: the driver's reading.
607    pub fn any(self) -> bool {
608        self.transition
609            || self.cycle
610            || self.depart
611            || self.requested
612            || self.autoscroll
613            || self.scroll
614    }
615
616    /// Anything but a cycle: what a test waits on when the view has a
617    /// cycle that will never let `any()` clear. An eased scroll is a
618    /// finite leg like a transition, so it is in (RG20).
619    pub fn beyond_cycles(self) -> bool {
620        self.transition || self.depart || self.requested || self.autoscroll || self.scroll
621    }
622}
623
624impl Core {
625    /// This window's palette, as of this frame
626    /// (`docs/adr/0019-a-theme-derived-from-appearance-and-accent.md`).
627    /// Resolved from [`Core::theme_source`] and `env.system` at the start
628    /// of every frame, so it is already right by the time a view runs.
629    ///
630    /// Frame-stable on purpose: every widget in one frame paints from the
631    /// same palette, whatever the driver does to `env` while the view
632    /// runs. A host that writes `env.system` *directly* and wants the new
633    /// answer before its next frame calls [`Core::refresh_theme`]; every
634    /// env setter a binding exposes already does.
635    pub fn theme(&self) -> &Theme {
636        &self.theme
637    }
638
639    /// Re-resolve the palette from `env.system` now, rather than at the
640    /// start of the next frame. What an env setter calls after writing.
641    pub fn refresh_theme(&mut self) {
642        self.theme = self.theme_source.resolve(&self.env.system);
643    }
644
645    /// Writes what the user set in the OS and re-resolves the palette from
646    /// it, so a host that pushes the appearance and reads the theme back
647    /// before its next frame sees the answer (ADR 0019). The one door for
648    /// a binding's env setter: writing `env.system` by hand and forgetting
649    /// the refresh was a decision each of them had to remember.
650    pub fn set_system(&mut self, system: SystemEnv) {
651        self.env.system = system;
652        self.refresh_theme();
653    }
654
655    /// The env reading's inputs (`schema::ENV_FIELDS`): the stored env and
656    /// the frame's own facts — viewport, scale, focus — that ride in the
657    /// same reading.
658    pub fn env_facts(&self) -> crate::schema::EnvFacts {
659        crate::schema::EnvFacts {
660            env: self.env,
661            // The frame's viewport, as its `ENV_FIELDS` row says: what the
662            // dock leaves, not the window it was begun with (backlog F43
663            // — this read `self.viewport`, and an app under `KUI_DEVTOOLS`
664            // sized its tiers to a window it did not have).
665            viewport: self.viewport(),
666            scale: self.scale,
667            focus: self.focus(),
668            focus_visible: self.focus_visible(),
669            region: self.region(),
670            caret_visible: self.caret_visible(),
671        }
672    }
673
674    /// Whether anyone actually *chose* the accent — the OS reported one,
675    /// or the app set or pinned one — as opposed to the palette falling
676    /// back to kui's own blue.
677    ///
678    /// The question the `accent` row asks before it repaints anything:
679    /// that row has always meant "the accent colour where there is one,
680    /// the `bg` I declared where there is not", so a view can name its
681    /// own fallback and a host that knows nothing changes nothing. The
682    /// theme widened *where* the accent comes from without widening
683    /// *whether* there is one.
684    pub fn has_accent(&self) -> bool {
685        match self.theme_source {
686            ThemeSource::Derived => self.env.system.accent.is_some(),
687            ThemeSource::DerivedWithAccent(_) | ThemeSource::Pinned(_) => true,
688        }
689    }
690
691    /// Where the palette comes from. [`ThemeSource::Derived`] by default.
692    pub fn theme_source(&self) -> ThemeSource {
693        self.theme_source
694    }
695
696    /// Change where the palette comes from. Takes effect on the next
697    /// frame, and immediately for anything reading [`Core::theme`] after
698    /// this call, so a host may set it before its first frame or in a
699    /// handler and get the same answer either way.
700    pub fn set_theme_source(&mut self, source: ThemeSource) {
701        self.theme_source = source;
702        self.refresh_theme();
703    }
704
705    /// Pin a palette: this exact [`Theme`], following neither the OS's
706    /// appearance nor its accent. Shorthand for
707    /// [`ThemeSource::Pinned`].
708    pub fn set_theme(&mut self, theme: Theme) {
709        self.set_theme_source(ThemeSource::Pinned(theme));
710    }
711
712    /// Keep following the OS's light/dark, but paint this accent instead
713    /// of the OS's. Shorthand for [`ThemeSource::DerivedWithAccent`], and
714    /// what an app with a brand colour wants.
715    pub fn set_accent(&mut self, accent: Color) {
716        self.set_theme_source(ThemeSource::DerivedWithAccent(accent));
717    }
718
719    /// Go back to following the OS for both — the default.
720    pub fn derive_theme(&mut self) {
721        self.set_theme_source(ThemeSource::Derived);
722    }
723
724    /// The sizes the stock widgets are built from — the palette's other
725    /// axis (`crate::metrics`, backlog T2). [`Metrics::default`] until
726    /// the app sets one; nothing in the OS is followed.
727    pub fn metrics(&self) -> &crate::metrics::Metrics {
728        &self.metrics
729    }
730
731    /// Declare the tokens the running origin references by name
732    /// (`docs/adr/0027-tokens-beside-the-theme.md`): the host's outside a
733    /// fill, the filling extension's inside one. Replaces that origin's
734    /// table whole, so an app whose lengths change with a viewport tier
735    /// declares again on `resize`. A name a role owns is dropped with a
736    /// `reserved-token` warning, once per name. A derived token whose
737    /// source did not resolve is dropped with `unknown-token`, naming both.
738    pub fn set_tokens(&mut self, tokens: crate::tokens::Tokens) {
739        for name in tokens.reserved() {
740            let w = Warning {
741                code: crate::diag::RESERVED_TOKEN,
742                key: Key::ROOT.str(crate::diag::RESERVED_TOKEN).str(name),
743                message: format!(
744                    "`{name}` is a theme or metrics role, so the token is dropped: `${name}` \
745                     always means the role's value, and an app does not shadow one"
746                ),
747            };
748            self.diag.raise(w);
749        }
750        // A derived token whose source did not resolve (ADR 0028) is
751        // dropped the same way, and the warning names both ends: the
752        // token that is gone and the name that was not there for it.
753        for u in tokens.unresolved() {
754            let w = Warning {
755                code: crate::diag::UNKNOWN_TOKEN,
756                key: Key::ROOT.str(crate::diag::UNKNOWN_TOKEN).str(&u.token),
757                message: format!(
758                    "`${}` is dropped: it derives from `${}`, which is no colour token \n                     declared before it and no theme role",
759                    u.token, u.source
760                ),
761            };
762            self.diag.raise(w);
763        }
764        self.tokens.insert(self.origin, tokens);
765    }
766
767    /// Whether `origin` has declared a table — what an extension asks
768    /// before declaring the one it was loaded with, so a per-frame `view`
769    /// declares once.
770    pub fn tokens_declared(&self, origin: OriginId) -> bool {
771        self.tokens.contains_key(&origin)
772    }
773
774    /// The running origin's own table, if it declared one.
775    pub fn tokens(&self) -> Option<&crate::tokens::Tokens> {
776        self.tokens.get(&self.origin)
777    }
778
779    /// What a `$name` in a prop resolves to this frame: the running
780    /// origin's table over the host's, the theme's and metrics' roles in
781    /// front of both. What every binding lowers a reference through.
782    pub fn token_lookup(&self) -> crate::tokens::TokenLookup<'_> {
783        let own = self.tokens.get(&self.origin);
784        let host = if self.origin == OriginId::HOST {
785            None
786        } else {
787            self.tokens.get(&OriginId::HOST)
788        };
789        crate::tokens::TokenLookup {
790            own,
791            host,
792            theme: &self.theme,
793            metrics: &self.metrics,
794            session: Some(&self.session),
795        }
796    }
797
798    /// A binding lowered a `family` that names nothing installed or
799    /// loaded: raise `unknown-family`, once per name (ADR 0037). The text
800    /// shapes as sans, which is what the message says.
801    pub fn warn_unknown_family(&mut self, name: &str) {
802        self.diag.raise(Warning {
803            code: crate::diag::UNKNOWN_FAMILY,
804            key: Key::ROOT.str(crate::diag::UNKNOWN_FAMILY).str(name),
805            message: format!(
806                "`family` named {name:?}, and no installed or loaded font family has that name, \
807                 so the text shapes as sans; `systemFonts()` lists the names there are, and \
808                 `sans`, `serif` and `mono` are kui's own"
809            ),
810        });
811        // A family a sibling prop registered in the same parse is listed
812        // by the next frame's sync; nothing to do here.
813    }
814
815    /// A binding lowered a reference that did not resolve: raise
816    /// `unknown-token`, once per name, saying which slot asked. The slot
817    /// keeps its default, which is what the message says.
818    pub fn warn_unknown_token(&mut self, err: &crate::tokens::TokenError) {
819        let name = match err {
820            crate::tokens::TokenError::Unknown(n)
821            | crate::tokens::TokenError::Kind { name: n, .. } => n,
822        };
823        let w = Warning {
824            code: crate::diag::UNKNOWN_TOKEN,
825            key: Key::ROOT.str(crate::diag::UNKNOWN_TOKEN).str(name),
826            message: err.to_string(),
827        };
828        self.diag.raise(w);
829    }
830
831    /// Makes `metrics` the frame's: every stock widget from the next node
832    /// on is built from it, and `ui.metrics()` reads it back. Logical px,
833    /// before `env.scale`; a density is the app's to choose
834    /// (`Metrics::compact`, `Metrics::scaled`).
835    pub fn set_metrics(&mut self, metrics: crate::metrics::Metrics) {
836        self.metrics = metrics;
837    }
838
839    /// A core with a session of its own — one window, nothing shared.
840    pub fn new() -> Self {
841        Self::new_in(&Session::new())
842    }
843
844    /// A core joining an existing session: it draws with the same fonts,
845    /// images, sounds, shaping caches and glyph atlas as every other core
846    /// constructed against `session`, and plays through the same audio
847    /// device. Everything else — tree, focus, scroll, viewport — is this
848    /// window's alone.
849    pub fn new_in(session: &Session) -> Self {
850        let mut core = Self {
851            session: session.clone(),
852            text: TextSystem::new(),
853            cells: crate::cells::CellStore::new(),
854            atlas: GlyphAtlas::new(),
855            resources: SharedResources::new(session),
856            audio: SharedAudio::new(session),
857            font_names: FxHashMap::default(),
858            fonts_rev: u64::MAX,
859            weights_rev: 0,
860            images_rev: 0,
861            interaction: Interaction::default(),
862            scroll: ScrollStore::default(),
863            edit: EditStore::default(),
864            anim: AnimStore::default(),
865            depart: DepartStore::default(),
866            stats: FrameStats::default(),
867            env: Env::default(),
868            theme_source: ThemeSource::Derived,
869            theme: Theme::default(),
870            tokens: Default::default(),
871            metrics: crate::metrics::Metrics::default(),
872            window_title: None,
873            always_on_top: false,
874            secure_input: false,
875            option_as_alt: crate::input::OptionAsAlt::None,
876            focus: None,
877            focus_visible: false,
878            declared_focus: Vec::new(),
879            declared_focus_last: Vec::new(),
880            focus_asked: false,
881            key_labels: LabelIndex::default(),
882            key_labels_last: LabelIndex::default(),
883            slot_labels: LabelIndex::default(),
884            ns_depth: usize::MAX,
885            ns_key: Key::ROOT,
886            dt_app: None,
887            dt_dock: None,
888            dt_window: false,
889            dt_theme: None,
890            dt_menus: None,
891            dt_built: None,
892            dt_tabs: Vec::new(),
893            dt_area: Rect::new(0.0, 0.0, 0.0, 0.0),
894            building: false,
895            declared_windows: Vec::new(),
896            declared_windows_last: Vec::new(),
897            keys_held: Vec::new(),
898            pressed_mods: None,
899            access: Default::default(),
900            access_built: 0,
901            access_inputs: None,
902            access_rebuilds: 0,
903            tree: Tree::new(),
904            inspect: false,
905            dt_inspect: false,
906            inspected: Vec::new(),
907            prev_tree: Tree::new(),
908            lines: Default::default(),
909            fragments: Default::default(),
910            stock_polygon: None,
911            hit_shapes: Default::default(),
912            display: DisplayList::default(),
913            viewport: Size::ZERO,
914            scale: 1.0,
915            stack: Vec::new(),
916            counters: Vec::new(),
917            origin: OriginId::HOST,
918            clips: Vec::new(),
919            clip_ids: Vec::new(),
920            opacity: Vec::new(),
921            float_root: Vec::new(),
922            float_stack: Vec::new(),
923            hints: Vec::new(),
924            menu: None,
925            menu_actions: Vec::new(),
926            menu_editor: None,
927            native_menus: false,
928            menu_bar: None,
929            menu_bar_rev: 0,
930            menu_bar_origin: OriginId::HOST,
931            menu_bar_open: None,
932            menu_bar_root: None,
933            selects: Vec::new(),
934            native_menu_bar: false,
935            lookup_available: false,
936            selection: None,
937            cell_selection: None,
938            awaiting_selection: false,
939            awaiting_paste: false,
940            file_ask: Default::default(),
941            select_dragging: None,
942            drag_follow: None,
943            last_frame_time: None,
944            line_carry: None,
945            scroll_latch: Default::default(),
946            sel_ords: Vec::new(),
947            sel_ends: None,
948            scopes: Vec::new(),
949            rows: Vec::new(),
950            modal: None,
951            modal_focus: Vec::new(),
952            type_ahead: String::new(),
953            type_ahead_at: None,
954            items_scratch: Vec::new(),
955            ghost_opacity: Vec::new(),
956            ghost_clip: Vec::new(),
957            ghost_clip_ids: Vec::new(),
958            ghost_rect: Vec::new(),
959            frame_requested: false,
960            trace: cause::Trace::default(),
961            pending_reveal: Vec::new(),
962            pending_reveal_labels: Vec::new(),
963            focus_reported: Vec::new(),
964            pending_scroll_labels: Vec::new(),
965            pending_focus_step: None,
966            region: None,
967            region_held: false,
968            region_focus: Vec::new(),
969            pending_region: None,
970            ime_rect: None,
971            sink_caret: None,
972            sink_caret_solid: false,
973            sink_caret_stamp: 0,
974            pending: Vec::new(),
975            framed: false,
976            system_seen: SystemEnv::default(),
977            system_fonts_seen: 0,
978            frame_no: 0,
979            layouts: FxHashMap::default(),
980            announcements: Vec::new(),
981            last_announcement: None,
982            events_answered: 0,
983            diag: Diagnostics::default(),
984        };
985        core.sync_font_names();
986        core
987    }
988
989    // -- Measurement ----------------------------------------------------
990    // The layout engine measures text every frame; these hand the same
991    // numbers to the view, so it never re-derives them by hand.
992
993    /// Measures `content` in `style` without adding a node: its unwrapped
994    /// size, or with `max_w` (logical px) its size once wrapped to that
995    /// width — what layout would give a text node with that content and
996    /// style, `wrap` / `max_lines` / `ellipsis` included. Logical px at
997    /// the scale of the current or last frame (1 before any frame).
998    /// Shapes through the text cache, so measuring a string and then
999    /// drawing it shapes once.
1000    pub fn measure_text(
1001        &mut self,
1002        content: &str,
1003        style: &TextStyle,
1004        max_w: Option<f32>,
1005    ) -> TextMetrics {
1006        let sess = &mut *self.session.state();
1007        self.text
1008            .measure(content, style, &sess.resources, &mut sess.fonts, max_w)
1009    }
1010
1011    /// `measure_text` for a rich-text paragraph.
1012    pub fn measure_rich_text(
1013        &mut self,
1014        spans: &[Span<'_>],
1015        base: &TextStyle,
1016        max_w: Option<f32>,
1017    ) -> TextMetrics {
1018        let sess = &mut *self.session.state();
1019        self.text
1020            .measure_rich(spans, base, &sess.resources, &mut sess.fonts, max_w)
1021    }
1022
1023    /// Where a point lands in the text node `key` drew: a byte offset into
1024    /// its content and the visual row within that node — counted across
1025    /// every run the key covers by where the rows sit, so a `line` row of
1026    /// inline runs is one row and a wrapped run as many as it wrapped to
1027    /// (backlog AR30); not the ordinal `line` node a pointer event names
1028    /// — or `None` for a key that is not a text node or was not drawn
1029    /// (backlog C18). A `role="none"` subtree under the key (a gutter) is
1030    /// not its text, as the access tree reads it. `point` is logical
1031    /// viewport px — the `x`/`y` a click or drag event carries — so a
1032    /// custom editor turns the event into a caret position with one call
1033    /// instead of measuring prefixes or assuming a cell width. Answered
1034    /// from the frame that finished: between frames that is the layout the
1035    /// pointer was over, and during a build it is the last one, since the
1036    /// node being declared has no layout yet. A wrapped node answers in
1037    /// the width it was drawn at.
1038    pub fn text_hit(&self, key: Key, point: Vec2) -> Option<TextHit> {
1039        self.text
1040            .hit_at(key, point.plus(self.dt_shift()), self.building)
1041    }
1042
1043    /// The caret rect for byte `byte` of the text node `key` drew: logical
1044    /// viewport px, zero wide, one line tall — where a caret, an IME
1045    /// candidate window or a selection edge goes. `byte` past the content
1046    /// is the end. Answered from the same frame `text_hit` is.
1047    pub fn caret_rect(&self, key: Key, byte: usize) -> Option<Rect> {
1048        let shift = self.dt_shift();
1049        self.text
1050            .caret_at(key, byte, self.building)
1051            .map(|r| Rect::new(r.x - shift.x, r.y - shift.y, r.w, r.h))
1052    }
1053
1054    // -- Announcements ---------------------------------------------------
1055
1056    /// Says something once, with no node behind it: "Saved", "3 results".
1057    /// Queued for [`Self::take_announcements`], the way `play` queues an
1058    /// audio command — an announcement is a consequence of an event, and
1059    /// the frame's tree, which is a function of state, has no place to
1060    /// keep one (see `docs/adr/0008-live-regions-and-announcements.md`).
1061    /// A region whose text changes on screen is the other half, and is
1062    /// the `live` prop instead.
1063    ///
1064    /// [`Live::Off`] and an empty string are both no-ops — the first so a
1065    /// caller can gate politeness without an `if`, the second because
1066    /// every platform needs a name to say.
1067    pub fn announce(&mut self, text: &str, live: crate::access::Live) {
1068        if live == crate::access::Live::Off || text.is_empty() {
1069            return;
1070        }
1071        if let Some((last, frame, answered)) = &self.last_announcement
1072            && last == text
1073            && *frame + 1 >= self.frame_no
1074            && *answered == self.events_answered
1075        {
1076            self.diag.raise(crate::diag::announcement_repeated(text));
1077        }
1078        self.last_announcement = Some((text.to_string(), self.frame_no, self.events_answered));
1079        self.announcements.push(crate::access::Announcement {
1080            text: text.to_string(),
1081            live,
1082        });
1083    }
1084
1085    /// Drains the announcements queued since the last drain. Windowed
1086    /// runners drain every frame whether or not assistive technology is
1087    /// attached, and discard what they cannot deliver, so a real app never
1088    /// accumulates and nothing is spoken minutes late; headless drivers
1089    /// assert on what comes back.
1090    pub fn take_announcements(&mut self) -> Vec<crate::access::Announcement> {
1091        std::mem::take(&mut self.announcements)
1092    }
1093
1094    /// Announcements queued and not yet drained (what `pending` is for
1095    /// audio commands).
1096    pub fn pending_announcements(&self) -> &[crate::access::Announcement] {
1097        &self.announcements
1098    }
1099
1100    // -- Diagnostics ----------------------------------------------------
1101
1102    /// Drains the warnings raised since the last drain (see [`crate::diag`]):
1103    /// silent misconfigurations the core noticed while finishing frames,
1104    /// each distinct (code, node) pair once. Windowed runners print them;
1105    /// headless tests assert on them.
1106    pub fn take_warnings(&mut self) -> Vec<Warning> {
1107        // Handles of other sessions resolve where the registry can see
1108        // them and this core cannot (shaping, the audio backend), so the
1109        // registry keeps them and the core draining warnings reports them.
1110        for f in self.session.state().resources.take_foreign() {
1111            self.diag.raise(crate::diag::foreign_resource(&f));
1112        }
1113        if let Some(w) = crate::diag::size_expressions_full() {
1114            self.diag.raise(w);
1115        }
1116        self.diag.take()
1117    }
1118
1119    /// Every warning this core has raised, drained or not, oldest first —
1120    /// for a reader that is not the driver. The runner drains
1121    /// [`Self::take_warnings`] after every frame and prints them, so a
1122    /// view that wants to *show* them (a development overlay) would
1123    /// otherwise never see one; this is the log the drain leaves behind.
1124    pub fn warnings_raised(&self) -> &[Warning] {
1125        self.diag.raised()
1126    }
1127
1128    /// Raises a warning a binding built (see [`crate::diag::unknown_prop`]):
1129    /// a frontend sees declarations the tree walk cannot, because a prop
1130    /// name nothing claims never becomes part of a node. Behind the same
1131    /// [`Self::set_diagnostics`] gate and the same once-per-(code, key)
1132    /// dedup as the checks, so a binding may raise one per node per frame.
1133    pub fn warn(&mut self, warning: Warning) {
1134        self.diag.raise(warning);
1135    }
1136
1137    /// Turns the diagnostic checks on or off. A bare `Core` has them on;
1138    /// drivers set them for the build they are in (the runner: debug on,
1139    /// release off; Node loops: off under `NODE_ENV=production`; a
1140    /// standalone C context: off until asked).
1141    pub fn set_diagnostics(&mut self, on: bool) {
1142        self.diag.enabled = on;
1143    }
1144
1145    pub fn diagnostics(&self) -> bool {
1146        self.diag.enabled
1147    }
1148
1149    /// Wheel line-deltas (e.g. winit's LineDelta) to logical px.
1150    pub fn lines_to_px(lines: f32) -> f32 {
1151        lines * SCROLL_LINE_PX
1152    }
1153
1154    /// Rasterizes outline glyphs as LCD subpixel coverage
1155    /// (`QuadKind::GlyphSubpixel`) instead of alpha masks. Drivers set it
1156    /// from what their renderer can blend per channel; flipping it drops
1157    /// the glyph atlas so every glyph re-rasterizes in the new mode.
1158    pub fn set_subpixel_text(&mut self, on: bool) {
1159        if self.text.set_subpixel(on) {
1160            self.atlas.clear();
1161        }
1162    }
1163
1164    pub fn subpixel_text(&self) -> bool {
1165        self.text.subpixel()
1166    }
1167
1168    /// The byte budget for the shaped-text cache (backlog C16): every
1169    /// text a frame draws is shaped once and kept, and past this many
1170    /// estimated bytes the least recently drawn entries go, down to three
1171    /// quarters of it, at the start of the next frame. What the last
1172    /// frame drew is never evicted, so a budget too small for one
1173    /// screenful costs re-shaping nothing — it only stops keeping what
1174    /// scrolled away. Default `DEFAULT_TEXT_CACHE_BYTES` (64 MB): a
1175    /// terminal streaming new lines lowers it, a document viewer that
1176    /// wants every page it showed to stay warm raises it. The clock that
1177    /// empties an idle cache after 300 frames is unchanged.
1178    pub fn set_text_cache_budget(&mut self, bytes: usize) {
1179        self.text.set_budget(bytes);
1180    }
1181
1182    pub fn text_cache_budget(&self) -> usize {
1183        self.text.budget()
1184    }
1185
1186    /// What the shaped-text cache holds, as the estimate the budget is
1187    /// charged against (a fixed floor per entry plus a per-glyph rate,
1188    /// calibrated against a counting allocator; see `text.rs`).
1189    pub fn text_cache_bytes(&self) -> usize {
1190        self.text.bytes()
1191    }
1192
1193    /// How many shaped texts the cache holds (a long line's chunks each
1194    /// count).
1195    pub fn text_cache_len(&self) -> usize {
1196        self.text.len()
1197    }
1198
1199    /// How many long lines — no-wrap texts past `LONG_LINE_BYTES`, shaped
1200    /// in chunks — are held (backlog C19).
1201    pub fn long_lines(&self) -> usize {
1202        self.text.long_lines()
1203    }
1204
1205    /// The frame clock for transitions: monotonic seconds, any origin.
1206    /// Drivers set it before every frame; a driver that never does gets
1207    /// snapping instead of animation.
1208    pub fn set_time(&mut self, now_secs: f64) {
1209        self.anim.set_time(now_secs);
1210        self.scroll.set_time(now_secs);
1211    }
1212
1213    /// True when the last frame left a transition mid-flight, or a view
1214    /// asked for another frame — drivers schedule one without waiting for
1215    /// input. One bool over every source; [`owed`](Self::owed) is the
1216    /// same reading by kind.
1217    pub fn animating(&self) -> bool {
1218        self.owed().any()
1219    }
1220
1221    /// What the last frame left owed, by kind (backlog F64). To a driver
1222    /// the kinds are one — it schedules the frame either way — but a
1223    /// test that wants to know whether the *transitions* have run out
1224    /// under a keyframe cycle that never will reads `cycle` apart from
1225    /// the rest: [`Owed::beyond_cycles`] is that wait's predicate.
1226    pub fn owed(&self) -> Owed {
1227        let (transition, cycle) = self.anim.owes();
1228        Owed {
1229            transition,
1230            cycle,
1231            depart: self.depart.animating(),
1232            requested: self.frame_requested || self.tree.any_animate,
1233            autoscroll: self.autoscrolling(),
1234            scroll: self.scroll.animating(),
1235        }
1236    }
1237
1238    /// Asks the driver for one more frame right after this one. A view
1239    /// that sets up a transition by drawing a starting state (a new split
1240    /// drawn collapsed so it can slide open) needs the next frame to come
1241    /// without waiting for input — the starting state itself snaps, so
1242    /// nothing is mid-flight yet to request it.
1243    ///
1244    /// Traced ([`Self::set_frame_trace`]), the calling line is kept as
1245    /// the frame's [`cause::FrameRequest`], which is why this tracks its
1246    /// caller.
1247    #[track_caller]
1248    pub fn request_frame(&mut self) {
1249        self.frame_requested = true;
1250        self.trace_request();
1251    }
1252
1253    /// Starts a frame. Build the tree through the returned `Ui` (or the
1254    /// `Core` builder methods directly), then `Ui::finish` — the one door
1255    /// out of a frame, which runs the extension fills, the devtools panel
1256    /// and the open menu before layout. A driver holding a bare `Core`
1257    /// mid-frame finishes through `Ui::wrap(core).finish()`.
1258    pub fn frame(&mut self, viewport: Size, scale: f32) -> Ui<'_> {
1259        self.begin_frame(viewport, scale);
1260        Ui::new(self)
1261    }
1262
1263    /// `frame` with something to fill the slots the view declares — the
1264    /// runner's extension list (`[Box<dyn Extension>]` is a `Fill`), or a
1265    /// test's stand-in. `Ui::slot` calls it in place, and `Ui::finish`
1266    /// lets it fill `"root"` and report unknown slots before layout. See
1267    /// `docs/adr/0014-slots-an-extension-fills-in-place.md`.
1268    pub fn frame_with<'a>(
1269        &'a mut self,
1270        viewport: Size,
1271        scale: f32,
1272        filler: &'a mut dyn crate::slot::Fill,
1273    ) -> Ui<'a> {
1274        self.begin_frame(viewport, scale);
1275        Ui::with_filler(self, filler)
1276    }
1277
1278    /// The session this core draws from. Hand it to `Core::new_in` to open
1279    /// another window sharing its fonts, images, sounds and glyph atlas.
1280    pub fn session(&self) -> &Session {
1281        &self.session
1282    }
1283
1284    /// Runs an edit-store operation against the session's font system —
1285    /// the one the text cache shapes with, so an editor and a text node
1286    /// measure the same. The session borrow lasts exactly the call.
1287    fn edit_with_fonts<T>(
1288        &mut self,
1289        f: impl FnOnce(&mut EditStore, &mut cosmic_text::FontSystem) -> T,
1290    ) -> T {
1291        let sess = &mut *self.session.state();
1292        f(&mut self.edit, &mut sess.fonts)
1293    }
1294
1295    /// The viewport (logical px) the current frame was begun with — the
1296    /// window, less the devtools' dock while the panel is docked
1297    /// (`docs/adr/0024`): what the host lays out into. Changes to it
1298    /// arrive as `resize` events (see `take_pending_events`), a dock
1299    /// coming, going or resizing among them.
1300    pub fn viewport(&self) -> Size {
1301        Size::new(self.dt_area.w, self.dt_area.h)
1302    }
1303
1304    /// The device pixel ratio the current frame was begun with.
1305    pub fn scale(&self) -> f32 {
1306        self.scale
1307    }
1308
1309    /// The origin nodes opened right now are tagged with: `OriginId::HOST`
1310    /// in the host's own view, the filling extension's inside a fill (see
1311    /// `Core::fill`).
1312    pub fn origin(&self) -> OriginId {
1313        self.origin
1314    }
1315
1316    /// The finished frame's draw data: display list plus the glyph atlas the
1317    /// renderer mirrors (mutable so it can clear the dirty flag).
1318    pub fn output(&mut self) -> (&DisplayList, &mut GlyphAtlas) {
1319        (&self.display, &mut self.atlas)
1320    }
1321
1322    pub fn begin_frame(&mut self, viewport: Size, scale: f32) {
1323        // First, while the last frame's tree and every store's reading of
1324        // it are still whole: why this frame runs, and who held it
1325        // (backlog F111).
1326        self.trace_begin_frame();
1327        // Against the finished frame, before anything below clears it: a
1328        // held drag re-places its live end where the last layout moved
1329        // the text under the pointer, and steps its scroller when the
1330        // pointer is past the edge (ADR 0029).
1331        self.follow_drag();
1332        self.age_type_ahead();
1333        // A window that changed size is a fact the driver reports, so the
1334        // core turns it into data like any other: `{kind="resize", width,
1335        // height, scale}` on the root, pending for the driver to route
1336        // after the frame. The first frame establishes the viewport rather
1337        // than resizing it.
1338        // The host's viewport is what the dock leaves of the window
1339        // (ADR 0024), so a dock that comes, goes or is dragged is a
1340        // resize too.
1341        let area = self.devtools_area(viewport);
1342        if self.framed
1343            && (area.w != self.dt_area.w || area.h != self.dt_area.h || scale != self.scale)
1344        {
1345            self.pending.push(UiEvent {
1346                origin: OriginId::HOST,
1347                window: WindowId::MAIN,
1348                key: Key::ROOT,
1349                payload: Value::map([
1350                    ("kind", Value::str("resize")),
1351                    ("width", Value::Float(area.w as f64)),
1352                    ("height", Value::Float(area.h as f64)),
1353                    ("scale", Value::Float(scale as f64)),
1354                ]),
1355                slot: None,
1356            });
1357        }
1358        self.dt_area = area;
1359        // And what the user set in the OS — and whether assistive
1360        // technology is listening, which rides in the same reading. A
1361        // driver that learns of a change writes it into `env` and asks
1362        // for a redraw — which is
1363        // enough for a host whose view is a function the runner calls
1364        // every frame, and nothing at all for one that retains the tree
1365        // it was handed (Node, C, Lua): its `view` runs when a message
1366        // changes the model, so the change has to *be* a message. The
1367        // first frame establishes the reading rather than reporting it,
1368        // the way the viewport does.
1369        if self.framed && self.env.system != self.system_seen {
1370            let sys = self.env.system;
1371            self.pending.push(UiEvent {
1372                origin: OriginId::HOST,
1373                window: WindowId::MAIN,
1374                key: Key::ROOT,
1375                payload: Value::map([
1376                    ("kind", Value::str("system")),
1377                    ("appearance", Value::str(sys.appearance.name())),
1378                    (
1379                        "accent",
1380                        sys.accent
1381                            .map_or(Value::Null, |c| Value::Int(c.to_hex() as i64)),
1382                    ),
1383                    ("motion", Value::str(sys.motion.name())),
1384                    (
1385                        "locale",
1386                        sys.locale.map_or(Value::Null, |l| Value::str(l.as_str())),
1387                    ),
1388                    ("assistive", Value::str(sys.assistive.name())),
1389                ]),
1390                slot: None,
1391            });
1392        }
1393        self.system_seen = self.env.system;
1394        // And the installed fonts, rescanned since the last frame and found
1395        // changed (`reload_system_fonts`, which the winit runner calls when
1396        // the OS says so): a message for the same reason as `system`, an
1397        // app holding `systemFonts()` in its model having nothing else to
1398        // re-read it on. The first frame establishes it, as above.
1399        let fonts_rev = self.session.state().system_fonts_rev;
1400        if self.framed && fonts_rev != self.system_fonts_seen {
1401            self.pending.push(UiEvent {
1402                origin: OriginId::HOST,
1403                window: WindowId::MAIN,
1404                key: Key::ROOT,
1405                payload: Value::map([("kind", Value::str("fonts"))]),
1406                slot: None,
1407            });
1408        }
1409        self.system_fonts_seen = fonts_rev;
1410        // The palette is a function of what the OS said and what the app
1411        // asked for, so it is recomputed rather than invalidated: a
1412        // couple of dozen float ops once a frame, against a cache that
1413        // would have to be poked from every writer of `env.system`.
1414        self.refresh_theme();
1415        self.framed = true;
1416        self.frame_no += 1;
1417        // The layout rects a node reported, swept on the one cadence
1418        // every by-last-use store sweeps on (`retain::sweep_cutoff`, AR45).
1419        if let Some(cutoff) = crate::retain::sweep_cutoff(self.frame_no) {
1420            self.layouts.retain(|_, (_, seen)| *seen >= cutoff);
1421        }
1422        self.viewport = viewport;
1423        self.scale = scale;
1424        self.window_title = None;
1425        self.always_on_top = false;
1426        self.secure_input = false;
1427        self.option_as_alt = crate::input::OptionAsAlt::None;
1428        // The drawn menu bar's root is this frame's: a view that stops
1429        // calling `widgets::menu_bar` leaves nothing behind for the next
1430        // event to land on. Re-recorded while the widget builds.
1431        self.menu_bar_root = None;
1432        // And the select fields, re-declared by the ones the view builds.
1433        self.selects.clear();
1434        // Last frame's focus declarations are what this frame's are
1435        // compared against (see `set_key_focus`).
1436        std::mem::swap(&mut self.declared_focus, &mut self.declared_focus_last);
1437        self.declared_focus.clear();
1438        // And the labels `key_of` resolves through, the same way.
1439        std::mem::swap(&mut self.key_labels, &mut self.key_labels_last);
1440        self.key_labels.clear();
1441        // Slots are positions, not declarations to diff: one clear.
1442        self.slot_labels.clear();
1443        self.ns_depth = usize::MAX;
1444        self.building = true;
1445        // And the window declarations, which `finish_frame` diffs the same
1446        // way (see `declare_window`).
1447        std::mem::swap(&mut self.declared_windows, &mut self.declared_windows_last);
1448        self.declared_windows.clear();
1449        // A frame that declared an `exit` may be the last one some node is
1450        // ever seen in, so it is kept whole: the two tree buffers swap
1451        // roles instead of one being cleared, which costs an allocation
1452        // that already existed and no copying. Nothing else keeps it — a
1453        // frame with no exits empties the spare, so a stale tree can never
1454        // be diffed against.
1455        let keep_prev = self.tree.any_exit;
1456        if keep_prev {
1457            std::mem::swap(&mut self.tree, &mut self.prev_tree);
1458        } else {
1459            self.prev_tree.clear();
1460        }
1461        self.tree.clear();
1462        self.display.clear();
1463        // Removed handles the backend has not heard of, and this window's
1464        // atlas slots for removed images (AR8); kept on the session
1465        // because a removal can land between frames, after the list was
1466        // cleared, and through a window that never draws again.
1467        self.sync_dropped();
1468        // Before anything is emitted: the one point where the atlas may
1469        // empty its page — one the last frame extended, or one about to
1470        // fill — without a quad sampling what it dropped (F99).
1471        self.atlas.begin_frame();
1472        // An image's spare buffer outlives its stream by a few frames (W20).
1473        self.session.state().resources.release_spares();
1474        // Before the text store begins its frame, as a scale change is.
1475        self.sync_weights();
1476        // The text list goes with the tree: a kept frame's text nodes carry
1477        // that frame's `TextId`s, and nothing else can resolve them.
1478        self.text.begin_frame(
1479            &mut self.session.state().fonts,
1480            scale,
1481            keep_prev,
1482            self.frame_no,
1483        );
1484        // And the strokes, for the same reason: a kept frame's `line`
1485        // nodes index that frame's list.
1486        self.lines.begin_frame(keep_prev);
1487        self.fragments.begin_frame(keep_prev);
1488        self.cells.begin_frame(scale);
1489        self.sync_font_names();
1490        self.anim.begin_frame(self.frame_no);
1491        self.depart.begin_frame(self.frame_no);
1492        // The two stores that keep state by key across a key's absence:
1493        // they stamp this frame onto what it declares, and cap what it
1494        // does not (backlog F26).
1495        self.edit.begin_frame(self.frame_no);
1496        self.scroll.begin_frame(self.frame_no);
1497        self.tree.push(
1498            NIL,
1499            Key::ROOT,
1500            OriginId::HOST,
1501            NodeSpec::column().fill(),
1502            NodeContent::Container,
1503        );
1504        self.stack.clear();
1505        self.stack.push(0);
1506        self.counters.clear();
1507        self.counters.push(0);
1508        // A frame that ended with nodes unclosed must not leak its hints
1509        // into the next one.
1510        self.hints.clear();
1511        self.origin = OriginId::HOST;
1512        self.frame_requested = false;
1513        // And the lines that asked, with it: an ask the reset above
1514        // forgets is not one the next frame was held by.
1515        self.trace_forget_requests();
1516        self.devtools_begin_frame();
1517    }
1518}
1519
1520impl Default for Core {
1521    fn default() -> Self {
1522        Self::new()
1523    }
1524}
1525
1526/// A frontend that draws into the shared tree each frame — the trait the
1527/// runner uses to host Lua (or any other) extensions without knowing what
1528/// they are. Origins are assigned by the runner.
1529///
1530/// Where it draws is a slot the host declared
1531/// (`docs/adr/0014-slots-an-extension-fills-in-place.md`): `slots` names
1532/// the ones it fills, `view` is called once per frame for each of them
1533/// with which one it is, and an extension naming none is called once
1534/// after the host's view for the reserved `"root"` slot — the sequence
1535/// every extension got before slots existed. `on_event` answers with
1536/// replies: values the runner hands to the host's own `on_event`, with
1537/// this extension's origin on them.
1538pub trait Extension {
1539    fn name(&self) -> &str;
1540    /// The slot names this extension fills; empty means `"root"`. The one
1541    /// entry [`crate::slot::ANY_SLOT`] (`"*"`) means every name declared
1542    /// under its namespace, for an extension whose slots are not known
1543    /// when it loads — a Lua host whose `init.lua` registers views at
1544    /// runtime, one slot per view — and it then gets no `unknown-slot`
1545    /// warning, since there is no list to check against (backlog K1).
1546    fn slots(&self) -> &[String] {
1547        &[]
1548    }
1549    fn view(&mut self, slot: &crate::slot::Slot<'_>, ui: &mut Ui<'_>) -> Result<(), String>;
1550    /// One of this extension's events; the values returned are replies
1551    /// to the host (ADR 0014 decision 6), delivered in order.
1552    fn on_event(&mut self, ev: &UiEvent) -> Vec<Value>;
1553}