Rhai scripting
Pixel Stroke embeds Rhai, a small scripting language written in Rust that runs inside the editor's WebAssembly bundle. Every function in the API reference is registered on the Rhai engine, so your scripts can call it.
Scripts live as .rhai files on disk, which the
desktop app reads live, or you can paste them into the
in-editor console. The same script runs in the web and desktop
builds.
Hello, retro stable
A first script. It defines a macro that applies two filters in a row, then runs it on the active layer. Each filter returns the number of pixels it changed.
// Save as scripts/examples/retro.rhai
fn retro(levels, block) {
ps_filter_posterize(levels);
ps_filter_pixelate(block);
}
retro(4, 2);
Run the file from the in-editor console, or evaluate a single
line with script:
run scripts/examples/retro.rhai
script ps_filter_invert()
Want to see more first? The scripting demo on the home page runs six longer scripts in your browser with the same bindings, one line at a time. Take them apart: landscape.rhai paints a night scene from a seed, gameboy.rhai converts art to a four-shade palette with ordered dithering, team_colors.rhai palette-swaps a sprite sheet, dungeon.rhai generates a roguelike floor, life.rhai runs Conway's Game of Life, and plasma.rhai draws a demoscene plasma.
Macros stable
Any fn you define in Rhai is a macro you can
call. Macros are ordinary functions: they take arguments and
can call any registered binding. A function can't read the
variables around it, so pass in whatever it needs.
Rhai never turns an integer into a float for you. Where a
binding takes a float, write the decimal point:
ps_filter_hue_shift(30.0) runs,
ps_filter_hue_shift(30) fails with
Function not found. The
API reference marks float parameters.
Example scripts ship with the editor, under scripts/examples/.
Panel scripts stable
The editor's side-panels (toolbar, layer list, timeline,
color picker, palette manager, tilemap, atlas, bones, navigator,
artboard) are all Rhai scripts under
scripts/panels/. The same Rhai engine drives them.
So you can fork a panel, change how it behaves, or write a new one without touching the Rust core.
Layers and frames stable
Working with layers and frames from a script:
// add a highlights layer at 80% opacity
let hi = ps.layer_add("Highlights");
ps.layer_opacity(hi, 0.8);
// duplicate the current frame; subsequent edits affect the new frame
ps_timeline_duplicate_frame(ps_timeline_current_frame());
// pixel-perfect mode for crisp 1px strokes
ps_brush_pixel_perfect(true);
State machines experimental
Animation states and the transitions between them, driven by
named inputs. A transition's condition is JSON: an
input plus one of bool_eq,
float_gt, float_lt,
float_eq or trigger (an array of them
means all must hold).
// idle until the character moves, then run
ps_state_machine_add_state("idle", "clip_idle", "loop", 1.0);
ps_state_machine_add_state("run", "clip_run", "loop", 1.0);
ps_state_machine_add_transition("idle", "run",
"{\"input\":\"speed\",\"float_gt\":0.1}", 0.0);
ps_state_machine_set_input("speed", 1.0);
print(ps_state_machine_current_state()); // run
Building a custom palette stable
// build a 4-stop custom palette
ps_palette_new("Aurora");
ps_palette_add_color("Aurora", "#0a0a1e");
ps_palette_add_color("Aurora", "#3a8fff");
ps_palette_add_color("Aurora", "#ec2a2a");
ps_palette_set_active("Aurora");
The same approach loads GIMP .gpl files or plain
hex lists. The API reference lists the
full palette family.
Coming next soon
- Keybinding registration:
keybind.set("B", "retro") - Command palette entries:
cmd.register(name, fn, hint) - Stamp-brush capture from a selection (the editor exposes the binding once the underlying tool lands).