Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/bindings-roadmap.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@ no further significant ReScript → AffineScript work is tractable.

|1
|*PixiJS core* (Application, Container, Sprite, Graphics, Text, Ticker, FederatedEvent)
|`◑` partial (Application / Container / Sprite / Graphics / Text / Texture / Ticker landed; FederatedEvent + extensive accessor coverage deferred)
|`◑` partial (Application / Container / Sprite / Graphics / Text / Texture / Ticker landed; Container 8.x transform-and-event surface — scale / pivot / rotation / alpha / zIndex / sortableChildren / eventMode / cursor + on/off pointer-event registration + Sprite anchor — landed; opaque `FederatedPointerEvent` Json accessors + `parent` read accessor + Point/Rectangle/Circle helper types + sprite atlases + filters + hitArea deferred)
|`stdlib/Pixi.affine` (eventual home: `affinescript-pixijs` as a separate repo per the SNIFs/typed-wasm precedent)
|idaptik `src/bindings/Pixi.res`; all 215 `src/app/*.res` files depend on this. Restarted 2026-05-28 — the prior `affinescript-pixijs/` directory used the obsolete `.as` extension + AGPL-3.0-or-later headers + a Zig→C→WASM-import architecture incompatible with the Deno-ESM emitter; it was deleted and the surface rebuilt in `stdlib/Pixi.affine` matching the wasmCall / motion pattern. Test fixture: `tests/codegen-deno/pixi_smoke.{affine,harness.mjs}`. Follow-ups: extensive Container accessors (anchor, scale, pivot, parent, zIndex, eventMode, filters, hitArea); FederatedEvent + on/off pointer-event surface; Point/Rectangle/Circle helper types; sprite atlases.
|idaptik `src/bindings/Pixi.res`; all 215 `src/app/*.res` files depend on this. Restarted 2026-05-28 — the prior `affinescript-pixijs/` directory used the obsolete `.as` extension + AGPL-3.0-or-later headers + a Zig→C→WASM-import architecture incompatible with the Deno-ESM emitter; it was deleted and the surface rebuilt in `stdlib/Pixi.affine` matching the wasmCall / motion pattern. Container accessor + on/off expansion landed 2026-05-31. Test fixture: `tests/codegen-deno/pixi_smoke.{affine,harness.mjs}` exercises every shipped extern, including `smokeAccessorsFlow` over the 11-extern 8.x transform-and-event addition. Follow-ups: typed `FederatedPointerEvent` accessors (read global/client x,y; target; stopPropagation); `parent` read accessor with Option-null handling; Point/Rectangle/Circle helper types; sprite atlases; filters; hitArea.

