View
View is the focus tree and composition layer for interactive components. It does not do layout. You keep using ordinary Ratatui layouts and widgets, then call view.render_child(...) where a registered component should appear.
In the demo, a and b jump between panes, Tab and Shift+Tab move through the focused pane, Enter or Space presses buttons, and Alt+T opens the theme picker.
Root View
The root view is where focus and theme are wired to app state.
use ratcn::{Button, Theme};
use ratcn::view::{FocusPolicy, FocusState, View};
struct AppState {
focus: FocusState,
theme: Theme,
}
enum Msg {
FocusChanged(FocusState),
Save,
}
let root = View::new("root")
.focus(|s: &AppState| &s.focus, Msg::FocusChanged)
.theme(|s: &AppState| &s.theme)
.focus_policy(FocusPolicy::Wrap)
.child("save", Button::new("Save").on_press(Msg::Save))
.content(|frame, _state, view| {
view.render_child(frame, "save", view.area());
});View API
View::new(id): Creates a focus scope with a stable id..focusable(): Makes the view itself focusable even without focusable children..child(id, component): Registers a focusable child. Declaration order is Tab order..content(|frame, state, view| ...): Renders the view body and places registered children..focus(read, on_change): Binds the root focus path to app state..theme(read): Reads the activeThemefrom app state..focus_policy(policy): Controls what happens when Tab reaches the scope edge..render(frame, area, state): Paints the whole tree..handle_event(event, state): Routes input to the focused leaf and returns anEventResult..focus_path(state, &[ids]): Builds a focus path to a specific nested child.
Use .focus_path(...) for programmatic focus jumps. Passing a path to a nested view descends to that view's first focusable child.
ViewCtx API
The content closure receives ViewCtx, usually named view.
view.area(): The rectangle this view is rendering into.view.render_child(frame, id, area): Renders a registered child with its focus context.view.contains_focus(): True if the focus path passes through this view. Useful for pane borders.view.theme(): The active theme, useful for static Ratatui content in the closure.
Focus Policy
FocusPolicy::Escape is the default. When Tab reaches the last focusable child, the event bubbles to the parent scope. This creates whole-tree traversal across nested views.
FocusPolicy::Wrap cycles within the current scope. It is useful for roots, dialogs, and panes that should trap Tab until app-level logic moves focus away.
Events
handle_event(...) returns EventResult<M>.
| Result | Meaning |
|---|---|
EventResult::Emit(msg) | The tree handled the event and produced an app message. |
EventResult::Consumed | The tree handled the event without a message. |
EventResult::Ignored | Nothing handled the event; app-level shortcuts may act on it. |
match root.handle_event(&event, &state) {
EventResult::Emit(msg) => update(&mut state, msg),
EventResult::Consumed => {}
EventResult::Ignored => handle_app_shortcut(event),
}