Input
Input is a controlled single-line text component. It owns no text; your app stores an InputState, and the component emits the next state after each edit. InputWidget is the paint-only layer.
Focus the demo, type into the focused input, Tab between fields, and press Alt+T to choose a bundled theme.
Interactive Component
use ratcn::{Input, edit::InputState};
use ratcn::view::View;
struct AppState {
focus: FocusState,
theme: Theme,
query: InputState,
query_invalid: bool,
}
enum Msg {
FocusChanged(FocusState),
QueryChanged(InputState),
Search,
}
View::new("root")
.focus(|s: &AppState| &s.focus, Msg::FocusChanged)
.theme(|s: &AppState| &s.theme)
.child(
"query",
Input::new()
.value(|s: &AppState| &s.query)
.on_change(Msg::QueryChanged)
.on_enter(Msg::Search)
.title("Search")
.placeholder("Type to search...")
.invalid(|s: &AppState| s.query_invalid),
)
.content(|frame, _state, view| view.render_child(frame, "query", area));State Model
InputState stores both the string and the cursor. Because the value lives in app state, other messages can clear, prefill, validate, or replace the input without asking the component for anything.
let empty = InputState::default();
let prefilled = InputState::new("hello");The edit engine uses character indexes, not byte offsets, so cursor movement, deletion, and insertion are UTF-8 safe.
Input API
Input::new(): Creates an unbound input. Without.value(...), it only shows its placeholder..value(|state| &state.field): Binds the displayed value to anInputStatein app state..on_change(Msg::Ctor): Emits the nextInputStateafter edits. Omit for read-only display..on_enter(msg): Emitsmsgon unmodified Enter..title("..."): Draws a themed border with a left-aligned title..placeholder("..."): Text shown dimmed when the value is empty..disabled(|state| ...): Renders disabled, ignores edits, and leaves Tab traversal while true..invalid(|state| ...): Applies invalid styling while staying editable and focusable.
Events
Input emits through .on_change(...) after edits and .on_enter(...) on unmodified Enter. If .value(...) or .on_change(...) is missing, edit events are ignored because the component has nowhere to read or persist state. Disabled inputs ignore events and are skipped by Tab traversal.
Plain character keys insert text. Arrow keys, Home, End, Backspace, Delete, and common Ctrl word/line shortcuts are handled by the shared edit engine. Paste is normalized to a single line: line breaks and tabs become spaces, other control characters are dropped.
Paint Widget
Use InputWidget when you already own focus, events, and editing and only want the styled renderer.
use ratcn::{InputWidget, Theme};
frame.render_widget(
InputWidget::new(value)
.placeholder("Enter your name...")
.cursor(cursor)
.focused(is_focused)
.disabled(is_disabled)
.invalid(is_invalid)
.themed(&theme),
area,
);| Method | Description |
|---|---|
InputWidget::new(value) | Creates a paint-only input line. |
.placeholder(text) | Placeholder shown when value is empty and unfocused. |
.cursor(index) | Character index of the cursor. |
.focused(bool) | Draws cursor and focused background. |
.is_focused() | Returns whether focused styling is enabled. |
.disabled(bool) | Draws disabled styling. |
.is_disabled() | Returns whether disabled styling is enabled. |
.invalid(bool) | Draws invalid styling. |
.is_invalid() | Returns whether invalid styling is enabled. |
.themed(&theme) | Applies theme colors. |
.style(TextInputStyle) | Uses an explicit text input style. |
.text_color(...), .placeholder_color(...), .background_color(...), .focused_background_color(...), .cursor_color(...) | Override individual colors. |