前三期聊了选型、对比、跑 demo。如果你跟着上一期把 counter 跑起来了,窗口弹出来那一瞬间应该还挺爽的。

然后你打开 src/main.rs,往下翻了几行。

不是熟悉的 Rust 结构体,不是 fn main(),而是一堆看起来像 CSS 又像 JSON、但两边都不像的东西。

我当时是半夜看的,盯着屏幕愣了大概两分钟。

然后去搜教程。更懵了。有的文章在讲 live_design!,有的在讲 script_mod!,还有在讲 Splash 的。我花了一整个晚上才搞明白它们之间的关系。

这一期就是想帮你省掉那个晚上。

先交底:这篇不是 Splash 语法大全,也不是官方文档翻译。它就是一份"第一次打开 Makepad 代码时,先看懂哪几块就够了"的导航。

1. 先别读代码,先搞清楚一个时间线

很多人卡在这一步,问题不在代码本身,而在于看了不同时代的资料。

Makepad 的 UI 描述方式经历过一次比较大的转向:

  • 老版本:用 live_design! 宏写 UI,语法混在 Rust 宏里
  • 当前版本(Makepad 2.0 / dev 分支):用 script_mod! + Splash,UI 描述变成了一套独立的 DSL

这不是"旧语法换了几个关键字"那种小改。是整个 UI 描述层的组织思路变了。

所以你搜到 2023 年的教程全是 live_design! { ... },而官方 dev 分支打开全是 script_mod! 和 Splash。你没看错,它们就是同一个框架在两个时间点的样子。

这个时间差不说清楚,新手大概率会越搜越乱。

截至 2026 年 6 月,官方 dev 分支的示例已经全面偏向 script_mod! / Splash。这一篇也按新写法来。

2. 第一层:UI 声明 —— 界面长什么样

先解决最让人困惑的问题:在 Makepad 里,一个界面到底是怎么被"描述"出来的。

2.1 一个最小示例

以官方 counter 为参考,简化后大概长这样:

script_mod!(
    mod my_app {
        main_window := Window {
            window.inner_size: vec2(420, 220)
            body +: {
                View {
                    width: Fill,
                    height: Fill,
                    flow: Down,
                    align: Center

                    Label {
                        text: "Count: 0"
                    }

                    Button {
                        text: "Increment"
                    }
                }
            }
        }
    }
);

我第一次看到这段代码,脑子里有两个问号:

  1. script_mod! 是啥?这算 Rust 代码吗?
  2. main_window := Window { ... },这 := 又是什么语法?

2.2 先别管 script_mod!

script_mod! 是个宏,编译时 Makepad 会把里面的 DSL 展开成框架能理解的东西。

第一次读,别追宏展开。追了就出不来了。

你只需要先抓住一件事:这段代码定义了一个窗口,窗口里有一个垂直排列的视图,视图里放了一个标签和一个按钮。够了。

2.3 :=+: 记两个词就行

这两个符号是 Makepad DSL 里出现频率最高的,也是新手最容易懵的:

  • main_window := Window { ... }:声明一个叫 main_window 的东西,类型是 Window
  • body +: { ... }:往 body 里面追加内容

粗暴理解:

  • := → “我声明一个”
  • +: → “往里面塞”

不严谨。但够用。第一遍读不卡住就是胜利。

2.4 先认控件名,别的往后放

读 Makepad UI 代码最省力的办法:

先找你认识的单词,忽略不认识的语法。

上面那段代码,你盯住这四个词就行:

  • Window — 窗口
  • View — 容器
  • Label — 文本
  • Button — 按钮

这四个认出来,界面的骨架已经在你脑子里了。vec2(420, 220)FillDown 第二轮再看,别急。

3. 第二层:布局和样式 —— 东西怎么摆

知道"有什么"了,下一步:这些东西为什么摆在那个位置。

3.1 flow 管方向

flow 是 Makepad 里用得最多的布局属性。没有之一。

View {
    flow: Down,    // 从上往下排
    // flow: Right, // 从左往右排
}

Down 是垂直,Right 是水平。和 CSS 的 flex-direction: column / row 一个意思,只是名字更短。

3.2 align 管对齐

View {
    flow: Down,
    align: Center,
}

align 管的是子元素在排列方向上的对齐。CenterStartEnd,三个值够用了。

3.3 width / height 管尺寸

View {
    width: Fill,   // 撑满
    height: 200,   // 固定 200
}

Button {
    width: Fit,    // 自适应内容
}

Fill 撑满,Fit 自适应,具体数字就是固定尺寸。flow + align + width/height,三个属性组合起来,常见布局基本都能出来。

3.4 样式直接写在控件上

