Implements event handling and action patterns for Makepad applications, covering custom actions, MatchEvent trait, widget actions, and centralized action handling in App. Based on Robrix and Moly codebases.
How this skill is triggered — by the user, by Claude, or both
Slash command
/agentic-awesome-skills:robius-event-actionThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Best practices for event handling and action patterns in Makepad applications based on Robrix and Moly codebases.
Best practices for event handling and action patterns in Makepad applications based on Robrix and Moly codebases.
Source codebases:
Use this skill when:
use makepad_widgets::*;
/// Actions emitted by the Message widget
#[derive(Clone, DefaultNone, Debug)]
pub enum MessageAction {
/// User wants to react to a message
React { details: MessageDetails, reaction: String },
/// User wants to reply to a message
Reply(MessageDetails),
/// User wants to edit a message
Edit(MessageDetails),
/// User wants to delete a message
Delete(MessageDetails),
/// User requested to open context menu
OpenContextMenu { details: MessageDetails, abs_pos: DVec2 },
/// Required default variant
None,
}
/// Data associated with a message action
#[derive(Clone, Debug)]
pub struct MessageDetails {
pub room_id: OwnedRoomId,
pub event_id: OwnedEventId,
pub content: String,
pub sender_id: OwnedUserId,
}
impl Widget for Message {
fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) {
self.view.handle_event(cx, event, scope);
let area = self.view.area();
match event.hits(cx, area) {
Hit::FingerDown(_fe) => {
cx.set_key_focus(area);
}
Hit::FingerUp(fe) => {
if fe.is_over && fe.is_primary_hit() && fe.was_tap() {
// Emit widget action
cx.widget_action(
self.widget_uid(),
&scope.path,
MessageAction::Reply(self.get_details()),
);
}
}
Hit::FingerLongPress(lpe) => {
cx.widget_action(
self.widget_uid(),
&scope.path,
MessageAction::OpenContextMenu {
details: self.get_details(),
abs_pos: lpe.abs,
},
);
}
_ => {}
}
}
}
impl MatchEvent for App {
fn handle_startup(&mut self, cx: &mut Cx) {
// Called once on app startup
self.initialize(cx);
}
fn handle_actions(&mut self, cx: &mut Cx, actions: &Actions) {
for action in actions {
// Pattern 1: Direct downcast for non-widget actions
if let Some(action) = action.downcast_ref::<LoginAction>() {
match action {
LoginAction::LoginSuccess => {
self.app_state.logged_in = true;
self.update_ui_visibility(cx);
}
LoginAction::LoginFailure(error) => {
self.show_error(cx, error);
}
}
continue; // Action handled
}
// Pattern 2: Widget action cast
if let MessageAction::OpenContextMenu { details, abs_pos } =
action.as_widget_action().cast()
{
self.show_context_menu(cx, details, abs_pos);
continue;
}
// Pattern 3: Match on downcast_ref for enum variants
match action.downcast_ref() {
Some(AppStateAction::RoomFocused(room)) => {
self.app_state.selected_room = Some(room.clone());
continue;
}
Some(AppStateAction::NavigateToRoom { destination }) => {
self.navigate_to_room(cx, destination);
continue;
}
_ => {}
}
// Pattern 4: Modal actions
match action.downcast_ref() {
Some(ModalAction::Open { kind }) => {
self.ui.modal(ids!(my_modal)).open(cx);
continue;
}
Some(ModalAction::Close { was_internal }) => {
if *was_internal {
self.ui.modal(ids!(my_modal)).close(cx);
}
continue;
}
_ => {}
}
}
}
}
impl AppMain for App {
fn handle_event(&mut self, cx: &mut Cx, event: &Event) {
// Forward to MatchEvent
self.match_event(cx, event);
// Pass events to widget tree
let scope = &mut Scope::with_data(&mut self.app_state);
self.ui.handle_event(cx, event, scope);
}
}
Emitted by widgets, handled in the same frame:
// Emitting
cx.widget_action(
self.widget_uid(),
&scope.path,
MyAction::Something,
);
// Handling (two patterns)
// Pattern A: Direct cast for widget actions
if let MyAction::Something = action.as_widget_action().cast() {
// handle...
}
// Pattern B: With widget UID matching
if let Some(uid) = action.as_widget_action().widget_uid() {
if uid == my_expected_uid {
if let MyAction::Something = action.as_widget_action().cast() {
// handle...
}
}
}
Posted from async tasks, received in next event cycle:
// In async task
Cx::post_action(DataFetchedAction { data });
SignalToUI::set_ui_signal(); // Wake UI thread
// Handling in App (NOT widget actions)
if let Some(action) = action.downcast_ref::<DataFetchedAction>() {
self.process_data(&action.data);
}
For app-wide state changes:
// Using cx.action() for global actions
cx.action(NavigationAction::GoBack);
// Handling
if let Some(NavigationAction::GoBack) = action.downcast_ref() {
self.navigate_back(cx);
}
impl Widget for MyWidget {
fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) {
let area = self.view.area();
match event.hits(cx, area) {
Hit::FingerDown(fe) => {
cx.set_key_focus(area);
// Start drag, capture, etc.
}
Hit::FingerUp(fe) => {
if fe.is_over && fe.is_primary_hit() {
if fe.was_tap() {
// Single tap
}
if fe.was_long_press() {
// Long press
}
}
}
Hit::FingerMove(fe) => {
// Drag handling
}
Hit::FingerHoverIn(_) => {
self.animator_play(cx, id!(hover.on));
}
Hit::FingerHoverOut(_) => {
self.animator_play(cx, id!(hover.off));
}
Hit::FingerScroll(se) => {
// Scroll handling
}
_ => {}
}
}
}
fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) {
if let Event::KeyDown(ke) = event {
match ke.key_code {
KeyCode::Return if !ke.modifiers.shift => {
self.submit(cx);
}
KeyCode::Escape => {
self.cancel(cx);
}
KeyCode::KeyC if ke.modifiers.control || ke.modifiers.logo => {
self.copy_to_clipboard(cx);
}
_ => {}
}
}
}
For handling async updates:
fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) {
if let Event::Signal = event {
// Poll update queues
while let Some(update) = PENDING_UPDATES.pop() {
self.apply_update(cx, update);
}
}
}
Widget emits action → Parent catches and re-emits with more context:
// In child widget
cx.widget_action(
self.widget_uid(),
&scope.path,
ItemAction::Selected(item_id),
);
// In parent widget's handle_event
if let ItemAction::Selected(item_id) = action.as_widget_action().cast() {
// Add context and forward to App
cx.widget_action(
self.widget_uid(),
&scope.path,
ListAction::ItemSelected {
list_id: self.list_id.clone(),
item_id,
},
);
}
DefaultNone derive: All action enums must have a None variantcontinue after handling: Prevents unnecessary processingas_widget_action().cast()SignalToUI::set_ui_signal(): After posting actions from asyncMessageAction::Reply not MessageAction::Action1references/action-patterns.md - Additional action patterns (Robrix)references/event-handling.md - Event handling reference (Robrix)references/moly-action-patterns.md - Moly-specific patterns
npx claudepluginhub sickn33/agentic-awesome-skills --plugin agentic-awesome-skills14plugins reuse this skill
First indexed Jun 3, 2026
Showing the 6 earliest of 14 plugins
Implements event handling and action patterns for Makepad applications, covering custom actions, MatchEvent trait, widget actions, and centralized action handling in App. Based on Robrix and Moly codebases.
Handles Makepad event and action handling for mouse, keyboard, touch, and lifecycle events, plus widget-to-parent action propagation.
Handles Makepad 2.0 events and actions via Splash inline handlers (on_click, on_render, on_startup) and Rust MatchEvent trait for UI interactions and business logic.