基本概念

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 通过 EventActionScope 这三个重要机制来实现运行时交互。其细节在事件章节详细介绍。

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}