[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill-f0869511-8d63-4441-9348-5d01d9ca1938":3,"$fbbkJTjjlUZKkmyGRvO_1WWLPXLUDoYjRqovuRg2IkXs":43},{"id":4,"title":5,"description":6,"categoryId":7,"moduleId":8,"tags":9,"prompt":10,"icon":11,"source":12,"sourceUrl":13,"authorId":14,"authorName":15,"isPublic":16,"stars":17,"runs":18,"createdAt":19,"updatedAt":19,"module":20,"category":27,"packages":34},"f0869511-8d63-4441-9348-5d01d9ca1938","robius-event-action","|","cat_life_career","mod_other","sickn33,other","---\nname: robius-event-action\ndescription: |\n  CRITICAL: Use for Robius event and action patterns. Triggers on:\n  custom action, MatchEvent, post_action, cx.widget_action,\n  handle_actions, DefaultNone, widget action, event handling,\n  事件处理, 自定义动作\nrisk: unknown\nsource: community\n---\n\n# Robius Event and Action Patterns Skill\n\nBest practices for event handling and action patterns in Makepad applications based on Robrix and Moly codebases.\n\n**Source codebases:**\n- **Robrix**: Matrix chat client - MessageAction, RoomsListAction, AppStateAction\n- **Moly**: AI chat application - StoreAction, ChatAction, NavigationAction, Timer patterns\n\n## When to Use\nUse this skill when:\n- Implementing custom actions in Makepad\n- Handling events in widgets\n- Centralizing action handling in App\n- Widget-to-widget communication\n- Keywords: makepad action, makepad event, widget action, handle_actions, cx.widget_action\n\n## Custom Action Pattern\n\n### Defining Domain-Specific Actions\n\n```rust\nuse makepad_widgets::*;\n\n\u002F\u002F\u002F Actions emitted by the Message widget\n#[derive(Clone, DefaultNone, Debug)]\npub enum MessageAction {\n    \u002F\u002F\u002F User wants to react to a message\n    React { details: MessageDetails, reaction: String },\n    \u002F\u002F\u002F User wants to reply to a message\n    Reply(MessageDetails),\n    \u002F\u002F\u002F User wants to edit a message\n    Edit(MessageDetails),\n    \u002F\u002F\u002F User wants to delete a message\n    Delete(MessageDetails),\n    \u002F\u002F\u002F User requested to open context menu\n    OpenContextMenu { details: MessageDetails, abs_pos: DVec2 },\n    \u002F\u002F\u002F Required default variant\n    None,\n}\n\n\u002F\u002F\u002F Data associated with a message action\n#[derive(Clone, Debug)]\npub struct MessageDetails {\n    pub room_id: OwnedRoomId,\n    pub event_id: OwnedEventId,\n    pub content: String,\n    pub sender_id: OwnedUserId,\n}\n```\n\n### Emitting Actions from Widgets\n\n```rust\nimpl Widget for Message {\n    fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) {\n        self.view.handle_event(cx, event, scope);\n\n        let area = self.view.area();\n        match event.hits(cx, area) {\n            Hit::FingerDown(_fe) => {\n                cx.set_key_focus(area);\n            }\n            Hit::FingerUp(fe) => {\n                if fe.is_over && fe.is_primary_hit() && fe.was_tap() {\n                    \u002F\u002F Emit widget action\n                    cx.widget_action(\n                        self.widget_uid(),\n                        &scope.path,\n                        MessageAction::Reply(self.get_details()),\n                    );\n                }\n            }\n            Hit::FingerLongPress(lpe) => {\n                cx.widget_action(\n                    self.widget_uid(),\n                    &scope.path,\n                    MessageAction::OpenContextMenu {\n                        details: self.get_details(),\n                        abs_pos: lpe.abs,\n                    },\n                );\n            }\n            _ => {}\n        }\n    }\n}\n```\n\n## Centralized Action Handling in App\n\n### Using MatchEvent Trait\n\n```rust\nimpl MatchEvent for App {\n    fn handle_startup(&mut self, cx: &mut Cx) {\n        \u002F\u002F Called once on app startup\n        self.initialize(cx);\n    }\n\n    fn handle_actions(&mut self, cx: &mut Cx, actions: &Actions) {\n        for action in actions {\n            \u002F\u002F Pattern 1: Direct downcast for non-widget actions\n            if let Some(action) = action.downcast_ref::\u003CLoginAction>() {\n                match action {\n                    LoginAction::LoginSuccess => {\n                        self.app_state.logged_in = true;\n                        self.update_ui_visibility(cx);\n                    }\n                    LoginAction::LoginFailure(error) => {\n                        self.show_error(cx, error);\n                    }\n                }\n                continue;  \u002F\u002F Action handled\n            }\n\n            \u002F\u002F Pattern 2: Widget action cast\n            if let MessageAction::OpenContextMenu { details, abs_pos } =\n                action.as_widget_action().cast()\n            {\n                self.show_context_menu(cx, details, abs_pos);\n                continue;\n            }\n\n            \u002F\u002F Pattern 3: Match on downcast_ref for enum variants\n            match action.downcast_ref() {\n                Some(AppStateAction::RoomFocused(room)) => {\n                    self.app_state.selected_room = Some(room.clone());\n                    continue;\n                }\n                Some(AppStateAction::NavigateToRoom { destination }) => {\n                    self.navigate_to_room(cx, destination);\n                    continue;\n                }\n                _ => {}\n            }\n\n            \u002F\u002F Pattern 4: Modal actions\n            match action.downcast_ref() {\n                Some(ModalAction::Open { kind }) => {\n                    self.ui.modal(ids!(my_modal)).open(cx);\n                    continue;\n                }\n                Some(ModalAction::Close { was_internal }) => {\n                    if *was_internal {\n                        self.ui.modal(ids!(my_modal)).close(cx);\n                    }\n                    continue;\n                }\n                _ => {}\n            }\n        }\n    }\n}\n\nimpl AppMain for App {\n    fn handle_event(&mut self, cx: &mut Cx, event: &Event) {\n        \u002F\u002F Forward to MatchEvent\n        self.match_event(cx, event);\n\n        \u002F\u002F Pass events to widget tree\n        let scope = &mut Scope::with_data(&mut self.app_state);\n        self.ui.handle_event(cx, event, scope);\n    }\n}\n```\n\n## Action Types\n\n### Widget Actions (UI Thread)\n\nEmitted by widgets, handled in the same frame:\n\n```rust\n\u002F\u002F Emitting\ncx.widget_action(\n    self.widget_uid(),\n    &scope.path,\n    MyAction::Something,\n);\n\n\u002F\u002F Handling (two patterns)\n\u002F\u002F Pattern A: Direct cast for widget actions\nif let MyAction::Something = action.as_widget_action().cast() {\n    \u002F\u002F handle...\n}\n\n\u002F\u002F Pattern B: With widget UID matching\nif let Some(uid) = action.as_widget_action().widget_uid() {\n    if uid == my_expected_uid {\n        if let MyAction::Something = action.as_widget_action().cast() {\n            \u002F\u002F handle...\n        }\n    }\n}\n```\n\n### Posted Actions (From Async)\n\nPosted from async tasks, received in next event cycle:\n\n```rust\n\u002F\u002F In async task\nCx::post_action(DataFetchedAction { data });\nSignalToUI::set_ui_signal();  \u002F\u002F Wake UI thread\n\n\u002F\u002F Handling in App (NOT widget actions)\nif let Some(action) = action.downcast_ref::\u003CDataFetchedAction>() {\n    self.process_data(&action.data);\n}\n```\n\n### Global Actions\n\nFor app-wide state changes:\n\n```rust\n\u002F\u002F Using cx.action() for global actions\ncx.action(NavigationAction::GoBack);\n\n\u002F\u002F Handling\nif let Some(NavigationAction::GoBack) = action.downcast_ref() {\n    self.navigate_back(cx);\n}\n```\n\n## Event Handling Patterns\n\n### Hit Testing\n\n```rust\nimpl Widget for MyWidget {\n    fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) {\n        let area = self.view.area();\n        match event.hits(cx, area) {\n            Hit::FingerDown(fe) => {\n                cx.set_key_focus(area);\n                \u002F\u002F Start drag, capture, etc.\n            }\n            Hit::FingerUp(fe) => {\n                if fe.is_over && fe.is_primary_hit() {\n                    if fe.was_tap() {\n                        \u002F\u002F Single tap\n                    }\n                    if fe.was_long_press() {\n                        \u002F\u002F Long press\n                    }\n                }\n            }\n            Hit::FingerMove(fe) => {\n                \u002F\u002F Drag handling\n            }\n            Hit::FingerHoverIn(_) => {\n                self.animator_play(cx, id!(hover.on));\n            }\n            Hit::FingerHoverOut(_) => {\n                self.animator_play(cx, id!(hover.off));\n            }\n            Hit::FingerScroll(se) => {\n                \u002F\u002F Scroll handling\n            }\n            _ => {}\n        }\n    }\n}\n```\n\n### Keyboard Events\n\n```rust\nfn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) {\n    if let Event::KeyDown(ke) = event {\n        match ke.key_code {\n            KeyCode::Return if !ke.modifiers.shift => {\n                self.submit(cx);\n            }\n            KeyCode::Escape => {\n                self.cancel(cx);\n            }\n            KeyCode::KeyC if ke.modifiers.control || ke.modifiers.logo => {\n                self.copy_to_clipboard(cx);\n            }\n            _ => {}\n        }\n    }\n}\n```\n\n### Signal Events\n\nFor handling async updates:\n\n```rust\nfn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) {\n    if let Event::Signal = event {\n        \u002F\u002F Poll update queues\n        while let Some(update) = PENDING_UPDATES.pop() {\n            self.apply_update(cx, update);\n        }\n    }\n}\n```\n\n## Action Chaining Pattern\n\nWidget emits action → Parent catches and re-emits with more context:\n\n```rust\n\u002F\u002F In child widget\ncx.widget_action(\n    self.widget_uid(),\n    &scope.path,\n    ItemAction::Selected(item_id),\n);\n\n\u002F\u002F In parent widget's handle_event\nif let ItemAction::Selected(item_id) = action.as_widget_action().cast() {\n    \u002F\u002F Add context and forward to App\n    cx.widget_action(\n        self.widget_uid(),\n        &scope.path,\n        ListAction::ItemSelected {\n            list_id: self.list_id.clone(),\n            item_id,\n        },\n    );\n}\n```\n\n## Best Practices\n\n1. **Use `DefaultNone` derive**: All action enums must have a `None` variant\n2. **Use `continue` after handling**: Prevents unnecessary processing\n3. **Downcast pattern for async actions**: Posted actions are not widget actions\n4. **Widget action cast for UI actions**: Use `as_widget_action().cast()`\n5. **Always call `SignalToUI::set_ui_signal()`**: After posting actions from async\n6. **Centralize in App::handle_actions**: Keep action handling in one place\n7. **Use descriptive action names**: `MessageAction::Reply` not `MessageAction::Action1`\n\n## Reference Files\n\n- `references\u002Faction-patterns.md` - Additional action patterns (Robrix)\n- `references\u002Fevent-handling.md` - Event handling reference (Robrix)\n- `references\u002Fmoly-action-patterns.md` - Moly-specific patterns\n  - Store-based action forwarding\n  - Timer-based retry pattern\n  - Radio button navigation\n  - External link handling\n  - Platform-conditional actions (#[cfg])\n  - UiRunner event handling\n\n## Limitations\n- Use this skill only when the task clearly matches the scope described above.\n- Do not treat the output as a substitute for environment-specific validation, testing, or expert review.\n- Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.\n","","imported","https:\u002F\u002Fgithub.com\u002Fsickn33\u002Fantigravity-awesome-skills","user_system_seed","SkillOPIC",true,105,708,"2026-05-16 13:37:19",{"id":8,"name":21,"slug":22,"icon":23,"description":24,"sort":25,"createdAt":26},"其他","other","mdi-page-next-outline","其他类型Skill",5,"2026-05-16 12:53:40",{"id":7,"name":28,"slug":29,"icon":30,"description":31,"moduleId":8,"sort":32,"skillCount":33,"createdAt":26},"职场发展","career","mdi-briefcase-outline","面试准备、简历优化、职业规划",4,575,[35],{"id":36,"skillId":4,"version":37,"fileName":38,"fileSize":39,"filePath":40,"fileHash":41,"manifest":42,"createdAt":19},"e5a754f7-b178-4022-8ed3-20b810ecd930","1.0.0","robius-event-action.zip",3349,"uploads\u002Fskills\u002Ff0869511-8d63-4441-9348-5d01d9ca1938\u002Frobius-event-action.zip","04a5c731996135697c0a9a980f529e6ae3f5440ce335bc65dd3c81477909bdd2","[{\"path\":\"SKILL.md\",\"isDirectory\":false,\"size\":10547}]",{"code":44,"message":45,"data":46},200,"success",{"items":47,"stats":48,"page":51},[],{"averageRating":49,"totalRatings":49,"ratingCounts":50},0,[49,49,49,49,49],{"limit":52,"offset":49,"hasMore":53,"nextOffset":52,"ratedOnly":16},15,false]