Widget 渲染机制

绘制流程控制

DrawStep 状态机

1pub trait DrawStepApi {
2    fn done() -> DrawStep {
3        Result::Ok(())
4    }
5    fn make_step_here(arg: WidgetRef) -> DrawStep {
6        Result::Err(arg)
7    }
8    fn make_step() -> DrawStep {
9        Result::Err(WidgetRef::empty())
10    }
11    fn is_done(&self) -> bool;
12    fn is_step(&self) -> bool;
13    fn step(self) -> Option<WidgetRef>;
14}
15
16impl DrawStepApi for DrawStep {
17    fn is_done(&self) -> bool {
18        match *self {
19            Result::Ok(_) => true,
20            Result::Err(_) => false,
21        }
22    }
23    fn is_step(&self) -> bool {
24        match *self {
25            Result::Ok(_) => false,
26            Result::Err(_) => true,
27        }
28    }
29
30    fn step(self) -> Option<WidgetRef> {
31        match self {
32            Result::Ok(_) => None,
33            Result::Err(nd) => Some(nd),
34        }
35    }
36}
37
38pub type DrawStep = Result<(), WidgetRef>;

基于 Result 设计状态机的优势:

1.支持增量渲染

  • Result::Err(WidgetRef) 表示渲染未完成,需要继续下一步
  • Result::Ok(()) 表示渲染完成
  • 这种设计让渲染过程可以被分成多个步骤执行

2.状态保持

  • 使用 Result 可以在每个渲染步骤之间保持状态
  • Err(WidgetRef) 携带了下一步需要渲染的组件信息

3.控制流管理

  • 通过 ? 运算符优雅地处理渲染流程
  • 容易实现渲染的暂停、继续和终止
  • 易于优化,支持复杂交互

4.内存效率

  • Result 是零成本抽象
  • 状态机切换不会产生额外开销

绘制流程

下面通过一个简单 Button widget 来说明绘制流程:

1pub struct Button {
2    // 绘制状态机
3    #[rust] draw_state: DrawStateWrap<DrawState>,
4    // 布局信息
5    #[layout] layout: Layout,
6    // 定位信息
7    #[walk] walk: Walk,
8    // 背景绘制
9    #[live] draw_bg: DrawColor,
10    // 文本绘制
11    #[live] draw_text: DrawText,
12}
13
14impl Widget for Button {
15    fn draw_walk(&mut self, cx: &mut Cx2d, scope: &mut Scope, walk: Walk) -> DrawStep {
16        // 1. 初始化绘制状态
17        if self.draw_state.begin(cx, DrawState::DrawBackground) {
18            // 开始布局计算
19            self.draw_bg.begin(cx, walk, self.layout);
20            return DrawStep::make_step();
21        }
22
23        // 2. 绘制背景
24        if let Some(DrawState::DrawBackground) = self.draw_state.get() {
25            self.draw_bg.end(cx);
26            // 切换到文本绘制状态
27            self.draw_state.set(DrawState::DrawText);
28            return DrawStep::make_step();
29        }
30
31        // 3. 绘制文本
32        if let Some(DrawState::DrawText) = self.draw_state.get() {
33            let text_walk = Walk::size(Size::Fill, Size::Fit);
34            self.draw_text.draw_walk(cx, scope, text_walk)?;
35            self.draw_state.end();
36        }
37
38        DrawStep::done()
39    }
40}
41
42// 绘制状态枚举
43#[derive(Clone)]
44enum DrawState {
45    DrawBackground,
46    DrawText
47}

关键点解析:

  1. 状态管理
  • DrawStateWrap 管理绘制状态
  • 每个状态对应一个绘制阶段
  • 可以在任何阶段中断和恢复
  1. 布局系统
1// 设置布局
2self.draw_bg.begin(cx, walk, self.layout);
3
4// Turtle 会自动处理:
5// - 元素位置计算
6// - Margin/Padding 处理
7// - 子元素布局
  1. 渐进式绘制
1// 返回继续标记
2return DrawStep::make_step();
3
4// 返回完成标记
5return DrawStep::done();
  1. 状态转换
1// 切换到下一个状态
2self.draw_state.set(DrawState::DrawText);
3
4// 结束绘制
5self.draw_state.end();

绘制模型

Makepad 采用了一个复杂但高效的延迟绘制系统。

这个系统的核心特点是:绘制命令不会立即执行,而是被收集到绘制列表(DrawList)中,最后统一处理。

