makepad/docs/agents/splash.md
vjroger 2da550ca3d docs: the agent guides cover the designer's routes, the tween inspector, hidden windows on Windows and the surface material
tweaker.md gains "Structural edits: the designer" (the /design/* routes
and how a session's patch is persisted) and "Motion: the tween inspector"
(the Motion tab and /tweak/op?op=motion, with loop and yoyo), and its
"Persist the result" starts from a design session when one is open.
app-remote.md points at /design/* and says MAKEPAD_HIDE_WINDOWS works on
Windows as well as macOS, as the D3D11 and window code implement it.
splash.md gains a Materials note on mod.sdf.Material and the packing every
material widget shares. The code they describe is upstream already.
2026-10-02 08:52:56 +02:00

290 lines
11 KiB
Markdown

# Splash and widget reference
Use this for current `script_mod!` work. Repository policy is in
[AGENTS.md](../../AGENTS.md). Check the implementation closest to your task
before copying a signature; the archived `live_design!` tree is not the
reference for current APIs.
## Working sources
| Task | Reference |
|---|---|
| Small app and startup | [Counter example](../../examples/counter/src/main.rs) |
| Broader widget examples | [Splash example](../../examples/splash/src/main.rs) |
| Widget registration and exports | [widgets/core/src/lib.rs](../../widgets/core/src/lib.rs), families in [widgets/src/lib.rs](../../widgets/src/lib.rs) |
| Styling and animation | [Button](../../widgets/core/src/button.rs) |
| Widget lookup and updates | [Widget API](../../widgets/core/src/widget.rs) |
| Virtualized list templates | [PortalList](../../widgets/core/src/portal_list.rs) |
| Tree drawing and open state | [FileTree](../../widgets/families/data/src/file_tree.rs) |
| Generated widget APIs | [Widget derives](../../widgets/derive_widget/src/derive_widget.rs) |
| Shader instance memory | [DrawVars](../../platform/src/draw_vars.rs) |
| Script/compiler regression cases | [Script test crate](../../platform/script/test) |
Use the widget exports and source for availability and feature gates instead
of maintaining a separate widget catalogue here.
## App structure
A compact pattern using the current counter example's startup and lookup
APIs follows. Keep the registration style of the app you are editing.
```rust
pub use makepad_widgets;
use makepad_widgets::*;
app_main!(App);
script_mod! {
use mod.prelude.widgets.*
startup() do #(App::script_component(vm)){
ui: Root{
main_window := Window{
window.inner_size: vec2(420, 220)
body +: {
flow: Down
status := Label{text: "Ready"}
press := Button{text: "Press"}
}
}
}
}
}
#[derive(Script, ScriptHook)]
pub struct App {
#[live] ui: WidgetRef,
}
impl MatchEvent for App {
fn handle_actions(&mut self, cx: &mut Cx, actions: &Actions) {
if self.ui.button(cx, ids!(press)).clicked(actions) {
self.ui.label(cx, ids!(status)).set_text(cx, "Pressed");
}
}
}
impl AppMain for App {
fn script_mod(vm: &mut ScriptVm) -> ScriptValue {
crate::makepad_widgets::script_mod(vm);
self::script_mod(vm)
}
fn handle_event(&mut self, cx: &mut Cx, event: &Event) {
self.match_event(cx, event);
self.ui.handle_event(cx, event, &mut Scope::empty());
}
}
```
For a new example crate, use an adjacent crate's Cargo configuration and
update the workspace and `makepad.splash`. Startup helpers and app traits
can change; the source links above are the reference.
## Object syntax, scope, and registration
- Properties use `name: value`; named child/template instances use
`name := Type{...}`.
- Assigning a module export uses `mod.widgets.Name = ...`. This is an
assignment, not the old property syntax.
- App scripts import `mod.prelude.widgets.*`. Widget implementations use
`mod.prelude.widgets_internal.*` and other required modules.
- `let` bindings are local to the script module. Export shared definitions
through `mod.*`, then import that module. Rust's `crate.*` is not a
Splash module path, and Rust `pub` is not used on Splash assignments.
- Register widgets with `#(Type::register_widget(vm))`, other components
with `#(Type::script_component(vm))`, and draw shaders with
`#(Type::script_shader(vm))`.
- Register dependencies before modules whose UI instantiates them. Preserve
the host's existing app/module registration sequence.
- Derive fields and attributes depend on the type. Copy the nearest current
implementation; `#[source]` is not a universal requirement for every
Script-derived struct. Inspect the derive parser if a field type fails.
A widget's styled default commonly has this shape:
```rust
script_mod! {
use mod.prelude.widgets_internal.*
mod.widgets.MyButtonBase = #(MyButton::register_widget(vm))
mod.widgets.MyButton = set_type_default() do mod.widgets.MyButtonBase{
width: Fill
height: Fit
draw_bg +: {color: theme.color_bg_app}
}
}
```
This assumes a matching Rust widget and draw fields are registered; it is
a pattern, not a standalone widget implementation.
## Values and property merging
Use `+:` for a partial override of an inherited typed object:
```rust
draw_bg +: {color: #f00}
padding: Inset{left: 10 top: 4}
align: Align{x: 0.5 y: 0.5}
```
Replacing a typed object can discard its other fields or methods. Walk and
layout values should use the constructor expected by the property.
Runtime property updates interpolate Rust values with `#(expr)`:
```rust
script_apply_eval!(cx, item, {
height: #(height)
draw_bg +: {color: #(color)}
});
```
Use `theme.*` for theme values. Resource references use `crate_resource`;
copy a nearby current resource path rather than translating an old
`dep("crate://...")` example mechanically. Cursor values use the exposed
enum, such as `MouseCursor.Hand`. If an enum is not exposed, inspect its
registration rather than silently discarding a required property.
Rust tokenizes code inside `script_mod!` before Splash sees it. Use escaped
colors such as `#x2ecc71` and `#x1e1e2e` where a digit followed by `e` or
`E` would otherwise be parsed as an exponent.
For Rust action enums, use `#[derive(Default)]` and `#[default]` on the
default variant, not the old `DefaultNone` derive.
In script functions, `a.b ?? d` reads an optional field: a missing field
(or a field of nil) is `d`, where `a.b |? d` only replaces a nil value.
`mod(a, b)` is the floored modulo of shading languages (the result takes
b's sign); `mod` alone is still the module root, and `modf(a, b)` is the
truncating remainder. `int(x)`, `i32`, `u32` and `f32` values index arrays
and do arithmetic as the number they hold. `log(x)` from `mod.math` is the
natural log, and `ln(x)` is the same function (script, shaders, kernels);
`mod.std.log(v)` prints.
## Templates and lists
Script objects distinguish ordinary `key: value` properties in their map
from named `name := Type{...}` items in their vector. List widgets collect
the latter as templates during application.
```rust
list := PortalList{
width: Fill
height: Fill
Item := View{
height: 40
title := Label{text: "Default"}
}
}
```
The current PortalList keeps templates as rooted `ScriptObjectRef` values,
collects them outside eval-only application, and updates instantiated items
on reload. Preserve those lifetime rules in custom list widgets.
During list drawing, use the current `set_item_range`, `next_visible_item`,
and `item` APIs. Widget lookup includes the context:
```rust
let item = list.item(cx, item_id, id!(Item));
item.label(cx, ids!(title)).set_text(cx, &format!("Item {item_id}"));
item.draw_all(cx, &mut Scope::empty());
```
For Dock-local templates, use a script-level `let` or module export when a
component must be reused outside that Dock. For custom widgets, turtle
drawing, FileTree traversal, and full animation state definitions, follow
the linked implementations instead of maintaining duplicate tutorials.
### `scan:` on widgets that create children from data
A web build ships only what a collect run saw the app use (see
[web-pack.md](web-pack.md)). A widget that creates children from data or
strings (PortalList, FlatList, page and tab switchers, anything that picks a
template at run time) gets a `scan:` list of every template it can create,
written together with the templates, by default:
```rust
list := PortalList{
scan: [@Item, @Header]
Item := View{ title := Label{text: "Default"} }
Header := View{ caption := Label{text: "Section"} }
}
```
Entries name the widget's own templates (`@Item`) or are template objects
themselves (`RowView{}`). Without a `scan:` list the collect run spawns every
template the widget holds. When a web build misses something, its log names
the widget path and file:line: add the missing template to that widget's
`scan:` list.
## Shaders and animation
- Per-draw shader values use `instance(value)`; shared values use
`uniform(value)`. A varying hover color must not accidentally be uniform.
- Texture declarations use `texture_2d(float)`.
- Shader functions use `pixel: fn() {...}` and method calls use `.`, not
Rust `::`. Chained `color.mix(other, amount)` is a useful styling idiom.
- Use `modf(a, b)` for float modulo and `atan2(y, x)` for the two-argument
arctangent. `atan(x)` and `fract(x)` are also available.
- Animator states use id values such as `default: @off`. Use the current
Button implementation for state definitions and transition constructors.
- Prefer enum `match` with a catch-all. A failure in supported shader enum
matching needs a compiler regression case and fix, not an integer-branch
workaround in the widget.
Custom draw shaders need `#[repr(C)]`. Keep non-instance fields before the
draw base and shader instance fields after it:
```rust
#[derive(Script, ScriptHook)]
#[repr(C)]
pub struct MyDrawShader {
#[rust] cached_state: bool,
#[deref] draw_super: DrawQuad,
#[live] tint: Vec4f,
}
```
`DrawVars::as_slice()` reads across the base into trailing instance fields.
Putting non-instance state in that region corrupts the GPU instance buffer,
including when another draw shader extends yours. Follow the base shader's
current layout and registration.
### Materials
`mod.sdf.Material` (`draw/src/shader/surface.rs`) lights a shape from the
signed distance a pixel function already holds: `face(...)` shades a face
from its distance, outward gradient, elevation and the packed `light`,
`relief`, `finish` and `tune` vec4s; `cast(...)` returns what a raised face
throws on its ground, to lay under it with `sdf.clear`; `box_cov` is the
blurred box coverage an inner shadow is cut from. Its helpers are `fn`
declarations in the object's own scope, so a member calls another member by
its bare `m_*` name, never through `Material.`.
The base shaders of `RoundedView`, `PanelView`, `Button`, `CheckBox`,
`Toggle`, `Slider` and `RotaryKnob` read it behind a `material` uniform
defaulting to `theme.material_level`, packed exactly as `ReliefView` packs
its own (`material_light`, `material_relief`, `material_finish`,
`material_tune`, `material_shadow`, `material_inner`, the three inks). Keep
that packing when a widget joins: a stylesheet's tokens must mean the same
thing on every surface. Gate the whole branch on the uniform, so a stock
theme pays one compare per draw call and draws exactly what it always did,
and put the room a shadow needs inside the control's own rect
(`material_margin`), since makepad clips by default.
## Per-frame script cost
- Script runs interpreted, about 1-2 µs a function call. Per-frame code (a
property `fn(t)`, a Draw2D `draw`) should call native helpers on whole
arrays, such as `c.segs(flat_array, {m alpha})` and `c.curve(...)`, not a
script loop per element.
- Work over roughly 1k elements a frame belongs in a `Kernel` (native code,
every core; `active_count: fn(t)` runs only the live elements).
- In Motion documents, `memo(f, t)` runs a shared per-frame helper (a
camera, a beat) once per frame however many properties read it. Wrap a
vector result in an array: `[v]`.
- Measure a Motion document with `makepad-motion bench doc --realtime`. The
Stage guide `stage-motion/performance.md` has the rules and budgets.