Makepad Framework 架构

🚧 基于rik分支

源码地址: https://github.com/makepad/makepad/tree/rik

Makepad Framework 主要包含两大部分:

  • makepad-platform: 平台抽象层。它对跨平台的各种操作做了抽象和封装。
  • makepad-widget:Makepad 内置 UI Widget组件库。它提供了一系列内置的 Widget 组件,用于构建用户界面和一个支持 DSL 设计的保留模式小部件系统。

Makepad Platform

Makepad Platform 是平台抽象层。这意味着,它对跨平台的各种操作做了抽象和封装。

主要包含以下部分:

  • 具有键盘、鼠标和触摸输入的窗口系统
  • 着色器编译器和图形接口
  • 网络接口
  • 视频和音频接口
  • Live 系统(makepad 重点特性)

代码组织结构

platform 的代码组织结构分为如下部分:

源码地址:https://github.com/makepad/makepad/tree/rik/platform

- derive_live - live_compiler - live_tokenizer - shader_compiler - src - Cargo.toml - build.rs
  • derive_live / live_compiler / live_tokenizer 属于 Live 系统,我们随后介绍。
  • shader_compiler 是着色器编译器。

在 Makepad 的 platform/src 目录下,我们找到了多个 Rust 源文件(.rs 文件),以及一些子目录,大概分类如下。

  • 核心模块 (cx.rs): 该文件包含了 Makepad 平台层的核心数据结构和函数(例如,上下文、配置参数等)。
  • 渲染和图形 (draw_list.rs, draw_matrix.rs, draw_shader.rs, draw_vars.rs): 这些文件涉及到图形渲染的逻辑,包括着色器管理、渲染队列和变换矩阵的处理。
  • 事件处理 (event 目录): 事件处理对于 UI 框架至关重要。这个目录包含处理各种用户输入和系统事件的代码。
  • 操作系统特定的代码 (os 目录): UI 框架通常需要处理不同操作系统的特定情况。这个目录包含用于不同平台的特定实现代码。
  • 音频和媒体 (audio.rs, audio_stream.rs, media_api.rs, midi.rs): 这些文件包含音频播放、流媒体处理和 MIDI 设备交互等功能的实现。
  • 网络和通信 (web_socket.rs): WebSocket 实现文件,用于网络通信和数据交换。
  • 窗口管理和输入 (window.rs, cursor.rs): 窗口创建和管理,以及光标处理等功能的实现。

看得出来,src 中主要还是封装了与 事件、UI 渲染、音视频、网络等相关跨平台的底层接口。我们暂且不去深入细节,接下来了解下什么是 Live 系统。

Live 系统

Live 系统是 makepad 提供的一种实时系统。因为 Rust 语言是静态编译语言,不像动态语言那样能实现运行时实时编码,所以 makepad 提供这套 Live 系统来实现实时编码的效果。正如第一章中对 makepad 愿景展示视频里所见,对代码的实时修改,无需重新编译代码,即可更新 UI。

目前,使用 makepad studio 和 vscode,可以看到实时重载(live reload)所见即所得的效果,后续支持更多 IDE 和 编辑器。

Live 系统是依赖于 Rust 的宏来实现的。这里有一个 Live 语法示例:

1live_design! {
2    Button = {{Button}} {
3		color: #f00
4    }
5}
6
7#[derive(Live)]
8pub struct Button {
9	#[live] color: DVec4,
10}

上面这段代码的作用是定义了一个 可设置颜色的 按钮(Button)。

其中的 live_design!#[derive(Live)]#[live] 这些过程宏就属于 Live 系统,它们共同作用于 Button 结构体,就创建了这个「按钮」小部件(Widget)。

  • #[derive(Live)] 是 Rust 中的派生宏,它会自动为 Button 结构体实现 Live trait,这是 「按钮」控件能与 Live 系统的交互需要的所有必要的机制。

  • #[live] 属性定义了 Button 结构体的那个字段要参与实时更新,这里是颜色(color)字段。

  • live_design! 宏则像是 CSS 样式那样,为「按钮」小部件定义了基本样式,比如设置了默认的颜色为 #f00

整套代码的工作机制如下图所示。

Live 系统工作机制

我们暂且不去深入 Live 系统的工作原理,我们只需要理解这套系统的作用和其语法即可。

具体更多细节将在后面Live DSL章节介绍。

MPSL 着色器 DSL 语言

Live 系统还包含了 Makepad 自定义的着色器语言 MPSL (Makepad’s custom shader language),它是一种统一的语言,可以转换为图形 API (openGL/ DirectX / Metal)的着色器语言。

