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.
11 KiB
Splash and widget reference
Use this for current script_mod! work. Repository policy is in
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 |
| Broader widget examples | Splash example |
| Widget registration and exports | widgets/core/src/lib.rs, families in widgets/src/lib.rs |
| Styling and animation | Button |
| Widget lookup and updates | Widget API |
| Virtualized list templates | PortalList |
| Tree drawing and open state | FileTree |
| Generated widget APIs | Widget derives |
| Shader instance memory | DrawVars |
| Script/compiler regression cases | Script test crate |
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.
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 usename := 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 usemod.prelude.widgets_internal.*and other required modules. letbindings are local to the script module. Export shared definitions throughmod.*, then import that module. Rust'scrate.*is not a Splash module path, and Rustpubis 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:
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:
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):
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.
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:
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). 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:
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 useuniform(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::. Chainedcolor.mix(other, amount)is a useful styling idiom. - Use
modf(a, b)for float modulo andatan2(y, x)for the two-argument arctangent.atan(x)andfract(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
matchwith 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:
#[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 Draw2Ddraw) should call native helpers on whole arrays, such asc.segs(flat_array, {m alpha})andc.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 guidestage-motion/performance.mdhas the rules and budgets.