DrawList 是 Makepad 绘制系统的核心。让我们看看它的结构:

1// 在 platform/src/draw_list.rs 中
2pub struct CxDrawList {
3    pub debug_id: LiveId,
4    pub codeflow_parent_id: Option<DrawListId>,
5    pub redraw_id: u64,
6    pub pass_id: Option<PassId>,
7    pub draw_items: CxDrawItems,
8    pub draw_list_uniforms: CxDrawListUniforms,
9    pub rect_areas: Vec<CxRectArea>,
10}
11
12pub struct CxDrawItems {
13    pub(crate) buffer: Vec<CxDrawItem>,
14    used: usize
15}
16
17pub struct CxDrawItem {
18    pub redraw_id: u64,
19    pub kind: CxDrawKind,
20    pub draw_item_id: usize,
21    pub instances: Option<Vec<f32>>,
22    pub os: CxOsDrawCall
23}

当我们调用 redraw 命令时,实际发生的是:

1impl DrawQuad {
2    pub fn draw(&mut self, cx: &mut Cx2d) {
3        // 1. 不是立即绘制,而是收集绘制命令
4        if let Some(mi) = &mut self.many_instances {
5            // 批处理模式:将实例数据添加到缓冲区
6            // 这允许多个相似的绘制操作被批量处理,大大减少 GPU 调用次数
7            mi.instances.extend_from_slice(self.draw_vars.as_slice());
8        }
9        else if self.draw_vars.can_instance() {
10            // 单实例模式:创建新的实例
11            let new_area = cx.add_aligned_instance(&self.draw_vars);
12            self.draw_vars.area = cx.update_area_refs(self.draw_vars.area, new_area);
13        }
14    }
15}

绘制命令合并:

1impl CxDrawList {
2    pub fn find_appendable_drawcall(
3        &mut self,
4        sh: &CxDrawShader,
5        draw_vars: &DrawVars
6    ) -> Option<usize> {
7        // 尝试找到可以合并的绘制调用
8        if let Some((_,draw_call)) = self.draw_items.iter_mut()
9            .find(|item| item.can_append(sh, draw_vars)) {
10            return Some(draw_call);
11        }
12        None
13    }
14}

视图优化:

1enum ViewOptimize {
2    None,
3    DrawList,    // 使用独立绘制列表
4    Texture     // 渲染到纹理缓存
5}

比如 Makepad 内置的 View Widget ,就用了 ViewOptimize 优化。

1.DrawList 模式会为视图创建一个独立的绘制列表,这个列表可以被缓存和重用。

适用场景:

  • 内容变化频率适中的界面
  • 需要保持交互响应性的复杂视图
  • 包含大量子元素的容器

2.Texture 模式会将整个视图渲染到一个纹理中,然后将这个纹理作为一个整体来使用。

适用场景:

  • 静态或很少变化的内容
  • 视觉效果复杂但内容相对稳定的视图
  • 需要特殊视觉效果的界面(如模糊、变换等)

在实践中,可以合理划分视图层级。

1// 推荐的视图层级结构
2RootView (No Optimization)
3├── StaticBackground (Texture)
4├── ContentArea (DrawList)
5│   ├── StaticWidgets (Texture)
6│   └── DynamicWidgets (DrawList)
7└── OverlayLayer (No Optimization)

绘制区域(Area)管理

在 Makepad 中,区域管理的核心是 Area 类型,它用于跟踪和管理 widget 在屏幕上的绘制区域。

每个 Widget 都有一个关联的 Area,这个 Area 不仅用于确定绘制位置,还用于事件处理(Event)和命中(Hit)检测。

1// 核心区域类型定义
2#[derive(Clone, Copy, Debug)]
3pub enum Area {
4    Empty,
5    Instance(InstanceArea), // 实例区域(用于渲染实例)
6    Rect(RectArea) // 矩形区域(用于基础图形)
7}
8
9pub struct RectArea {
10    pub draw_list_id: DrawListId,
11    pub redraw_id: u64,
12    pub rect_id: usize
13}
14
15pub struct InstanceArea {
16    pub draw_list_id: DrawListId,
17    pub draw_item_id: usize,
18    pub instance_count: usize,
19    pub instance_offset: usize,
20    pub redraw_id: u64
21}

Area 的生命周期如下图:

Area 管理的核心实现如下:

  • 区域创建和分配