下面是一个例子:

1live_design!{
2		DrawColor = {{DrawColor}} {
3				fn pixel(self) -> vec4 {
4					return vec4(self.color.rgb * self.color.a, self.color.a);
5				}
6		}
7}
8
9#[derive(Live)]
10#[repr(C)]
11pub struct DrawColor {
12		#[deref] pub draw_super: DrawQuad,
13		#[live] pub color: Vec4,
14}

MPSL 代码是可以嵌入到 Live DSL 代码中的。其中 DrawColor 的字段 draw_super 类型 DrawQuad ,代表该结构体继承了 DrawQuad 的行为 (为这个字段实现了 deref trait)。这算是一个 Rust/C 中的技巧,算是一种行为代理。DrawQuad 是 Makepad 框架中 draw crate 提供的功能,代表了绘制人意四边形的基本着色器。

说明

利用 Deref trait 来实现继承,被认为是一种反模式。

因为 Deref 是隐式行为,这和 Rust 的显式哲学是相反的,滥用它容易出错。 但是这里的例子是 OK 的,因为这种方法可以方便地利用框架本身的能力,不属于滥用。

示例代码中 #[[repr(C)] 也是 MPSL 着色器语言的硬性要求,这是为结构体指定了兼容 C-ABI 的布局,目的是为了阻止 Rust 编译器对字段重新排序。

同样,本章不会去深入着色器语法和其渲染机制的更多细节,您看查看着色语言章节介绍。

为什么不使用脚本语言?

你可能会对 Live System 产生这样的疑问:为什么不用 javascript/ lua ,亦或像 slint 那样自己开发一套脚本语言呢,为什么一定要使用现在这种 Rust 宏的写法?

用脚本语言编写的代码可以在运行中进行解释或重新编译,从而让开发周期变得更短。遗憾的是,脚本语言缺乏编译语言的性能优势,这又与 Makepad 力求支撑的重渲染应用产生冲突。将脚本语言和 Rust 结合使用时,代码的归属又极难平衡,因为最佳方案总是因应用程序而异的。

为了解决这一困境才选择使用现在的混合方法的方案:

  • Makepad 应用程序使用 Rust 编写,但负责应用程序外观(比如样式)的部分则用 Live DSL 编写。

  • Live 语言的主要作用类似于 CSS:它描述了应用程序的用户界面应该如何呈现。特别是与 CSS 相似,Live 语言可以方便地覆盖样式。

相较于 Rust 代码,Live 代码是在运行时编译和执行的。

另外,将来为 Makepad 构建的可视化设计器(visual designer,比如 Makepad studio 之类)能感知代码中哪些部分是 Live 代码。当使用可视化设计器来修改一段 Live 代码时,可视化设计器不会重新编译应用程序,而是将更新后的 Live 代码发送给运行中的应用程序。这样,应用程序就可以重新编译并执行新的 Live 代码,从而立刻反映出代码中的变化而无需重新编译 Rust 代码或者重启应用程序。

绘图层

Makepad 的 2D 和 3D 绘图层(drawing layer)也包含在 Makepad platform 中。

绘图层包含了下面内容:

  • 即时模式(immediate mode) 2D 上下文
  • 布局系统(可以看作是嵌套的盒子模型,只是以半即时模式执行)
  • 字体引擎
  • 矢量引擎
  • 图像解码器
  • 基础着色器
  • 着色器标准库

绘图层的更多细节留待后面特定章节来介绍。

Makepad Widget

Makepad Platform 并行存在的另一部分是 Makepad Widget

Makepad Widget 构建在 makepad draw 之上。它包含以下内容:

  • 一组内置的基本 Widget ,包括按钮、复选框、列表等等常用的控件。
  • 一个支持 DSL 设计的保留模式小部件系统。

makepad-widget 的 src 组织结构中包含了很多文件,代表了不同的 UI 控件大概可以作如下分类:

  1. 控件实现:如 button.rs, check_box.rs, slider.rs, text_input.rs 等,这些文件可能包含各种 UI 控件的实现。
  2. 辅助功能:如 image_cache.rs, keyboard_view.rs, scroll_bars.rs,这些可能提供支持控件显示和交互的附加功能。
  3. 特殊控件:如 color_picker.rs, popup_menu.rs, tab_bar.rs 等,代表更复杂或特定用途的控件。
  4. 布局和视图:如 dock.rs, splitter.rs, view.rs 等,这些可能用于组织和管理 UI 控件的布局。
  5. 事件相关:widget_match_event.rs 可能与事件处理机制相关。

更多细节我们在后面 Makepad Widget 章节中介绍。