- name
- makepad-2.0-splash
- description
- CRITICAL: Use for Makepad 2.0 Splash scripting language. Triggers on:
splash language, makepad script, script_mod!, makepad scripting, splash 脚本,
makepad 2.0 script, mod.state, on_render, script_eval, streaming evaluation,
splash syntax, splash vm, let binding, splash functions, hot reload, live reload,
ScriptModKey, script_mod_overrides, checkpoint, incremental parsing,
canvas splash, POST splash, fn tick, on_audio, set_text, tab switching,
音乐播放器, token monitor, driver script, audio API,
热重载, 脚本引擎, 增量解析
# Makepad 2.0 Splash Scripting Language
Splash is Makepad 2.0's core runtime UI scripting language, released February 12, 2026. It replaces the old compile-time `live_design!` macro system with a runtime `script_mod!` macro that enables hot reload, streaming evaluation, and AI-first code generation.
## Core Concepts
### Script Structure
Every Splash script starts with a `use` import and is embedded in Rust via the `script_mod!{}` macro:
```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
}
}
}
}
}
```
### Syntax Rules
- **No commas** between properties -- whitespace-delimited
- **No semicolons** -- cleaner syntax optimized for LLM generation
- **Property assignment**: `key: value`
- **Dot-path shorthand**: `draw_bg.color: #f00` (equivalent to `draw_bg +: { color: #f00 }`)
- **Merge operator**: `key +: { ... }` extends parent without replacing
- **Named children**: `name := Widget{...}` (addressable, overridable per-instance)
- **Let bindings**: `let MyTemplate = Widget{...}` (local scope, must be defined before use)
- **Rust binding**: `#(Struct::register_widget(vm))` connects Splash to Rust structs
- **Debug logging**: `~expression` logs value during evaluation
### State Management
State is managed via the `mod.state` object and reactive `on_render` callbacks:
```
// 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 }
}
}
```
### Event Handling
Events are handled both inline in Splash and from 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()
}
```
From Rust, use `script_eval!` to execute Splash code:
```rust
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()
});
}
}
}
```
### Functions
```
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()
}
```
### Control Flow
```
// 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 Requests
```
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 */ }
}
```
Streaming responses use `is_streaming: true` with `on_stream` and `on_complete` callbacks.
### HTML Parsing
```
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
```
### Streaming Evaluation
Splash's parser supports checkpoint-based incremental parsing, designed for AI/LLM streaming code generation:
```rust
// Rust API for streaming evaluation
vm.eval_with_append_source(script_mod, &code, NIL.into())
```
This enables real-time UI updates as code is generated token-by-token, without requiring a complete script before evaluation.
### Hot Reload & Script Mod Tracking
Splash scripts support hot reload via the `--hot` flag. The VM tracks each `script_mod!` block with a unique `ScriptModKey` (file, line, column):
```rust
// 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
```
**How hot reload works:**
1. File watcher (`makepad_live_reload_core`) detects source file changes
2. `script_mod!` blocks are extracted from Rust source (handles raw strings, comments, char literals)
3. Rust placeholder counts (`#(...)`) are tracked -- adding/removing placeholders requires full rebuild
4. Validated script mods are applied via `script_mod_overrides`
5. IP-to-location mapping provides source maps for error reporting (fallback to nearest token for synthetic opcodes)
**ScriptSource variants:**
- `ScriptSource::Mod` -- Standard module evaluation (startup)
- `ScriptSource::Streaming` -- Incremental streaming evaluation (AI/LLM)
## Critical Layout Rules
1. **Always set `height: Fit` on containers** -- default `height: Fill` causes invisible UI (0px height)
2. **Use `width: Fill` on the root container** -- never fixed pixel width at the top level
3. **Set `new_batch: true`** on any View with `show_bg: true` that contains text children
4. **Use `:=` for named children** in templates -- without it, text overrides fail silently
5. **`draw_bg.border_radius` takes a float**, not an Inset -- `draw_bg.border_radius: 16.0`
6. **Use styled Views** (`RoundedView`, `SolidView`) instead of raw `View{show_bg: true}`
## Widget Reference
Core containers: `View`, `SolidView`, `RoundedView`, `RectView`, `RoundedShadowView`, `CircleView`, `GradientXView`, `GradientYView`, `ScrollXYView`, `ScrollXView`, `ScrollYView`
Text: `Label`, `H1`-`H4`, `P`, `TextBox`, `TextInput`, `LinkLabel`, `Markdown`, `Html`
Controls: `Button`, `ButtonFlat`, `ButtonFlatter`, `CheckBox`, `Toggle`, `RadioButton`, `Slider`, `DropDown`
Layout: `Splitter`, `FoldHeader`, `Hr`, `Vr`, `Filler`
Lists: `PortalList`, `FlatList`
Navigation: `Modal`, `Tooltip`, `PopupNotification`, `SlidePanel`, `ExpandablePanel`, `PageFlip`, `StackNavigation`
Dock: `Dock`, `DockSplitter`, `DockTabs`, `DockTab`
Media: `Image`, `Icon`, `LoadingSpinner`, `Vector`, `MathView`, `MapView`
## Canvas: Rendering Splash from Claude Code
Makepad Canvas (`tools/canvas/`) is a standalone app that renders Splash code received via HTTP/WS. Used by Claude Code for visual output.
### HTTP API (recommended for sending Splash)
```bash
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 Event Listening (for receiving click events)
```bash
# 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"}
```
### Interactive Buttons
Use `name := Button{...}` to create clickable buttons. The `name` is sent in click events:
```
View{width:Fill height:Fit flow:Right spacing:12
btn_save := Button{text:"Save"}
btn_cancel := Button{text:"Cancel"}
}
```
When clicked: `{"event":"click","widget":"btn_save"}`
### Vector Animations in 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 Syntax (CRITICAL -- differs from script_mod!)
When generating Splash for Canvas HTTP rendering (`POST /splash`), use these EXACT patterns. Canvas Splash syntax differs from `script_mod!` macro context in several critical ways:
**1. Properties use dot-path inline, NOT nested blocks:**
```
// 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. Border radius is `draw_bg.radius`, NOT `draw_bg.border_radius`:**
```
// WRONG
draw_bg.border_radius: 8.0
// CORRECT
draw_bg.radius: 8.
```
**3. Padding uses explicit `Inset{}` type with trailing-dot floats:**
```
// 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 uses explicit `Align{}` type:**
```
// WRONG
align: { y: 0.5 }
// CORRECT
align: Align{y: 0.5}
align: Center
```
**5. Float values use trailing dot:**
```
// WRONG // CORRECT
8.0 8.
16.0 16.
0.5 0.5
```
**6. `SolidView` and `RoundedView` do NOT need `show_bg: true` or `new_batch: true`** -- they render backgrounds out of the box.
**7. Use `--data-binary` for multi-line Splash via curl** -- plain `-d` strips newlines.
### Proven Canvas Dashboard Template
Source: `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
在 GitHub 查看