gpui-actions

Actions and event handling in GPUI applications. Use when implementing user interactions, keyboard shortcuts, custom actions, or event-driven behavior.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "gpui-actions" with this command: npx skills add cnwzhu/gpui-skills/cnwzhu-gpui-skills-gpui-actions

GPUI Actions

This skill covers actions, events, and user interaction handling in GPUI.

Overview

GPUI actions provide:

  • Type-safe event handling with the Action trait
  • Keyboard shortcut binding with action dispatch
  • Mouse/touch events with .on_click(), .on_mouse_down(), etc.
  • Focus management for keyboard navigation

Defining Actions

Simple Actions

Use the actions! macro for actions with no data:

use gpui::*;

actions!(my_app, [Save, Open, Close, Refresh]);

// These actions have no associated data

Actions with Data

Use #[derive(Clone, PartialEq)] with the Action derive:

use gpui::*;

#[derive(Clone, PartialEq)]
struct SetCount {
    value: usize,
}

impl_actions!(my_app, [SetCount]);

Action Documentation

Doc comments on actions are displayed to users:

actions!(editor, [
    /// Save the current file
    Save,
    /// Open a file
    Open,
]);

Mouse Events

on_click

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);
            })
    }
}

on_click with cx.listener

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();
                    }))
            )
    }
}

Other Mouse Events

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
    })

Keyboard Events

Key Press Handler

div()
    .on_key_down(|event, window, cx| {
        if event.keystroke.key == "Enter" {
            println!("Enter pressed");
        }
    })
    .on_key_up(|event, window, cx| {
        // Key released
    })

Modifier Keys

.on_key_down(|event, window, cx| {
    if event.keystroke.modifiers.control && event.keystroke.key == "s" {
        println!("Ctrl+S pressed");
        // Handle save action
    }
})

Action Handlers

Registering Action Handlers

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();
            }))
    }
}

Dispatching Actions

// Dispatch from window
window.dispatch_action(Save.boxed_clone(), cx);

// Dispatch from focus handle
focus_handle.dispatch_action(&Save, window, cx);

Focus Management

Focus Handles

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())
    }
}

Focus Events

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");
    }))

Practical Examples

Button Component

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
    }
}

Form with Multiple Actions

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")
    }
}

Drag and Drop

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")
    }
}

Event Propagation

Stopping Propagation

div()
    .on_click(|event, _window, cx| {
        println!("Child clicked");
        // Prevent parent from receiving event
        event.stop_propagation();
    })

Best Practices

  1. Use cx.listener(): For accessing entity state in event handlers
  2. Call cx.notify(): After state changes that affect rendering
  3. Use actions for keyboard shortcuts: More maintainable than key handlers
  4. Handle focus properly: Use track_focus() and FocusHandle
  5. Document actions: Add doc comments for better UX
  6. Use type-safe actions: Prefer actions over string-based events

Common Mistakes

MistakeProblemSolution
Forgetting cx.notify()UI doesn't updateCall after state changes
Not using cx.listener()Can't access entityUse cx.listener()
Nested event handlersConfusing behaviorUse event propagation control
Missing focus trackingKeyboard events don't workUse .track_focus()
Not boxing actionsType errorsUse .boxed_clone() for dispatch

Summary

  • Use actions!() macro for simple actions
  • Use .on_click(cx.listener(...)) for clickable elements
  • Use .on_action() for action handlers
  • Use FocusHandle for keyboard focus
  • Call cx.notify() after state changes
  • Dispatch actions with window.dispatch_action()

References

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

gpui-testing

No summary provided by upstream source.

Repository SourceNeeds Review
General

gpui-troubleshooting

No summary provided by upstream source.

Repository SourceNeeds Review
General

gpui-fundamentals

No summary provided by upstream source.

Repository SourceNeeds Review
General

gpui-init

No summary provided by upstream source.

Repository SourceNeeds Review