1impl Widget {
2    fn draw_walk(&mut self, cx: &mut Cx2d, scope: &mut Scope, walk: Walk) -> DrawStep {
3        // 从布局计算开始
4        if self.draw_state.begin(cx, DrawState::Begin) {
5            // 开始布局计算
6            cx.begin_turtle(walk, self.layout);
7
8            // 区域分配
9            cx.walk_turtle_with_area(&mut self.area, walk);
10
11            // 区域更新和引用管理
12            self.area = cx.update_area_refs(self.area, new_area);
13        }
14    }
15}
  • 区域更新和追踪
1impl Cx2d {
2    pub fn update_area_refs(&mut self, old_area: Area, new_area: Area) -> Area {
3        if old_area == Area::Empty {
4            return new_area;
5        }
6
7        // 更新IME区域
8        if self.ime_area == old_area {
9            self.ime_area = new_area;
10        }
11
12        // 更新手指事件区域
13        self.fingers.update_area(old_area, new_area);
14
15        // 更新拖放区域
16        self.drag_drop.update_area(old_area, new_area);
17
18        // 更新键盘焦点区域
19        self.keyboard.update_area(old_area, new_area);
20
21        new_area
22    }
23}
  • 区域裁剪,用于控制了内容的可见范围,维护视觉边界,处理内容溢出。
    • 对于实例区域(Instance):裁剪信息存储在着色器的绘制调用统一变量中
    • 对于矩形区域(Rect):裁剪信息直接存储在 RectArea 结构体中:
1pub struct CxRectArea {
2    pub rect: Rect,                // 矩形本身
3    pub draw_clip: (DVec2, DVec2)  // 存储裁剪范围的最小点和最大点
4}

裁剪实现细节核心的裁剪功能在 Area 源码中的 clipped_rect() 方法中实现。

对于实例区域(Instance):

  1. 裁剪边界以四个值(minX, minY, maxX, maxY)的形式存储在着色器统一变量中
  2. 在绘制时,着色器会应用这些裁剪边界来限制像素的绘制范围
  3. 裁剪在顶点着色器阶段通过限制顶点位置来实现

对于矩形区域(Rect):

  1. 裁剪边界直接存储在 RectArea 结构中
  2. 裁剪通过矩形与其裁剪边界的求交来实现
  3. 这会生成一个表示可见部分的新矩形

事件处理

Makepad的事件系统分为几个层级:

1// 顶层事件枚举
2pub enum Event {
3    FingerDown(FingerDownEvent),
4    FingerUp(FingerUpEvent),
5    FingerMove(FingerMoveEvent),
6    KeyDown(KeyEvent),
7    KeyUp(KeyEvent),
8    // ...
9}
10
11// 事件命中检测结果
12pub enum Hit {
13    KeyFocus(KeyFocusEvent),
14    FingerDown(FingerDownEvent),
15    Nothing
16}

事件的分发遵循以下流程:

1impl Widget for MyWidget {
2    fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) {
3        // 检查事件是否命中当前widget的area
4        match event.hits(cx, self.area()) {
5            Hit::FingerDown(e) => {
6                // 处理点击事件
7                cx.widget_action(uid, &scope.path, MyAction::Clicked);
8            }
9            Hit::KeyDown(e) => {
10                // 处理键盘事件
11            }
12            _ => ()
13        }
14    }
15}

下面是一个简单的 Button 事件处理的示例:

1#[derive(Live)]
2pub struct Button {
3    #[rust] pub area: Area,
4    #[live] pub text: String,
5    #[animator] pub animator: Animator,
6}
7
8impl Widget for Button {
9    fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) {
10        let uid = self.widget_uid();
11
12        // 处理动画事件
13        if self.animator_handle_event(cx, event).must_redraw() {
14            self.redraw(cx);
15        }
16
17        match event.hits(cx, self.area()) {
18            Hit::FingerDown(_) => {
19                // 触发点击动画
20                self.animator_play(cx, id!(down.on));
21                // 发送点击事件action
22                cx.widget_action(uid, &scope.path, ButtonAction::Clicked);
23            }
24            Hit::FingerUp(_) => {
25                self.animator_play(cx, id!(down.off));
26            }
27            _ => ()
28        }
29    }
30}

事件系统

