State
Cx
The context every build closure, event handler and task completion receives, with every method grouped by purpose.
Cxcomponent(|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))
})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_clipboardandcx.start_drag, - listeners such as
cx.on_close_requestedandcx.on_app_windows, Kind::mountand 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.
Creates a reactive value in the current scope. See signal.
Creates a derived value that stops propagation when unchanged. See memo.
Runs a side effect now and after every change to what it read. See effect.
Creates untracked storage in the current scope. See store.
Shares a value with every scope below the current one. See provide.
Finds the nearest provided value of type T.
Lends two signals at once and subscribes the running observer to both. Panics when either was disposed.
Runs a closure whose reads subscribe nothing.
Runs a closure when the current scope is disposed.
Stops one effect before its scope ends.
Scopes and diagnostics
Also from Runtime. App code rarely calls these. Components and tests do.
The scope new signals, effects and tasks are created in.
The scope that lives as long as the runtime. It is never disposed.
Creates a child of the current scope.
Creates a child of the given scope.
Makes a scope current and returns the previous one. Restore it with a second call.
Runs a closure with a scope current, then restores the previous one.
Frees a scope, its children and everything created in them, and runs their cleanups. Disposing the root scope does nothing.
Whether a scope has not been disposed.
The parent of a scope.
Makes a scope receive panics from observers inside it. error_boundary(..) is built on this.
Returns the observer panics no error boundary handled since the last call.
Runs dirty observers until everything is consistent. The view calls it for you after each event.
Whether any observer waits to run.
Number of live signals, memos and stores. Useful in leak tests.
Number of live effects and memos.
Number of live scopes, including the root.
Async work and timing
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.
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.
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.
The window's light or dark scheme. It drives .dark(..) variants and is also provided in the root scope's environment.
Device pixels per logical pixel, so pixel art can be made at the size it shows.
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.
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.
Follows the node's laid-out size, updated after every layout pass.
Follows where the node is laid out in the window.
Follows whether the node is disabled by its .disabled(..) modifier.
Focus and synthetic input
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.
Fires a DOM input event on the node after the current handler. Text fields use it.
Fires a DOM change event on the node after the current handler.
Fires a DOM submit event on the node after the current handler.
Clipboard
Puts text on the system clipboard.
Requests the clipboard's text. It arrives as a paste into the focused element.
Offers the same content in several MIME types. An item in a text type is also offered under the other text types.
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
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
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.
Minimizes the window.
Maximizes the window, or restores it when it already is.
Asks to close the window the way its close button does. on_close_requested listeners can still keep it open.
Closes the window without asking the on_close_requested listeners.
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.
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.
Changes when the layer surface takes keyboard focus, for example KeyboardInteractivity::OnDemand only while a text field is open. Applied with the next frame.
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.
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.
Stops showing the icon of an app.
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].
Calls the listener with the app id when the compositor asks to start an app whose icon was activated, until the current scope ends.
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.
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
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.
Frees an image once nothing on screen draws it. Drawing it afterwards draws nothing.
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
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.
Mounts a view as a child of nothing. The caller attaches the returned root.
Mounts views and makes them the node's children, replacing previous ones.
Makes already-built nodes the node's children.
Sets the ARIA role the kind implies.
Applies a default style that the element's own modifiers override.
Binds a default style property to signals.
Adds a listener on behalf of the kind, such as a toggle flipping its signal on click.
Makes the node display text, recomputed when the signals it reads change. Sets the role to text.
Makes the node draw with a painter. The painter re-runs when the node's size or a signal it read changes.
Makes the node draw a registered image at its natural size unless styled otherwise. Sets the role to image.
Sets the checked state reported to assistive technology.
Binds the checked state to signals.
Binds the accessible value, such as a text field's contents, to signals.
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.