|2
|*@pixi/sound* (`Sound.from`, play, stop, volume, setVolume, loop)
Expand Down
22 changes: 22 additions & 0 deletions lib/codegen_deno.ml
Original file line number Diff line number Diff line change
Expand Up @@ -275,9 +275,20 @@ const __as_pixiContainerNew = () => new globalThis.__as_pixi.Container();
const __as_pixiContainerAddChild = (p, c) => { p.addChild(c); return 0; };
const __as_pixiContainerRemoveChild = (p, c) => { p.removeChild(c); return 0; };
const __as_pixiContainerSetPosition = (c, x, y) => { c.x = x; c.y = y; return 0; };
const __as_pixiContainerSetScale = (c, x, y) => { c.scale.set(x, y); return 0; };
const __as_pixiContainerSetPivot = (c, x, y) => { c.pivot.set(x, y); return 0; };
const __as_pixiContainerSetRotation = (c, rad) => { c.rotation = rad; return 0; };
const __as_pixiContainerSetAlpha = (c, a) => { c.alpha = a; return 0; };
const __as_pixiContainerSetZIndex = (c, z) => { c.zIndex = z; return 0; };
const __as_pixiContainerSetSortableChildren = (c, v) => { c.sortableChildren = v; return 0; };
const __as_pixiContainerSetEventMode = (c, mode) => { c.eventMode = mode; return 0; };
const __as_pixiContainerSetCursor = (c, cursor) => { c.cursor = cursor; return 0; };
const __as_pixiContainerSetVisible = (c, v) => { c.visible = v; return 0; };
const __as_pixiContainerOn = (c, event, handler) => { c.on(event, handler); return 0; };
const __as_pixiContainerOff = (c, event, handler) => { c.off(event, handler); return 0; };
const __as_pixiContainerDestroy = (c) => { c.destroy(); return 0; };
const __as_pixiSpriteFrom = (t) => new globalThis.__as_pixi.Sprite(t);
const __as_pixiSpriteSetAnchor = (s, x, y) => { s.anchor.set(x, y); return 0; };
// Upcasts are identity — PIXI's class hierarchy makes Sprite/Graphics/
// Text actual Container subclasses, so the JS object is the same.
const __as_pixiSpriteAsContainer = (s) => s;
Expand Down Expand Up @@ -516,9 +527,20 @@ let () =
b "pixiContainerAddChild" (fun a -> Printf.sprintf "__as_pixiContainerAddChild(%s, %s)" (arg 0 a) (arg 1 a));
b "pixiContainerRemoveChild" (fun a -> Printf.sprintf "__as_pixiContainerRemoveChild(%s, %s)" (arg 0 a) (arg 1 a));
b "pixiContainerSetPosition" (fun a -> Printf.sprintf "__as_pixiContainerSetPosition(%s, %s, %s)" (arg 0 a) (arg 1 a) (arg 2 a));
b "pixiContainerSetScale" (fun a -> Printf.sprintf "__as_pixiContainerSetScale(%s, %s, %s)" (arg 0 a) (arg 1 a) (arg 2 a));
b "pixiContainerSetPivot" (fun a -> Printf.sprintf "__as_pixiContainerSetPivot(%s, %s, %s)" (arg 0 a) (arg 1 a) (arg 2 a));
b "pixiContainerSetRotation" (fun a -> Printf.sprintf "__as_pixiContainerSetRotation(%s, %s)" (arg 0 a) (arg 1 a));
b "pixiContainerSetAlpha" (fun a -> Printf.sprintf "__as_pixiContainerSetAlpha(%s, %s)" (arg 0 a) (arg 1 a));
b "pixiContainerSetZIndex" (fun a -> Printf.sprintf "__as_pixiContainerSetZIndex(%s, %s)" (arg 0 a) (arg 1 a));
b "pixiContainerSetSortableChildren" (fun a -> Printf.sprintf "__as_pixiContainerSetSortableChildren(%s, %s)" (arg 0 a) (arg 1 a));
b "pixiContainerSetEventMode" (fun a -> Printf.sprintf "__as_pixiContainerSetEventMode(%s, %s)" (arg 0 a) (arg 1 a));
b "pixiContainerSetCursor" (fun a -> Printf.sprintf "__as_pixiContainerSetCursor(%s, %s)" (arg 0 a) (arg 1 a));
b "pixiContainerSetVisible" (fun a -> Printf.sprintf "__as_pixiContainerSetVisible(%s, %s)" (arg 0 a) (arg 1 a));
b "pixiContainerOn" (fun a -> Printf.sprintf "__as_pixiContainerOn(%s, %s, %s)" (arg 0 a) (arg 1 a) (arg 2 a));
b "pixiContainerOff" (fun a -> Printf.sprintf "__as_pixiContainerOff(%s, %s, %s)" (arg 0 a) (arg 1 a) (arg 2 a));
b "pixiContainerDestroy" (fun a -> Printf.sprintf "__as_pixiContainerDestroy(%s)" (arg 0 a));
b "pixiSpriteFrom" (fun a -> Printf.sprintf "__as_pixiSpriteFrom(%s)" (arg 0 a));
b "pixiSpriteSetAnchor" (fun a -> Printf.sprintf "__as_pixiSpriteSetAnchor(%s, %s, %s)" (arg 0 a) (arg 1 a) (arg 2 a));
b "pixiSpriteAsContainer" (fun a -> Printf.sprintf "__as_pixiSpriteAsContainer(%s)" (arg 0 a));
b "pixiTextureFromUrl" (fun a -> Printf.sprintf "__as_pixiTextureFromUrl(%s)" (arg 0 a));
b "pixiGraphicsNew" (fun _ -> "__as_pixiGraphicsNew()");
Expand Down
46 changes: 46 additions & 0 deletions stdlib/Pixi.affine
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,50 @@ pub extern fn pixiContainerRemoveChild(parent: Container, child: Container) -> I
/// Set `c.x` and `c.y` in one call. Returns 0.
pub extern fn pixiContainerSetPosition(c: Container, x: Float, y: Float) -> Int;