事件系统分层架构:

  • 底层事件系统(Event) - 处理系统和UI基础事件
  • 中层动作系统(Action) - 处理组件间通信和状态更新
  • 上层消息系统(Signal/Channel) - 处理跨线程通信

Makepad 的线程模型

Makepad 的线程模型

Makepad 分为主 UI 线程和其他多个 Worker 线程

  1. 单一UI渲染线程
  • UI 渲染和事件处理在主线程进行
  • 主线程运行事件循环(event_loop)
  • 所有 UI 更新必须发生在主线程
  1. 多个 Worker 线程
  • 通过线程池管理后台任务
  • 提供多种线程池实现以满足不同需求
  • 工作线程不直接操作 UI
  1. 线程间通信
  • Action 系统用于线程间消息传递
  • Signal 机制用于线程同步
  • Channel 用于数据传输

主线程(UI线程)模型:

1// app_main 宏中定义了主线程入口
2pub fn app_main() {
3    // 创建 Cx
4    let mut cx = Rc::new(RefCell::new(Cx::new(Box::new(move |cx, event| {
5        // 主要事件处理循环
6        if let Event::Startup = event {
7            *app.borrow_mut() = Some($app::new_main(cx));
8        }
9        if let Event::LiveEdit = event {
10            app.borrow_mut().update_main(cx);
11        }
12        app.borrow_mut().as_mut().unwrap().handle_event(cx, event);
13    }))));
14
15    // 注册组件、初始化等
16    $app::register_main_module(&mut *cx.borrow_mut());
17    live_design(&mut *cx.borrow_mut());
18    cx.borrow_mut().init_cx_os();
19
20    // 启动事件循环
21    Cx::event_loop(cx);
22}

线程间通信机制:

1// 全局 Action 发送通道
2static ACTION_SENDER_GLOBAL: Mutex<Option<Sender<ActionSendSync>>> = Mutex::new(None);
3
4// UI 信号机制
5pub struct SignalToUI(Arc<AtomicBool>);
6
7// 线程通信 Receiver/Sender
8pub struct ToUIReceiver<T> {
9    sender: Sender<T>,
10    pub receiver: Receiver<T>,
11}
12
13pub struct ToUISender<T> {
14    sender: Sender<T>,
15}

包含了线程池:

1// 标准线程池,用于简单的任务执行
2pub struct RevThreadPool {
3    tasks: Arc<Mutex<Vec<Box<dyn FnOnce() + Send + 'static>>>>,
4}
5
6// 带标签的线程池,用于需要分类和取消的任务
7pub struct TagThreadPool<T: Clone + Send + 'static + PartialEq> {
8    tasks: Arc<Mutex<Vec<(T, Box<dyn FnOnce(T) + Send + 'static>)>>>,
9}
10
11// 消息线程池,用于线程间持续通信
12pub struct MessageThreadPool<T: Clone + Send + 'static> {
13    sender: Sender<Box<dyn FnOnce(Option<T>) + Send + 'static>>,
14    msg_senders: Vec<Sender<T>>,
15}

主要通信流:

1// 1. 工作线程发送 Action 到主线程
2Cx::post_action(action); // 通过全局 ACTION_SENDER 发送
3
4// 2. 主线程处理接收到的 Action
5impl Cx {
6    pub fn handle_action_receiver(&mut self) {
7        while let Ok(action) = self.action_receiver.try_recv() {
8            self.new_actions.push(action);
9        }
10        self.handle_actions();
11    }
12}
13
14// 3. UI 状态更新通知
15SignalToUI::set_ui_signal(); // 通知 UI 需要更新

事件系统概览

Makepad 提供 Event 机制,用于自底向上传播(由系统/框架分发给组件)来自系统底层的事件(如鼠标、键盘、触摸等)。

Event 是同步处理的全局性事件。

1pub enum Event {
2    // 应用生命周期事件
3    Startup,
4    Shutdown,
5    Foreground,
6    Background,
7    Resume,
8    Pause,
9
10    // UI交互事件
11    Draw(DrawEvent),
12    MouseDown(MouseDownEvent),
13    MouseMove(MouseMoveEvent),
14    KeyDown(KeyEvent),
15    TextInput(TextInputEvent),
16
17    // 自定义事件
18    Signal,  // 用于线程间通讯
19    Actions(ActionsBuf), // 自定义动作的容器
20    Timer(TimerEvent),  // 定时器事件
21}

此外,Makepad 也提供 Action 机制,用于自顶向下传播(由组件发送给父组件/监听者)的内部业务动作。

