pub struct App { /* private fields */ }Expand description
One window’s widget tree, reactive runtime and frame pipeline.
The app owns everything the frame touches. App::frame is the only method
that mutates the tree, and it does so in one fixed order, so an effect never
observes a half-applied frame.
§Examples
use smudge_core::app::{App, AppConfig, RepaintMode};
use smudge_core::geometry::{Color, Size};
use smudge_core::style::px;
use smudge_core::widget::panel;
let mut app = App::new(
AppConfig {
viewport: Size {
width: 320.0,
height: 200.0,
},
scale: 1.0,
clear_color: Color::WHITE,
repaint: RepaintMode::Damage,
},
|| panel().width(px(120.0)).height(px(40.0)).background(Color::BLACK),
);
let full = app.frame().full;
assert!(full, "the first frame repaints the whole surface");
assert_eq!(app.stats().damage_rects, 1);
let empty = app.frame().damage.is_empty();
assert!(empty, "a frame with no changes damages nothing");Implementations§
Source§impl App
impl App
Sourcepub fn new<E: IntoElement>(config: AppConfig, build: impl FnOnce() -> E) -> App
pub fn new<E: IntoElement>(config: AppConfig, build: impl FnOnce() -> E) -> App
Creates an app and mounts the element that build returns.
Installs the reactive runtime on the calling thread, then runs build
and mounts its element inside an owner that lives as long as the app, so
every binding sees the context that build provides. The mounted tree
is laid out and painted by the first call to App::frame, which
repaints the whole surface.
Timers read the operating system’s clock. Use App::with_clock to
run them on another one.
Sourcepub fn with_clock<E: IntoElement>(
config: AppConfig,
clock: Clock,
build: impl FnOnce() -> E,
) -> App
pub fn with_clock<E: IntoElement>( config: AppConfig, clock: Clock, build: impl FnOnce() -> E, ) -> App
Creates an app like App::new whose timers and tick callback read
clock.
The clock is in place before build runs, so a timer that build
creates is scheduled on it too. A test passes a
VirtualClock and advances it by hand.
Sourcepub fn set_viewport(&mut self, viewport: Size, scale: f32)
pub fn set_viewport(&mut self, viewport: Size, scale: f32)
Changes the viewport and the scale, and makes the next frame repaint the whole surface.
The viewport is in logical pixels and the scale is the number of physical pixels per logical pixel. A viewport that differs from the last one also runs the layout pass again.
Sourcepub fn set_repaint_mode(&mut self, mode: RepaintMode)
pub fn set_repaint_mode(&mut self, mode: RepaintMode)
Changes how much of the surface each frame repaints, and makes the next frame repaint the whole surface.
Sourcepub fn set_theme(&mut self, theme: Theme)
pub fn set_theme(&mut self, theme: Theme)
Replaces the theme that style values which name a token resolve
against, and marks for repainting every node whose last paint looked
up a token, whether through its style or inside its widget’s own
paint.
The tree starts on Theme::light. Nodes whose paint resolves no
value from the theme paint the same pixels under any theme and are not
repainted, so a switch damages only what the switch can change. An
equal theme marks nothing.
use smudge_core::app::{App, AppConfig, RepaintMode};
use smudge_core::geometry::{Color, Size};
use smudge_core::style::px;
use smudge_core::theme::{ColorToken, Theme};
use smudge_core::widget::panel;
let mut app = App::new(
AppConfig {
viewport: Size {
width: 120.0,
height: 80.0,
},
scale: 1.0,
clear_color: Color::WHITE,
repaint: RepaintMode::Damage,
},
|| panel().width(px(40.0)).height(px(20.0)).background(ColorToken::SURFACE),
);
app.frame();
app.set_theme(Theme::dark());
app.frame();
assert_eq!(app.stats().nodes_painted, 1);Sourcepub fn bind_theme(&mut self, get: impl Fn() -> Theme + 'static)
pub fn bind_theme(&mut self, get: impl Fn() -> Theme + 'static)
Installs one effect that keeps the theme current.
The closure is read when it is installed and again whenever a signal it
reads changes; each run hands the theme it returns to
App::set_theme through the update queue, so the change lands inside
a frame like any other reactive update. Pass a closure that builds the
theme from a signal to switch the theme reactively:
use smudge_core::app::{App, AppConfig, RepaintMode};
use smudge_core::geometry::{Color, Size};
use smudge_core::reactive::{prelude::*, signal_local};
use smudge_core::style::px;
use smudge_core::theme::{ColorToken, Theme};
use smudge_core::widget::panel;
let (dark, set_dark) = signal_local(false);
let mut app = App::new(
AppConfig {
viewport: Size {
width: 120.0,
height: 80.0,
},
scale: 1.0,
clear_color: Color::WHITE,
repaint: RepaintMode::Damage,
},
|| panel().width(px(40.0)).height(px(20.0)).background(ColorToken::SURFACE),
);
app.bind_theme(move || if dark.get() { Theme::dark() } else { Theme::light() });
app.frame();
set_dark.set(true);
app.frame();
assert_eq!(app.stats().nodes_painted, 1);The binding runs under the app’s root owner, so it reports its wakes
to the callback set with App::set_wake_callback and stops when the
app is dropped.
Sourcepub fn set_clipboard(&mut self, clipboard: Box<dyn Clipboard>)
pub fn set_clipboard(&mut self, clipboard: Box<dyn Clipboard>)
Installs the clipboard the app’s widgets copy and paste through.
Until one is installed, a clipboard read or write finds nothing, so
cut, copy and paste keys do nothing. The windowing layer installs one
that reaches the system clipboard; a test installs a
MemoryClipboard and keeps a
clone to seed or read the text. Installing again replaces the
previous clipboard.
Sourcepub fn set_wake_callback(&self, callback: impl Fn() + 'static)
pub fn set_wake_callback(&self, callback: impl Fn() + 'static)
Sets the callback that a wake in this app’s tree runs to request a redraw.
A write that queues this app’s first effect since the last flush runs
the callback once, so the platform learns that the next frame has work
to do. Every binding of this app reports its wake here, and an app on
another part of the thread neither sees this app’s wakes nor hides
them. A write made while App::frame runs its timers, its tick and
its updates runs no callback, because the frame runs the effects it
wakes before it lays out; a write made later in the frame, while a
widget paints, does. See
set_wake_callback for the
exact rules.
Sourcepub fn set_remote_waker(&self, waker: impl Fn() + Send + Sync + 'static)
pub fn set_remote_waker(&self, waker: impl Fn() + Send + Sync + 'static)
Sets the callback that a message from another thread runs to ask for the frame that applies it.
The platform installs one that posts a wake event to the event loop,
so a thread that sends does not have to poll and the window does not
wake for anything else. A headless app needs none: its next
App::frame call finds the values on its own. Channels created
before this call get the waker too, and later ones start with it.
Sourcepub fn release_press(&mut self)
pub fn release_press(&mut self)
Ends the held press as if it had been cancelled, without running the
pressed node’s on_press.
The windowing layer calls this when the window system takes over the
press, as it does for an interactive move or resize: the release of
that press never reaches the app, so without this the press would stay
captured and hover would stop following the pointer. The pressed node
loses its pressed state and hears on_pointer_up, so a drag it started
ends. A window system may still refuse the gesture after the call that
starts it succeeded, and then the release arrives after all: a primary
release that comes with no press held, before the next primary press,
reaches no handler. With no press held this does nothing.
Sourcepub fn take_window_commands(&mut self) -> Vec<WindowCommand>
pub fn take_window_commands(&mut self) -> Vec<WindowCommand>
Takes every window command queued so far, in the order the handlers queued them.
The windowing layer calls this after it hands an input event over, so a command starts its move or resize while the press that queued it is still being handled. A headless application has no windowing layer and no need to take the commands, so they stay queued for a test to inspect. Taking them empties the queue.
Sourcepub fn set_window_state(&mut self, state: WindowState)
pub fn set_window_state(&mut self, state: WindowState)
Replaces the window state the application’s
maximized reads.
The windowing layer calls this when the window’s maximized state may have changed. An equal report changes nothing, so a binding that reads the state reruns only on a real change.
Sourcepub fn cursor(&self) -> Cursor
pub fn cursor(&self) -> Cursor
Returns the cursor the pointer should show.
This is the cursor of the deepest enabled node under the pointer that
set one with the cursor builder,
or Cursor::Default when no node
under the pointer sets one or the pointer left the window. While a
press is held, the walk starts at the pressed node instead. The
windowing layer reads it after every input event and every frame and
shows it on the window.
Sourcepub fn set_tick(&mut self, tick: impl FnMut(Instant) + 'static)
pub fn set_tick(&mut self, tick: impl FnMut(Instant) + 'static)
Sets a callback that runs once at the start of every frame with the current time.
The time comes from the app’s Clock, the same one its timers
read, so a test that runs the app on a virtual clock drives both.
Sourcepub fn next_deadline(&self) -> Option<Instant>
pub fn next_deadline(&self) -> Option<Instant>
Returns the earliest time a timer fires at, or None when none is
scheduled.
A platform that has no tick callback waits until this deadline, so a pending timer wakes the window when it comes due instead of the window polling.
Sourcepub fn run_due_timers(&mut self)
pub fn run_due_timers(&mut self)
Runs the timers that are due on the app’s clock, outside a frame.
A timer that writes a signal wakes the app through the callback set
with App::set_wake_callback, like any write between frames, and a
timer that changes nothing asks for nothing. A platform calls this
when App::next_deadline passes, so a due timer costs a frame only
when it has something to show. App::frame runs due timers itself
as well, so a caller that draws every frame needs no call.
Sourcepub fn needs_frame(&self) -> bool
pub fn needs_frame(&self) -> bool
Returns whether an event left work that only a frame can finish.
An input event can change how the tree looks without writing a signal,
for example by hovering a node with a hover style, so the reactive wake
callback never hears of it. A message from another thread also leaves
such work: the values its channel queues are the frame’s to deliver.
A platform calls this after handing events over and asks for a frame
when it is true. Most pointer moves change
nothing, and asking for a frame every time would present the window
for nothing.
Sourcepub fn handle_event(&mut self, event: InputEvent)
pub fn handle_event(&mut self, event: InputEvent)
Handles one input event between frames.
The pointer events are routed to the tree: a move moves the hovered
chain, a primary press captures the node under the pointer and focuses
it when it can take the focus, and a release runs the captured node’s
on_press when it happens over it. A press elsewhere clears the focus,
except while a focus trap is mounted and the press lands outside it,
and any press turns the keyboard navigation mode off. A wheel scroll
goes to the deepest node in the hovered chain that has a wheel handler.
Key events are routed to the focused node and bubble to its ancestors
until a handler takes them, starting at the root when nothing anchors
the key and at the active focus trap when the key would otherwise
start outside it, and an unhandled Tab or Shift+Tab moves the focus, staying
inside the active focus trap when one is mounted. Handlers run at
once, inside their node’s owner, and a handler that writes a signal
makes the change visible with the next call to App::frame. The app
also records the pointer position from pointer events and the keyboard
modifiers from key and modifiers events.
Sourcepub fn pointer_position(&self) -> Point
pub fn pointer_position(&self) -> Point
Returns the pointer position the last pointer event reported, in logical pixels.
The position starts at the origin and moves only when a pointer event carries a position, so it lags the real pointer until the first event arrives.
Sourcepub fn modifiers(&self) -> Modifiers
pub fn modifiers(&self) -> Modifiers
Returns the keyboard modifiers the last key or modifiers event reported.
They start at none and change only when one of those events arrives.
Sourcepub fn focused(&self) -> Option<WidgetId>
pub fn focused(&self) -> Option<WidgetId>
Returns the node that holds the keyboard focus, when one does.
A press on a focusable node focuses it, a press elsewhere clears the focus, and Tab moves it. While a focus trap is mounted, a press outside the trap leaves the focus where it is, and Tab moves it within the trap only. Removing the focused node clears the focus, and removing or hiding a trap returns the focus to the node that held it when the trap mounted or last showed.
Returns whether the keyboard navigation mode is on.
A Tab or an arrow key turns the mode on and a pointer press turns it off, so a focus ring can draw for keyboard users alone. A node carries the keyboard focus bit only while it holds the focus and this mode is on.
Sourcepub fn ime_area(&self) -> Option<Rect>
pub fn ime_area(&self) -> Option<Rect>
Returns the area the focused widget reports to the input method, in window coordinates, when it reports one.
This is None while no widget reports an area, which is the default,
while nothing is focused, and while the focused node sits outside an
active focus trap. A window presents the area to the
operating system so the input method can anchor its candidate window to
it.
Sourcepub fn surface_size(&self) -> (u32, u32)
pub fn surface_size(&self) -> (u32, u32)
Returns the surface size in physical pixels.
This is the viewport multiplied by the scale and rounded to the nearest pixel. A viewport or scale that gives a negative or NaN size gives zero on that axis.
Sourcepub fn frame(&mut self) -> FrameOutput<'_>
pub fn frame(&mut self) -> FrameOutput<'_>
Runs one frame and returns what the renderer has to draw.
The frame runs these steps in order:
- The values other threads sent through the app’s channels: each handler runs inside the owner that created its channel, delivering at most what the queue held when the drain reached it.
- The timers that are due on the app’s clock, then the tick callback, when one is set.
- Reactive effects and the updates they queue, until neither has work left. A signal write therefore lands before layout, including one a channel handler or a timer made at the start of the frame.
- The layout pass, which runs the layout engine only when a node needs it.
- The paint pass, which regenerates the operations of the nodes that changed and reports their old and new bounds as damage.
- The display list, rebuilt only when something may have changed.
- Damage conversion and merging, then the output.
The output borrows the app until it is dropped, so read App::stats
either before the output is kept or after it is let go.
§Panics
Panics in debug builds when the update queue does not settle within a fixed number of rounds, which means applying updates keeps waking a binding: a mounted child or a widget’s bind function writes a signal that leads back to the same update. Release builds carry on with the state they reached.
Also panics in debug builds when one binding runs too many times in one flush, which means it writes a signal that wakes it again, directly or through another binding. A release build never returns from that frame.
Sourcepub fn stats(&self) -> &FrameStats
pub fn stats(&self) -> &FrameStats
Returns the timings and counts of the last frame.
Sourcepub fn visit_nodes(&self, visit: impl FnMut(usize, &'static str, Rect))
pub fn visit_nodes(&self, visit: impl FnMut(usize, &'static str, Rect))
Visits every visible node of the tree in paint order, for diagnostics.
visit receives the node’s depth, with the root at zero, the full name
of the widget’s concrete type, and the node’s absolute rectangle in
logical pixels as the last layout pass left it. A hidden node and its
subtree are skipped, because they are laid out as empty and paint
nothing. The tree itself stays private, so this is the only way code
outside this crate can describe the structure of a frame.
§Examples
use smudge_core::app::{App, AppConfig, RepaintMode};
use smudge_core::geometry::{Color, Size};
use smudge_core::style::px;
use smudge_core::widget::panel;
let mut app = App::new(
AppConfig {
viewport: Size {
width: 100.0,
height: 50.0,
},
scale: 1.0,
clear_color: Color::WHITE,
repaint: RepaintMode::Damage,
},
|| panel().child(panel().width(px(10.0)).height(px(10.0))),
);
app.frame();
let mut depths = Vec::new();
app.visit_nodes(|depth, _type_name, _rect| depths.push(depth));
assert_eq!(depths, [0, 1]);Sourcepub fn semantics_nodes(&self) -> Vec<SemanticsNode>
pub fn semantics_nodes(&self) -> Vec<SemanticsNode>
Projects every visible node that reports a role, in paint order.
This is the tree an accessibility backend reads. A node is projected when its element set a role or its widget reports one; the children of a node without a role attach to the nearest projected ancestor. A hidden node and its subtree are skipped, because they paint nothing. The bounds are in logical pixels, as the last layout pass left them.
Sourcepub fn semantics_roots(&self) -> Vec<WidgetId>
pub fn semantics_roots(&self) -> Vec<WidgetId>
Returns the projected nodes that no projected ancestor holds, in paint order.
These are the nodes an accessibility backend attaches to the root it adds for the window.
Sourcepub fn semantics_node(&self, id: WidgetId) -> Option<SemanticsNode>
pub fn semantics_node(&self, id: WidgetId) -> Option<SemanticsNode>
Projects one node with the children the projection gives it.
Returns None when the id is stale, when the node or an ancestor
is hidden, or when neither the element nor the widget reports a role
for it.
Sourcepub fn has_a11y_changes(&self) -> bool
pub fn has_a11y_changes(&self) -> bool
Returns whether the projection has unread changes.
A backend that only rebuilds when this returns true skips frames that changed nothing projected.
Sourcepub fn take_a11y_changes(&mut self) -> A11yChanges
pub fn take_a11y_changes(&mut self) -> A11yChanges
Takes the projected nodes that changed since the last call.
A change to a node the projection leaves out is reported as its
nearest projected ancestor, whose child list it changed, or as
A11yChanges::roots_changed when no ancestor projects. A repaint
of such a node, such as a caret’s blink, changes nothing projected
and is not reported. Stale and
hidden ids drop out, and a frame that changed too much sets
A11yChanges::reset instead of listing every node, so a backend can
rebuild from scratch. Take these only when no frame is pending: a
node that changes again while it waits for a frame is not queued
twice, so changes taken before that frame miss what it does.
Sourcepub fn press(&mut self, id: WidgetId) -> bool
pub fn press(&mut self, id: WidgetId) -> bool
Runs the press handler of id, as an accessibility action would.
This is the same path a press of Enter or Space takes, so a disabled
or hidden node runs nothing and false is returned. true means a
handler ran.
Sourcepub fn request_focus(&mut self, id: WidgetId) -> bool
pub fn request_focus(&mut self, id: WidgetId) -> bool
Moves the keyboard focus to id, as an accessibility action would.
A node that is not focusable, or that cannot hold the focus because
it is disabled or hidden, is left alone and false is returned. A
node that already holds the focus keeps it and true is returned.
Unlike the autofocus request, an active focus trap does not confine
this one: the trap binds Tab movement and pointer presses, not an
explicit request that names its node.