/// `c.scale.set(x, y)` — uniform-or-per-axis scaling. Returns 0.
pub extern fn pixiContainerSetScale(c: Container, x: Float, y: Float) -> Int;

/// `c.pivot.set(x, y)` — origin for rotation and scaling. Returns 0.
pub extern fn pixiContainerSetPivot(c: Container, x: Float, y: Float) -> Int;

/// `c.rotation = rad` — rotation in radians. Returns 0.
pub extern fn pixiContainerSetRotation(c: Container, rad: Float) -> Int;

/// `c.alpha = a` — opacity (0.0–1.0). Returns 0.
pub extern fn pixiContainerSetAlpha(c: Container, a: Float) -> Int;

/// `c.zIndex = z` — draw-order index; only honoured when the parent
/// has `sortableChildren = true`. Returns 0.
pub extern fn pixiContainerSetZIndex(c: Container, z: Int) -> Int;

/// `c.sortableChildren = v` — enables zIndex sort of children on this
/// container. Returns 0.
pub extern fn pixiContainerSetSortableChildren(c: Container, v: Bool) -> Int;

/// `c.eventMode = mode` — pointer-event participation. PIXI 8 accepts
/// `"static"`, `"dynamic"`, `"passive"`, `"none"`, `"auto"`. Returns 0.
pub extern fn pixiContainerSetEventMode(c: Container, mode: String) -> Int;

/// `c.cursor = cursor` — CSS cursor name displayed while pointer is
/// over this container (`"pointer"`, `"grab"`, …). Returns 0.
pub extern fn pixiContainerSetCursor(c: Container, cursor: String) -> Int;

/// Set `c.visible`. Returns 0.
pub extern fn pixiContainerSetVisible(c: Container, v: Bool) -> Int;

/// `c.on(event, handler)` — register a `FederatedPointerEvent`
/// handler. `event` is a PIXI 8 event string (`"pointerdown"`,
/// `"pointerup"`, `"pointertap"`, `"pointerover"`, `"pointerout"`,
/// `"pointermove"`, `"pointercancel"`, `"globalpointermove"`).
/// `handler` is an opaque JS function (`(event: Json) => void`) — the
/// `FederatedPointerEvent` arg is exposed as `Json` and read with
/// existing Json accessors. Returns 0.
pub extern fn pixiContainerOn(c: Container, event: String, handler: Json) -> Int;

/// `c.off(event, handler)` — deregister a previously-registered
/// handler. Same `event`/`handler` shape as `pixiContainerOn`. Returns 0.
pub extern fn pixiContainerOff(c: Container, event: String, handler: Json) -> Int;

/// `c.destroy()` — recursively free this container and its children.
/// Returns 0.
pub extern fn pixiContainerDestroy(c: Container) -> Int;
Expand All @@ -95,6 +136,11 @@ pub extern fn pixiContainerDestroy(c: Container) -> Int;
/// `new PIXI.Sprite(texture)`.
pub extern fn pixiSpriteFrom(texture: Texture) -> Sprite;

