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