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}
关键点解析:
- 状态管理
- DrawStateWrap 管理绘制状态
- 每个状态对应一个绘制阶段
- 可以在任何阶段中断和恢复
- 布局系统
1// 设置布局
2self.draw_bg.begin(cx, walk, self.layout);
3
4// Turtle 会自动处理:
5// - 元素位置计算
6// - Margin/Padding 处理
7// - 子元素布局
- 渐进式绘制
1// 返回继续标记
2return DrawStep::make_step();
3
4// 返回完成标记
5return DrawStep::done();
- 状态转换
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):
- 裁剪边界以四个值(minX, minY, maxX, maxY)的形式存储在着色器统一变量中
- 在绘制时,着色器会应用这些裁剪边界来限制像素的绘制范围
- 裁剪在顶点着色器阶段通过限制顶点位置来实现
对于矩形区域(Rect):
- 裁剪边界直接存储在
RectArea
结构中
- 裁剪通过矩形与其裁剪边界的求交来实现
- 这会生成一个表示可见部分的新矩形
事件处理
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 的线程模型](https://trusted-heron-8b2.notion.site/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2Fee83deb1-85d6-437c-894c-b4353fce458b%2Fa3942c5c-f498-47e6-bcac-6a82b18d3537%2Fmakepad-thread-model.png?table=block&id=15269449-ef79-8015-8867-fbaf2f6197d5&spaceId=ee83deb1-85d6-437c-894c-b4353fce458b&width=1200&userId=&cache=v2)
Makepad 分为主 UI 线程
和其他多个 Worker 线程
。
- 单一UI渲染线程:
- UI 渲染和事件处理在主线程进行
- 主线程运行事件循环(event_loop)
- 所有 UI 更新必须发生在主线程
- 多个 Worker 线程:
- 通过线程池管理后台任务
- 提供多种线程池实现以满足不同需求
- 工作线程不直接操作 UI
- 线程间通信:
- 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 被收集到 context
的 action 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 中事件处理优先级如下:
- 动画事件 (Animator)
- 直接交互事件 (Hit)
- 通用系统事件 (MatchEvent)
- 业务 (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}