etui/app
Types
Values
pub fn run(
b: backend.Backend(backend_state),
init_state: state,
render: fn(state) -> List(backend.RenderOp),
on_event: fn(backend.InputEvent, state) -> state,
should_quit: fn(state) -> Bool,
poll_timeout_ms: fn(state) -> Int,
) -> AppResult(state)
Run the app loop over raw render ops.
Lifecycle:
b.init(), enter raw mode, alt screen.- Loop:
render(state)→ emit ops →b.poll()→on_event(). - Exit when
should_quit(state)returnsTrue. b.cleanup(), always runs, even on panic.
app.run(
default.new(),
Model(count: 0),
fn(m) { [Write(int.to_string(m.count))] },
fn(ev, m) { case ev { KeyPress("q") -> m KeyPress(_) -> Model(count: m.count + 1) _ -> m } },
fn(m) { m.count >= 10 },
fn(_) { 16 },
)
pub fn run_animated(
b: backend.Backend(backend_state),
init_state: state,
render: fn(state, geometry.Rect, anim.AnimState) -> buffer.Buffer,
on_event: fn(backend.InputEvent, state) -> state,
should_quit: fn(state) -> Bool,
poll_timeout_ms: fn(state) -> Int,
) -> AppResult(state)
Like run_buffered but passes an anim.AnimState to the render function,
auto-ticked every frame. Use when your UI has spinners, blinking widgets,
marquees, or any frame-dependent animation, no manual tick needed.
app.run_animated(
default.new(),
Model(quit: False),
fn(m, screen, anim_state) {
buffer.buffer_new(screen)
|> spinner.render(area, spinner.spinner_new() |> spinner.with_frame(anim_state.frame))
},
fn(ev, m) { case ev { backend.KeyPress("q") -> Model(quit: True) _ -> m } },
fn(m) { m.quit },
fn(_) { 16 },
)
pub fn run_buffered(
b: backend.Backend(backend_state),
init_state: state,
render: fn(state, geometry.Rect) -> buffer.Buffer,
on_event: fn(backend.InputEvent, state) -> state,
should_quit: fn(state) -> Bool,
poll_timeout_ms: fn(state) -> Int,
) -> AppResult(state)
High-level app loop. The render function produces a Buffer; the loop
diffs it against the previous frame and emits only the changed cells.
First frame: full repaint. Subsequent frames: diff. On Resize: full
repaint at the new size.
app.run_buffered(
default.new(),
Model(count: 0),
fn(m, screen) {
buffer.buffer_new(screen)
|> paragraph.render(screen, paragraph.paragraph_new(int.to_string(m.count)))
},
fn(ev, m) { case ev { KeyPress("q") -> m _ -> m } },
fn(m) { m.quit },
fn(_) { 16 },
)
pub fn run_buffered_cursor(
b: backend.Backend(backend_state),
init_state: state,
render: fn(state, geometry.Rect) -> #(
buffer.Buffer,
Result(geometry.Position, Nil),
),
on_event: fn(backend.InputEvent, state) -> state,
should_quit: fn(state) -> Bool,
poll_timeout_ms: fn(state) -> Int,
) -> AppResult(state)
Like run_buffered but the render function also returns where the hardware
cursor belongs, as Result(geometry.Position, Nil).
Ok(pos)shows the cursor atpos(0-based). Use for text inputs and text areas where the user needs to see the insertion point.Error(Nil)hides it. Use for read-only views.