State

Cx

The context every build closure, event handler and task completion receives, with every method grouped by purpose.

FNCx
Where a Cx shows up
component(|cx| {
    let count = cx.signal(0);
    cx.spawn(load_count(), move |cx, saved| count.set(cx, saved));
    cx.on_close_requested(move |cx, event| {
        if count.get(cx) > 0 {
            event.prevent_default();
        }
    });
    button("+1").on_click(move |cx, _| count.update(cx, |count| *count += 1))
})
Signature
pub struct Cx { /* private */ }
 
impl Deref for Cx {
    type Target = Runtime;
}
 
impl DerefMut for Cx {}

Cx owns the window's reactive runtime and its element tree. It derefs to Runtime, so cx.signal(..), count.get(cx) and count.set(cx, ..) all work on it directly. Every other method talks to the view or to the platform.

You receive &mut Cx in these places:

  • the closure of component(|cx| ..), once, while the view is built,
  • every event handler, such as .on_click(|cx, event| ..),
  • the completion of cx.spawn, cx.request_animation_frame, cx.read_clipboard and cx.start_drag,
  • listeners such as cx.on_close_requested and cx.on_app_windows,
  • Kind::mount and app tool handlers.

bind, memo, effect and canvas painters receive &mut Runtime instead. They read and write signals but cannot call the methods on this page that are not part of the runtime.

Scope rules

Many methods tie what they create to the current scope: signals, effects, tasks, animation frames, close listeners, app icon listeners and app tools. When that scope is disposed, they go away with it.

In a component body the current scope is the component's own. In an event handler it is the window's root scope, because handlers do not switch scopes. So a task spawned from on_click is not cancelled when the component unmounts. Write its result with try_set, or spawn from the body.

Reactive state

These come from Runtime through Deref. Each has its own page.

signalfn<T: 'static>(&mut self, T) -> Signal<T>

Creates a reactive value in the current scope. See signal.

memofn<T: PartialEq + 'static>(&mut self, impl FnMut(&mut Runtime) -> T + 'static) -> Memo<T>

Creates a derived value that stops propagation when unchanged. See memo.

