Event

GPUI provides Event as a typed notification mechanism between Entities. An Event reports something that already happened; unlike an Action, it does not use Focus, Key Contexts, KeyBindings, or the Dispatch Path.

#Action in, Event out

An Action can cause the state change, but Event delivery starts after that change:

Chat changes state → emit(MessageSent) → subscribers receive Event → Workspace updates
Chat emits one MessageSent Event to independent Workspace, Activity Log, and Telemetry subscribers Chat emits one MessageSent Event to independent Workspace, Activity Log, and Telemetry subscribers
  • Action carries intent inward: “send this message.”
  • Event reports the result outward: “this message was sent.”

The command owner handles the Action and changes its state. It then emits an Event so owners or services can react without being coupled to the command’s UI entry point. See Action for Focus, KeyBindings, and Action dispatch.

#Define and emit an Event

Define the facts an Entity can report and implement EventEmitter:

#[derive(Clone, Debug)]
enum ChatEvent {
    DraftChanged,
    MessageSent { message_id: MessageId },
}

impl EventEmitter<ChatEvent> for Chat {}

Emit the Event after the state change succeeds:

fn finish_send(&mut self, message_id: MessageId, cx: &mut Context<Self>) {
    self.draft.clear();
    cx.emit(ChatEvent::MessageSent { message_id });
}

Name Events as facts in the past tense: MessageSent, Saved, or Dismissed. A command-style name such as SendMessage belongs to an Action.

#Subscribe from the owner

The owner stores subscriptions on the same View that subscribes. This follows the pattern used by GPUI Kit examples:

struct Workspace {
    chat: Entity<Chat>,
    _subscriptions: Vec<Subscription>,
}

impl Workspace {
    fn new(cx: &mut Context<Self>) -> Self {
        let chat = cx.new(Chat::new);
        let _subscriptions = vec![cx.subscribe(&chat, |workspace, _, event, _cx| {
            if matches!(event, ChatEvent::MessageSent { .. }) {
                workspace.refresh_conversation();
            }
        })];

        Self { chat, _subscriptions }
    }
}

Do not leave the returned Subscription in a local variable: it is dropped when the function returns, which disconnects the observer. Keeping _subscriptions on Workspace gives both the same lifetime. When the View is dropped, its subscriptions are dropped and disconnected too. Avoid storing View-scoped subscriptions in a longer-lived global owner: keeping callbacks and captured resources alive after the View is gone can cause a memory leak.

Use cx.subscribe_in(..., window, ...) when the callback needs &mut Window; keep that returned Subscription in the same field as well.

#Action or Event?

QuestionUseExamples
Is this an instruction a user or caller wants performed?ActionSave, Delete, Open Search
Should it be bindable to a key or shown in a menu?ActionCopy, Toggle Sidebar, Rename
Is this a fact reported after state or lifecycle changed?EventValueChanged, Saved, Dismissed
Should an owner observe a child independently of its UI tree?EventInput changed, row selected, dialog submitted
Is it only a pointer gesture with no other command entry point?callbackhover, drag delta, pointer position

Use both when a command produces a fact other parts of the application need to observe: handle the Action first, commit the state change, then emit the Event.