Antigravity-awesome-skills robius-event-action
install
source · Clone the upstream repo
git clone https://github.com/sickn33/antigravity-awesome-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/sickn33/antigravity-awesome-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/antigravity-awesome-skills-claude/skills/robius-event-action" ~/.claude/skills/sickn33-antigravity-awesome-skills-robius-event-action && rm -rf "$T"
manifest:
plugins/antigravity-awesome-skills-claude/skills/robius-event-action/SKILL.mdsource content
Robius Event and Action Patterns Skill
Best practices for event handling and action patterns in Makepad applications based on Robrix and Moly codebases.
Source codebases:
- Robrix: Matrix chat client - MessageAction, RoomsListAction, AppStateAction
- Moly: AI chat application - StoreAction, ChatAction, NavigationAction, Timer patterns
When to Use
Use this skill when:
- Implementing custom actions in Makepad
- Handling events in widgets
- Centralizing action handling in App
- Widget-to-widget communication
- Keywords: makepad action, makepad event, widget action, handle_actions, cx.widget_action
Custom Action Pattern
Defining Domain-Specific Actions
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, }
Emitting Actions from Widgets
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, }, ); } _ => {} } } }
Centralized Action Handling in App
Using MatchEvent Trait
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); } }
Action Types
Widget Actions (UI Thread)
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 Actions (From Async)
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); }
Global Actions
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); }
Event Handling Patterns
Hit Testing
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 } _ => {} } } }
Keyboard Events
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); } _ => {} } } }
Signal Events
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); } } }
Action Chaining Pattern
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, }, ); }
Best Practices
- Use
derive: All action enums must have aDefaultNone
variantNone - Use
after handling: Prevents unnecessary processingcontinue - Downcast pattern for async actions: Posted actions are not widget actions
- Widget action cast for UI actions: Use
as_widget_action().cast() - Always call
: After posting actions from asyncSignalToUI::set_ui_signal() - Centralize in App::handle_actions: Keep action handling in one place
- Use descriptive action names:
notMessageAction::ReplyMessageAction::Action1
Reference Files
- Additional action patterns (Robrix)references/action-patterns.md
- Event handling reference (Robrix)references/event-handling.md
- Moly-specific patternsreferences/moly-action-patterns.md- Store-based action forwarding
- Timer-based retry pattern
- Radio button navigation
- External link handling
- Platform-conditional actions (#[cfg])
- UiRunner event handling
Limitations
- Use this skill only when the task clearly matches the scope described above.
- Do not treat the output as a substitute for environment-specific validation, testing, or expert review.
- Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.