Handle a click
retroglyph-ui has no retained widget tree: nothing remembers “button A is at this rectangle”
between frames. Instead,
Interaction tracks
pointer and keyboard state across frames, and each frame’s draw call re-declares where its widgets
are by calling into it, the same immediate-mode shape as drawing itself.
Pairing a surface with Interaction
Interaction::frame
wraps one frame: it hands back a
Ui, which pairs that frame’s
Surface with the Interaction context so a single call
(Ui::show) both
hit-tests and draws an
InteractiveWidget
like Button from the one
id/rect a call site names:
fn draw_button(ui: &mut Ui<'_, '_, ButtonId>, rect: Rect, id: ButtonId, label: &str) -> bool {
let theme = Theme::DARK;
let button = Button::new(label)
.style(Style::new().fg(theme.fg).bg(theme.panel_bg))
.hovered_style(Style::new().fg(theme.fg).bg(theme.hover_bg))
.pressed_style(Style::new().fg(theme.fg).bg(theme.press_bg))
.focused_style(Style::new().fg(theme.accent).bg(theme.panel_bg));
ui.show(rect, id, &button).clicked()
}
id is any Copy + Eq type the app defines (an enum listing every interactive widget on screen,
typically); Interaction<Id> uses it to track which widget is hovered, pressed, and focused across
frames, and to resolve Tab/Shift+Tab cycling between them. ui.show returns a
Response; .clicked()
is true for exactly one frame, the one where a press-then-release (or a drag that stayed inside
the widget) completed inside rect, or Enter/Space activated it while focused.
Before drawing, every event for the frame needs to reach the Interaction: feed each one to
ui.interaction().handle_event(event) (see 10_widgets_interaction.rs’s tick for the full
event-draining loop this snippet sits inside) so hover/press/focus state reflects that frame’s input
before any widget asks ui.show what happened to it.
Styling by response state
Button::style/hovered_style/pressed_style/focused_style (or .theme(), see
Theme a widget) pick the four states a click can pass through; the widget
itself decides which one applies each frame from the Response ui.show resolves internally, so
the call site never branches on hover/press by hand.
Running it
cargo run --example 10_widgets_interaction --features crossterm
cargo run --example 10_widgets_interaction --features software
cargo run --example 10_widgets_interaction # headless fallback, prints a few frames to stdout
See also
- Draw a panel, for widgets that only draw and never read
Interaction. examples/examples/09_widgets_dashboard.rsandexamples/examples/17_theme_switch.rsforInteractionshared across several widget kinds (Table,List,Tabs) in one frame.