/// `s.anchor.set(x, y)` — anchor point on the sprite for positioning
/// and rotation. `0.0, 0.0` is top-left; `0.5, 0.5` centres the sprite
/// on its `x, y`. Returns 0.
pub extern fn pixiSpriteSetAnchor(s: Sprite, x: Float, y: Float) -> Int;

/// Upcast a Sprite to its Container superclass. Zero-cost identity
/// lowering — the underlying JS value is the same object.
pub extern fn pixiSpriteAsContainer(s: Sprite) -> Container;
Expand Down
26 changes: 25 additions & 1 deletion tests/codegen-deno/pixi_smoke.affine
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
// extern shape (constructor, mutator, async init, upcast).

use Deno::{Json};
use Pixi::{Application, Container, Sprite, Graphics, Text, Texture, pixiAppInit, pixiAppStage, pixiContainerNew, pixiContainerAddChild, pixiContainerSetPosition, pixiContainerSetVisible, pixiSpriteFrom, pixiSpriteAsContainer, pixiTextureFromUrl, pixiGraphicsNew, pixiGraphicsRect, pixiGraphicsFill, pixiGraphicsAsContainer};
use Pixi::{Application, Container, Sprite, Graphics, Text, Texture, pixiAppInit, pixiAppStage, pixiContainerNew, pixiContainerAddChild, pixiContainerSetPosition, pixiContainerSetVisible, pixiContainerSetScale, pixiContainerSetPivot, pixiContainerSetRotation, pixiContainerSetAlpha, pixiContainerSetZIndex, pixiContainerSetSortableChildren, pixiContainerSetEventMode, pixiContainerSetCursor, pixiContainerOn, pixiContainerOff, pixiSpriteFrom, pixiSpriteSetAnchor, pixiSpriteAsContainer, pixiTextureFromUrl, pixiGraphicsNew, pixiGraphicsRect, pixiGraphicsFill, pixiGraphicsAsContainer};

pub fn smokeInit(options: Json) -> Application / { Async } = pixiAppInit(options);

Expand All @@ -27,3 +27,27 @@ pub fn smokeGraphicsFlow(app: Application, color: Int) -> Int {
pixiContainerAddChild(pixiAppStage(app), pixiGraphicsAsContainer(g));
0
}

/// Exercises every Container accessor + Sprite anchor + on/off
/// FederatedEvent registration shape — the PixiJS 8 transform-and-event
/// surface idaptik's 215 src/app files lean on.
pub fn smokeAccessorsFlow(app: Application, url: String, onDown: Json, onMove: Json) -> Int {
let stage = pixiAppStage(app);
let texture = pixiTextureFromUrl(url);
let sprite = pixiSpriteFrom(texture);
let spriteC = pixiSpriteAsContainer(sprite);
pixiSpriteSetAnchor(sprite, 0.5, 0.5);
pixiContainerSetScale(spriteC, 2.0, 3.0);
pixiContainerSetPivot(spriteC, 10.0, 20.0);
pixiContainerSetRotation(spriteC, 1.5708);
pixiContainerSetAlpha(spriteC, 0.75);
pixiContainerSetZIndex(spriteC, 7);
pixiContainerSetSortableChildren(stage, true);
pixiContainerSetEventMode(spriteC, "static");
pixiContainerSetCursor(spriteC, "pointer");
pixiContainerOn(spriteC, "pointerdown", onDown);
pixiContainerOn(spriteC, "pointermove", onMove);
pixiContainerAddChild(stage, spriteC);
pixiContainerOff(spriteC, "pointermove", onMove);
0
}
54 changes: 51 additions & 3 deletions tests/codegen-deno/pixi_smoke.harness.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,49 @@ const initCalls = [];
const textureUrls = [];
const operationsLog = [];

class MockPoint {
constructor() { this.x = 0; this.y = 0; }
set(x, y) { this.x = x; this.y = y; }
}

