Back to list
cnwzhu

gpui-actions

by cnwzhu

4🍴 0📅 Jan 16, 2026

SKILL.md


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

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

Score

Total Score

45/100

Based on repository quality metrics

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

0/10
説明文

100文字以上の説明がある

0/10
人気

GitHub Stars 100以上

0/15
最近の活動

3ヶ月以内に更新がある

0/10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

0/5
タグ

1つ以上のタグが設定されている

0/5

Reviews

💬

Reviews coming soon