Skip to main content

Runtime integration

Event loops, keymaps, time, effects, and host ownership.

The canonical termrock-catalog application is the executable on-ramp. It mounts the shared catalog shell and state model used by native, headless, and browser hosts; runtime behavior remains independent of product-specific state:

cargo run -p termrock-catalog --release
cargo run -p termrock-catalog --release -- --page datagrid

TermRock does not require an application architecture. Consumers may use a direct loop, Elm-style update flow, components, or another state model while keeping rendering and interaction primitives shared.

Runtime keymaps

Keymap is one resolved source for dispatch, hints, glyph lookup, and conflict diagnostics. Static defaults use KeyBinding::borrowed and Keymap::from_static without startup allocation. Clone that map and apply runtime configuration before entering the event loop; its first successful edit copies the table, while unchanged chord and label data stays borrowed.

use termrock::{
    input::KeyCode,
    keymap::{KeyBinding, KeyChord, Keymap, Visibility},
};

#[derive(Clone, Copy, PartialEq)]
enum Action { Quit }

static BINDINGS: &[KeyBinding<Action>] = &[KeyBinding::borrowed(
    &[KeyChord::plain(KeyCode::Char('q'))],
    Action::Quit,
    Some("quit"),
    Visibility::Shown,
    None,
)];

let mut keymap = Keymap::from_static(BINDINGS);
keymap.remap(Action::Quit, vec![KeyChord::ctrl(KeyCode::Char('c'))]);
assert_eq!(keymap.dispatch(KeyChord::ctrl(KeyCode::Char('c'))), Some(Action::Quit));
assert_eq!(keymap.glyph_for(Action::Quit), "Ctrl-C");

remap clears an explicit glyph so the canonical glyph follows the new first chord. Use replace when configuration intentionally supplies a custom grouped glyph. conflicts reports declaration-ordered collisions; dispatch remains deterministic and first-binding-wins. With the serde feature, serializing a borrowed map and deserializing it produces an owned map ready for mutation.

Crossterm runner

With the crossterm feature, runtime::run centralizes terminal mode setup, neutral event conversion, redraw cadence, deadline-aware polling, and reverse restoration. Mutable render state remains valid; effects and process policy remain application-owned.

use std::ops::ControlFlow;
use termrock::runtime::{Instant, RunOptions, run};

# struct App;
# impl App {
#   fn render(&mut self, _: &mut ratatui_core::terminal::Frame<'_>, _: termrock::runtime::FrameTick) {}
#   fn update(&mut self, _: termrock::input::Event, _: termrock::runtime::FrameTick) -> ControlFlow<()> { ControlFlow::Break(()) }
#   fn next_deadline(&self) -> Option<Instant> { None }
# }
# let mut app = App;
run(
    &mut app,
    RunOptions::default(),
    App::render,
    App::update,
    App::next_deadline,
)?;
# Ok::<(), std::io::Error>(())

Runner samples one FrameTick before each draw and passes the same value to that cycle's update. It polls for the lesser of RunOptions::poll_timeout and the model's next deadline. Return None when no animation or TTL needs a timed wakeup. Widgets consume time only through passed FrameTick values, making tests deterministic through FrameTick::manual.

On this page