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: Int,
) -> AppResult(state)
Run the app loop.
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 },
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: 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) {
let frame = anim_state.frame
buffer.buffer_new(screen)
|> spinner.render(area, spinner.spinner_new() |> spinner.with_frame(frame))
},
fn(ev, m) { case ev { backend.KeyPress("q") -> Model(quit: True) _ -> m } },
fn(m) { m.quit },
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: 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 to_ansi (clean slate). Subsequent frames: diff_to_ansi.
On Resize: full re-render at 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 },
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: Int,
) -> AppResult(state)
Like run_buffered but the render function also returns an optional cursor
position 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 the cursor. Use for read-only views.
The cursor is hidden automatically on init and restored on exit.
app.run_buffered_cursor(
default.new(),
Model(text: "", cursor: 0),
fn(m, screen) {
let buf = buffer.buffer_new(screen) |> input.render(area, w, input_state)
let cursor_pos = geometry.Position(x: area.x + input_state.cursor_x + 1, y: area.y)
#(buf, Ok(cursor_pos))
},
on_event,
fn(m) { m.quit },
16,
)