mcpField
M
makepad-2.0-dsl

makepad-2.0-dsl

github.com/zhanghandong开发75253 次安装29 周前更新
代码仓库

关于 makepad-2.0-dsl

面向 Makepad 2.0 开发者的 DSL 语法技能,讲解 Splash 与 `script_mod!` 的属性、注册和模块规则,并汇总常见错误及修正方式。

主要功能

  1. 01属性、实例与合并语法
  2. 02组件和控件注册模式
  3. 03模块组织与跨模块共享
  4. 04运行时属性更新
  5. 05常见语法陷阱参考

适用场景

  1. 01将旧版 `live_design!` 写法迁移到 2.0
  2. 02定义并注册自定义控件
  3. 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:不要使用 DefaultNone derive;使用带 #[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.0hover: instance(0.0)
uniform color: #fffcolor: uniform(#fff)
draw_bg: {}(替换)draw_bg +: {}(合并)
default: offdefault: @off
fn pixel(self)pixel: fn()
item.apply_over(cx, live!{...})script_apply_eval!(cx, item, {...})

参考文件

评论

支持 Markdown