Skip to main content

App

Struct App 

Source
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

Source

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.

Source

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.

Source

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.

Source

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.

Source

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);
Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

pub fn now(&self) -> Instant

Returns the current time of the app’s clock.

Source

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.

Source

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.

Source

pub fn has_tick(&self) -> bool

Returns whether a frame tick callback is set.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

pub fn keyboard_navigation(&self) -> bool

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.

Source

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.

Source

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.

Source

pub fn frame(&mut self) -> FrameOutput<'_>

Runs one frame and returns what the renderer has to draw.

The frame runs these steps in order:

  1. 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.
  2. The timers that are due on the app’s clock, then the tick callback, when one is set.
  3. 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.
  4. The layout pass, which runs the layout engine only when a node needs it.
  5. The paint pass, which regenerates the operations of the nodes that changed and reports their old and new bounds as damage.
  6. The display list, rebuilt only when something may have changed.
  7. 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.

Source

pub fn stats(&self) -> &FrameStats

Returns the timings and counts of the last frame.

Source

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]);
Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Auto Trait Implementations§

§

impl !Freeze for App

§

impl !RefUnwindSafe for App

§

impl !Send for App

§

impl !Sync for App

§

impl !UnwindSafe for App

§

impl Forget for App

§

impl Move for App

§

impl Unpin for App

§

impl UnsafeUnpin for App

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.