关于 makepad-2.0-dsl
面向 Makepad 2.0 开发者的 DSL 语法技能,讲解 Splash 与 `script_mod!` 的属性、注册和模块规则,并汇总常见错误及修正方式。
主要功能
- 01属性、实例与合并语法
- 02组件和控件注册模式
- 03模块组织与跨模块共享
- 04运行时属性更新
- 05常见语法陷阱参考
适用场景
- 01将旧版 `live_design!` 写法迁移到 2.0
- 02定义并注册自定义控件
- 03排查布局或属性覆盖问题
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ zhanghandong/ makepad-skills --skill makepad-2.0-dsl安装后在 Claude Code 中输入/makepad-2.0-dsl确认已加载。
附带文件references/dsl-syntax-reference.mdreferences/property-system.md
详细介绍
Makepad 2.0 DSL 语法技能
概述
Makepad 2.0 用由 Splash 脚本语言驱动的运行时宏 script_mod! 替代了编译时宏 live_design!。本技能涵盖完整的 DSL 语法、属性系统、注册模式和常见陷阱。
关键语法规则
属性赋值:使用冒号,而非等号
key: value // CORRECT - colon syntax
key = value // WRONG - old 1.x syntax, no longer works
属性之间以空格或换行分隔。同级元素之间不使用逗号。
View{
width: Fill
height: Fit
flow: Down
spacing: 10
padding: 15
}
命名实例::= 运算符
使用 := 创建可寻址的命名组件实例:
my_button := Button{ text: "Click me" }
title := Label{ text: "Hello" }
命名实例:
- 可通过
id!(my_button)或ids!(my_button)从 Rust 代码寻址 - 可通过点路径语法覆盖:
MyTemplate{ title.text: "New text" } - 存储在脚本对象的
vec中(而非map)
普通属性使用 :,并存入 map:
width: Fill // regular property -> map
label := Label{} // named child -> vec
合并运算符:+:
+: 运算符会扩展/合并父项,而不是替换:
draw_bg +: {
color: #f00 // Only overrides color, keeps all other draw_bg properties
}
不使用 +: 时,会替换整个属性:
draw_bg: { color: #f00 } // REPLACES all of draw_bg - loses hover, border, etc.
draw_bg +: { color: #f00 } // MERGES - only changes color, keeps everything else
点路径简写
点路径是合并操作的语法糖:
draw_bg.color: #f00
// is equivalent to:
draw_bg +: { color: #f00 }
draw_text.text_style.font_size: 14
// is equivalent to:
draw_text +: { text_style +: { font_size: 14 } }
Let 绑定:局部模板
let 会在 script_mod! 块中创建可复用的局部模板:
let MyCard = RoundedView{
width: Fill height: Fit
padding: 16 flow: Down spacing: 8
draw_bg.color: #2a2a3d
draw_bg.border_radius: 8.0
title := Label{ text: "Default Title" draw_text.color: #fff }
body := Label{ text: "" draw_text.color: #aaa }
}
// Instantiate and override:
MyCard{ title.text: "Card 1" body.text: "Content here" }
MyCard{ title.text: "Card 2" body.text: "More content" }
重要:let 绑定仅在其所在的 script_mod! 块内有效。其他 script_mod! 块无法访问它们。若要在模块间共享,请将其存储在 mod.widgets.* 中。
展开运算符:..
继承另一个定义的所有属性:
set_type_default() do #(DrawMyShader::script_shader(vm)){
..mod.draw.DrawQuad // Inherit from DrawQuad
}
脚本模块结构
基本应用结构
use makepad_widgets::*;
app_main!(App);
script_mod!{
use mod.prelude.widgets.*
load_all_resources() do #(App::script_component(vm)){
ui: Root{
main_window := Window{
window.inner_size: vec2(800, 600)
body +: {
// UI content here
my_button := Button{ text: "Click" }
}
}
}
}
}
impl App {
fn run(vm: &mut ScriptVm) -> Self {
crate::makepad_widgets::script_mod(vm); // 1. Register base widgets
App::from_script_mod(vm, self::script_mod)
}
}
#[derive(Script, ScriptHook)]
pub struct App {
#[source] source: ScriptObjectRef, // REQUIRED for Script-derived structs
#[live] ui: WidgetRef,
}
impl MatchEvent for App {
fn handle_actions(&mut self, cx: &mut Cx, actions: &Actions) {
if self.ui.button(ids!(my_button)).clicked(actions) {
log!("Button clicked!");
}
}
}
impl AppMain for App {
fn handle_event(&mut self, cx: &mut Cx, event: &Event) {
self.match_event(cx, event);
self.ui.handle_event(cx, event, &mut Scope::empty());
}
}
组件定义模块
script_mod!{
use mod.prelude.widgets_internal.* // For widget library internals
use mod.widgets.* // Access other registered widgets
// Step 1: Register the Rust struct as a widget base
mod.widgets.MyWidgetBase = #(MyWidget::register_widget(vm))
// Step 2: Create a styled variant with default properties
mod.widgets.MyWidget = set_type_default() do mod.widgets.MyWidgetBase{
width: Fill
height: Fit
padding: theme.space_2
draw_bg +: {
color: theme.color_bg_app
}
}
}
注册模式
组件注册
适用于实现了 Widget trait 的结构体:
mod.widgets.MyWidgetBase = #(MyWidget::register_widget(vm))
Rust 端:
#[derive(Script, ScriptHook, Widget)]
pub struct MyWidget {
#[source] source: ScriptObjectRef, // REQUIRED
#[walk] walk: Walk,
#[layout] layout: Layout,
#[redraw] #[live] draw_bg: DrawQuad,
#[live] draw_text: DrawText,
#[rust] my_state: i32, // Runtime-only, not exposed to script
}
组件类型注册
适用于需要与脚本集成的非组件结构体:
mod.widgets.MyComponentBase = #(MyComponent::script_component(vm))
绘制着色器注册
适用于具有着色器字段的自定义绘制类型:
set_type_default() do #(DrawMyShader::script_shader(vm)){
..mod.draw.DrawQuad // Inherit from DrawQuad
}
Rust 端:
#[derive(Script, ScriptHook)]
#[repr(C)]
pub struct DrawMyShader {
#[deref] draw_super: DrawQuad,
#[live] my_param: f32,
}
设置类型默认值
mod.widgets.MyWidget = set_type_default() do mod.widgets.MyWidgetBase{
width: Fill height: Fit
draw_bg +: { color: theme.color_bg_app }
}
注册顺序(至关重要)
必须先注册组件模块,再注册使用它们的 UI 模块:
impl App {
fn run(vm: &mut ScriptVm) -> Self {
crate::makepad_widgets::script_mod(vm); // 1. Base widgets FIRST
crate::my_widgets::script_mod(vm); // 2. Custom widgets SECOND
crate::app_ui::script_mod(vm); // 3. UI using widgets THIRD
App::from_script_mod(vm, self::script_mod) // 4. App component LAST
}
}
多模块聚合(lib.rs 模式)
pub fn script_mod(vm: &mut ScriptVm) {
crate::module_a::script_mod(vm);
crate::module_b::script_mod(vm);
// ... all widget modules
}
Prelude 系统
可用的 Prelude
| Prelude | 使用场景 |
|---|---|
mod.prelude.widgets.* | 应用开发——包含所有标准组件 |
mod.prelude.widgets_internal.* | 组件库内部开发 |
Prelude 别名语法
mod.prelude.widgets = {
..mod.std, // Spread all of mod.std into scope
theme:mod.theme, // Create 'theme' as alias for mod.theme
draw:mod.draw, // Create 'draw' as alias for mod.draw
}
如果不使用别名(即使用 mod.theme, 而不写 theme:),模块仍会被引入,但没有可访问的名称。
跨模块共享
mod 对象是唯一的共享方式
// In widget_module.rs - export to mod.widgets namespace
script_mod!{
use mod.prelude.widgets_internal.*
mod.widgets.MyWidget = set_type_default() do mod.widgets.MyWidgetBase{ ... }
}
// In app_ui.rs - import via mod.widgets
script_mod!{
use mod.prelude.widgets.*
use mod.widgets.* // Now MyWidget is in scope
// ...
MyWidget{}
}
use crate.module.* 不起作用——crate. 前缀在 script_mod 中不可用。
运行时属性更新
使用 script_apply_eval!,而不是旧的 apply_over + live!:
// Old system
item.apply_over(cx, live!{ height: (height) });
// New system - use #(expr) for Rust expression interpolation
script_apply_eval!(cx, item, {
height: #(height)
draw_bg: { is_even: #(if is_even { 1.0 } else { 0.0 }) }
});
调试日志
使用 ~expression 在脚本求值期间记录值:
script_mod!{
~mod.theme // Logs the theme object
~mod.prelude.widgets // Logs what's in the prelude
~some_variable // Logs a variable's value
}
常见陷阱
1. 缺少 #[source] source: ScriptObjectRef
所有派生自 Script 的结构体都必须包含此字段:
#[derive(Script, ScriptHook)]
pub struct MyStruct {
#[source] source: ScriptObjectRef, // REQUIRED - will fail without it
// ...
}
2. 容器缺少 height: Fit
默认高度为 Fill。在 Fit 父项中,Fill 会造成循环依赖,导致高度为 0,因而不可见:
// WRONG - invisible!
View{ flow: Down
Label{ text: "You can't see me" }
}
// CORRECT
View{ height: Fit flow: Down
Label{ text: "Visible!" }
}
3. 混淆 : 和 :=
key: value-- 设置属性(存储在 map 中)name := Widget{}-- 创建可寻址的命名子项(存储在 vec 中)label := Label{ text: "x" }-- 命名实例,可通过Template{ label.text: "y" }覆盖label: Label{ text: "x" }-- 匿名实例,无法寻址,覆盖会静默失败
4. 忘记使用 +: 合并运算符
// WRONG - replaces ALL of draw_bg (loses hover, border, animations)
draw_bg: { color: #f00 }
// CORRECT - merges, only changes color
draw_bg +: { color: #f00 }
5. 主题访问方式错误
// WRONG
color: THEME_COLOR_BG // old 1.x constant syntax
color: (THEME_COLOR_BG) // old 1.x parenthesized reference
// CORRECT
color: theme.color_bg_app
padding: theme.space_2
font_size: theme.font_size_p
6. 包含 'e' 的十六进制颜色需要 #x 前缀
Rust 分词器会将十六进制字面量中的 e/E 解释为科学计数法中的指数:
// WRONG - Rust parse error: "expected at least one digit in exponent"
color: #2ecc71
color: #1e1e2e
// CORRECT - use #x prefix
color: #x2ecc71
color: #x1e1e2e
// Colors without 'e' work fine with plain #
color: #ff4444 // OK
color: #44cc44 // OK
7. pub 关键字在 script_mod 中无效
// WRONG
pub mod.widgets.MyWidget = ...
// CORRECT - visibility is controlled by Rust module system
mod.widgets.MyWidget = ...
8. 边距/内边距的 Inset{...} 构造语法
// WRONG
margin: { left: 10 }
align: { x: 0.5 y: 0.5 }
// CORRECT - use constructor syntax
margin: Inset{ left: 10 }
align: Align{ x: 0.5 y: 0.5 }
padding: Inset{ top: 5 bottom: 5 left: 10 right: 10 }
// Bare number for uniform values is OK
padding: 15
margin: 0.
9. 使用 #[repr(C)] 绘制着色器结构体字段顺序
非实例数据(#[rust]、非实例的 #[live] 字段)必须放在 #[deref] 之前。只有实例字段(着色器输入)放在之后:
// CORRECT
#[derive(Script, ScriptHook)]
#[repr(C)]
pub struct MyDrawShader {
#[live] pub svg: Option<ScriptHandleRef>, // non-instance, BEFORE deref
#[rust] my_state: bool, // non-instance, BEFORE deref
#[deref] pub draw_super: DrawQuad, // contains DrawVars + base instances
#[live] pub tint: Vec4f, // instance field, AFTER deref - OK
}
// WRONG - #[rust] after instance fields corrupts GPU buffer
#[derive(Script, ScriptHook)]
#[repr(C)]
pub struct MyDrawShader {
#[deref] pub draw_super: DrawQuad,
#[live] pub tint: Vec4f,
#[rust] my_state: bool, // BAD: between instance fields
}
10. 不要在 script_mod! 中的首段代码之前添加注释!
Rust 过程宏的令牌流会剥离注释,从而导致错误位置偏移:
// WRONG
script_mod!{
// This comment shifts error line info
use mod.prelude.widgets.*
}
// CORRECT - start with real code immediately
script_mod!{
use mod.prelude.widgets.*
// Comments after first code are fine
}
其他易错点
- 光标值:使用
cursor: MouseCursor.Hand,而不是cursor: Hand或cursor: @Hand - 资源路径:使用
crate_resource("self://path"),而不是dep("crate://self/path") - 纹理声明:使用
tex: texture_2d(float),而不是tex: texture2d - 着色器中的
mod与modf:浮点取模使用modf(a, b),不要使用mod(a, b) - 枚举默认值:枚举默认值使用带
@前缀的default: @off - DefaultNone derive:不要使用
DefaultNonederive;使用带#[default]属性的#[derive(Default)] - 着色器中的方法链式调用:使用
.method(),而不是::method()(例如,Sdf2d.viewport(...)) - 颜色混合:优先使用
color1.mix(color2, hover)链式调用,而不是嵌套调用mix() - 缺少组件注册:在
App::run()中调用crate::makepad_widgets::script_mod(vm),并且要放在自定义模块之前
语法速查
| 旧版(live_design!) | 新版(script_mod!) |
|---|---|
<BaseWidget> | mod.widgets.BaseWidget{} 或 BaseWidget{}(如果已导入) |
{{StructName}} | #(Struct::register_widget(vm)) |
(THEME_COLOR_X) | theme.color_x |
<THEME_FONT> | theme.font_regular |
instance hover: 0.0 | hover: instance(0.0) |
uniform color: #fff | color: uniform(#fff) |
draw_bg: {}(替换) | draw_bg +: {}(合并) |
default: off | default: @off |
fn pixel(self) | pixel: fn() |
item.apply_over(cx, live!{...}) | script_apply_eval!(cx, item, {...}) |