Agent skill
robius-event-action
CRITICAL: Use for Robius event and action patterns. Triggers on: custom action, MatchEvent, post_action, cx.widget_action, handle_actions, DefaultNone, widget action, event handling, 事件处理, 自定义动作
Install this agent skill to your Project
npx add-skill https://github.com/ZhangHanDong/makepad-skills/tree/main/skills/robius-event-action
SKILL.md
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
Triggers
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
DefaultNonederive: All action enums must have aNonevariant - Use
continueafter handling: Prevents unnecessary processing - Downcast pattern for async actions: Posted actions are not widget actions
- Widget action cast for UI actions: Use
as_widget_action().cast() - Always call
SignalToUI::set_ui_signal(): After posting actions from async - Centralize in App::handle_actions: Keep action handling in one place
- Use descriptive action names:
MessageAction::ReplynotMessageAction::Action1
Reference Files
references/action-patterns.md- Additional action patterns (Robrix)references/event-handling.md- Event handling reference (Robrix)references/moly-action-patterns.md- Moly-specific patterns- Store-based action forwarding
- Timer-based retry pattern
- Radio button navigation
- External link handling
- Platform-conditional actions (#[cfg])
- UiRunner event handling
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
makepad-splash
CRITICAL: Use for Makepad Splash scripting language. Triggers on: splash language, makepad script, makepad scripting, script!, cx.eval, makepad dynamic, makepad AI, splash 语言, makepad 脚本
makepad-platform
CRITICAL: Use for Makepad cross-platform support. Triggers on: makepad platform, makepad os, makepad macos, makepad windows, makepad linux, makepad android, makepad ios, makepad web, makepad wasm, makepad metal, makepad d3d11, makepad opengl, makepad webgl, OsType, CxOs, makepad 跨平台, makepad 平台支持
robius-app-architecture
CRITICAL: Use for Robius app architecture patterns. Triggers on: Tokio, async, submit_async_request, 异步, 架构, SignalToUI, Cx::post_action, worker task, app structure, MatchEvent, handle_startup
robius-widget-patterns
CRITICAL: Use for Robius widget patterns. Triggers on: apply_over, TextOrImage, modal, 可复用, 模态, collapsible, drag drop, reusable widget, widget design, pageflip, 组件设计, 组件模式
makepad-reference
CRITICAL: Use for Makepad troubleshooting and reference. Triggers on: troubleshoot, error, debug, fix, problem, issue, no matching field, parse error, widget not found, UI not updating, code quality, refactor, responsive layout, adaptive, api docs, reference, documentation, 故障排除, 错误, 调试, 问题, 修复
makepad-dsl
CRITICAL: Use for Makepad DSL syntax and inheritance. Triggers on: makepad dsl, live_design, makepad inheritance, makepad prototype, "<Widget>", "Foo = { }", makepad object, makepad property, makepad DSL 语法, makepad 继承, makepad 原型, 如何定义 makepad 组件
Didn't find tool you were looking for?