effectfn(&mut self, impl FnMut(&mut Runtime) + 'static) -> EffectId

Runs a side effect now and after every change to what it read. See effect.

storefn<T: 'static>(&mut self, T) -> Store<T>

Creates untracked storage in the current scope. See store.

providefn<T: 'static>(&mut self, T) -> Signal<T>

Shares a value with every scope below the current one. See provide.

environmentfn<T: 'static>(&self) -> Option<Signal<T>>

Finds the nearest provided value of type T.

with2fn<A: 'static, B: 'static, R>(&mut self, Signal<A>, Signal<B>, impl FnOnce(&A, &B) -> R) -> R

Lends two signals at once and subscribes the running observer to both. Panics when either was disposed.

untrackfn<R>(&mut self, impl FnOnce(&mut Runtime) -> R) -> R

Runs a closure whose reads subscribe nothing.

on_cleanupfn(&mut self, impl FnOnce(&mut Runtime) + 'static)

Runs a closure when the current scope is disposed.

dispose_effectfn(&mut self, EffectId)

Stops one effect before its scope ends.

Scopes and diagnostics

Also from Runtime. App code rarely calls these. Components and tests do.

current_scopefn(&self) -> ScopeId

The scope new signals, effects and tasks are created in.

root_scopefn(&self) -> ScopeId

The scope that lives as long as the runtime. It is never disposed.

create_scopefn(&mut self) -> ScopeId

Creates a child of the current scope.

create_child_scopefn(&mut self, ScopeId) -> ScopeId

Creates a child of the given scope.

set_current_scopefn(&mut self, ScopeId) -> ScopeId

Makes a scope current and returns the previous one. Restore it with a second call.

with_scopefn<R>(&mut self, ScopeId, impl FnOnce(&mut Runtime) -> R) -> R

Runs a closure with a scope current, then restores the previous one.

dispose_scopefn(&mut self, ScopeId)

Frees a scope, its children and everything created in them, and runs their cleanups. Disposing the root scope does nothing.

is_scope_alivefn(&self, ScopeId) -> bool

Whether a scope has not been disposed.

parent_scopefn(&self, ScopeId) -> Option<ScopeId>

The parent of a scope.

set_error_boundaryfn(&mut self, ScopeId, impl FnMut(&mut Runtime, &ObserverPanic) + 'static)

Makes a scope receive panics from observers inside it. error_boundary(..) is built on this.

take_panicsfn(&mut self) -> Vec<ObserverPanic>

Returns the observer panics no error boundary handled since the last call.

flushfn(&mut self) -> Result<FlushStats, FlushError>

Runs dirty observers until everything is consistent. The view calls it for you after each event.

has_pendingfn(&self) -> bool

Whether any observer waits to run.

signal_countfn(&self) -> usize

Number of live signals, memos and stores. Useful in leak tests.

observer_countfn(&self) -> usize

Number of live effects and memos.

scope_countfn(&self) -> usize

Number of live scopes, including the root.

Async work and timing

spawnfn<T: Send + 'static>(&mut self, impl Future<Output = T> + Send + 'static, impl FnOnce(&mut Cx, T) + 'static) -> TaskId

Runs a future off the UI thread and calls the second closure with its output on the UI thread. The task is cancelled when the current scope is disposed first. See Async.

request_animation_framefn(&mut self, impl FnOnce(&mut Cx, Duration) + 'static)

Calls the closure with the frame time just before the next frame is laid out, like the DOM's requestAnimationFrame. Ask again from inside it to run every frame. Dropped when the current scope ends first. Use it for simulations and values that drive layout. Use .transition(..) for everything else.

with_animationfn(&mut self, impl Into<Curve>, impl FnOnce(&mut Cx))

Runs the closure so every property change it causes animates with the curve, like SwiftUI's withAnimation.

button("Expand").on_click(move |cx, _| {
    cx.with_animation(Spring::snappy(), |cx| expanded.set(cx, true))
})

Window and display signals

Each returns a signal the view keeps up to date. Read it inside bind to react to changes.

color_schemefn(&self) -> Signal<ColorScheme>

The window's light or dark scheme. It drives .dark(..) variants and is also provided in the root scope's environment.

scale_factorfn(&self) -> Signal<f32>

Device pixels per logical pixel, so pixel art can be made at the size it shows.

output_sizefn(&self) -> Signal<Option<Size>>

Logical size of the output the window is on, once the platform reports it. Layer surfaces use it to place themselves against the whole screen.

platform_capabilitiesfn(&self) -> Signal<Capabilities>

What the platform supports, such as materials, popups or text_input, so a component picks a fallback instead of relying on something absent.

let scheme = cx.color_scheme();
text(bind(move |rt| match scheme.get(rt) {
    ColorScheme::Dark => "Dark".to_owned(),
    ColorScheme::Light => "Light".to_owned(),
}))

Observing nodes

These take a NodeId, which you get inside Kind::mount or from cx.build(..). Calling one twice for the same node returns the same signal.

observe_sizefn(&mut self, NodeId) -> Signal<Size>

Follows the node's laid-out size, updated after every layout pass.

observe_rectfn(&mut self, NodeId) -> Signal<Rect>

Follows where the node is laid out in the window.

observe_disabledfn(&mut self, NodeId) -> Signal<bool>

Follows whether the node is disabled by its .disabled(..) modifier.

Focus and synthetic input

request_focusfn(&mut self, NodeId)

Moves keyboard focus to the node after the current event, or by the end of the frame when no event is being handled, as when a component mounts.

emit_inputfn(&mut self, NodeId, impl Into<String>)

Fires a DOM input event on the node after the current handler. Text fields use it.

emit_changefn(&mut self, NodeId, impl Into<String>)

Fires a DOM change event on the node after the current handler.

emit_submitfn(&mut self, NodeId, impl Into<String>)

Fires a DOM submit event on the node after the current handler.

Clipboard

copy_to_clipboardfn(&mut self, impl Into<String>)

Puts text on the system clipboard.

paste_from_clipboardfn(&mut self)

Requests the clipboard's text. It arrives as a paste into the focused element.

set_clipboardfn(&mut self, Vec<(String, Vec<u8>)>)

Offers the same content in several MIME types. An item in a text type is also offered under the other text types.

read_clipboardfn(&mut self, &[&str], impl FnOnce(&mut Cx, Option<ClipboardData>) + 'static)

Reads the clipboard in the first of the MIME types it holds, best first. The closure gets None when it holds none of them.

button("Paste image").on_click(move |cx, _| {
    cx.read_clipboard(&["image/png", "text/uri-list"], move |cx, data| {
        if let Some(data) = data {
            pasted_type.set(cx, data.mime_type);
        }
    })
})

See Clipboard and drag.

Drag out of the window

start_dragfn(&mut self, DragSource, impl FnOnce(&mut Cx, DragOutcome) + 'static) -> bool

Hands a drag source to the platform while the pointer button is held. Call it from a dragstart or pointer-down handler. The closure learns whether a target took the drag. Returns false, dropping both, while an earlier drag is still in flight.

Window control

begin_window_movefn(&mut self)

Lets the user drag the window by the element being pressed. Call it from a pointer-down handler, as a client-drawn title bar does.

minimize_windowfn(&mut self)

Minimizes the window.

toggle_maximize_windowfn(&mut self)

Maximizes the window, or restores it when it already is.

request_closefn(&mut self)

Asks to close the window the way its close button does. on_close_requested listeners can still keep it open.

close_windowfn(&mut self)

Closes the window without asking the on_close_requested listeners.

on_close_requestedfn(&mut self, impl FnMut(&mut Cx, &mut Event<()>) + 'static)

Calls the listener when the user or the compositor asks to close the window, until the current scope ends. event.prevent_default() keeps the window open. Without listeners the window closes right away.

set_idle_inhibitedfn(&mut self, bool)

Keeps the system from idling, such as screen blanking or sleep, while the window is visible. It is released with the window, so a crash cannot keep the machine awake.

cx.on_close_requested(move |cx, event| {
    if unsaved.get(cx) {
        event.prevent_default();
        confirm_open.set(cx, true);
    }
});
 
button("Discard and close").on_click(|cx, _| cx.close_window())

Layer surfaces

Panels, docks and overlays run as layer surfaces. Toplevel windows ignore these two methods. See Windows.

set_keyboard_interactivityfn(&mut self, KeyboardInteractivity)

Changes when the layer surface takes keyboard focus, for example KeyboardInteractivity::OnDemand only while a text field is open. Applied with the next frame.

set_layer_heightfn(&mut self, LayerExtent)

Resizes the layer surface along its height, for example to give a panel room only while it is open. Every pixel of surface costs GPU memory whether or not anything is drawn there. The new size arrives as a resize.

App icons

A dock or launcher tells the compositor where it draws each app's icon, so windows open out of it and minimize into it.

set_app_iconfn(&mut self, &str, Rect, f32)

Shows the icon of app_id at a rect with a corner radius, where it rests when the window is fully shown. Calling it again moves the icon. An unchanged placement sends nothing.

remove_app_iconfn(&mut self, &str)

Stops showing the icon of an app.

activate_app_iconfn(&mut self, &str) -> bool

Hands a click on an icon to the compositor, which restores and focuses the app's windows or asks on_launch_app listeners to start it. Returns false when the platform has no app icons or the window does not show this one, so the caller starts the app itself. The result is #[must_use].

on_launch_appfn(&mut self, impl FnMut(&mut Cx, &str) + 'static)

Calls the listener with the app id when the compositor asks to start an app whose icon was activated, until the current scope ends.

on_app_windowsfn(&mut self, impl FnMut(&mut Cx, &AppWindows) + 'static)

Calls the listener whenever an app's windows change, starting with every app already running. AppWindows has app_id, count and focused. A count of 0 means the app has no window left.

on_app_icon_liftedfn(&mut self, impl FnMut(&mut Cx, &str, bool) + 'static)

Calls the listener with true when the compositor starts drawing an app's icon as part of a window's motion, and false when it stops. In between, the icon keeps its place but should not be drawn.

Images

register_imagefn(&mut self, u32, u32, Vec<u8>) -> ImageId

Queues straight-alpha RGBA8 pixels of the given width and height for the renderer and returns the id to draw them by. The byte length must be width * height * 4, or the upload is rejected with CUI0401.

release_imagefn(&mut self, ImageId)

Frees an image once nothing on screen draws it. Drawing it afterwards draws nothing.

release_image_on_cleanupfn(&mut self, ImageId)

Releases the image when the current scope ends, so an image lives exactly as long as the component that registered it. image(..) does this for you.

Agents

register_toolfn(&mut self, AppTool)

Exposes an action to AI agents over MCP while the current component stays mounted. AppTool::new(name, description, input_schema, handler) takes a handler of type FnMut(&mut Cx, &str) -> Result<String, String>, with JSON text in and out. Call .destructive() to make agents confirm first.

use crownui::ext::AppTool;
 
cx.register_tool(AppTool::new(
    "clear_todos",
    "Removes every finished todo",
    r#"{"type":"object","properties":{}}"#,
    move |cx, _arguments| {
        todos.update(cx, |list| list.retain(|todo| !todo.done));
        Ok("{}".to_owned())
    },
).destructive());

See Agents.

Building nodes

These are for Kind implementations, the low-level way to write a widget. App code builds views with functions such as vstack and text instead. See Custom components.

buildfn(&mut self, impl View) -> NodeId

Mounts a view as a child of nothing. The caller attaches the returned root.

set_childrenfn(&mut self, NodeId, impl ViewSeq)

Mounts views and makes them the node's children, replacing previous ones.

adopt_childrenfn(&mut self, NodeId, Vec<NodeId>)

Makes already-built nodes the node's children.

set_rolefn(&mut self, NodeId, Role)

Sets the ARIA role the kind implies.

set_default_stylefn(&mut self, NodeId, StyleProp)

Applies a default style that the element's own modifiers override.

bind_stylefn(&mut self, NodeId, impl FnMut(&mut Runtime) -> StyleProp + 'static)

Binds a default style property to signals.

add_listenerfn(&mut self, NodeId, Listener)

Adds a listener on behalf of the kind, such as a toggle flipping its signal on click.

set_textfn(&mut self, NodeId, impl IntoProp<String>)

Makes the node display text, recomputed when the signals it reads change. Sets the role to text.

set_canvasfn(&mut self, NodeId, impl FnMut(&mut Runtime, Size, &mut DrawList) + 'static)

Makes the node draw with a painter. The painter re-runs when the node's size or a signal it read changes.

set_imagefn(&mut self, NodeId, ImageId, Size)

Makes the node draw a registered image at its natural size unless styled otherwise. Sets the role to image.

set_checkedfn(&mut self, NodeId, Option<bool>)

Sets the checked state reported to assistive technology.

bind_checkedfn(&mut self, NodeId, impl FnMut(&mut Runtime) -> bool + 'static)

Binds the checked state to signals.

bind_valuefn(&mut self, NodeId, impl FnMut(&mut Runtime) -> String + 'static)

Binds the accessible value, such as a text field's contents, to signals.

treefn(&self) -> &ElementTree

Read-only access to the element tree.

Why one context

CrownUI has no globals and no interior mutability. Everything a handler can touch, from signals to the clipboard queue, lives in one value the UI thread owns, and you get it as &mut Cx. The borrow checker can then prove that nothing else is reading the tree while you change it, without a lock.

Most platform methods do not act right away. copy_to_clipboard, request_focus, minimize_window and the rest record a request, and the runtime carries it out after the handler returns. That keeps a handler free of I/O and lets tests run the same code against a headless platform.

The cost is that Cx is a wide type. It mixes reactive state, view building and platform requests in one place. Closures that only need state get &mut Runtime instead, which is why bind and effect cannot open a clipboard or move focus.