这些 Action 是可以同步也可以异步。

总结 Event 和 Action 区别

  • Event 是系统级别的输入事件,自底而上传播底层事件。
  • Action 是组件级别的业务动作,自顶而下传播业务动作。
1// Action 特征定义
2pub trait ActionTrait: 'static {
3    fn debug_fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result;
4    fn ref_cast_type_id(&self) -> TypeId;
5}
6
7// 具体 Action 示例,定义 Button 的业务代码
8#[derive(Clone, Debug)]
9pub enum ButtonAction {
10    Clicked,
11    Pressed,
12    Released
13}

Makepad 通过 widget_action 提供了一个统一的 Action 发送和处理机制,并且 Action 可以携带数据和状态。

1// Action的包装结构
2pub struct WidgetAction {
3    pub action: Box<dyn WidgetActionTrait>,
4    pub data: Option<Arc<dyn ActionTrait>>, // 关联数据
5    pub widgets: SmallVec<[WidgetRef;4]>, // 发送action的组件引用
6    pub widget_uid: WidgetUid,  // 组件唯一ID
7    pub path: HeapLiveIdPath,   // 组件路径
8    pub group: Option<WidgetActionGroup> // 分组信息
9}
10
11// Action分组信息
12pub struct WidgetActionGroup {
13    pub group_uid: WidgetUid,
14    pub item_uid: WidgetUid,
15}

组件通过 widget_action 发送 Action:

1impl WidgetActionCxExt for Cx {
2    // 发送一个简单的action
3    fn widget_action(
4        &mut self,
5        widget_uid: WidgetUid,  // 组件ID
6        path: &HeapLiveIdPath,  // 组件路径
7        t: impl WidgetActionTrait // Action内容
8    ) {
9        self.action(WidgetAction {
10            widget_uid,
11            data: None,
12            path: path.clone(),
13            widgets: Default::default(),
14            action: Box::new(t),
15            group: None,
16        })
17    }
18
19    // 发送带数据的action
20    fn widget_action_with_data(
21        &mut self,
22        action_data: &WidgetActionData,
23        widget_uid: WidgetUid,
24        path: &HeapLiveIdPath,
25        t: impl WidgetActionTrait,
26    ) {
27        self.action(WidgetAction {
28            widget_uid,
29            data: action_data.clone_data(),
30            path: path.clone(),
31            widgets: Default::default(),
32            action: Box::new(t),
33            group: None,
34        })
35    }
36}
37
38#[derive(Default)]
39pub struct WidgetActionData{
40    data: Option<Arc<dyn ActionTrait>>
41}

Action 被收集到 contextaction buffer :

1impl Cx {
2    pub fn action(&mut self, action: impl ActionTrait) {
3        self.new_actions.push(Box::new(action));
4    }
5}

接收方获取所有 Actions:

1// 捕获某个事件处理过程中产生的所有actions
2let actions = cx.capture_actions(|cx| {
3    self.button.handle_event(cx, event, scope);
4});

再查找特定的 Action :

1impl WidgetActionsApi for Actions {
2    // 通过组件路径查找action
3    fn widget_action(&self, path: &[LiveId]) -> Option<&WidgetAction> {
4        for action in self {
5            if let Some(action) = action.downcast_ref::<WidgetAction>() {
6                let mut ap = action.path.data.iter().rev();
7                if path.iter().rev().all(|p| ap.find(|&ap| p == ap).is_some()) {
8                    return Some(action)
9                }
10            }
11        }
12        None
13    }
14
15    // 通过组件ID查找action
16    fn find_widget_action(&self, widget_uid: WidgetUid) -> Option<&WidgetAction> {
17        for action in self {
18            if let Some(action) = action.downcast_ref::<WidgetAction>() {
19                if action.widget_uid == widget_uid {
20                    return Some(action);
21                }
22            }
23        }
24        None
25    }
26}

Action 的类型转换与处理:

1// 示例: 处理按钮点击事件
2impl ButtonRef {
3    pub fn clicked(&self, actions: &Actions) -> bool {
4        if let ButtonAction::Clicked = actions.find_widget_action(self.widget_uid()).cast() {
5            return true
6        }
7        false
8    }
9}
10
11// 使用示例
12let actions = cx.capture_actions(|cx| {
13    self.button.handle_event(cx, event, scope);
14});
15
16if self.button.clicked(&actions) {
17    // 处理点击事件
18}

