Makepad UI 代码怎么读:别被语法吓住

前三期聊了选型、对比、跑 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 为参考,简化后大概长这样:

rust 复制代码
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 里用得最多的布局属性。没有之一。

text 复制代码
View {
    flow: Down,    // 从上往下排
    // flow: Right, // 从左往右排
}

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

3.2 align 管对齐

text 复制代码
View {
    flow: Down,
    align: Center,
}

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

3.3 width / height 管尺寸

text 复制代码
View {
    width: Fill,   // 撑满
    height: 200,   // 固定 200
}

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

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

3.4 样式直接写在控件上

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

text 复制代码
Label {
    text: "Hello"
    draw_text: {
        color: #333333,
        font_size: 16.0
    }
}

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

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

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

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

rust 复制代码
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 的点击为例,简化后大概是这样:

rust 复制代码
// 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! 模块里:

rust 复制代码
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 代码,卡在哪一步?评论区聊聊。我猜很多人的答案都差不多。

相关推荐
2401_868534781 小时前
常见的 vue面试题目
前端·javascript·vue.js
前端市界1 小时前
实用指南:如何本地化部署 Sentry (Self-Hosted) 完整教程
前端
颂love1 小时前
TypeScript速学
前端·javascript·typescript
IT策士1 小时前
第 46 篇 k8s之CI/CD 集成:GitOps 理念与 ArgoCD
前端·容器·kubernetes
Dalydai1 小时前
AI 辅助大屏开发:怎么让 AI 干活,但别让它干砸
前端
凌涘1 小时前
深入理解 JavaScript 执行机制:从执行上下文到调用栈全解析
前端·javascript
utmhikari1 小时前
【AI原生】用Vibe Coding写产品前端原型的一些经验
前端·ai·产品经理·web·web开发·ai-native·qoder
li星野1 小时前
从零搭建文件上传系统:FastAPI 后端 + Streamlit 前端
前端·状态模式·fastapi
YAwu111 小时前
手写一个符合 Promise/A+ 规范的 Promise(附完整代码)
前端·javascript