Robius事件与动作模式技能Skill robius-event-action

本技能专注于在Makepad应用程序中实现事件处理和动作模式的最佳实践,基于Robrix和Moly代码库。它提供了自定义动作定义、小部件事件处理、集中动作处理以及异步动作集成等模式,适用于UI开发和前端应用构建。关键词:Makepad事件处理,动作模式,UI开发,Rust编程,Robrix,Moly,小部件动作,异步处理,前端框架。

前端开发 0 次安装 0 次浏览 更新于 3/13/2026

名称: robius-event-action 描述: | 关键:用于Robius事件和动作模式。触发条件: 自定义动作,MatchEvent,post_action,cx.widget_action, handle_actions,DefaultNone,小部件动作,事件处理, 事件处理,自定义动作

Robius事件与动作模式技能

基于Robrix和Moly代码库的Makepad应用程序中事件处理和动作模式的最佳实践。

源代码库:

  • Robrix:Matrix聊天客户端 - MessageAction,RoomsListAction,AppStateAction
  • Moly:AI聊天应用程序 - StoreAction,ChatAction,NavigationAction,计时器模式

触发条件

在以下情况下使用此技能:

  • 在Makepad中实现自定义动作
  • 在小部件中处理事件
  • 在应用程序中集中处理动作
  • 小部件间通信
  • 关键词:makepad action, makepad event, widget action, handle_actions, cx.widget_action

自定义动作模式

定义领域特定动作

use makepad_widgets::*;

/// 由Message小部件发出的动作
#[derive(Clone, DefaultNone, Debug)]
pub enum MessageAction {
    /// 用户想对消息做出反应
    React { details: MessageDetails, reaction: String },
    /// 用户想回复消息
    Reply(MessageDetails),
    /// 用户想编辑消息
    Edit(MessageDetails),
    /// 用户想删除消息
    Delete(MessageDetails),
    /// 用户请求打开上下文菜单
    OpenContextMenu { details: MessageDetails, abs_pos: DVec2 },
    /// 必需的默认变体
    None,
}

/// 与消息动作关联的数据
#[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() {
                    // 发出小部件动作
                    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,
                    },
                );
            }
            _ => {}
        }
    }
}

应用程序中集中动作处理

使用MatchEvent特性

impl MatchEvent for App {
    fn handle_startup(&mut self, cx: &mut Cx) {
        // 在应用启动时调用一次
        self.initialize(cx);
    }

    fn handle_actions(&mut self, cx: &mut Cx, actions: &Actions) {
        for action in actions {
            // 模式1:非小部件动作的直接向下转型
            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;  // 动作已处理
            }

            // 模式2:小部件动作转换
            if let MessageAction::OpenContextMenu { details, abs_pos } =
                action.as_widget_action().cast()
            {
                self.show_context_menu(cx, details, abs_pos);
                continue;
            }

            // 模式3:对枚举变体进行向下转型匹配
            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;
                }
                _ => {}
            }

            // 模式4:模态动作
            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) {
        // 转发到MatchEvent
        self.match_event(cx, event);

        // 将事件传递给小部件树
        let scope = &mut Scope::with_data(&mut self.app_state);
        self.ui.handle_event(cx, event, scope);
    }
}

动作类型

小部件动作(UI线程)

由小部件发出,在同一帧中处理:

// 发出
cx.widget_action(
    self.widget_uid(),
    &scope.path,
    MyAction::Something,
);

// 处理(两种模式)
// 模式A:对小部件动作的直接转换
if let MyAction::Something = action.as_widget_action().cast() {
    // 处理...
}

// 模式B:带有小部件UID匹配
if let Some(uid) = action.as_widget_action().widget_uid() {
    if uid == my_expected_uid {
        if let MyAction::Something = action.as_widget_action().cast() {
            // 处理...
        }
    }
}

发布动作(来自异步)

从异步任务发布,在下一次事件周期中接收:

// 在异步任务中
Cx::post_action(DataFetchedAction { data });
SignalToUI::set_ui_signal();  // 唤醒UI线程

// 在应用程序中处理(不是小部件动作)
if let Some(action) = action.downcast_ref::<DataFetchedAction>() {
    self.process_data(&action.data);
}

全局动作

用于应用范围的状态变更:

// 使用cx.action()进行全局动作
cx.action(NavigationAction::GoBack);

// 处理
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);
                // 开始拖拽,捕获等
            }
            Hit::FingerUp(fe) => {
                if fe.is_over && fe.is_primary_hit() {
                    if fe.was_tap() {
                        // 单点触摸
                    }
                    if fe.was_long_press() {
                        // 长按
                    }
                }
            }
            Hit::FingerMove(fe) => {
                // 拖拽处理
            }
            Hit::FingerHoverIn(_) => {
                self.animator_play(cx, id!(hover.on));
            }
            Hit::FingerHoverOut(_) => {
                self.animator_play(cx, id!(hover.off));
            }
            Hit::FingerScroll(se) => {
                // 滚动处理
            }
            _ => {}
        }
    }
}

键盘事件

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);
            }
            _ => {}
        }
    }
}

信号事件

用于处理异步更新:

fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) {
    if let Event::Signal = event {
        // 轮询更新队列
        while let Some(update) = PENDING_UPDATES.pop() {
            self.apply_update(cx, update);
        }
    }
}

动作链模式

小部件发出动作 → 父组件捕获并重新发出带更多上下文:

// 在子小部件中
cx.widget_action(
    self.widget_uid(),
    &scope.path,
    ItemAction::Selected(item_id),
);

// 在父小部件的handle_event中
if let ItemAction::Selected(item_id) = action.as_widget_action().cast() {
    // 添加上下文并转发到应用程序
    cx.widget_action(
        self.widget_uid(),
        &scope.path,
        ListAction::ItemSelected {
            list_id: self.list_id.clone(),
            item_id,
        },
    );
}

最佳实践

  1. 使用DefaultNone派生:所有动作枚举必须有一个None变体
  2. 处理后使用continue:防止不必要的处理
  3. 异步动作的向下转型模式:发布动作不是小部件动作
  4. UI动作的小部件动作转换:使用as_widget_action().cast()
  5. 始终调用SignalToUI::set_ui_signal():从异步发布动作后
  6. 集中在App::handle_actions:将动作处理集中在一个地方
  7. 使用描述性动作名称MessageAction::Reply而不是MessageAction::Action1

参考文件

  • references/action-patterns.md - 其他动作模式(Robrix)
  • references/event-handling.md - 事件处理参考(Robrix)
  • references/moly-action-patterns.md - Moly特定模式
    • 基于存储的动作转发
    • 基于计时器的重试模式
    • 单选按钮导航
    • 外部链接处理
    • 平台条件动作(#[cfg])
    • UiRunner事件处理