Actions and event handling in GPUI applications. Use when implementing user interactions, keyboard shortcuts, custom actions, or event-driven behavior.
This skill covers actions, events, and user interaction handling in GPUI.
GPUI actions provide:
.on_click(), .on_mouse_down(), etc.Use the actions! macro for actions with no data:
use gpui::*;
actions!(my_app, [Save, Open, Close, Refresh]);
// These actions have no associated data
Use #[derive(Clone, PartialEq)] with the Action derive:
use gpui::*;
#[derive(Clone, PartialEq)]
struct SetCount {
value: usize,
}
impl_actions!(my_app, [SetCount]);
Doc comments on actions are displayed to users:
actions!(editor, [
/// Save the current file
Save,
/// Open a file
Open,
]);
use gpui::*;
impl Render for MyView {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
div()
.child("Click me")
.on_click(|event, window, cx| {
println!("Clicked at: {:?}", event.position);
})
}
}
Use cx.listener() to access the entity:
impl Render for Counter {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
div()
.child(format!("Count: {}", self.count))
.child(
div()
.child("Increment")
.on_click(cx.listener(|this, event, window, cx| {
this.count += 1;
cx.notify();
}))
)
}
}
div()
.on_mouse_down(|event, window, cx| {
// Mouse button pressed
})
.on_mouse_up(|event, window, cx| {
// Mouse button released
})
.on_mouse_move(|event, window, cx| {
// Mouse moved over element
})
.on_hover(|is_hovered, window, cx| {
// Hover state changed
})
div()
.on_key_down(|event, window, cx| {
if event.keystroke.key == "Enter" {
println!("Enter pressed");
}
})
.on_key_up(|event, window, cx| {
// Key released
})
.on_key_down(|event, window, cx| {
if event.keystroke.modifiers.control && event.keystroke.key == "s" {
println!("Ctrl+S pressed");
// Handle save action
}
})
use gpui::*;
actions!(my_app, [Increment, Decrement]);
struct Counter {
count: i32,
}
impl Render for Counter {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
div()
.child(format!("Count: {}", self.count))
.on_action(cx.listener(|this, action: &Increment, window, cx| {
this.count += 1;
cx.notify();
}))
.on_action(cx.listener(|this, action: &Decrement, window, cx| {
this.count -= 1;
cx.notify();
}))
}
}
// Dispatch from window
window.dispatch_action(Save.boxed_clone(), cx);
// Dispatch from focus handle
focus_handle.dispatch_action(&Save, window, cx);
use gpui::*;
struct InputField {
focus_handle: FocusHandle,
text: SharedString,
}
impl InputField {
fn new(cx: &mut Context<Self>) -> Self {
Self {
focus_handle: cx.focus_handle(),
text: "".into(),
}
}
}
impl Render for InputField {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
div()
.track_focus(&self.focus_handle)
.on_click(cx.listener(|this, _event, _window, cx| {
this.focus_handle.focus(cx);
}))
.child(self.text.clone())
}
}
div()
.track_focus(&self.focus_handle)
.on_focus(cx.listener(|this, _event, _window, cx| {
println!("Gained focus");
}))
.on_blur(cx.listener(|this, _event, _window, cx| {
println!("Lost focus");
}))
use gpui::*;
#[derive(IntoElement)]
struct Button {
label: SharedString,
on_click: Option<Box<dyn Fn(&mut App) + 'static>>,
}
impl Button {
fn new(label: impl Into<SharedString>) -> Self {
Self {
label: label.into(),
on_click: None,
}
}
fn on_click(mut self, handler: impl Fn(&mut App) + 'static) -> Self {
self.on_click = Some(Box::new(handler));
self
}
}
impl RenderOnce for Button {
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
let mut div = div()
.px_4()
.py_2()
.bg(rgb(0x3b82f6))
.text_color(rgb(0xffffff))
.rounded(px(6.0))
.cursor_pointer()
.child(self.label);
if let Some(handler) = self.on_click {
div = div.on_click(move |_event, _window, cx| {
handler(cx);
});
}
div
}
}
actions!(form, [Submit, Cancel, Reset]);
struct LoginForm {
username: SharedString,
password: SharedString,
}
impl Render for LoginForm {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
div()
.v_flex()
.gap_4()
.on_action(cx.listener(|this, _: &Submit, window, cx| {
println!("Submitting: {}", this.username);
// Handle submit
}))
.on_action(cx.listener(|this, _: &Cancel, window, cx| {
println!("Cancelled");
// Handle cancel
}))
.on_action(cx.listener(|this, _: &Reset, window, cx| {
this.username = "".into();
this.password = "".into();
cx.notify();
}))
.child("Login Form")
}
}
struct DraggableItem {
position: Point<Pixels>,
is_dragging: bool,
drag_offset: Point<Pixels>,
}
impl Render for DraggableItem {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
div()
.absolute()
.left(self.position.x)
.top(self.position.y)
.w(px(100.0))
.h(px(100.0))
.bg(rgb(0x3b82f6))
.cursor_pointer()
.on_mouse_down(cx.listener(|this, event, _window, cx| {
this.is_dragging = true;
this.drag_offset = event.position - this.position;
cx.notify();
}))
.on_mouse_up(cx.listener(|this, _event, _window, cx| {
this.is_dragging = false;
cx.notify();
}))
.on_mouse_move(cx.listener(|this, event, _window, cx| {
if this.is_dragging {
this.position = event.position - this.drag_offset;
cx.notify();
}
}))
.child("Drag me")
}
}
div()
.on_click(|event, _window, cx| {
println!("Child clicked");
// Prevent parent from receiving event
event.stop_propagation();
})
track_focus() and FocusHandle| Mistake | Problem | Solution |
|---------|---------|----------|
| Forgetting cx.notify() | UI doesn't update | Call after state changes |
| Not using cx.listener() | Can't access entity | Use cx.listener() |
| Nested event handlers | Confusing behavior | Use event propagation control |
| Missing focus tracking | Keyboard events don't work | Use .track_focus() |
| Not boxing actions | Type errors | Use .boxed_clone() for dispatch |
actions!() macro for simple actions.on_click(cx.listener(...)) for clickable elements.on_action() for action handlersFocusHandle for keyboard focuscx.notify() after state changeswindow.dispatch_action()Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer