mcpField
M
makepad-2.0-splash

makepad-2.0-splash

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

关于 makepad-2.0-splash

Splash 是 Makepad 2.0 的运行时 UI 脚本语言,可通过 `script_mod!` 嵌入 Rust。它支持热重载和增量求值,也面向流式代码生成,可用于 Makepad UI 开发。

主要功能

  1. 01在 Rust 中编写运行时 UI 脚本
  2. 02脚本模块热重载
  3. 03增量与流式求值
  4. 04响应式状态与事件处理
  5. 05HTTP 请求与 HTML 解析
  6. 06用于构建 UI 的控件库

适用场景

  1. 01用运行时脚本构建 Makepad 界面
  2. 02通过热重载更新 UI 脚本
  3. 03在流式代码生成期间渲染 UI
  4. 04处理界面事件和响应式状态

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/zhanghandong/makepad-skills --skill makepad-2.0-splash

安装后在 Claude Code 中输入/makepad-2.0-splash确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
canvas-splash-patterns.md
splash-language-reference.md
splash-scripting-patterns.md

详细介绍

Makepad 2.0 Splash 脚本语言

Splash 是 Makepad 2.0 的核心运行时 UI 脚本语言,于 2026 年 2 月 12 日发布。它用运行时 script_mod! 宏取代了旧的编译时 live_design! 宏系统,可实现热重载、流式求值和 AI 优先的代码生成。

核心概念

脚本结构

每个 Splash 脚本都以 use 导入语句开头,并通过 script_mod!{} 宏嵌入 Rust:

use makepad_widgets::*;

app_main!(App);

script_mod! {
    use mod.prelude.widgets.*

    // let bindings, functions, state, and UI definitions go here

    startup() do #(App::script_component(vm)){
        ui: Root{
            main_window := Window{
                window.inner_size: vec2(800, 600)
                body +: {
                    // UI content
                }
            }
        }
    }
}
语法规则
  • 属性之间不使用逗号——以空白分隔
  • 不使用分号——语法更简洁,针对 LLM 代码生成进行了优化
  • 属性赋值:key: value
  • 点路径简写:draw_bg.color: #f00(等同于 draw_bg +: { color: #f00 })
  • 合并运算符:key +: { ... } 扩展父级,而不替换它
  • 命名子项:name := Widget{...}(可寻址,并且可按实例覆盖)
  • Let 绑定:let MyTemplate = Widget{...}(局部作用域,必须在使用前定义)
  • Rust 绑定:#(Struct::register_widget(vm)) 将 Splash 连接到 Rust 结构体
  • 调试日志:~expression 在求值期间记录值
状态管理

状态通过 mod.state 对象和响应式 on_render 回调进行管理:

// Define state
let state = { counter: 0 }
mod.state = state

// Reactive rendering -- re-runs when .render() is called
main_view := View{
    on_render: ||{
        Label{ text: "Count: " + state.counter }
    }
}
事件处理

事件既可在 Splash 中内联处理,也可从 Rust 中处理:

// Inline event handlers in Splash
add_button := Button{
    text: "Add"
    on_click: ||{
        add_todo(ui.todo_input.text(), "")
        ui.todo_input.set_text("")
    }
}

// TextInput return key
todo_input := TextInput{
    on_return: || ui.add_button.on_click()
}

// Startup event
on_startup: ||{
    ui.main_view.render()
}

在 Rust 中,使用 script_eval! 执行 Splash 代码:

impl MatchEvent for App {
    fn handle_actions(&mut self, cx: &mut Cx, actions: &Actions) {
        if self.ui.button(cx, ids!(increment_button)).clicked(actions) {
            script_eval!(cx, {
                mod.state.counter += 1
                ui.main_view.render()
            });
        }
    }
}
函数
fn tag_color(tag) {
    if tag == "dev" theme.color_highlight
    else if tag == "design" theme.color_selection_focus
    else theme.color_highlight
}

fn add_todo(text, tag) {
    todos.push({text: text, tag: tag, done: false})
    ui.todo_list.render()
}
控制流
// If/else
if todos.len() == 0
    EmptyState{}
else for i, todo in todos {
    TodoItem{ label.text: todo.text }
}

// For loops
for i, item in array {
    Label{ text: item.name }
}

// While
while condition { ... }
HTTP 请求
let req = net.HttpRequest{
    url: "https://api.example.com/data"
    method: net.HttpMethod.GET
    headers: {"User-Agent": "MakepadApp/1.0"}
}
net.http_request(req) do net.HttpEvents{
    on_response: |res| {
        let text = res.body.to_string()
        let json = res.body.parse_json()
    }
    on_error: |e| { /* handle error */ }
}

流式响应使用 is_streaming: true,并配合 on_stream 和 on_complete 回调。

HTML 解析
let doc = html_string.parse_html()
doc.query("p")              // all <p> elements
doc.query("#main")           // by id
doc.query("p.bold")          // by class
doc.query("div > p")         // direct children
doc.query("p[0]").text       // text content
doc.query("a@href")          // attribute value
流式求值

Splash 解析器支持基于检查点的增量解析,专为 AI/LLM 流式代码生成而设计:

// Rust API for streaming evaluation
vm.eval_with_append_source(script_mod, &code, NIL.into())

这样,代码生成时即可逐个 token 实时更新 UI,无需等到完整脚本生成后再进行求值。

热重载与脚本模块跟踪

Splash 脚本支持通过 --hot 标志进行热重载。VM 使用唯一的 ScriptModKey(文件、行、列)跟踪每个 script_mod! 代码块:

// Internal: ScriptModKey uniquely identifies a script_mod! block
ScriptModKey { file: "src/app.rs", line: 5, col: 1 }

// Runtime substitution via overrides
ScriptCode::script_mod_overrides  // HashMap of ScriptModKey -> updated source

