Skip to main content

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}