Views
A bar shows data that changes: the time, the battery, the volume. Most UI toolkits build the widgets once, and you write code to update each widget when its data changes. Amane works differently. A view is a function that describes what a window shows, and Amane calls it again whenever that should change, so there’s no update code to write.
This page explains when a view runs, and the one rule that follows from that.
A view with live data
use amane::{App, Battery, Full, LayerWindow, Service, Text};
fn main() {
App::new().window(view).run();
}
fn view() -> LayerWindow {
let battery = Battery::read();
LayerWindow::new()
.width(Full)
.height(30.0)
.child(Text::new(format!("battery {}%", battery.percent())))
}
When the battery goes from 80% to 79%, the text changes on its own.
When a view runs
view is called again every time the window needs to change:
- Amane calls
view. viewreads the battery (80%) and builds a new window with aTextthat says 80%.- Amane draws it, then throws the widgets away.
- The battery drops to 79%.
- Amane calls
viewagain. It reads 79% and builds a newTextthat says 79%.
The first draw and every update go through the same function, so the screen always matches the data.
Amane also knows which windows to call again. Battery::read() records that this view read the battery. When the battery changes, only the windows whose view read it are drawn again. A window that never reads the battery is left alone. State and Services explains this in detail.
Keeping state outside the view
The widgets are thrown away after every frame. So:
Nothing stored in a widget survives to the next frame.
Anything that has to last, like a counter, whether a menu is open, or text the user typed, has to live outside the view. Amane gives you three places for it:
- a Service, for your own state (State and Services)
- a built-in name-keyed store, used by
ScrollAreaandTextInputto keep their scroll position and text between frames. You give each one a name, likeScrollArea::new("apps", ...). - a
staticyou manage yourself, like athread_local!or aMutex. The window you click or type in always redraws after the event, so a static changed by its own input handler shows up. But a static changed anywhere else, like from a thread or another window’s handler, redraws nothing. A Service redraws every window that reads it, wherever the change comes from.
What a view should and shouldn’t do
A view runs often, sometimes 60 times a second during an animation, on the same thread that draws every window. So a view should:
- read data (
SomeService::read()) and build widgets - not wait on anything: no running commands, no reading files, no network calls, no
sleep
Slow work goes in a Service’s own thread instead. The view then reads the result.
Splitting a view into functions
App::window takes fn() -> LayerWindow: a function that takes nothing and returns a window. It can’t be a closure that captures variables. That’s on purpose: since a view can’t capture anything, everything it shows has to come from somewhere Amane can watch.
You can split a view into helper functions freely:
use amane::{App, Color, Full, LayerWindow, Parent, Rectangle, Row, Text, children};
fn main() {
App::new().window(view).run();
}
fn view() -> LayerWindow {
LayerWindow::new()
.width(Full)
.height(30.0)
.child(Row::new(children![label("left"), label("right")]))
}
fn label(content: &str) -> Rectangle {
Rectangle::new()
.width(Parent)
.height(Parent)
.fill(Color::BLACK)
.child(Text::new(content).color(Color::WHITE))
}
Helpers can take arguments and return any widget type. Only the top-level view has a fixed signature.
Terms
- View: a function that builds a window’s widgets from the current data.
- Frame: one run of the view, plus drawing the result.
- Immediate mode: this style of UI, where the widgets are built again for every frame instead of being kept and updated.