这一点和 CSS 差别很大。Makepad 没有单独的样式表,颜色、字体、间距全写在控件上:

Label {
    text: "Hello"
    draw_text: {
        color: #333333,
        font_size: 16.0
    }
}

我第一次看到这种写法,第一反应是"这也太不分离了吧"。但用了两天之后发现,看一个控件就能看到它的全部外观,不用去另一个文件翻 class。说实话,小项目里挺爽的。

4. 第三层:事件和逻辑 —— 点了为什么有反应

界面能看了,布局能摆了。最后一步:点了按钮,为什么会有反应。

4.1 UI 描述和 Rust 逻辑在同一个 script_mod! 里,但角色分开

script_mod!(
    mod my_app {
        // 第一部分:UI 描述(DSL)
        main_window := Window { ... }

        // 第二部分:Rust 逻辑
        #[run]
        fn main() -> Result<(), String> {
            // 初始化
        }

        #[event]
        fn handle_button_click(&mut self, cx: &mut Cx, actions: &Actions) {
            // 事件处理
        }
    }
);

UI 描述只管"长什么样",Rust 逻辑管"点完发生什么"。它们写在一个宏里,但分工很明确。

4.2 按钮点击是怎么接上的

以 counter 的点击为例,简化后大概是这样:

// UI 侧:给按钮一个 id
Button {
    id: increment_button,
    text: "Increment"
}

// Rust 侧:通过 id 找到按钮,判断是否被点击
if self.ui.button(cx, ids!(increment_button)).clicked(actions) {
    self.count += 1;
    self.ui.label(cx, ids!(count_label)).set_text(cx, &format!("Count: {}", self.count));
    self.ui.redraw(cx);
}

整个连接靠的就是 id

UI 里给控件起个名字(id: increment_button),Rust 里通过同一个名字找到它(ids!(increment_button)),然后判断事件、更新状态、触发重绘。

所以我读 Makepad 代码有个习惯:先扫一遍 UI 描述,把所有 id 圈出来。这些 id 就是 UI 和逻辑之间的接头暗号,找到它们,后面的关系网就清楚了。

4.3 状态放哪

Makepad 里状态直接挂在 script_mod! 模块里:

script_mod!(
    mod my_app {
        count: i32 = 0,
        name: String = String::new(),

        main_window := Window { ... }

        #[event]
        fn handle_click(&mut self, cx: &mut Cx, actions: &Actions) {
            // self.count, self.name 直接用
        }
    }
);

不需要单独的 store,不需要 context。字段就挂在模块上,self.xxx 直接访问。中小型应用里这个模型用起来很快。

5. 一条偷懒的阅读顺序

如果现在你打开一个 Makepad 示例,不知道从哪开始看,按这个顺序来:

  1. 先找 main_window — 界面入口,往下追
  2. 认控件名WindowViewLabelButton……先在脑子里画个树
  3. flowalign — 搞清楚东西为什么在这个位置
  4. 找所有 id — 有 id 的控件,说明 Rust 那边会用到它
  5. 顺着 id 跳到 Rust — 看事件处理、状态更新

这个顺序走一遍,大部分示例就不会再给人"一堆符号看不懂"的感觉了。

6. 我踩过的两个坑

6.1 拿 CSS 心智硬套

Makepad 的 DSL 和 CSS 有几个词长得像(widthalign),但布局模型完全不是 Flexbox。

  • 没有 display: flexflow 就是排列方式
  • 没有 margin / padding 的完整对应
  • 样式不级联,父元素不影响子元素

我当时卡在这上面至少两个小时,一直在找"Makepad 的 padding 怎么写"。后来发现有些东西它就是没有。接受"这是一套新东西"比纠结"为什么和 CSS 不一样"快得多。

6.2 一上来就想搞懂宏展开

script_mod! 展开之后代码量很大,充满框架内部细节。

第一次读,把它当黑盒。先理解里面的 DSL 结构和 Rust 逻辑怎么组织。等你整个框架有感觉了,回头看宏展开,那个时机才对。

7. 和上一期的关系

从第三期直接过来的话:

  • 第三期:把窗口跑起来,按钮能点
  • 这一期:看懂这些按钮和文字是怎么被定义、怎么被摆放、怎么连上逻辑的

两期合在一起,你就能从"能跑"走到"能读"。

总结

这一期其实就一件事:帮你建立 Makepad UI 代码的阅读框架。

认控件名 → 看 flow/align → 顺 id 找逻辑。这个顺序抓住,大部分代码就不会再让你愣在那了。

下一期拆事件处理、状态管理和组件通信。从"能看的界面"到"能交互的应用"。

你第一次打开 Makepad 代码,卡在哪一步?评论区聊聊。我猜很多人的答案都差不多。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