基本概念
Live 语言是一个专门用于 UI 组件和样式的领域特定语言(DSL)。它的基本结构是以节点为单位,每个节点可以包含属性和子节点。
Live DSL
是在运行时实时编译的,支持热重载和动态更新。
这种设计适合的场景如下:
- 需要高度动态性的UI系统
- 需要热重载的开发环境
- 需要运行时修改UI的场景
生命周期
Live DSL 运行时编译大概过程如下:
1. 输入阶段
第一步输入 Live Documents 。
1// Live DSL 代码
2Button = {
3 width: 100,
4 height: 50,
5 text: "Click me"
6}
2. Live Compiler 阶段
Live Tokenizer
分词
1pub struct TokenWithSpan {
2 pub token: LiveToken,
3 pub span: TextSpan
4}
5
6// 将代码转换成 token 流
7[
8 Token::Ident("Button"),
9 Token::Punct("="),
10 Token::Open(Brace),
11 Token::Ident("width"),
12 Token::Punct(":"),
13 Token::Int(100),
14 ...
15]
Live Parser
解析
1pub struct LiveParser<'a> {
2 pub token_index: usize,
3 pub file_id: LiveFileId,
4 pub tokens_with_span: Cloned<Iter<'a, TokenWithSpan>>,
5 // ...
6}
7
8// 将 token 流解析成初始的节点结构
Live Original
存储了从源代码解析得到的原始节点数据,包含:
- 保存了完整的源码映射信息
- 包含了设计时和编辑时的元信息
- 作为展开过程的输入
- 支持错误定位和报告
1pub struct LiveOriginal {
2 pub nodes: Vec<LiveNode>, // 解析出的节点列表
3 pub edit_info: Vec<LiveNode>, // 编辑相关的元数据节点
4 pub design_info: Vec<LiveDesignInfo>, // 设计阶段的信息
5 pub tokens: Vec<TokenWithSpan>, // 源代码的token流
6}
比如:
1// 源代码
2Button = {
3 width: 100,
4 height: 50,
5 label = {
6 text: "Click me"
7 }
8}
9
10// 解析成 LiveOriginal.nodes
11[
12 LiveNode { id: "Button", value: Object },
13 LiveNode { id: "width", value: Int64(100) },
14 LiveNode { id: "height", value: Int64(50) },
15 LiveNode { id: "label", value: Object },
16 LiveNode { id: "text", value: String("Click me") },
17 LiveNode { value: Close }, // label的结束
18 LiveNode { value: Close } // Button的结束
19]
LiveOriginal
相当于 Live DSL 的 AST(抽象语法树)。
3. 展开阶段
展开阶段是对 DSL 中的组件声明进行扩展(expand),比如处理属性展开、继承、建立组件关系,并设置运行时上下文。这为后续的渲染和交互打下基础。
1// 简写形式
2Button = <Button> {
3 text: "Click me"
4}
5
6// 展开后
7Button = <Button> {
8 text: "Click me",
9 draw_text: {
10 color: #000
11 },
12 draw_bg: {...}
13}
Live Expander
1pub struct LiveExpander<'a> {
2 pub live_registry: &'a LiveRegistry,
3 pub in_crate: LiveId,
4 pub in_file_id: LiveFileId,
5 pub errors: &'a mut Vec<LiveError>,
6}
7
8// 处理继承、引用等关系
Live Expanded
1pub struct LiveExpanded {
2 pub nodes: Vec<LiveNode>,
3}
4. 实例化阶段
BTreeMap 具有以下特点:
- BTreeMap 默认按 Key 排序,可以保证组件初始化、渲染和销毁的顺序稳定性;正确处理父子组件间的依赖关系;方便调试和日志记录。
- BTreeMap 的树状结构让 LiveNode 数据的内存布局更加紧凑。相关组件数据倾向于在内存中相邻,对缓存友好,可能使实际性能比 HashMap 更好。BTreeMap 的空间利用率通常比 HashMap 高。
- BTreeMap 的树结构更加稳定,增删组件不会导致大规模内存重分配,性能可以更好的预测。
所以它适合用于 Widget Registry 。
1pub struct WidgetRegistry {
2 pub map: BTreeMap<LiveType, (LiveComponentInfo, Box<dyn WidgetFactory>)>,
3}
Widget 实例
1pub trait Widget: WidgetNode {
2 fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope);
3 fn draw(&mut self, cx: &mut Cx2d, scope: &mut Scope) -> DrawStep;
4 // ...
5}
6
7pub trait WidgetNode: LiveApply {
8 // 获取设计时组件实例
9 fn widget_design(&mut self) -> Option<&mut dyn WidgetDesign> {
10 return None;
11 }
12 // 通过 UID 查找组件
13 fn uid_to_widget(&self, _uid: WidgetUid) -> WidgetRef;
14 // 查找组件
15 fn find_widgets(&self, _path: &[LiveId], _cached: WidgetCache, _results: &mut WidgetSet);
16 // 计算布局信息
17 fn walk(&mut self, _cx: &mut Cx) -> Walk;
18 // 获取区域
19 fn area(&self) -> Area;
20 // 重绘
21 fn redraw(&mut self, _cx: &mut Cx);
22}
Widget Node 是 UI 组件树中的基本节点,它提供基础的布局、遍历和绘制能力,且负责组件的区域管理和重绘,支持组件查找和导航。 同时是 Widget trait 的基础要求。
一个 Widget 实例可以包含多个 Node,每个 Node 负责特定的底层功能。Widget 通过组合 Node 实现完整功能。
5. 运行时交互
1impl Widget {
2 fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) {
3 // 处理事件
4 }
5
6 fn draw(&mut self, cx: &mut Cx2d, scope: &mut Scope) {
7 // 渲染逻辑
8 }
9}
Widget 通过 Event
、Action
和 Scope
这三个重要机制来实现运行时交互。其细节在事件章节详细介绍。
Live Node
Live Node 可以理解为解析以后的每个 AST 节点。
1. 节点的基本结构
在代码实现中,节点由 LiveNode 结构表示:
1pub struct LiveNode {
2 pub origin: LiveNodeOrigin, // 节点的源信息
3 pub id: LiveId, // 节点的标识符
4 pub value: LiveValue, // 节点的值
5}
2. 节点的类型(LiveValue)
节点可以包含多种类型的值:
1pub enum LiveValue {
2 // 基础值类型
3 None,
4 Bool(bool),
5 Int64(i64),
6 Float64(f64),
7 Color(u32),
8 String(Arc<String>),
9
10 // 复合类型
11 Object, // 对象节点
12 Array, // 数组节点
13 Clone{...}, // 克隆/继承节点
14 Class{...}, // 类定义节点
15
16 // DSL 特殊类型
17 DSL{...}, // DSL代码块
18 Import(...), // 导入声明
19}
3. 节点的组织方式
1Button = { // 这是一个节点
2 width: 100, // 这也是一个节点
3 height: 50, // 这也是一个节点
4}
1Window = { // 父节点
2 header = { // 子节点
3 title = { // 孙节点
4 text: "Hello" // 叶节点
5 }
6 }
7}
1List = {
2 items: [ // 数组节点
3 {text: "1"}, // 子节点 1
4 {text: "2"}, // 子节点 2
5 {text: "3"} // 子节点 3
6 ]
7}