3. Update state
Turn the button into a counter. Complete 2. Handle a button click first. Work in the same app directory and keep the project files and catalog from earlier lessons.
1. Add the counter messages
Append to resource/locales/en/app.ftl:
counter_value = Count: { $count }
add_one = Add one
$count is a Fluent argument. The generated counter_value accessor accepts that argument
and checks its name at compile time.
2. Store and display the count
Replace all of src/lib.rs:
use day::prelude::*;
day::resources!();
day::day_start!(options: res::window_options(), root);
pub fn root() -> impl Piece {
let count = Signal::new(0i64);
column((
label(res::str::counter_value(count)).id(id!(counter_label)),
button(res::str::add_one())
.action(move || count.update(|n| *n = n.saturating_add(1)))
.id(id!(increment_button)),
))
.spacing(Space::M)
.padding(Space::L)
}
A signal is a reactive value: reading it through a binding connects the UI to future changes.
Signal::new(0i64) starts the count at zero in the root’s current scope. Pass count to the
localized accessor so the label follows changes. Passing count.get() would take a snapshot
instead.
The move closure captures the signal handle. Handles are copyable; both the label and button
refer to the same value. update changes it and notifies its dependents. saturating_add
keeps this counter from overflowing at the integer limit. The callback runs on the UI
thread; do not send a Signal to a worker thread.
3. Observe the state
day launch -t macos-appkit
The label starts at Count: 0. Press Add one twice; it should read Count: 2. Quit and relaunch: the count returns to zero. This app stores the count in memory only.
Troubleshooting
If the count never changes, check that the label receives count, not count.get(), and that
the callback calls count.update(...). If the generated function has the wrong arguments,
check that counter_value includes { $count } in the catalog, then rebuild.
Next step
Continue to 4. Arrange the controls.