class MockContainer {
constructor() {
this.x = 0; this.y = 0; this.visible = true;
this.x = 0; this.y = 0;
this.scale = new MockPoint();
this.pivot = new MockPoint();
this.rotation = 0;
this.alpha = 1;
this.zIndex = 0;
this.sortableChildren = false;
this.eventMode = "auto";
this.cursor = "";
this.visible = true;
this.children = [];
this.handlers = new Map();
}
addChild(c) { this.children.push(c); operationsLog.push("addChild"); }
removeChild(c) {
this.children = this.children.filter((ch) => ch !== c);
operationsLog.push("removeChild");
}
on(event, handler) {
if (!this.handlers.has(event)) this.handlers.set(event, []);
this.handlers.get(event).push(handler);
}
off(event, handler) {
const list = this.handlers.get(event);
if (!list) return;
this.handlers.set(event, list.filter((h) => h !== handler));
}
destroy() { operationsLog.push("destroy"); }
}

class MockSprite extends MockContainer {
constructor(texture) { super(); this.texture = texture; }
constructor(texture) {
super();
this.texture = texture;
this.anchor = new MockPoint();
}
}

class MockGraphics extends MockContainer {
Expand Down Expand Up @@ -60,7 +88,7 @@ globalThis.__as_pixi = {
},
};

const { smokeInit, smokeSpriteFlow, smokeGraphicsFlow } = await import("./pixi_smoke.deno.js");
const { smokeInit, smokeSpriteFlow, smokeGraphicsFlow, smokeAccessorsFlow } = await import("./pixi_smoke.deno.js");

// Async init returns an Application after `await app.init(options)`
const app = await smokeInit({ width: 800, height: 600, backgroundColor: 0x1099bb });
Expand All @@ -83,4 +111,24 @@ const graphics = app.stage.children[1];
assert.deepEqual(graphics.paths, [{ kind: "rect", x: 0, y: 0, w: 50, h: 50 }], "rect path recorded");
assert.deepEqual(graphics.fills, [{ color: 0xff0000 }], "fill recorded with color");

// Accessors flow: every Container 8.x setter + Sprite anchor + on/off
const onDown = (e) => { onDown.lastEvent = e; };
const onMove = (e) => { onMove.lastEvent = e; };
assert.equal(smokeAccessorsFlow(app, "/assets/btn.png", onDown, onMove), 0, "smokeAccessorsFlow returns 0");
assert.equal(textureUrls.length, 2, "second Texture.from recorded");
assert.equal(app.stage.children.length, 3, "accessor sprite added as third child");
const aSprite = app.stage.children[2];
assert.equal(aSprite.anchor.x, 0.5, "sprite.anchor.x"); assert.equal(aSprite.anchor.y, 0.5, "sprite.anchor.y");
assert.equal(aSprite.scale.x, 2.0, "container.scale.x"); assert.equal(aSprite.scale.y, 3.0, "container.scale.y");
assert.equal(aSprite.pivot.x, 10.0, "container.pivot.x"); assert.equal(aSprite.pivot.y, 20.0, "container.pivot.y");
assert.equal(aSprite.rotation, 1.5708, "container.rotation");
assert.equal(aSprite.alpha, 0.75, "container.alpha");
assert.equal(aSprite.zIndex, 7, "container.zIndex");
assert.equal(app.stage.sortableChildren, true, "stage.sortableChildren set");
assert.equal(aSprite.eventMode, "static", "container.eventMode");
assert.equal(aSprite.cursor, "pointer", "container.cursor");
assert.equal(aSprite.handlers.get("pointerdown").length, 1, "pointerdown handler registered");
assert.equal(aSprite.handlers.get("pointermove").length, 0, "pointermove handler off after off()");
assert.equal(aSprite.handlers.get("pointerdown")[0], onDown, "pointerdown handler identity preserved");

console.log("pixi_smoke.harness.mjs OK");
Loading