这套机制让 Makepad 的组件能够灵活地进行状态传递和事件通信,同时保持了良好的解耦性和可维护性。

事件处理流程

因为 Widget trait 中的 handle_event 主要关注两个方面:

1impl Widget for MyWidget {
2    fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) {
3        // 1. 处理区域内的命中测试事件(点击、拖拽等)
4        match event.hits(cx, self.area()) {
5            Hit::FingerDown(e) => { ... }
6            Hit::KeyDown(e) => { ... }
7        }
8
9        // 2. 处理动画相关事件
10        if self.animator_handle_event(cx, event).must_redraw() {
11            self.draw_key.area().redraw(cx)
12        }
13    }
14}

但实际有很多事件是与 Area 无关的,比如:

  • 生命周期事件(启动、关闭)
  • 全局事件(前台、后台切换)
  • Action 处理
  • 绘制事件
  • 动画帧更新

如果这些事件都在每个 Widget 中处理的话, match event 分支就会很冗余。

1// 不用 MatchEvent
2impl Widget for MyWidget {
3    fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) {
4        match event {
5            Event::Startup => { ... }
6            Event::Draw(e) => { ... }
7            Event::NextFrame(e) => { ... }
8            Event::Actions(e) => { ... }
9            // 还要处理命中测试
10            _ => match event.hits(cx, self.area()) {
11                Hit::FingerDown(e) => { ... }
12            }
13        }
14    }
15}

所以,Makepad 提供 MatchEvent trait,来提供一系列默认实现的方法,让代码更清晰:

1#[derive(Default)]
2struct MyComplexWidget {
3    area: Area,
4    value: f64,
5    animator: Animator
6}
7
8// Widget trait 处理核心交互逻辑
9impl Widget for MyComplexWidget {
10    fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) {
11        let uid = self.widget_uid();
12
13        // 1. 动画处理
14        if self.animator_handle_event(cx, event).must_redraw() {
15            self.redraw(cx);
16        }
17
18        // 2. 交互事件处理
19        match event.hits(cx, self.area) {
20            Hit::FingerDown(_) => {
21                self.animator_play(cx, id!(down.on));
22                cx.widget_action(uid, &scope.path, MyAction::Clicked);
23            }
24            Hit::KeyDown(ke) => {
25                // 键盘事件处理
26            }
27            _ => ()
28        }
29
30        // 3. 使用 MatchEvent 处理其他事件
31        self.match_event(cx, event);
32    }
33}
34
35// MatchEvent trait 处理业务逻辑
36impl MatchEvent for MyComplexWidget {
37    // 生命周期事件
38    fn handle_startup(&mut self, cx: &mut Cx) {
39        // 初始化配置
40    }
41
42    // 状态更新
43    fn handle_actions(&mut self, cx: &mut Cx, actions: &Actions) {
44        for action in actions {
45            if let MyAction::ValueChanged(new_value) = action.cast() {
46                self.value = new_value;
47                self.redraw(cx);
48            }
49        }
50    }
51
52    // 绘制相关
53    fn handle_draw_2d(&mut self, cx: &mut Cx2d) {
54        // 自定义绘制逻辑
55    }
56
57    // 动画帧
58    fn handle_next_frame(&mut self, cx: &mut Cx, e: &NextFrameEvent) {
59        // 动画更新
60    }
61}

这样组件就可以专注于实现自己关注的事件处理逻辑,而不用写大量的匹配代码。

Makepad 中事件处理优先级如下:

  1. 动画事件 (Animator)
  2. 直接交互事件 (Hit)
  3. 通用系统事件 (MatchEvent)
  4. 业务 (Actions)

Signal 机制

1// UI信号机制
2pub struct SignalToUI(Arc<AtomicBool>);
3
4impl SignalToUI {
5    // 设置UI信号
6    pub fn set_ui_signal() {
7        UI_SIGNAL.store(true, Ordering::SeqCst)
8    }
9
10    // 检查并清除信号
11    pub fn check_and_clear(&self) -> bool {
12        self.0.swap(false, Ordering::SeqCst)
13    }
14}
15
16// UI消息通道
17pub struct ToUIReceiver<T> {
18    sender: Sender<T>,
19    pub receiver: Receiver<T>,
20}
21
22pub struct ToUISender<T> {
23    sender: Sender<T>,
24}