热重载的工作原理:

  1. 文件监视器(makepad_live_reload_core)检测源文件更改
  2. 从 Rust 源代码中提取 script_mod! 代码块(可处理原始字符串、注释和字符字面量)
  3. 跟踪 Rust 占位符数量(#(...))——添加或移除占位符需要完整重新构建
  4. 通过 script_mod_overrides 应用通过验证的脚本模块
  5. IP 到位置的映射为错误报告提供源映射(对于合成操作码,则回退到最近的 token)

ScriptSource 变体:

  • ScriptSource::Mod——标准模块求值(启动时)
  • ScriptSource::Streaming——增量流式求值(AI/LLM)

关键布局规则

  1. 始终为容器设置 height: Fit——默认的 height: Fill 会导致 UI 不可见(高度为 0px)
  2. 在根容器上使用 width: Fill——顶层绝不要使用固定像素宽度
  3. 对于包含文本子项且 show_bg: true 的任何 View,设置 new_batch: true
  4. 在模板中为命名子项使用 :=——否则文本覆盖会静默失败
  5. draw_bg.border_radius 接受浮点数,而非 Inset——draw_bg.border_radius: 16.0
  6. 使用带样式的 View(RoundedView、SolidView),而非原始的 View{show_bg: true}

Widget 参考

核心容器:View、SolidView、RoundedView、RectView、RoundedShadowView、CircleView、GradientXView、GradientYView、ScrollXYView、ScrollXView、ScrollYView

文本:Label、H1-H4、P、TextBox、TextInput、LinkLabel、Markdown、Html

控件:Button、ButtonFlat、ButtonFlatter、CheckBox、Toggle、RadioButton、Slider、DropDown

布局:Splitter、FoldHeader、Hr、Vr、Filler

列表:PortalList、FlatList

导航:Modal、Tooltip、PopupNotification、SlidePanel、ExpandablePanel、PageFlip、StackNavigation

停靠:Dock、DockSplitter、DockTabs、DockTab

媒体:Image、Icon、LoadingSpinner、Vector、MathView、MapView

Canvas:从 Claude Code 渲染 Splash

Makepad Canvas(tools/canvas/)是一个独立应用,可通过 HTTP/WS 渲染接收到的 Splash 代码。Claude Code 会用它输出可视化内容。

HTTP API(建议用于发送 Splash)
PORT=$(cat /tmp/makepad-canvas.port)

# Full render
curl -s -X POST "http://127.0.0.1:$PORT/splash" -d 'View{width:Fill height:Fit Label{text:"Hello"}}'

# Streaming render
curl -s -X POST "http://127.0.0.1:$PORT/splash/stream"                    # begin
curl -s -X POST "http://127.0.0.1:$PORT/splash/stream" -d 'View{...'      # append
curl -s -X POST "http://127.0.0.1:$PORT/splash/end"                       # end

# Clear
curl -s -X POST "http://127.0.0.1:$PORT/clear"
WS 事件监听(用于接收点击事件)
# Long-lived WS connection receives button click events as JSON
mkfifo /tmp/ws_fifo; (sleep 99999 > /tmp/ws_fifo &)
websocat ws://127.0.0.1:$PORT < /tmp/ws_fifo > /tmp/canvas_events &

# Events arrive as: {"event":"click","widget":"btn_name"}
交互式按钮

使用 name := Button{...} 创建可点击按钮。点击事件中会发送 name:

View{width:Fill height:Fit flow:Right spacing:12
  btn_save := Button{text:"Save"}
  btn_cancel := Button{text:"Cancel"}
}

点击时:{"event":"click","widget":"btn_save"}

Splash 中的矢量动画
// Pulsing dot (loop_:true = indefinite, NOT "indefinite"!)
Vector{width:16 height:16
  Circle{cx:8 cy:8 r:6 fill:#x44ddaa opacity:Tween{from:0.3 to:1.0 dur:1.5 loop_:true}}
}

// Moving dot with color change
Vector{width:Fill height:30
  Path{d:"M 20 15 L 400 15" stroke:#x222244 stroke_width:1.}
  Circle{cx:Tween{from:20 to:400 dur:3.0 loop_:true} cy:15 r:4 fill:Tween{from:#x44ddaa to:#xffaa44 dur:3.0 loop_:true}}
}
Canvas Splash 语法(重要——与 script_mod! 不同)

为 Canvas HTTP 渲染生成 Splash 时(POST /splash),请使用以下精确模式。Canvas Splash 语法在几个关键方面不同于 script_mod! 宏上下文:

1. 属性使用点路径内联,而非嵌套块:

// WRONG -- nested block syntax does not render backgrounds
RoundedView{height: Fit draw_bg: { color: #x1a1a2e border_radius: 8.0 } }

// CORRECT -- dot-path inline
RoundedView{width: Fill height: Fit draw_bg.color: #x1a1a2e draw_bg.radius: 8.}

2. 边框圆角使用 draw_bg.radius,而非 draw_bg.border_radius:

// WRONG
draw_bg.border_radius: 8.0

// CORRECT
draw_bg.radius: 8.

3. 内边距使用显式的 Inset{} 类型,浮点数末尾带点:

// WRONG -- bare number or nested block
padding: 20
padding: { top: 20 bottom: 20 }

// CORRECT
padding: Inset{left: 20. right: 20. top: 16. bottom: 16.}

4. 对齐使用显式的 Align{} 类型:

// WRONG
align: { y: 0.5 }

// CORRECT
align: Align{y: 0.5}
align: Center

5. 浮点数末尾带点:

// WRONG       // CORRECT
8.0            8.
16.0           16.
0.5            0.5

6. SolidView 和 RoundedView 无需设置 show_bg: true 或 new_batch: true——它们默认就会渲染背景。

7. 使用 curl 发送多行 Splash 时,请使用 --data-binary——普通的 -d 会移除换行符。

经验证的 Canvas 仪表板模板

来源:tools/canvas/examples/token-dashboard.splash

SolidView{width: Fill height: Fit draw_bg.color: #x0c0c18 flow: Down padding: Inset{left: 32. right: 32. top: 24. bottom: 24.} spacing: 20

    // Title
    Label{text: "Dashboard Title" draw_text.color: #xeeeeff draw_text.text_style.font_size: 20}

    // Card row
    View{width: Fill height: Fit flow: Right spacing: 16
        RoundedView{width: Fill height: Fit draw_bg.color: #x161628 draw_bg.radius: 8. padding: Inset{left: 20. right: 20. top: 16. bottom: 16.} flow: Down spacing: 6
            Label{text: "Metric Name" draw_text.color: #x888899 draw_text.text_style.font_size: 10}
            Label{text: "Value" draw_text.color: #xcc66ff draw_text.text_style.font_size: 28}
        }
    }

    // Horizontal bar chart row
    View{width: Fill height: Fit flow: Right spacing: 8 align: Align{y: 0.5}
        Label{text: "Label" width: 100 draw_text.color: #xbbbbbb draw_text.text_style.font_size: 10}
        RoundedView{width: 200 height: 12 draw_bg.color: #xf44336 draw_bg.radius: 2.}
        Label{text: "200" draw_text.color: #x777777 draw_text.text_style.font_size: 10}
    }

    // Vertical bar chart (bars bottom-aligned)
    View{width: Fill height: 130 flow: Right spacing: 2 align: Align{y: 1.0}
        View{width: Fill height: Fit flow: Down align: Center spacing: 4
            RoundedView{width: 14 height: 80 draw_bg.color: #x7733cc draw_bg.radius: 2.}
            Label{text: "Mon" draw_text.color: #x444455 draw_text.text_style.font_size: 7}
        }
    }
}
Canvas 提示
  • Splash 使用 HTTP,事件使用 WS——这是最可靠的模式
  • 矢量形状属性:fill、stroke、stroke_width——而非 draw_bg.*
  • CJK/中文文本:正文和代码块均支持。CodeEditor 为 CJK 字符使用双倍宽度列(已于 2026-03-23 修复)。主题 font_code 将 LXGWWenKai 作为中文回退字体。
  • 较大的 POST 请求体:Canvas 支持最大 512KB 的 HTTP 请求体
  • 发布/订阅广播:所有已连接的 WS 客户端都会收到点击事件
  • 健康检查:使用 GET /ping(而非 /health)
  • 监视器开关:/tmp/canvas-monitor-active 标志文件控制状态栏自动刷新。Canvas 侧边栏中有一个切换按钮。
编译与求值:两种本质不同的 Widget 创建路径

理解这一区别对于调试任何 Splash 求值问题都至关重要:

Compiled path (script_mod!, examples, studio):
  parse → compile → execute → create_widget
    → script_apply(FULL type default object with all vec + map entries)
    → on_after_apply(value = full object) ✓  ← ScriptHook runs
    → Templates registered, #[live] fields populated from type default

Eval path (POST /splash, Splash.set_text):
  parse → eval → create_widget_from_prototype
    → script_apply(EVAL VALUE ONLY, e.g. {body: "..."})
    → on_after_apply: SKIPPED (is_eval() guard in TextFlow/Markdown)
    → Templates NOT registered, #[live] fields use defaults (not type default)
方面编译(script_mod!)求值(set_text)
Widget 创建完整的 Rust + ScriptVm 初始化仅求值 ScriptVm 字符串
on_after_apply使用完整类型默认值调用不会调用
类型默认 vec(命名子项)完整继承在 proto 副本中可能丢失条目
类型默认 map(属性)应用于 #[live] 字段仅应用求值结果
模板注册在 apply 期间通过 ScriptHook 注册必须在 draw_walk 中延迟完成
ScrollYView可正常工作渲染为空白——请使用 View

求值路径问题的修复模式:在 draw_walk 中实现延迟初始化,检测缺失状态,并通过 cx.with_vm(|vm| vm.bx.heap.type_default_for_object(...)) 查找类型默认值。这正是修复 Markdown 的 code_block 模板继承问题所采用的方法。

Splash 求值注意事项(严重——2026-03-23 总结)

这些问题仅影响通过 Splash 运行时求值创建的组件(POST /splash、Splash.set_text()),不影响编译后的 script_mod! 组件:

1. ScrollYView 在 Splash 求值中不起作用——渲染为空白

// WRONG -- Splash eval renders nothing
ScrollYView{ width: Fill height: Fill flow: Down
    Label{ text: "invisible" }
}

// CORRECT -- use View, Canvas wraps it in its own ScrollYView
View{ width: Fill height: Fit flow: Down
    Label{ text: "visible" }
}

2. 对于通过求值创建的组件,不会调用 on_after_apply / ScriptHook

当 Splash 求值创建组件(例如 Markdown{body: "..."})时,永远不会调用 ScriptHook::on_after_apply 回调。任何依赖 on_after_apply 的初始化逻辑都必须有备用路径(例如,在 draw_walk 中延迟初始化)。

3. 求值路径不会完整继承类型默认属性

当 set_type_default() 为组件类型设置额外属性(例如 use_code_block_widget: true)或命名子项(例如 code_block := View{...})时,通过 Splash 求值创建的实例可能不会继承这些属性或子项。#[live] 字段只会从求值应用值(例如 {body: "..."})获取值,而不会从完整的类型默认值获取。

解决方法:在 draw_walk 中检查是否缺少状态,并通过 vm.bx.heap.type_default_for_object() 查找类型默认值。

4. 类型默认值中的 vec 条目(命名子项)可能不会复制到实例

虽然存在 copy_type_default_vec,但 set_type_default() 中的 vec 条目可能会在注册到实例创建的过程中丢失。new_with_proto_impl 中自动执行的原型 vec 复制会从直接原型复制,而直接原型中的条目可能少于类型默认值中的条目。

5. 在 Splash 中嵌套 Markdown{} 可以正常工作,但需要类型默认模板

当 Canvas 使用 code_block := View{CodeView{...}} 覆盖 mod.widgets.Markdown,而 Splash 求值创建 Markdown{body: "..."} 时,code_block 模板不会自动提供。解决方法(位于 widgets/src/markdown.rs)是在绘制时延迟查找类型默认值,并注册缺失的模板。


调试 Splash

命令行标志
标志说明
--hot启用热重载:监视 script_mod! 源文件,并在保存时自动刷新 UI。只会重新加载 Splash DSL;Rust 更改需要重新编译。
--stdin-loopStudio 模式:通过 stdin/websocket 与 Makepad Studio 通信。供 Studio 内部使用。
打印调试信息

std.println() / std.print() 是主要的调试工具。输出会同时显示在终端和 Studio 的 Log View 中:

std.println("debug: state.counter = " + state.counter)

~expression 调试日志语法也会在求值过程中打印值:

~state.counter    // prints the value of state.counter during eval
Makepad Studio 集成

Studio 可以直接从 Run List 面板运行 Splash 脚本(会在项目根目录中查找 makepad.splash)。脚本错误会在 Log View 中显示,包括文件路径和错误详情。

在 Studio 中运行时,Splash 脚本会获得一个 hub 模块:

API说明
hub.run(env, cmd, args)从 Splash 启动子进程
hub.set_run_items(items)在 Studio 的 Run List 中注册可运行项
hub.studio_ipStudio 的 WebSocket 地址
当前限制
  • 不支持断点调试——Splash VM 不支持断点或单步执行
  • 没有 AST 转储标志——若要检查解析结果,需在 Rust 源代码(script/)中添加日志
  • 基于打印的调试——std.println() 和 ~expr 是主要的调试工具

文件参考

  • 完整语言手册:splash.md(2559 行)
  • 迁移指南:AGENTS.md(815 行)
  • 计数器示例:examples/counter/src/app.rs
  • 待办事项示例:examples/todo/src/app.rs
  • 详细参考:skills/makepad-2.0-splash/references/splash-language-reference.md
  • 模式指南:skills/makepad-2.0-splash/references/splash-scripting-patterns.md

Splash 实践经验(2026-03-31 总结,来自 Vox 语音输入应用的构建)

第 1 课:无法在 +: 块中添加 instance 字段

这是 Splash 最关键的限制。在 draw_bg +: { } 块内添加 instance my_var: 0.5 会导致运行时错误:"cannot push to frozen vec"。

// WRONG — runtime crash
draw_bg +: {
    instance hover: 0.0          // CRASH: cannot push to frozen vec
    pixel: fn() { ... }
}

// CORRECT — override pixel function only, use built-in variables
draw_bg +: {
    pixel: fn() {
        let t = self.draw_pass.time    // built-in, always available
        return Pal.premul(vec4(t, 0.0, 0.0, 1.0))
    }
}

解决方法:

  • 使用 self.draw_pass.time 实现基于时间的动画
  • 使用 self.pos、self.rect_size(始终可用)
  • 对于自定义实例变量,创建 Rust DrawQuad 子类型(参见 makepad-2.0-shaders skill)
  • 对于简单的动画指示器,使用 LoadingSpinner 组件
第 2 课:透明浮动窗口配置方法

要创建真正透明的覆盖层窗口,需要满足 三 个条件:

my_window := Window{
    show_caption_bar: false
    window.transparent: true             // 1. Window-level transparency
    pass.clear_color: #x00000000         // 2. Render pass clear to fully transparent
    body +: {
        // 3. Do NOT set draw_bg on body — it overrides transparency
        View{
            width: Fill height: Fill
            // Only visible elements show; rest is see-through
        }
    }
}

常见错误:

  • 仅设置 window.transparent: true——窗口仍然不透明(灰色背景)
  • 在 body 上设置 draw_bg.color: #x00000000——不会让窗口透明,只会变成黑色
  • 还必须从 Rust 将窗口配置为浮动面板:MacosWindowConfig::floating_panel() + Borderless

参考实现: Makepad tools/canvas/src/app.rs 使用了这一确切模式。

第 3 课:不存在的属性名称

Splash 会静默忽略未知属性。这些问题曾耗费我们大量调试时间:

错误正确说明
password: trueis_password: trueTextInput 密码模式
LoadingSpinner 上的 color: #fff(不支持)LoadingSpinner 没有 color 属性
window.backdrop: Vibrancy可用,但还需要 transparent: true单独设置 Backdrop 不会让窗口透明
第 4 课:Emoji 渲染

Makepad 的文本渲染器支持部分 Emoji,但并非全部:

可用不可用
🎙 🔍 🔄✨ ⏳

如果 Emoji 显示为方框或乱码,请尝试换一个。使用 BMP(基本多文种平面)中的基础 Emoji。

第 5 课:width: Fit + 较大的 border_radius = 尖角形状

在 height: 56 的胶囊形控件上使用 border_radius: 28.0 的 RoundedView,两端会变成菱形,而不是半圆。底层的 sdf.box() 公式在 radius >= min(w,h)/2 时会出错。

修复方法: 使用自定义 SDF 胶囊着色器,而不是 RoundedView:

View{
    show_bg: true
    draw_bg +: {
        pixel: fn() {
            let r = self.rect_size.y * 0.5
            let px = self.pos.x * self.rect_size.x
            let py = self.pos.y * self.rect_size.y
            let cx = clamp(px, r, max(r, self.rect_size.x - r))
            let d = length(vec2(px - cx, py - self.rect_size.y * 0.5)) - r
            let alpha = 1.0 - smoothstep(-1.0, 1.0, d)
            return Pal.premul(vec4(0.1, 0.1, 0.18, alpha * 0.82))
        }
    }
}
第 6 课:多窗口应用——窗口可见性控制

在 script_mod! 中声明的窗口会在启动时自动显示。Makepad WindowRef 没有 close()/open() 方法。

可行模式: 以正常尺寸声明窗口。需要时使用 configure_window() 将窗口置于前台。

// Show: configure_window triggers makeKeyAndOrderFront on macOS
let settings = self.ui.window(cx, ids!(settings_window));
settings.configure_window(cx, dvec2(480.0, 560.0), dvec2(500.0, 200.0), false, "Settings".into());

// Hide: resize to 1x1 (no close/minimize on WindowRef)
let capsule = self.ui.window(cx, ids!(capsule_window));
capsule.resize(cx, dvec2(1.0, 1.0));

注意: reposition(cx, dvec2(-9999, -9999)) 无法可靠地隐藏 macOS 浮动面板。

第 7 课:new_batch: true 与组件 Z 轴顺序

在自定义着色器 View 中添加 LoadingSpinner(或任何带有独立绘制着色器的子组件),可能会导致边缘溢出——子组件会绘制到父级 SDF 蒙版之外。

修复方法: 二选一:

  1. 在父级自身的像素着色器中绘制动画(不会遇到子组件 Z 轴顺序问题)
  2. 在父级上使用 clip_x: true + clip_y: true(可能无法完全解决问题)
第 8 课:基于时间的着色器动画需要持续重绘

着色器中的 self.draw_pass.time 只有在组件重绘时才会推进。若不显式重绘,时间就会冻结。

fn handle_next_frame(&mut self, cx: &mut Cx, _e: &NextFrameEvent) {
    if self.inner.state != STATE_IDLE {
        // Redraw the WINDOW (not just the view) to update draw_pass.time
        self.ui.widget(cx, ids!(my_window)).redraw(cx);
        self.inner.next_frame = cx.new_next_frame();
    }
}

关键点: 重绘 Window 组件,而不只是内部 View。绘制通道的时间是按窗口计算的。

第 9 课:脚本派生结构体中的复杂类型 #[rust] 字段

#[derive(Script, ScriptHook)] 结构体只能包含类型实现了 Default 的 #[rust] 字段。对于复杂的非 Default 类型(通道、句柄等),请将它们封装在 Inner 结构体中:

#[derive(Default)]
struct Inner {
    timer: Timer,
    rx: Option<crossbeam_channel::Receiver<u64>>,
    handle: Option<SomeNonDefaultHandle>,
}

#[derive(Script, ScriptHook)]
pub struct App {
    #[live] ui: WidgetRef,
    #[rust] inner: Inner,    // Single Default-able wrapper
}

评论

支持 Markdown