Build a counter with state
Store application state in a model, handle a click, and invalidate only the presentation that changed.
State lives in the model
The model is a normal Rust struct. Render receives &mut self and a Context tied to this presentation. There is no hidden global state or hook ordering.
#[derive(Default)]
struct Counter {
count: u32,
}
impl Render for Counter {
fn render(&mut self, cx: &mut Context<Self>) -> Element {
let palette = default_theme(cx.environment());
let theme = palette.resolve(cx.environment().color_scheme);
Element::column([
Element::text(format!("Count: {}", self.count)),
Button::new("increment", "Increment", theme.button())
.on_click(cx.callback(|app| {
app.count = app.count.saturating_add(1);
}))
.build(),
])
.gap(12.0)
}
}Local callbacks invalidate automatically
Context::callback registers state-only work and invalidates this presentation after it returns. The button owns the activation binding, so pointer, touch, Enter, Space and accessibility click all reach the same callback.
The model stays in control
The handler changes ordinary Rust state; the next render rebuilds the controlled widget from that state. Handler identities remain opaque and closures stay in the runtime rather than inside cloneable Element values.
Reference files
These are the implementation and guide files used for this chapter.
crates/argui-widget-gallery/src/pages/hot_reload.rsdocs/runtime/models.mdA compiled counter running in WebAssembly
The Rust file below is imported verbatim by this page and compiled into the WebAssembly application running underneath it.
use argui::{
runtime::{Context, Render},
text::TextStyle,
ui::{AlignItems, Element, Sides, percent},
widgets::{Button, default_theme},
};
#[derive(Default)]
pub struct Example {
count: u32,
}
impl Render for Example {
fn render(&mut self, cx: &mut Context<Self>) -> Element {
let themes = default_theme(cx.environment());
let theme = themes.resolve(cx.environment().color_scheme);
Element::column([
Element::text(format!("Count: {}", self.count)).text_style(TextStyle {
font_size: 34.0,
line_height: 42.0,
color: theme.foreground,
weight: 700,
..TextStyle::default()
}),
Button::new("increment", "Increment", theme.button())
.on_click(cx.callback(|app| app.count = app.count.saturating_add(1)))
.build(),
])
.width(percent(1.0))
.height(percent(1.0))
.align_items(AlignItems::CENTER)
.padding(Sides::length(28.0))
.gap(18.0)
.background(theme.background)
}
}
Starting this lesson’s WebAssembly module…