smudge_core/app.rs
1//! The application: the single entry point that runs a frame from reactive
2//! effects to damage.
3//!
4//! An [`App`] owns the reactive runtime, the widget tree, the update queue and
5//! the damage for one window. [`App::frame`] runs the whole pipeline in order,
6//! so a window, a headless test and a benchmark all drive the same code.
7
8use std::cell::RefCell;
9use std::rc::Rc;
10use std::time::Duration;
11
12use web_time::Instant;
13
14use crate::damage::DamageAccumulator;
15use crate::element::{IntoElement, UpdateQueue, mount};
16use crate::geometry::{Color, PixelRect, Point, Rect, Size};
17use crate::input::{InputEvent, Modifiers};
18use crate::paint::DisplayList;
19use crate::reactive::runtime::Scope;
20use crate::reactive::{self, Owner, RenderEffect};
21use crate::semantics::{A11yChanges, SemanticsNode};
22use crate::theme::Theme;
23use crate::timer::{self, Clock, TimerBook};
24use crate::tree::{WidgetId, WidgetTree};
25
26/// How many flush and apply rounds a frame runs before it reports a cycle.
27const MAX_FLUSH_ROUNDS: u32 = 16;
28
29/// How much of the surface a frame repaints.
30#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
31pub enum RepaintMode {
32 /// Repaint only the pixels that changed since the previous frame.
33 #[default]
34 Damage,
35 /// Repaint the whole surface on every frame.
36 Full,
37}
38
39/// The settings an [`App`] starts with.
40#[derive(Clone, Debug)]
41pub struct AppConfig {
42 /// The size of the window in logical pixels.
43 pub viewport: Size,
44 /// The number of physical pixels per logical pixel.
45 pub scale: f32,
46 /// The color a renderer fills the surface with before it replays the
47 /// display list.
48 pub clear_color: Color,
49 /// How much of the surface each frame repaints.
50 pub repaint: RepaintMode,
51}
52
53/// What a renderer draws for one frame.
54#[derive(Debug)]
55pub struct FrameOutput<'a> {
56 /// The operations to replay, in paint order.
57 pub list: &'a DisplayList,
58 /// The physical rectangles that changed since the previous frame.
59 pub damage: &'a [PixelRect],
60 /// Whether the frame repaints the whole surface.
61 pub full: bool,
62 /// The color the surface starts from, before `list` is replayed.
63 pub clear_color: Color,
64 /// The number of physical pixels per logical pixel.
65 pub scale: f32,
66}
67
68/// How long each step of the last frame took and how much work it did.
69#[derive(Clone, Debug, Default, PartialEq)]
70pub struct FrameStats {
71 /// Time spent running reactive effects and applying their updates.
72 pub flush: Duration,
73 /// Time spent in the layout pass.
74 pub layout: Duration,
75 /// Time spent in the paint pass.
76 pub paint: Duration,
77 /// Time spent rebuilding the display list.
78 pub list: Duration,
79 /// Time spent converting and merging the damage.
80 pub damage: Duration,
81 /// Whether the layout pass ran the layout engine.
82 pub layout_ran: bool,
83 /// How many nodes the paint pass regenerated.
84 pub nodes_painted: u32,
85 /// How many physical rectangles the damage holds.
86 pub damage_rects: u32,
87 /// How many physical pixels the damage covers, counting an overlap once per
88 /// rectangle.
89 pub damage_pixels: u64,
90 /// How many physical pixels the damage covered before the merge step
91 /// collapsed the rectangles, counting an overlap once per rectangle.
92 ///
93 /// Together with [`FrameStats::damage_pixels`] this shows what the merge
94 /// step traded: rectangles for area.
95 pub damage_pixels_before_merge: u64,
96 /// How many flush and apply rounds the frame ran.
97 pub flush_rounds: u32,
98}
99
100/// One window's widget tree, reactive runtime and frame pipeline.
101///
102/// The app owns everything the frame touches. [`App::frame`] is the only method
103/// that mutates the tree, and it does so in one fixed order, so an effect never
104/// observes a half-applied frame.
105///
106/// # Examples
107///
108/// ```
109/// use smudge_core::app::{App, AppConfig, RepaintMode};
110/// use smudge_core::geometry::{Color, Size};
111/// use smudge_core::style::px;
112/// use smudge_core::widget::panel;
113///
114/// let mut app = App::new(
115/// AppConfig {
116/// viewport: Size {
117/// width: 320.0,
118/// height: 200.0,
119/// },
120/// scale: 1.0,
121/// clear_color: Color::WHITE,
122/// repaint: RepaintMode::Damage,
123/// },
124/// || panel().width(px(120.0)).height(px(40.0)).background(Color::BLACK),
125/// );
126///
127/// let full = app.frame().full;
128/// assert!(full, "the first frame repaints the whole surface");
129/// assert_eq!(app.stats().damage_rects, 1);
130///
131/// let empty = app.frame().damage.is_empty();
132/// assert!(empty, "a frame with no changes damages nothing");
133/// ```
134pub struct App {
135 /// The widget tree the frame lays out and paints.
136 tree: WidgetTree,
137 /// The updates that reactive effects have queued.
138 queue: UpdateQueue,
139 /// The damage collected for the frame that is running.
140 damage: DamageAccumulator,
141 /// The display list of the last frame that built one.
142 list: DisplayList,
143 /// The settings the frame uses.
144 config: AppConfig,
145 /// The pointer position the last pointer event reported, in logical
146 /// pixels.
147 pointer: Point,
148 /// The keyboard modifiers the last key event reported.
149 modifiers: Modifiers,
150 /// The timings and counts of the last frame.
151 stats: FrameStats,
152 /// The callback that runs at the start of every frame.
153 tick: Option<Box<dyn FnMut(Instant)>>,
154 /// The timers of the app and the clock they read.
155 timers: Rc<RefCell<TimerBook>>,
156 /// The channels other threads send values through, and the book the
157 /// frame drains them from.
158 channels: Rc<RefCell<crate::channel::ChannelBook>>,
159 /// The window commands handlers queue and the state the window reports,
160 /// shared with every owner of the tree.
161 window: Rc<RefCell<crate::window::WindowBook>>,
162 /// Whether the next frame repaints the whole surface.
163 force_full: bool,
164 /// The owner that the build closure, the signals it created, every
165 /// node's owner and the theme bindings live under.
166 ///
167 /// Held so those stay alive for as long as the app does, and so the
168 /// cleanup at the end stops the theme bindings.
169 _root: Owner,
170 /// The clipboard the app's handlers copy and paste through, when one is
171 /// installed.
172 clipboard: Option<crate::clipboard::Slot>,
173}
174
175impl App {
176 /// Creates an app and mounts the element that `build` returns.
177 ///
178 /// Installs the reactive runtime on the calling thread, then runs `build`
179 /// and mounts its element inside an owner that lives as long as the app, so
180 /// every binding sees the context that `build` provides. The mounted tree
181 /// is laid out and painted by the first call to [`App::frame`], which
182 /// repaints the whole surface.
183 ///
184 /// Timers read the operating system's clock. Use [`App::with_clock`] to
185 /// run them on another one.
186 pub fn new<E: IntoElement>(config: AppConfig, build: impl FnOnce() -> E) -> App {
187 App::with_clock(config, Clock::system(), build)
188 }
189
190 /// Creates an app like [`App::new`] whose timers and tick callback read
191 /// `clock`.
192 ///
193 /// The clock is in place before `build` runs, so a timer that `build`
194 /// creates is scheduled on it too. A test passes a
195 /// [`VirtualClock`](crate::timer::VirtualClock) and advances it by hand.
196 pub fn with_clock<E: IntoElement>(
197 config: AppConfig,
198 clock: Clock,
199 build: impl FnOnce() -> E,
200 ) -> App {
201 reactive::init();
202 let root = Owner::new();
203 let timers = Rc::new(RefCell::new(TimerBook::new(clock)));
204 root.data().timer_book.replace(Some(Rc::downgrade(&timers)));
205 let channels = Rc::new(RefCell::new(crate::channel::ChannelBook::default()));
206 root.data()
207 .channel_book
208 .replace(Some(Rc::downgrade(&channels)));
209 let window = crate::window::new_book(&root);
210 root.data()
211 .window_book
212 .replace(Some(Rc::downgrade(&window)));
213 let mut tree = WidgetTree::new();
214 tree.set_scale(config.scale);
215 let queue = UpdateQueue::default();
216 root.with(|| mount(build().into_any(), &mut tree, &queue, None));
217
218 App {
219 tree,
220 queue,
221 damage: DamageAccumulator::new(),
222 list: DisplayList::new(),
223 config,
224 pointer: Point::default(),
225 modifiers: Modifiers::default(),
226 stats: FrameStats::default(),
227 tick: None,
228 timers,
229 channels,
230 window,
231 force_full: true,
232 _root: root,
233 clipboard: None,
234 }
235 }
236
237 /// Changes the viewport and the scale, and makes the next frame repaint the
238 /// whole surface.
239 ///
240 /// The viewport is in logical pixels and the scale is the number of
241 /// physical pixels per logical pixel. A viewport that differs from the last
242 /// one also runs the layout pass again.
243 pub fn set_viewport(&mut self, viewport: Size, scale: f32) {
244 self.config.viewport = viewport;
245 self.config.scale = scale;
246 self.tree.set_scale(scale);
247 self.force_full = true;
248 }
249
250 /// Changes how much of the surface each frame repaints, and makes the next
251 /// frame repaint the whole surface.
252 pub fn set_repaint_mode(&mut self, mode: RepaintMode) {
253 self.config.repaint = mode;
254 self.force_full = true;
255 }
256
257 /// Replaces the theme that style values which name a token resolve
258 /// against, and marks for repainting every node whose last paint looked
259 /// up a token, whether through its style or inside its widget's own
260 /// `paint`.
261 ///
262 /// The tree starts on [`Theme::light`]. Nodes whose paint resolves no
263 /// value from the theme paint the same pixels under any theme and are not
264 /// repainted, so a switch damages only what the switch can change. An
265 /// equal theme marks nothing.
266 ///
267 /// ```
268 /// use smudge_core::app::{App, AppConfig, RepaintMode};
269 /// use smudge_core::geometry::{Color, Size};
270 /// use smudge_core::style::px;
271 /// use smudge_core::theme::{ColorToken, Theme};
272 /// use smudge_core::widget::panel;
273 ///
274 /// let mut app = App::new(
275 /// AppConfig {
276 /// viewport: Size {
277 /// width: 120.0,
278 /// height: 80.0,
279 /// },
280 /// scale: 1.0,
281 /// clear_color: Color::WHITE,
282 /// repaint: RepaintMode::Damage,
283 /// },
284 /// || panel().width(px(40.0)).height(px(20.0)).background(ColorToken::SURFACE),
285 /// );
286 /// app.frame();
287 ///
288 /// app.set_theme(Theme::dark());
289 /// app.frame();
290 /// assert_eq!(app.stats().nodes_painted, 1);
291 /// ```
292 pub fn set_theme(&mut self, theme: Theme) {
293 self.tree.set_theme(theme);
294 }
295
296 /// Installs one effect that keeps the theme current.
297 ///
298 /// The closure is read when it is installed and again whenever a signal it
299 /// reads changes; each run hands the theme it returns to
300 /// [`App::set_theme`] through the update queue, so the change lands inside
301 /// a frame like any other reactive update. Pass a closure that builds the
302 /// theme from a signal to switch the theme reactively:
303 ///
304 /// ```
305 /// use smudge_core::app::{App, AppConfig, RepaintMode};
306 /// use smudge_core::geometry::{Color, Size};
307 /// use smudge_core::reactive::{prelude::*, signal_local};
308 /// use smudge_core::style::px;
309 /// use smudge_core::theme::{ColorToken, Theme};
310 /// use smudge_core::widget::panel;
311 ///
312 /// let (dark, set_dark) = signal_local(false);
313 /// let mut app = App::new(
314 /// AppConfig {
315 /// viewport: Size {
316 /// width: 120.0,
317 /// height: 80.0,
318 /// },
319 /// scale: 1.0,
320 /// clear_color: Color::WHITE,
321 /// repaint: RepaintMode::Damage,
322 /// },
323 /// || panel().width(px(40.0)).height(px(20.0)).background(ColorToken::SURFACE),
324 /// );
325 /// app.bind_theme(move || if dark.get() { Theme::dark() } else { Theme::light() });
326 /// app.frame();
327 ///
328 /// set_dark.set(true);
329 /// app.frame();
330 /// assert_eq!(app.stats().nodes_painted, 1);
331 /// ```
332 ///
333 /// The binding runs under the app's root owner, so it reports its wakes
334 /// to the callback set with [`App::set_wake_callback`] and stops when the
335 /// app is dropped.
336 pub fn bind_theme(&mut self, get: impl Fn() -> Theme + 'static) {
337 let queue = self.queue.clone();
338 self._root.with(|| {
339 RenderEffect::new(move || {
340 let theme = get();
341 let queue = queue.clone();
342 queue.push(Box::new(move |tree| tree.set_theme(theme)));
343 });
344 });
345 }
346
347 /// Installs the clipboard the app's widgets copy and paste through.
348 ///
349 /// Until one is installed, a clipboard read or write finds nothing, so
350 /// cut, copy and paste keys do nothing. The windowing layer installs one
351 /// that reaches the system clipboard; a test installs a
352 /// [`MemoryClipboard`](crate::clipboard::MemoryClipboard) and keeps a
353 /// clone to seed or read the text. Installing again replaces the
354 /// previous clipboard.
355 pub fn set_clipboard(&mut self, clipboard: Box<dyn crate::clipboard::Clipboard>) {
356 self.clipboard = Some(crate::clipboard::install(self._root.data(), clipboard));
357 }
358
359 /// Sets the callback that a wake in this app's tree runs to request a
360 /// redraw.
361 ///
362 /// A write that queues this app's first effect since the last flush runs
363 /// the callback once, so the platform learns that the next frame has work
364 /// to do. Every binding of this app reports its wake here, and an app on
365 /// another part of the thread neither sees this app's wakes nor hides
366 /// them. A write made while [`App::frame`] runs its timers, its tick and
367 /// its updates runs no callback, because the frame runs the effects it
368 /// wakes before it lays out; a write made later in the frame, while a
369 /// widget paints, does. See
370 /// [`set_wake_callback`](crate::reactive::set_wake_callback) for the
371 /// exact rules.
372 pub fn set_wake_callback(&self, callback: impl Fn() + 'static) {
373 self._root.set_wake_callback(callback);
374 }
375
376 /// Sets the callback that a message from another thread runs to ask for
377 /// the frame that applies it.
378 ///
379 /// The platform installs one that posts a wake event to the event loop,
380 /// so a thread that sends does not have to poll and the window does not
381 /// wake for anything else. A headless app needs none: its next
382 /// [`App::frame`] call finds the values on its own. Channels created
383 /// before this call get the waker too, and later ones start with it.
384 pub fn set_remote_waker(&self, waker: impl Fn() + Send + Sync + 'static) {
385 crate::channel::install_waker(&self.channels, std::sync::Arc::new(waker));
386 }
387
388 /// Ends the held press as if it had been cancelled, without running the
389 /// pressed node's `on_press`.
390 ///
391 /// The windowing layer calls this when the window system takes over the
392 /// press, as it does for an interactive move or resize: the release of
393 /// that press never reaches the app, so without this the press would stay
394 /// captured and hover would stop following the pointer. The pressed node
395 /// loses its pressed state and hears `on_pointer_up`, so a drag it started
396 /// ends. A window system may still refuse the gesture after the call that
397 /// starts it succeeded, and then the release arrives after all: a primary
398 /// release that comes with no press held, before the next primary press,
399 /// reaches no handler. With no press held this does nothing.
400 pub fn release_press(&mut self) {
401 self.tree.release_press();
402 }
403
404 /// Takes every window command queued so far, in the order the handlers
405 /// queued them.
406 ///
407 /// The windowing layer calls this after it hands an input event over, so
408 /// a command starts its move or resize while the press that queued it is
409 /// still being handled. A headless application has no windowing layer and
410 /// no need to take the commands, so they stay queued for a test to
411 /// inspect. Taking them empties the queue.
412 pub fn take_window_commands(&mut self) -> Vec<crate::window::WindowCommand> {
413 crate::window::take_commands(&self.window)
414 }
415
416 /// Replaces the window state the application's
417 /// [`maximized`](crate::window::maximized) reads.
418 ///
419 /// The windowing layer calls this when the window's maximized state may
420 /// have changed. An equal report changes nothing, so a binding that reads
421 /// the state reruns only on a real change.
422 pub fn set_window_state(&mut self, state: crate::window::WindowState) {
423 crate::window::set_state(&self.window, state);
424 }
425
426 /// Returns the cursor the pointer should show.
427 ///
428 /// This is the cursor of the deepest enabled node under the pointer that
429 /// set one with the [`cursor`](crate::element::Element::cursor) builder,
430 /// or [`Cursor::Default`](crate::window::Cursor::Default) when no node
431 /// under the pointer sets one or the pointer left the window. While a
432 /// press is held, the walk starts at the pressed node instead. The
433 /// windowing layer reads it after every input event and every frame and
434 /// shows it on the window.
435 pub fn cursor(&self) -> crate::window::Cursor {
436 self.tree.cursor()
437 }
438
439 /// Sets a callback that runs once at the start of every frame with the
440 /// current time.
441 ///
442 /// The time comes from the app's [`Clock`], the same one its timers
443 /// read, so a test that runs the app on a virtual clock drives both.
444 pub fn set_tick(&mut self, tick: impl FnMut(Instant) + 'static) {
445 self.tick = Some(Box::new(tick));
446 }
447
448 /// Returns the current time of the app's clock.
449 pub fn now(&self) -> Instant {
450 self.timers.borrow().clock.now()
451 }
452
453 /// Returns the earliest time a timer fires at, or `None` when none is
454 /// scheduled.
455 ///
456 /// A platform that has no tick callback waits until this deadline, so a
457 /// pending timer wakes the window when it comes due instead of the
458 /// window polling.
459 pub fn next_deadline(&self) -> Option<Instant> {
460 self.timers.borrow().next_deadline()
461 }
462
463 /// Runs the timers that are due on the app's clock, outside a frame.
464 ///
465 /// A timer that writes a signal wakes the app through the callback set
466 /// with [`App::set_wake_callback`], like any write between frames, and a
467 /// timer that changes nothing asks for nothing. A platform calls this
468 /// when [`App::next_deadline`] passes, so a due timer costs a frame only
469 /// when it has something to show. [`App::frame`] runs due timers itself
470 /// as well, so a caller that draws every frame needs no call.
471 pub fn run_due_timers(&mut self) {
472 timer::fire_due(&self.timers);
473 }
474
475 /// Returns whether a frame tick callback is set.
476 pub fn has_tick(&self) -> bool {
477 self.tick.is_some()
478 }
479
480 /// Returns whether an event left work that only a frame can finish.
481 ///
482 /// An input event can change how the tree looks without writing a signal,
483 /// for example by hovering a node with a hover style, so the reactive wake
484 /// callback never hears of it. A message from another thread also leaves
485 /// such work: the values its channel queues are the frame's to deliver.
486 /// A platform calls this after handing events over and asks for a frame
487 /// when it is `true`. Most pointer moves change
488 /// nothing, and asking for a frame every time would present the window
489 /// for nothing.
490 pub fn needs_frame(&self) -> bool {
491 self.tree.has_pending_work() || crate::channel::has_pending(&self.channels)
492 }
493
494 /// Handles one input event between frames.
495 ///
496 /// The pointer events are routed to the tree: a move moves the hovered
497 /// chain, a primary press captures the node under the pointer and focuses
498 /// it when it can take the focus, and a release runs the captured node's
499 /// `on_press` when it happens over it. A press elsewhere clears the focus,
500 /// except while a focus trap is mounted and the press lands outside it,
501 /// and any press turns the keyboard navigation mode off. A wheel scroll
502 /// goes to the deepest node in the hovered chain that has a wheel handler.
503 /// Key events are routed to the focused node and bubble to its ancestors
504 /// until a handler takes them, starting at the root when nothing anchors
505 /// the key and at the active focus trap when the key would otherwise
506 /// start outside it, and an unhandled Tab or Shift+Tab moves the focus, staying
507 /// inside the active focus trap when one is mounted. Handlers run at
508 /// once, inside their node's owner, and a handler that writes a signal
509 /// makes the change visible with the next call to [`App::frame`]. The app
510 /// also records the pointer position from pointer events and the keyboard
511 /// modifiers from key and modifiers events.
512 pub fn handle_event(&mut self, event: InputEvent) {
513 match event {
514 InputEvent::PointerMoved { position } => {
515 self.pointer = position;
516 self.tree.pointer_move(position);
517 }
518 InputEvent::PointerDown { position, button } => {
519 self.pointer = position;
520 self.tree.pointer_down(position, button);
521 }
522 InputEvent::PointerUp { position, button } => {
523 self.pointer = position;
524 self.tree.pointer_up(position, button);
525 }
526 InputEvent::PointerLeft => self.tree.pointer_left(),
527 InputEvent::Wheel { delta } => self.tree.wheel(delta),
528 InputEvent::Key(key) => {
529 self.tree.key(&key);
530 self.modifiers = key.modifiers;
531 }
532 InputEvent::ModifiersChanged(modifiers) => self.modifiers = modifiers,
533 InputEvent::ImeCommit { text } => self.tree.ime_commit(&text),
534 InputEvent::ImePreedit { text, cursor } => self.tree.ime_preedit(&text, cursor),
535 InputEvent::WindowFocus(focused) => {
536 // A window that loses the input focus drops its input method
537 // context, so a composition must not outlive the focus. The
538 // empty preedit is the clear.
539 if !focused {
540 self.tree.ime_preedit("", None);
541 }
542 }
543 }
544 }
545
546 /// Returns the pointer position the last pointer event reported, in
547 /// logical pixels.
548 ///
549 /// The position starts at the origin and moves only when a pointer event
550 /// carries a position, so it lags the real pointer until the first event
551 /// arrives.
552 pub fn pointer_position(&self) -> Point {
553 self.pointer
554 }
555
556 /// Returns the keyboard modifiers the last key or modifiers event
557 /// reported.
558 ///
559 /// They start at none and change only when one of those events arrives.
560 pub fn modifiers(&self) -> Modifiers {
561 self.modifiers
562 }
563
564 /// Returns the root owner, for the crate's own tests.
565 #[cfg(test)]
566 pub(crate) fn root(&self) -> &Owner {
567 &self._root
568 }
569
570 /// Returns the node that holds the keyboard focus, when one does.
571 ///
572 /// A press on a focusable node focuses it, a press elsewhere clears the
573 /// focus, and Tab moves it. While a focus trap is mounted, a press
574 /// outside the trap leaves the focus where it is, and Tab moves it within
575 /// the trap only. Removing the focused node clears the focus, and
576 /// removing or hiding a trap returns the focus to the node that held it
577 /// when the trap mounted or last showed.
578 pub fn focused(&self) -> Option<WidgetId> {
579 self.tree.focused()
580 }
581
582 /// Returns whether the keyboard navigation mode is on.
583 ///
584 /// A Tab or an arrow key turns the mode on and a pointer press turns it
585 /// off, so a focus ring can draw for keyboard users alone. A node carries
586 /// the keyboard focus bit only while it holds the focus and this mode is
587 /// on.
588 pub fn keyboard_navigation(&self) -> bool {
589 self.tree.keyboard_nav()
590 }
591
592 /// Returns the area the focused widget reports to the input method, in
593 /// window coordinates, when it reports one.
594 ///
595 /// This is [`None`] while no widget reports an area, which is the default,
596 /// while nothing is focused, and while the focused node sits outside an
597 /// active focus trap. A window presents the area to the
598 /// operating system so the input method can anchor its candidate window to
599 /// it.
600 pub fn ime_area(&self) -> Option<Rect> {
601 self.tree.ime_area()
602 }
603
604 /// Returns the surface size in physical pixels.
605 ///
606 /// This is the viewport multiplied by the scale and rounded to the nearest
607 /// pixel. A viewport or scale that gives a negative or NaN size gives zero
608 /// on that axis.
609 pub fn surface_size(&self) -> (u32, u32) {
610 let width = (self.config.viewport.width * self.config.scale).round();
611 let height = (self.config.viewport.height * self.config.scale).round();
612 (width.max(0.0) as u32, height.max(0.0) as u32)
613 }
614
615 /// Runs one frame and returns what the renderer has to draw.
616 ///
617 /// The frame runs these steps in order:
618 ///
619 /// 1. The values other threads sent through the app's channels: each
620 /// handler runs inside the owner that created its channel, delivering
621 /// at most what the queue held when the drain reached it.
622 /// 2. The timers that are due on the app's clock, then the tick callback,
623 /// when one is set.
624 /// 3. Reactive effects and the updates they queue, until neither has work
625 /// left. A signal write therefore lands before layout, including one a
626 /// channel handler or a timer made at the start of the frame.
627 /// 4. The layout pass, which runs the layout engine only when a node needs
628 /// it.
629 /// 5. The paint pass, which regenerates the operations of the nodes that
630 /// changed and reports their old and new bounds as damage.
631 /// 6. The display list, rebuilt only when something may have changed.
632 /// 7. Damage conversion and merging, then the output.
633 ///
634 /// The output borrows the app until it is dropped, so read [`App::stats`]
635 /// either before the output is kept or after it is let go.
636 ///
637 /// # Panics
638 ///
639 /// Panics in debug builds when the update queue does not settle within a
640 /// fixed number of rounds, which means applying updates keeps waking a
641 /// binding: a mounted child or a widget's bind function writes a signal
642 /// that leads back to the same update. Release builds carry on with the
643 /// state they reached.
644 ///
645 /// Also panics in debug builds when one binding runs too many times in one
646 /// flush, which means it writes a signal that wakes it again, directly or
647 /// through another binding. A release build never returns from that frame.
648 pub fn frame(&mut self) -> FrameOutput<'_> {
649 // The frame runs every effect its channels, its timers, its tick and
650 // its updates wake before it lays out, so reporting those wakes would
651 // only ask the platform for a second frame with nothing to do.
652 let quiet = QuietWakes::enter(&self._root);
653 crate::channel::drain_all(&self.channels);
654 timer::fire_due(&self.timers);
655
656 let now = self.now();
657 if let Some(tick) = &mut self.tick {
658 tick(now);
659 }
660
661 let flush_start = Instant::now();
662 let mut rounds = 0u32;
663 let mut settled = false;
664 loop {
665 rounds += 1;
666 let flushed = reactive::flush();
667 let applied = self.queue.apply(&mut self.tree);
668 if !flushed && !applied {
669 settled = true;
670 break;
671 }
672 debug_assert!(
673 rounds < MAX_FLUSH_ROUNDS,
674 "the update queue did not settle in {MAX_FLUSH_ROUNDS} rounds, \
675 so applying updates keeps waking a binding in a cycle, for \
676 example through a mounted child or a widget's bind function"
677 );
678 if rounds >= MAX_FLUSH_ROUNDS {
679 break;
680 }
681 }
682 self.stats.flush = flush_start.elapsed();
683 self.stats.flush_rounds = rounds;
684 drop(quiet);
685 if !settled {
686 // The capped loop left work queued that no frame has run yet.
687 self._root.wake();
688 }
689
690 let layout_start = Instant::now();
691 self.stats.layout_ran = self.tree.layout(self.config.viewport);
692 self.stats.layout = layout_start.elapsed();
693
694 self.damage.clear();
695 let paint_start = Instant::now();
696 self.stats.nodes_painted = self.tree.paint(&mut self.damage);
697 self.stats.paint = paint_start.elapsed();
698
699 let full = self.force_full || self.config.repaint == RepaintMode::Full;
700 self.force_full = false;
701
702 let list_start = Instant::now();
703 if full || self.stats.nodes_painted > 0 || !self.damage.is_empty() {
704 self.tree.build_display_list(&mut self.list);
705 }
706 self.stats.list = list_start.elapsed();
707
708 if full {
709 self.damage.add_full();
710 }
711
712 let (width, height) = self.surface_size();
713 let damage_start = Instant::now();
714 let (damage, damage_pixels_before_merge) =
715 self.damage
716 .finish_measured(self.config.scale, width, height);
717 self.stats.damage = damage_start.elapsed();
718 self.stats.damage_rects = damage.len() as u32;
719 self.stats.damage_pixels = damage.iter().map(|rect| rect.area()).sum();
720 self.stats.damage_pixels_before_merge = damage_pixels_before_merge;
721
722 FrameOutput {
723 list: &self.list,
724 damage,
725 full,
726 clear_color: self.config.clear_color,
727 scale: self.config.scale,
728 }
729 }
730
731 /// Returns the timings and counts of the last frame.
732 pub fn stats(&self) -> &FrameStats {
733 &self.stats
734 }
735
736 /// Visits every visible node of the tree in paint order, for diagnostics.
737 ///
738 /// `visit` receives the node's depth, with the root at zero, the full name
739 /// of the widget's concrete type, and the node's absolute rectangle in
740 /// logical pixels as the last layout pass left it. A hidden node and its
741 /// subtree are skipped, because they are laid out as empty and paint
742 /// nothing. The tree itself stays private, so this is the only way code
743 /// outside this crate can describe the structure of a frame.
744 ///
745 /// # Examples
746 ///
747 /// ```
748 /// use smudge_core::app::{App, AppConfig, RepaintMode};
749 /// use smudge_core::geometry::{Color, Size};
750 /// use smudge_core::style::px;
751 /// use smudge_core::widget::panel;
752 ///
753 /// let mut app = App::new(
754 /// AppConfig {
755 /// viewport: Size {
756 /// width: 100.0,
757 /// height: 50.0,
758 /// },
759 /// scale: 1.0,
760 /// clear_color: Color::WHITE,
761 /// repaint: RepaintMode::Damage,
762 /// },
763 /// || panel().child(panel().width(px(10.0)).height(px(10.0))),
764 /// );
765 /// app.frame();
766 ///
767 /// let mut depths = Vec::new();
768 /// app.visit_nodes(|depth, _type_name, _rect| depths.push(depth));
769 /// assert_eq!(depths, [0, 1]);
770 /// ```
771 pub fn visit_nodes(&self, mut visit: impl FnMut(usize, &'static str, Rect)) {
772 self.tree.visit_nodes(&mut visit);
773 }
774
775 /// Projects every visible node that reports a role, in paint order.
776 ///
777 /// This is the tree an accessibility backend reads. A node is projected
778 /// when its element set a role or its widget reports one; the children
779 /// of a node without a role attach to the nearest projected ancestor.
780 /// A hidden node and its subtree are skipped, because they paint
781 /// nothing. The bounds are in logical pixels, as the last layout pass
782 /// left them.
783 pub fn semantics_nodes(&self) -> Vec<SemanticsNode> {
784 self.tree.semantics_nodes()
785 }
786
787 /// Returns the projected nodes that no projected ancestor holds, in paint
788 /// order.
789 ///
790 /// These are the nodes an accessibility backend attaches to the root it
791 /// adds for the window.
792 pub fn semantics_roots(&self) -> Vec<WidgetId> {
793 self.tree.semantics_roots()
794 }
795
796 /// Projects one node with the children the projection gives it.
797 ///
798 /// Returns [`None`] when the id is stale, when the node or an ancestor
799 /// is hidden, or when neither the element nor the widget reports a role
800 /// for it.
801 pub fn semantics_node(&self, id: WidgetId) -> Option<SemanticsNode> {
802 self.tree.semantics_node(id)
803 }
804
805 /// Returns whether the projection has unread changes.
806 ///
807 /// A backend that only rebuilds when this returns true skips frames that
808 /// changed nothing projected.
809 pub fn has_a11y_changes(&self) -> bool {
810 self.tree.has_a11y_changes()
811 }
812
813 /// Takes the projected nodes that changed since the last call.
814 ///
815 /// A change to a node the projection leaves out is reported as its
816 /// nearest projected ancestor, whose child list it changed, or as
817 /// [`A11yChanges::roots_changed`] when no ancestor projects. A repaint
818 /// of such a node, such as a caret's blink, changes nothing projected
819 /// and is not reported. Stale and
820 /// hidden ids drop out, and a frame that changed too much sets
821 /// [`A11yChanges::reset`] instead of listing every node, so a backend can
822 /// rebuild from scratch. Take these only when no frame is pending: a
823 /// node that changes again while it waits for a frame is not queued
824 /// twice, so changes taken before that frame miss what it does.
825 pub fn take_a11y_changes(&mut self) -> A11yChanges {
826 self.tree.take_a11y_changes()
827 }
828
829 /// Runs the press handler of `id`, as an accessibility action would.
830 ///
831 /// This is the same path a press of Enter or Space takes, so a disabled
832 /// or hidden node runs nothing and `false` is returned. `true` means a
833 /// handler ran.
834 pub fn press(&mut self, id: WidgetId) -> bool {
835 self.tree.run_press(id)
836 }
837
838 /// Moves the keyboard focus to `id`, as an accessibility action would.
839 ///
840 /// A node that is not focusable, or that cannot hold the focus because
841 /// it is disabled or hidden, is left alone and `false` is returned. A
842 /// node that already holds the focus keeps it and `true` is returned.
843 /// Unlike the autofocus request, an active focus trap does not confine
844 /// this one: the trap binds Tab movement and pointer presses, not an
845 /// explicit request that names its node.
846 pub fn request_focus(&mut self, id: WidgetId) -> bool {
847 self.tree.request_focus(id)
848 }
849}
850
851/// Keeps the wakes of one app's owner tree unreported while its frame runs
852/// them itself, and reports them again when dropped, even when the frame
853/// unwinds.
854struct QuietWakes(Rc<Scope>);
855
856impl QuietWakes {
857 /// Silences the wakes of the tree `root` belongs to.
858 fn enter(root: &Owner) -> QuietWakes {
859 let scope = Rc::clone(&root.data().scope);
860 scope.quiet.set(true);
861 QuietWakes(scope)
862 }
863}
864
865impl Drop for QuietWakes {
866 fn drop(&mut self) {
867 self.0.quiet.set(false);
868 }
869}