diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 68b90600..33bac8ab 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -23,6 +23,12 @@ kept so existing scripts fail loudly (Refs #56) ==== Added +* Pixi/UI/Sound idaptik-reached remainder (#760): Graphics +circle/stroke/roundRect/path, per-axis Container x/y, parent Option, +interactiveChildren, ObservablePoint.set, Texture.WHITE, Assets +init/loadBundle, Renderer.resize, FancyButton onPress/onDown/onHover, +Slider/Input value, `@pixi/sound` singleton pauseAll/find/play(name). +Bun-ESM lowerings + smokes. Unused Pixi.res decls stay out. * CI gates on `.deed` (DEED grammar). Leftover `.a2ml` is a hard fail. `tools/check-deed-gates.sh` + `.github/workflows/deed-validate.yml`. Machine-readable manifests renamed `.a2ml` → `.deed`. Repo deed: diff --git a/docs/CAPABILITY-MATRIX.adoc b/docs/CAPABILITY-MATRIX.adoc index e77ed4ab..849c9e8c 100644 --- a/docs/CAPABILITY-MATRIX.adoc +++ b/docs/CAPABILITY-MATRIX.adoc @@ -298,8 +298,9 @@ their own ledger (link:SOUNDNESS.adoc[SOUNDNESS.adoc]), gated by (INT-09) closed 2026-07-25. `affinescript-pixijs` in-tree bindings (`stdlib/Pixi.affine` + UI/Sound) plus 56-A `stdlib/Dom.affine` / `stdlib/Console.affine` and 56 PR 4 Window/Document/utility externs - landed 2026-09-21; record-literal attrs + remaining idaptik Pixi - decls are still open (#56). The `-dom-loader` satellite was folded into + landed 2026-09-21; idaptik-reached Pixi/UI/Sound remainder landed + 2026-09-21 (#760). Unused Pixi.res decls (BigPool, Extensions, + ScrollBox, sprite atlases) are not in the #760 bar. The `-dom-loader` satellite was folded into `affine-js` (#489). == Change control diff --git a/docs/TECH-DEBT.adoc b/docs/TECH-DEBT.adoc index 7404b2dd..550e7576 100644 --- a/docs/TECH-DEBT.adoc +++ b/docs/TECH-DEBT.adoc @@ -26,9 +26,8 @@ index. Residual WASI follow-ups #485 (S5 filesystem) and #487 (socket recv/send/accept) have preview1 lowerings + gated smokes in tree; #486 flipped the *wrap* default to `--command` without the one-way compiler re-target (native-preview2 cleanup still open). -Satellite remainder is #56 (56-A Dom/Console + Pixi gap-fill + -Window/Document/utility PR 4 landed; record-literal attrs + remaining -idaptik Pixi decls still open). +Satellite remainder for Pixi is #760 (idaptik-reached decls landed; +unused Pixi.res surface is not a close-blocker). #56 is closed. ==== toc::[] @@ -487,16 +486,16 @@ Proven + locked (see INT-02) #182 CLOSED 2026-07-25) |SAT-04 |`affinescript-cadre` router runtime |S2 |**DONE** (=INT-09, CLOSED 2026-07-25) -|SAT-05 |`affinescript-pixijs` migration prerequisite |S3 |open #56 — -56-A + Pixi gap-fill + Window/Document/utility (PR 4) landed; remainder -is record-literal attrs + the rest of idaptik's Pixi.res decls +|SAT-05 |`affinescript-pixijs` migration prerequisite |S3 |#56 closed; +idaptik-reached Pixi/UI/Sound remainder landed #760. Unused decls +(BigPool, Extensions, ScrollBox, sprite atlases) stay out of bar |=== == Stage E — typed-wasm convergence runway Formal interface spec: link:specs/TYPED-WASM-INTERFACE.adoc[specs/TYPED-WASM-INTERFACE.adoc] -(+ link:specs/TYPED-WASM-INTERFACE.a2ml[machine companion]). +(+ link:specs/TYPED-WASM-INTERFACE.deed[machine companion]). Widening roadmap (tranches A..E, recommended sequencing): link:specs/TYPED-WASM-ROADMAP.adoc[specs/TYPED-WASM-ROADMAP.adoc]. Proposed ADRs: ADR-020 (schema versioning), ADR-021 (multi-producer diff --git a/docs/bindings-roadmap.adoc b/docs/bindings-roadmap.adoc index a042a741..19e82aab 100644 --- a/docs/bindings-roadmap.adoc +++ b/docs/bindings-roadmap.adoc @@ -49,21 +49,21 @@ no further significant → AffineScript work is tractable. |1 |*PixiJS core* (Application, Container, Sprite, Graphics, Text, Ticker, FederatedEvent) -|`◑` 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) +|`◑` partial (idaptik-reached surface landed #760: Application / Container / Sprite / Graphics / Text / Texture / Ticker / Point / Rectangle / Circle / ObservablePoint / BlurFilter / NineSliceSprite / Assets / Renderer; per-axis x/y, parent Option, interactiveChildren, addChildAt, removeChildren, Graphics circle/stroke/roundRect/path, Texture.WHITE, ticker deltaTime, renderer.resize, FederatedPointerEvent global + preventDefault) |`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. 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. +|idaptik `src/bindings/Pixi.res` (JoshuaJewell/IDApixiTIK). 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 Bun-ESM emitter; it was deleted and the surface rebuilt in `stdlib/Pixi.affine`. #760 ports only what `src/app` + `src/engine` still call. Test fixture: `tests/codegen-deno/pixi_smoke.{affine,harness.mjs}` (`smokeIdaptikReach` / `smokeAssets`). Unused decls (BigPool, Extensions, ResizePlugin, CanvasStyle, TextStyle, sprite atlases, stopPropagation target) stay out. |2 |*@pixi/sound* (`Sound.from`, play, stop, volume, setVolume, loop) -|`◐` scaffold (from / play / stop / pause / resume / setVolume / setLoop landed) +|`◑` partial (`Sound.from` transport + idaptik singleton `pauseAll` / `resumeAll` / `muteAll` / `unmuteAll` / `volumeAll` / `find` / `play(name)` landed #760) |`stdlib/PixiSound.affine` (eventual home: `affinescript-pixijs` sub-module, or `affinescript-pixi-sound`) -|idaptik `src/bindings/PixiSound.res`; BGM + SFX. Initial surface (`pixiSoundFrom` / `pixiSoundPlay` / `pixiSoundStop` / `pixiSoundPause` / `pixiSoundResume` / `pixiSoundSetVolume` / `pixiSoundSetLoop`) lands in `stdlib/` parallel to Motion / Http / Sqlite / Crypto; consumer provides `globalThis.__as_pixi_sound` (the `Sound` named export from `@pixi/sound`) at module-init time. Test fixture: `tests/codegen-deno/pixisound_smoke.{affine,harness.mjs}`. Follow-ups: `Sound.add` (multi-source registry), sprite atlases, async load (await + onload), `pauseAll` / `stopAll` / `resumeAll`, the `sound` singleton's filter pipeline. +|idaptik `src/bindings/PixiSound.res` talks to the `@pixi/sound` *singleton*, not `Sound.from`. Consumer sets `globalThis.__as_pixi_sound` to the `Sound` class and `globalThis.__as_pixi_sound_lib` to the `sound` singleton. Test fixture: `tests/codegen-deno/pixisound_smoke.{affine,harness.mjs}`. Follow-ups: `Sound.add`, sprite atlases, async load, singleton filter pipeline. |3 |*@pixi/ui* (Button, FancyButton, Switch, Slider, Input, ScrollBox) -|`◐` scaffold +|`◑` partial (idaptik-reached: FancyButton onPress/onDown/onHover, Slider value + addChild, Input value/onEnter/onChange, List.addChild, ProgressBar progress; Button/Switch constructors remain) |`stdlib/PixiUI.affine` (eventual home: `affinescript-pixijs` sub-module) -|idaptik `src/bindings/PixiUI.res`; all HUD / menu code. MVP surface (Button / FancyButton / Slider / Switch — `new` + primary callback + `AsContainer` upcast) lives in `stdlib/` alongside Pixi / PixiSound; consumer provides `globalThis.__as_pixi_ui` at module-init time. Test fixture: `tests/codegen-deno/pixiui_smoke.{affine,harness.mjs}`. Deferred follow-ups: Input (text-entry + focus + value), ScrollBox (scroll position + viewport), full event surface (onHover / onOut / onDown / onUp), FancyButton textures-per-state accessors, Slider min/max/step accessors. +|idaptik `src/bindings/PixiUI.res` HUD/menu. #760 adds the signals and value accessors `src/app` actually calls. Test fixture: `tests/codegen-deno/pixiui_smoke.{affine,harness.mjs}`. Deferred: ScrollBox, FancyButton textures-per-state, Slider min/max/step, Input focus. |4 |*motion* (animate, animateMini, tween, ease, spring) diff --git a/lib/codegen_deno.ml b/lib/codegen_deno.ml index a7a36c43..4cd32d21 100644 --- a/lib/codegen_deno.ml +++ b/lib/codegen_deno.ml @@ -381,6 +381,47 @@ const __as_pixiNineSliceSpriteAsContainer = (s) => s; const __as_pixiAssetsLoad = async (url) => globalThis.__as_pixi.Assets.load(url); const __as_pixiEventGlobalX = (e) => Number(e && e.global ? e.global.x : 0); const __as_pixiEventGlobalY = (e) => Number(e && e.global ? e.global.y : 0); +const __as_pixiContainerX = (c) => Number(c.x) || 0; +const __as_pixiContainerY = (c) => Number(c.y) || 0; +const __as_pixiContainerSetX = (c, x) => { c.x = x; return 0; }; +const __as_pixiContainerSetY = (c, y) => { c.y = y; return 0; }; +const __as_pixiContainerWidth = (c) => Number(c.width) || 0; +const __as_pixiContainerHeight = (c) => Number(c.height) || 0; +const __as_pixiContainerParent = (c) => (c.parent == null ? None : Some(c.parent)); +const __as_pixiContainerSetInteractiveChildren = (c, v) => { c.interactiveChildren = v; return 0; }; +const __as_pixiContainerRemoveChildren = (c) => { c.removeChildren(); return 0; }; +const __as_pixiContainerAddChildAt = (p, c, i) => { p.addChildAt(c, i); return 0; }; +const __as_pixiContainerSetHitAreaCircle = (c, area) => { c.hitArea = area; return 0; }; +const __as_pixiContainerScale = (c) => c.scale; +const __as_pixiContainerPivot = (c) => c.pivot; +const __as_pixiContainerAnchor = (c) => c.anchor; +const __as_pixiObservablePointSet = (p, x, y) => { p.set(x, y); return 0; }; +const __as_pixiObservablePointX = (p) => Number(p.x) || 0; +const __as_pixiObservablePointY = (p) => Number(p.y) || 0; +const __as_pixiObservablePointSetX = (p, x) => { p.x = x; return 0; }; +const __as_pixiObservablePointSetY = (p, y) => { p.y = y; return 0; }; +const __as_pixiGraphicsCircle = (g, x, y, r) => { g.circle(x, y, r); return 0; }; +const __as_pixiGraphicsRoundRect = (g, x, y, w, h, radius) => { g.roundRect(x, y, w, h, radius); return 0; }; +const __as_pixiGraphicsStroke = (g, options) => { g.stroke(options); return 0; }; +const __as_pixiGraphicsMoveTo = (g, x, y) => { g.moveTo(x, y); return 0; }; +const __as_pixiGraphicsLineTo = (g, x, y) => { g.lineTo(x, y); return 0; }; +const __as_pixiGraphicsQuadraticCurveTo = (g, cpx, cpy, x, y) => { g.quadraticCurveTo(cpx, cpy, x, y); return 0; }; +const __as_pixiTextAnchor = (t) => t.anchor; +const __as_pixiSpriteSetWidth = (s, w) => { s.width = w; return 0; }; +const __as_pixiSpriteSetHeight = (s, h) => { s.height = h; return 0; }; +const __as_pixiSpriteWidth = (s) => Number(s.width) || 0; +const __as_pixiSpriteHeight = (s) => Number(s.height) || 0; +const __as_pixiSpriteSetInteractive = (s, v) => { s.interactive = v; return 0; }; +const __as_pixiSpritePosition = (s) => s.position; +const __as_pixiTextureWhite = () => globalThis.__as_pixi.Texture.WHITE; +const __as_pixiTickerDeltaTime = (t) => Number(t.deltaTime) || 0; +const __as_pixiTickerRemove = (t, cb) => { t.remove(cb); return 0; }; +const __as_pixiAppRenderer = (app) => app.renderer; +const __as_pixiRendererResize = (r, w, h) => { r.resize(w, h); return 0; }; +const __as_pixiAssetsInit = async (options) => { await globalThis.__as_pixi.Assets.init(options); return 0; }; +const __as_pixiAssetsLoadBundle = async (name) => { await globalThis.__as_pixi.Assets.loadBundle(name); return 0; }; +const __as_pixiAssetsBackgroundLoadBundle = (name) => { globalThis.__as_pixi.Assets.backgroundLoadBundle([name]); return 0; }; +const __as_pixiEventPreventDefault = (e) => { if (e && typeof e.preventDefault === "function") e.preventDefault(); return 0; }; // ---- @pixi/ui (bindings #3): consumer-provided import ---- // Host JS environment exposes globalThis.__as_pixi_ui (the namespace // from `import * as PixiUI from "@pixi/ui"`). Tests set it in the @@ -409,6 +450,19 @@ const __as_pixiUiListNew = (options) => new globalThis.__as_pixi_ const __as_pixiUiListAsContainer = (l) => l; const __as_pixiUiInputNew = (options) => new globalThis.__as_pixi_ui.Input(options); const __as_pixiUiInputAsContainer = (i) => i; +const __as_pixiUiFancyButtonOnPress = (b, cb) => { b.onPress.connect(cb); return 0; }; +const __as_pixiUiFancyButtonOnDown = (b, cb) => { b.onDown.connect(cb); return 0; }; +const __as_pixiUiFancyButtonOnHover = (b, cb) => { b.onHover.connect(cb); return 0; }; +const __as_pixiUiSliderValue = (s) => Number(s.value) || 0; +const __as_pixiUiSliderSetValue = (s, v) => { s.value = v; return 0; }; +const __as_pixiUiSliderAddChild = (s, child) => { s.addChild(child); return 0; }; +const __as_pixiUiListAddChild = (l, child) => { l.addChild(child); return 0; }; +const __as_pixiUiInputValue = (i) => String(i.value ?? ""); +const __as_pixiUiInputSetValue = (i, v) => { i.value = v; return 0; }; +const __as_pixiUiInputOnEnter = (i, cb) => { i.onEnter.connect(cb); return 0; }; +const __as_pixiUiInputOnChange = (i, cb) => { i.onChange.connect(cb); return 0; }; +const __as_pixiUiProgressBarProgress = (p) => Number(p.progress) || 0; +const __as_pixiUiProgressBarSetProgress = (p, v) => { p.progress = v; return 0; }; // ---- @pixi/sound (bindings #2): consumer-provided import ---- // Host JS environment exposes globalThis.__as_pixi_sound (the `Sound` // named export from `@pixi/sound`). Tests set it in the harness before @@ -426,6 +480,15 @@ const __as_pixiSoundSetVolume = (s, vol) => { s.volume = vol; return 0; }; const __as_pixiSoundSetLoop = (s, loop) => { s.loop = loop; return 0; }; const __as_pixiSoundIsPlaying = (s) => Boolean(s.isPlaying); const __as_pixiSoundDuration = (s) => Number(s.duration) || 0; +const __as_pixiSoundPlayOptions = (s, options) => { s.play(options); return 0; }; +const __as_pixiSoundPauseAll = () => { globalThis.__as_pixi_sound_lib.pauseAll(); return 0; }; +const __as_pixiSoundResumeAll = () => { globalThis.__as_pixi_sound_lib.resumeAll(); return 0; }; +const __as_pixiSoundMuteAll = () => { globalThis.__as_pixi_sound_lib.muteAll(); return 0; }; +const __as_pixiSoundUnmuteAll = () => { globalThis.__as_pixi_sound_lib.unmuteAll(); return 0; }; +const __as_pixiSoundVolumeAll = () => Number(globalThis.__as_pixi_sound_lib.volumeAll) || 0; +const __as_pixiSoundSetVolumeAll = (vol) => { globalThis.__as_pixi_sound_lib.volumeAll = vol; return 0; }; +const __as_pixiSoundFind = (name) => globalThis.__as_pixi_sound_lib.find(name); +const __as_pixiSoundPlayName = (name, options) => { globalThis.__as_pixi_sound_lib.play(name, options); return 0; }; // ---- Ipc (bindings #9): web-platform MessageChannel/MessagePort ---- // Uses standard web globals (MessageChannel, structuredClone) — no // consumer-side init required. Available unmodified in modern JS runtimes, @@ -884,6 +947,47 @@ let () = b "pixiAssetsLoad" (fun a -> Printf.sprintf "(await __as_pixiAssetsLoad(%s))" (arg 0 a)); b "pixiEventGlobalX" (fun a -> Printf.sprintf "__as_pixiEventGlobalX(%s)" (arg 0 a)); b "pixiEventGlobalY" (fun a -> Printf.sprintf "__as_pixiEventGlobalY(%s)" (arg 0 a)); + b "pixiContainerX" (fun a -> Printf.sprintf "__as_pixiContainerX(%s)" (arg 0 a)); + b "pixiContainerY" (fun a -> Printf.sprintf "__as_pixiContainerY(%s)" (arg 0 a)); + b "pixiContainerSetX" (fun a -> Printf.sprintf "__as_pixiContainerSetX(%s, %s)" (arg 0 a) (arg 1 a)); + b "pixiContainerSetY" (fun a -> Printf.sprintf "__as_pixiContainerSetY(%s, %s)" (arg 0 a) (arg 1 a)); + b "pixiContainerWidth" (fun a -> Printf.sprintf "__as_pixiContainerWidth(%s)" (arg 0 a)); + b "pixiContainerHeight" (fun a -> Printf.sprintf "__as_pixiContainerHeight(%s)" (arg 0 a)); + b "pixiContainerParent" (fun a -> Printf.sprintf "__as_pixiContainerParent(%s)" (arg 0 a)); + b "pixiContainerSetInteractiveChildren" (fun a -> Printf.sprintf "__as_pixiContainerSetInteractiveChildren(%s, %s)" (arg 0 a) (arg 1 a)); + b "pixiContainerRemoveChildren" (fun a -> Printf.sprintf "__as_pixiContainerRemoveChildren(%s)" (arg 0 a)); + b "pixiContainerAddChildAt" (fun a -> Printf.sprintf "__as_pixiContainerAddChildAt(%s, %s, %s)" (arg 0 a) (arg 1 a) (arg 2 a)); + b "pixiContainerSetHitAreaCircle" (fun a -> Printf.sprintf "__as_pixiContainerSetHitAreaCircle(%s, %s)" (arg 0 a) (arg 1 a)); + b "pixiContainerScale" (fun a -> Printf.sprintf "__as_pixiContainerScale(%s)" (arg 0 a)); + b "pixiContainerPivot" (fun a -> Printf.sprintf "__as_pixiContainerPivot(%s)" (arg 0 a)); + b "pixiContainerAnchor" (fun a -> Printf.sprintf "__as_pixiContainerAnchor(%s)" (arg 0 a)); + b "pixiObservablePointSet" (fun a -> Printf.sprintf "__as_pixiObservablePointSet(%s, %s, %s)" (arg 0 a) (arg 1 a) (arg 2 a)); + b "pixiObservablePointX" (fun a -> Printf.sprintf "__as_pixiObservablePointX(%s)" (arg 0 a)); + b "pixiObservablePointY" (fun a -> Printf.sprintf "__as_pixiObservablePointY(%s)" (arg 0 a)); + b "pixiObservablePointSetX" (fun a -> Printf.sprintf "__as_pixiObservablePointSetX(%s, %s)" (arg 0 a) (arg 1 a)); + b "pixiObservablePointSetY" (fun a -> Printf.sprintf "__as_pixiObservablePointSetY(%s, %s)" (arg 0 a) (arg 1 a)); + b "pixiGraphicsCircle" (fun a -> Printf.sprintf "__as_pixiGraphicsCircle(%s, %s, %s, %s)" (arg 0 a) (arg 1 a) (arg 2 a) (arg 3 a)); + b "pixiGraphicsRoundRect" (fun a -> Printf.sprintf "__as_pixiGraphicsRoundRect(%s, %s, %s, %s, %s, %s)" (arg 0 a) (arg 1 a) (arg 2 a) (arg 3 a) (arg 4 a) (arg 5 a)); + b "pixiGraphicsStroke" (fun a -> Printf.sprintf "__as_pixiGraphicsStroke(%s, %s)" (arg 0 a) (arg 1 a)); + b "pixiGraphicsMoveTo" (fun a -> Printf.sprintf "__as_pixiGraphicsMoveTo(%s, %s, %s)" (arg 0 a) (arg 1 a) (arg 2 a)); + b "pixiGraphicsLineTo" (fun a -> Printf.sprintf "__as_pixiGraphicsLineTo(%s, %s, %s)" (arg 0 a) (arg 1 a) (arg 2 a)); + b "pixiGraphicsQuadraticCurveTo" (fun a -> Printf.sprintf "__as_pixiGraphicsQuadraticCurveTo(%s, %s, %s, %s, %s)" (arg 0 a) (arg 1 a) (arg 2 a) (arg 3 a) (arg 4 a)); + b "pixiTextAnchor" (fun a -> Printf.sprintf "__as_pixiTextAnchor(%s)" (arg 0 a)); + b "pixiSpriteSetWidth" (fun a -> Printf.sprintf "__as_pixiSpriteSetWidth(%s, %s)" (arg 0 a) (arg 1 a)); + b "pixiSpriteSetHeight" (fun a -> Printf.sprintf "__as_pixiSpriteSetHeight(%s, %s)" (arg 0 a) (arg 1 a)); + b "pixiSpriteWidth" (fun a -> Printf.sprintf "__as_pixiSpriteWidth(%s)" (arg 0 a)); + b "pixiSpriteHeight" (fun a -> Printf.sprintf "__as_pixiSpriteHeight(%s)" (arg 0 a)); + b "pixiSpriteSetInteractive" (fun a -> Printf.sprintf "__as_pixiSpriteSetInteractive(%s, %s)" (arg 0 a) (arg 1 a)); + b "pixiSpritePosition" (fun a -> Printf.sprintf "__as_pixiSpritePosition(%s)" (arg 0 a)); + b "pixiTextureWhite" (fun _ -> "__as_pixiTextureWhite()"); + b "pixiTickerDeltaTime" (fun a -> Printf.sprintf "__as_pixiTickerDeltaTime(%s)" (arg 0 a)); + b "pixiTickerRemove" (fun a -> Printf.sprintf "__as_pixiTickerRemove(%s, %s)" (arg 0 a) (arg 1 a)); + b "pixiAppRenderer" (fun a -> Printf.sprintf "__as_pixiAppRenderer(%s)" (arg 0 a)); + b "pixiRendererResize" (fun a -> Printf.sprintf "__as_pixiRendererResize(%s, %s, %s)" (arg 0 a) (arg 1 a) (arg 2 a)); + b "pixiAssetsInit" (fun a -> Printf.sprintf "(await __as_pixiAssetsInit(%s))" (arg 0 a)); + b "pixiAssetsLoadBundle" (fun a -> Printf.sprintf "(await __as_pixiAssetsLoadBundle(%s))" (arg 0 a)); + b "pixiAssetsBackgroundLoadBundle" (fun a -> Printf.sprintf "__as_pixiAssetsBackgroundLoadBundle(%s)" (arg 0 a)); + b "pixiEventPreventDefault" (fun a -> Printf.sprintf "__as_pixiEventPreventDefault(%s)" (arg 0 a)); (* ---- @pixi/ui (bindings #3) ---- *) b "pixiUiButtonNew" (fun a -> Printf.sprintf "__as_pixiUiButtonNew(%s)" (arg 0 a)); b "pixiUiButtonOnPress" (fun a -> Printf.sprintf "__as_pixiUiButtonOnPress(%s, %s)" (arg 0 a) (arg 1 a)); @@ -902,6 +1006,19 @@ let () = b "pixiUiListAsContainer" (fun a -> Printf.sprintf "__as_pixiUiListAsContainer(%s)" (arg 0 a)); b "pixiUiInputNew" (fun a -> Printf.sprintf "__as_pixiUiInputNew(%s)" (arg 0 a)); b "pixiUiInputAsContainer" (fun a -> Printf.sprintf "__as_pixiUiInputAsContainer(%s)" (arg 0 a)); + b "pixiUiFancyButtonOnPress" (fun a -> Printf.sprintf "__as_pixiUiFancyButtonOnPress(%s, %s)" (arg 0 a) (arg 1 a)); + b "pixiUiFancyButtonOnDown" (fun a -> Printf.sprintf "__as_pixiUiFancyButtonOnDown(%s, %s)" (arg 0 a) (arg 1 a)); + b "pixiUiFancyButtonOnHover" (fun a -> Printf.sprintf "__as_pixiUiFancyButtonOnHover(%s, %s)" (arg 0 a) (arg 1 a)); + b "pixiUiSliderValue" (fun a -> Printf.sprintf "__as_pixiUiSliderValue(%s)" (arg 0 a)); + b "pixiUiSliderSetValue" (fun a -> Printf.sprintf "__as_pixiUiSliderSetValue(%s, %s)" (arg 0 a) (arg 1 a)); + b "pixiUiSliderAddChild" (fun a -> Printf.sprintf "__as_pixiUiSliderAddChild(%s, %s)" (arg 0 a) (arg 1 a)); + b "pixiUiListAddChild" (fun a -> Printf.sprintf "__as_pixiUiListAddChild(%s, %s)" (arg 0 a) (arg 1 a)); + b "pixiUiInputValue" (fun a -> Printf.sprintf "__as_pixiUiInputValue(%s)" (arg 0 a)); + b "pixiUiInputSetValue" (fun a -> Printf.sprintf "__as_pixiUiInputSetValue(%s, %s)" (arg 0 a) (arg 1 a)); + b "pixiUiInputOnEnter" (fun a -> Printf.sprintf "__as_pixiUiInputOnEnter(%s, %s)" (arg 0 a) (arg 1 a)); + b "pixiUiInputOnChange" (fun a -> Printf.sprintf "__as_pixiUiInputOnChange(%s, %s)" (arg 0 a) (arg 1 a)); + b "pixiUiProgressBarProgress" (fun a -> Printf.sprintf "__as_pixiUiProgressBarProgress(%s)" (arg 0 a)); + b "pixiUiProgressBarSetProgress" (fun a -> Printf.sprintf "__as_pixiUiProgressBarSetProgress(%s, %s)" (arg 0 a) (arg 1 a)); (* ---- motion extras (bindings #4 follow-up) ---- *) b "motionAnimateMini" (fun a -> Printf.sprintf "__as_motionAnimateMini(%s, %s, %s)" (arg 0 a) (arg 1 a) (arg 2 a)); b "motionTween" (fun a -> Printf.sprintf "__as_motionTween(%s, %s, %s, %s)" (arg 0 a) (arg 1 a) (arg 2 a) (arg 3 a)); @@ -915,6 +1032,17 @@ let () = b "pixiSoundResume" (fun a -> Printf.sprintf "__as_pixiSoundResume(%s)" (arg 0 a)); b "pixiSoundSetVolume" (fun a -> Printf.sprintf "__as_pixiSoundSetVolume(%s, %s)" (arg 0 a) (arg 1 a)); b "pixiSoundSetLoop" (fun a -> Printf.sprintf "__as_pixiSoundSetLoop(%s, %s)" (arg 0 a) (arg 1 a)); + b "pixiSoundIsPlaying" (fun a -> Printf.sprintf "__as_pixiSoundIsPlaying(%s)" (arg 0 a)); + b "pixiSoundDuration" (fun a -> Printf.sprintf "__as_pixiSoundDuration(%s)" (arg 0 a)); + b "pixiSoundPlayOptions"(fun a -> Printf.sprintf "__as_pixiSoundPlayOptions(%s, %s)" (arg 0 a) (arg 1 a)); + b "pixiSoundPauseAll" (fun _ -> "__as_pixiSoundPauseAll()"); + b "pixiSoundResumeAll" (fun _ -> "__as_pixiSoundResumeAll()"); + b "pixiSoundMuteAll" (fun _ -> "__as_pixiSoundMuteAll()"); + b "pixiSoundUnmuteAll" (fun _ -> "__as_pixiSoundUnmuteAll()"); + b "pixiSoundVolumeAll" (fun _ -> "__as_pixiSoundVolumeAll()"); + b "pixiSoundSetVolumeAll" (fun a -> Printf.sprintf "__as_pixiSoundSetVolumeAll(%s)" (arg 0 a)); + b "pixiSoundFind" (fun a -> Printf.sprintf "__as_pixiSoundFind(%s)" (arg 0 a)); + b "pixiSoundPlayName" (fun a -> Printf.sprintf "__as_pixiSoundPlayName(%s, %s)" (arg 0 a) (arg 1 a)); (* ---- Ipc (bindings #9): MessageChannel/MessagePort + structuredClone ---- *) b "messageChannelNew" (fun _ -> "__as_messageChannelNew()"); b "messageChannelPort1" (fun a -> Printf.sprintf "__as_messageChannelPort1(%s)" (arg 0 a)); diff --git a/stdlib/Pixi.affine b/stdlib/Pixi.affine index 7ea895dd..58616ba7 100644 --- a/stdlib/Pixi.affine +++ b/stdlib/Pixi.affine @@ -9,7 +9,7 @@ // replacing idaptik's src/bindings/Pixi.res (the largest binding // surface in idaptik's 215-file src/app/ tree). // -// Targets the Deno-ESM backend. Consumer (or its host wrapper) sets +// Targets the Bun-ESM backend. Consumer (or its host wrapper) sets // `globalThis.__as_pixi` to the PIXI namespace at module-init time: // // import * as PIXI from "pixi.js"; @@ -33,6 +33,7 @@ module Pixi; use Deno::{Json}; +use prelude::{Option}; // ── Opaque host types ────────────────────────────────────────────── // @@ -244,3 +245,92 @@ pub extern fn pixiEventGlobalX(e: Json) -> Float; /// `event.global.y` on a FederatedPointerEvent exposed as Json. pub extern fn pixiEventGlobalY(e: Json) -> Float; + +// ── idaptik-reached remainder (#760) ──────────────────────────────── +// +// Audited against JoshuaJewell/IDApixiTIK `src/bindings/Pixi.res` and +// `src/app` + `src/engine` call sites. Only declarations a real build +// still reaches. Unused Pixi.res surface (BigPool, Extensions, +// ResizePlugin, CanvasStyle, TextStyle, DOM helpers already in +// Dom.affine) stays out. + +pub extern type Renderer; + +/// `c.x` / `c.y` getters. Pair with `pixiContainerSetX` / `SetY` +/// (idaptik uses per-axis writes far more than `setPosition`). +pub extern fn pixiContainerX(c: Container) -> Float; +pub extern fn pixiContainerY(c: Container) -> Float; +pub extern fn pixiContainerSetX(c: Container, x: Float) -> Int; +pub extern fn pixiContainerSetY(c: Container, y: Float) -> Int; + +/// Layout size. Sprite width/height setters are separate +/// (`pixiSpriteSetWidth`) because Container.width is computed. +pub extern fn pixiContainerWidth(c: Container) -> Float; +pub extern fn pixiContainerHeight(c: Container) -> Float; + +/// `c.parent` — None when the container is not on a scene graph. +pub extern fn pixiContainerParent(c: Container) -> Option; + +/// `c.interactiveChildren = v`. Returns 0. +pub extern fn pixiContainerSetInteractiveChildren(c: Container, v: Bool) -> Int; + +/// `c.removeChildren()`. Returns 0. +pub extern fn pixiContainerRemoveChildren(c: Container) -> Int; + +/// `parent.addChildAt(child, index)`. Returns 0. +pub extern fn pixiContainerAddChildAt(parent: Container, child: Container, index: Int) -> Int; + +/// `c.hitArea = circle`. Returns 0. +pub extern fn pixiContainerSetHitAreaCircle(c: Container, area: Circle) -> Int; + +/// `c.scale` / `c.pivot` / `c.anchor` as ObservablePoint. Idaptik +/// then calls `ObservablePoint.set` (36 call sites). +pub extern fn pixiContainerScale(c: Container) -> ObservablePoint; +pub extern fn pixiContainerPivot(c: Container) -> ObservablePoint; +pub extern fn pixiContainerAnchor(c: Container) -> ObservablePoint; + +pub extern fn pixiObservablePointSet(p: ObservablePoint, x: Float, y: Float) -> Int; +pub extern fn pixiObservablePointX(p: ObservablePoint) -> Float; +pub extern fn pixiObservablePointY(p: ObservablePoint) -> Float; +pub extern fn pixiObservablePointSetX(p: ObservablePoint, x: Float) -> Int; +pub extern fn pixiObservablePointSetY(p: ObservablePoint, y: Float) -> Int; + +/// Graphics path extras idaptik actually draws (circle 51, stroke 58, +/// roundRect 10, moveTo/lineTo, one quadraticCurveTo). +pub extern fn pixiGraphicsCircle(g: Graphics, x: Float, y: Float, r: Float) -> Int; +pub extern fn pixiGraphicsRoundRect(g: Graphics, x: Float, y: Float, w: Float, h: Float, radius: Float) -> Int; +pub extern fn pixiGraphicsStroke(g: Graphics, options: Json) -> Int; +pub extern fn pixiGraphicsMoveTo(g: Graphics, x: Float, y: Float) -> Int; +pub extern fn pixiGraphicsLineTo(g: Graphics, x: Float, y: Float) -> Int; +pub extern fn pixiGraphicsQuadraticCurveTo(g: Graphics, cpx: Float, cpy: Float, x: Float, y: Float) -> Int; + +/// `t.anchor` — Text.anchor.set is 28 idaptik call sites. +pub extern fn pixiTextAnchor(t: Text) -> ObservablePoint; + +pub extern fn pixiSpriteSetWidth(s: Sprite, w: Float) -> Int; +pub extern fn pixiSpriteSetHeight(s: Sprite, h: Float) -> Int; +pub extern fn pixiSpriteWidth(s: Sprite) -> Float; +pub extern fn pixiSpriteHeight(s: Sprite) -> Float; +pub extern fn pixiSpriteSetInteractive(s: Sprite, v: Bool) -> Int; +pub extern fn pixiSpritePosition(s: Sprite) -> ObservablePoint; + +/// `PIXI.Texture.WHITE`. +pub extern fn pixiTextureWhite() -> Texture; + +pub extern fn pixiTickerDeltaTime(t: Ticker) -> Float; +pub extern fn pixiTickerRemove(t: Ticker, callback: Json) -> Int; + +pub extern fn pixiAppRenderer(app: Application) -> Renderer; +pub extern fn pixiRendererResize(r: Renderer, w: Float, h: Float) -> Int; + +/// `PIXI.Assets.init(options)`. Returns 0. +pub extern fn pixiAssetsInit(options: Json) -> Int / { Async }; + +/// `PIXI.Assets.loadBundle(name)` (string form). Returns 0. +pub extern fn pixiAssetsLoadBundle(name: String) -> Int / { Async }; + +/// `PIXI.Assets.backgroundLoadBundle([name])`. Returns 0. +pub extern fn pixiAssetsBackgroundLoadBundle(name: String) -> Int; + +/// `event.preventDefault()` on a FederatedPointerEvent-as-Json. Returns 0. +pub extern fn pixiEventPreventDefault(e: Json) -> Int; diff --git a/stdlib/PixiSound.affine b/stdlib/PixiSound.affine index 110db7ec..436c1752 100644 --- a/stdlib/PixiSound.affine +++ b/stdlib/PixiSound.affine @@ -6,7 +6,7 @@ // // Provides a typed surface over `@pixi/sound`'s `Sound.from()` factory // plus the basic transport operations (play / stop / pause / resume, -// volume + loop control). Targets the Deno-ESM backend; the consumer +// volume + loop control). Targets the Bun-ESM backend; the consumer // (or its host wrapper) is responsible for putting the `Sound` class // from `@pixi/sound` at `globalThis.__as_pixi_sound` before any // generated module that uses these externs runs. The test harness @@ -77,3 +77,37 @@ pub extern fn pixiSoundIsPlaying(s: Sound) -> Bool; /// `sound.duration` — decoded length in seconds (0 if still loading). pub extern fn pixiSoundDuration(s: Sound) -> Float; + +// ── idaptik-reached remainder (#760) ──────────────────────────────── +// +// IDApixiTIK's PixiSound.res talks to the `@pixi/sound` *singleton* +// (`sound.pauseAll` / `find` / `play(name)` / `volumeAll`), not +// `Sound.from`. Host sets `globalThis.__as_pixi_sound_lib` to that +// singleton (the named `sound` export). Existing `pixiSoundFrom` +// still uses `__as_pixi_sound` (the `Sound` class). Not production-ready. + +/// `sound.play(s, options)` — instance play with the options object +/// idaptik passes (`{ loop, volume }`). Returns 0. +pub extern fn pixiSoundPlayOptions(s: Sound, options: Json) -> Int; + +/// `sound.pauseAll()`. Returns 0. +pub extern fn pixiSoundPauseAll() -> Int; + +/// `sound.resumeAll()`. Returns 0. +pub extern fn pixiSoundResumeAll() -> Int; + +/// `sound.muteAll()`. Returns 0. +pub extern fn pixiSoundMuteAll() -> Int; + +/// `sound.unmuteAll()`. Returns 0. +pub extern fn pixiSoundUnmuteAll() -> Int; + +/// `sound.volumeAll` getter/setter. +pub extern fn pixiSoundVolumeAll() -> Float; +pub extern fn pixiSoundSetVolumeAll(vol: Float) -> Int; + +/// `sound.find(name)` — look up a registered sound by alias. +pub extern fn pixiSoundFind(name: String) -> Sound; + +/// `sound.play(name, options)` — singleton play-by-alias. Returns 0. +pub extern fn pixiSoundPlayName(name: String, options: Json) -> Int; diff --git a/stdlib/PixiUI.affine b/stdlib/PixiUI.affine index e31c06cd..1977559f 100644 --- a/stdlib/PixiUI.affine +++ b/stdlib/PixiUI.affine @@ -21,7 +21,7 @@ // - FancyButton style accessors (textures per state) // - Slider min/max/step accessors // -// Targets the Deno-ESM backend. Consumer (or its host wrapper) sets +// Targets the Bun-ESM backend. Consumer (or its host wrapper) sets // `globalThis.__as_pixi_ui` to the `@pixi/ui` namespace at module- // init time: // @@ -139,3 +139,38 @@ pub extern fn pixiUiInputNew(options: Json) -> Input; /// Upcast Input → Container. Zero-cost identity lowering. pub extern fn pixiUiInputAsContainer(i: Input) -> Container; + +// ── idaptik-reached remainder (#760) ──────────────────────────────── +// +// FancyButton onPress/onDown/onHover, Slider value, List.addChild, +// Input value/onEnter/onChange. Position/size go through the +// Container upcast (`pixiContainerSetX` etc.). Not production-ready. + +/// `fancyButton.onPress.connect(callback)`. Returns 0. +pub extern fn pixiUiFancyButtonOnPress(b: FancyButton, callback: Json) -> Int; + +/// `fancyButton.onDown.connect(callback)`. Returns 0. +pub extern fn pixiUiFancyButtonOnDown(b: FancyButton, callback: Json) -> Int; + +/// `fancyButton.onHover.connect(callback)`. Returns 0. +pub extern fn pixiUiFancyButtonOnHover(b: FancyButton, callback: Json) -> Int; + +/// `slider.value` getter/setter. +pub extern fn pixiUiSliderValue(s: Slider) -> Float; +pub extern fn pixiUiSliderSetValue(s: Slider, v: Float) -> Int; + +/// `slider.addChild(child)`. `child` is an opaque display object. +pub extern fn pixiUiSliderAddChild(s: Slider, child: Json) -> Int; + +/// `list.addChild(child)`. +pub extern fn pixiUiListAddChild(l: List, child: Json) -> Int; + +/// `input.value` getter/setter + enter/change signals. +pub extern fn pixiUiInputValue(i: Input) -> String; +pub extern fn pixiUiInputSetValue(i: Input, v: String) -> Int; +pub extern fn pixiUiInputOnEnter(i: Input, callback: Json) -> Int; +pub extern fn pixiUiInputOnChange(i: Input, callback: Json) -> Int; + +/// `progressBar.progress` getter/setter (0–100 in @pixi/ui). +pub extern fn pixiUiProgressBarProgress(p: ProgressBar) -> Float; +pub extern fn pixiUiProgressBarSetProgress(p: ProgressBar, v: Float) -> Int; diff --git a/tests/codegen-deno/pixi_smoke.affine b/tests/codegen-deno/pixi_smoke.affine index 7ad3086e..7d74c390 100644 --- a/tests/codegen-deno/pixi_smoke.affine +++ b/tests/codegen-deno/pixi_smoke.affine @@ -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, pixiContainerSetScale, pixiContainerSetPivot, pixiContainerSetRotation, pixiContainerSetAlpha, pixiContainerSetZIndex, pixiContainerSetSortableChildren, pixiContainerSetEventMode, pixiContainerSetCursor, pixiContainerOn, pixiContainerOff, pixiSpriteFrom, pixiSpriteSetAnchor, pixiSpriteAsContainer, pixiTextureFromUrl, pixiGraphicsNew, pixiGraphicsRect, pixiGraphicsFill, pixiGraphicsAsContainer, pixiPointNew, pixiRectangleNew, pixiCircleNew, pixiSpriteSetTint, pixiSpriteSetBlendMode, pixiContainerSetHitArea, pixiContainerSetMask, pixiBlurFilterNew, pixiContainerSetFilter, pixiNineSliceSpriteFrom, pixiNineSliceSpriteAsContainer, pixiEventGlobalX, pixiEventGlobalY}; +use Pixi::{Application, Container, Sprite, Graphics, Text, Texture, Ticker, Renderer, ObservablePoint, Circle, pixiAppInit, pixiAppStage, pixiAppTicker, pixiAppRenderer, pixiContainerNew, pixiContainerAddChild, pixiContainerSetPosition, pixiContainerSetVisible, pixiContainerSetScale, pixiContainerSetPivot, pixiContainerSetRotation, pixiContainerSetAlpha, pixiContainerSetZIndex, pixiContainerSetSortableChildren, pixiContainerSetEventMode, pixiContainerSetCursor, pixiContainerOn, pixiContainerOff, pixiSpriteFrom, pixiSpriteSetAnchor, pixiSpriteAsContainer, pixiTextureFromUrl, pixiGraphicsNew, pixiGraphicsRect, pixiGraphicsFill, pixiGraphicsAsContainer, pixiPointNew, pixiRectangleNew, pixiCircleNew, pixiSpriteSetTint, pixiSpriteSetBlendMode, pixiContainerSetHitArea, pixiContainerSetMask, pixiBlurFilterNew, pixiContainerSetFilter, pixiNineSliceSpriteFrom, pixiNineSliceSpriteAsContainer, pixiEventGlobalX, pixiEventGlobalY, pixiContainerX, pixiContainerY, pixiContainerSetX, pixiContainerSetY, pixiContainerSetInteractiveChildren, pixiContainerRemoveChildren, pixiContainerAddChildAt, pixiContainerSetHitAreaCircle, pixiContainerScale, pixiObservablePointSet, pixiGraphicsCircle, pixiGraphicsRoundRect, pixiGraphicsStroke, pixiGraphicsMoveTo, pixiGraphicsLineTo, pixiTextNew, pixiTextAnchor, pixiTextAsContainer, pixiSpriteSetWidth, pixiSpriteSetHeight, pixiSpriteSetInteractive, pixiTextureWhite, pixiTickerDeltaTime, pixiRendererResize, pixiAssetsInit, pixiAssetsLoadBundle, pixiAssetsBackgroundLoadBundle, pixiEventPreventDefault}; pub fn smokeInit(options: Json) -> Application / { Async } = pixiAppInit(options); @@ -73,3 +73,48 @@ pub fn smokeGapFill(app: Application, url: String, event: Json) -> Int { let _y = pixiEventGlobalY(event); 0 } + +/// #760 idaptik-reached remainder: per-axis position, interactiveChildren, +/// Graphics circle/stroke/roundRect/path, ObservablePoint.set, Texture.WHITE, +/// ticker delta, renderer resize, preventDefault. +pub fn smokeIdaptikReach(app: Application, event: Json, strokeOpts: Json, textOpts: Json) -> Int { + let stage = pixiAppStage(app); + pixiContainerSetX(stage, 3.0); + pixiContainerSetY(stage, 4.0); + let _x = pixiContainerX(stage); + let _y = pixiContainerY(stage); + pixiContainerSetInteractiveChildren(stage, true); + let scale = pixiContainerScale(stage); + pixiObservablePointSet(scale, 1.5, 1.5); + let g = pixiGraphicsNew(); + pixiGraphicsCircle(g, 10.0, 10.0, 5.0); + pixiGraphicsRoundRect(g, 0.0, 0.0, 8.0, 8.0, 2.0); + pixiGraphicsMoveTo(g, 0.0, 0.0); + pixiGraphicsLineTo(g, 4.0, 4.0); + pixiGraphicsStroke(g, strokeOpts); + pixiContainerAddChildAt(stage, pixiGraphicsAsContainer(g), 0); + let circ = pixiCircleNew(0.0, 0.0, 12.0); + pixiContainerSetHitAreaCircle(pixiGraphicsAsContainer(g), circ); + let white = pixiTextureWhite(); + let sprite = pixiSpriteFrom(white); + pixiSpriteSetWidth(sprite, 32.0); + pixiSpriteSetHeight(sprite, 32.0); + pixiSpriteSetInteractive(sprite, true); + pixiContainerAddChild(stage, pixiSpriteAsContainer(sprite)); + let text = pixiTextNew(textOpts); + let _anchor = pixiTextAnchor(text); + pixiContainerAddChild(stage, pixiTextAsContainer(text)); + let ticker = pixiAppTicker(app); + let _dt = pixiTickerDeltaTime(ticker); + let renderer = pixiAppRenderer(app); + pixiRendererResize(renderer, 640.0, 480.0); + pixiEventPreventDefault(event); + pixiContainerRemoveChildren(stage); + 0 +} + +pub fn smokeAssets(options: Json, bundle: String) -> Int / { Async } { + let _i = pixiAssetsInit(options); + let _b = pixiAssetsLoadBundle(bundle); + pixiAssetsBackgroundLoadBundle(bundle) +} diff --git a/tests/codegen-deno/pixi_smoke.harness.mjs b/tests/codegen-deno/pixi_smoke.harness.mjs index ca17de12..a716b038 100644 --- a/tests/codegen-deno/pixi_smoke.harness.mjs +++ b/tests/codegen-deno/pixi_smoke.harness.mjs @@ -21,6 +21,7 @@ class MockContainer { this.x = 0; this.y = 0; this.scale = new MockPoint(); this.pivot = new MockPoint(); + this.anchor = new MockPoint(); this.rotation = 0; this.alpha = 1; this.zIndex = 0; @@ -28,14 +29,31 @@ class MockContainer { this.eventMode = "auto"; this.cursor = ""; this.visible = true; + this.interactiveChildren = false; + this.parent = null; + this.width = 0; + this.height = 0; this.children = []; this.handlers = new Map(); } - addChild(c) { this.children.push(c); operationsLog.push("addChild"); } + addChild(c) { + this.children.push(c); + c.parent = this; + operationsLog.push("addChild"); + } + addChildAt(c, i) { + this.children.splice(i, 0, c); + c.parent = this; + operationsLog.push("addChildAt"); + } removeChild(c) { this.children = this.children.filter((ch) => ch !== c); operationsLog.push("removeChild"); } + removeChildren() { + this.children = []; + operationsLog.push("removeChildren"); + } on(event, handler) { if (!this.handlers.has(event)) this.handlers.set(event, []); this.handlers.get(event).push(handler); @@ -53,14 +71,22 @@ class MockSprite extends MockContainer { super(); this.texture = texture; this.anchor = new MockPoint(); + this.position = new MockPoint(); + this.interactive = false; } } class MockGraphics extends MockContainer { - constructor() { super(); this.paths = []; this.fills = []; } + constructor() { super(); this.paths = []; this.fills = []; this.strokes = []; } rect(x, y, w, h) { this.paths.push({ kind: "rect", x, y, w, h }); } + circle(x, y, r) { this.paths.push({ kind: "circle", x, y, r }); } + roundRect(x, y, w, h, radius) { this.paths.push({ kind: "roundRect", x, y, w, h, radius }); } + moveTo(x, y) { this.paths.push({ kind: "moveTo", x, y }); } + lineTo(x, y) { this.paths.push({ kind: "lineTo", x, y }); } + quadraticCurveTo(cpx, cpy, x, y) { this.paths.push({ kind: "quadratic", cpx, cpy, x, y }); } fill(opts) { this.fills.push(opts); } - clear() { this.paths = []; this.fills = []; } + stroke(opts) { this.strokes.push(opts); } + clear() { this.paths = []; this.fills = []; this.strokes = []; } } class MockPointCtor { @@ -164,4 +190,23 @@ assert.equal(app.stage.children.length, 4, "nine-slice added as fourth child"); const nine = app.stage.children[3]; assert.ok(nine instanceof MockNineSliceSprite, "nine-slice upcast is identity"); +const reachEvent = { global: { x: 1, y: 2 }, preventDefault() { reachEvent.prevented = true; } }; +assert.equal(smokeIdaptikReach(app, reachEvent, { color: 0x00ff00, width: 2 }, { text: "hi" }), 0, "smokeIdaptikReach returns 0"); +assert.equal(app.stage.x, 3, "stage.x set per-axis"); +assert.equal(app.stage.y, 4, "stage.y set per-axis"); +assert.equal(app.stage.interactiveChildren, true, "interactiveChildren set"); +assert.equal(app.stage.scale.x, 1.5, "ObservablePoint.set on scale"); +assert.equal(app.renderer.width, 640, "renderer.resize width"); +assert.equal(app.renderer.height, 480, "renderer.resize height"); +assert.equal(reachEvent.prevented, true, "preventDefault reached host"); +assert.equal(app.stage.children.length, 0, "removeChildren cleared stage"); +assert.ok(operationsLog.includes("addChildAt"), "addChildAt observed"); +assert.ok(operationsLog.includes("removeChildren"), "removeChildren observed"); +assert.ok(operationsLog.includes("rendererResize"), "renderer.resize observed"); + +assert.equal(await smokeAssets({ basePath: "/assets" }, "ui"), 0, "smokeAssets returns 0"); +assert.deepEqual(assetInits, [{ basePath: "/assets" }], "Assets.init options"); +assert.deepEqual(bundles, ["ui"], "Assets.loadBundle name"); +assert.deepEqual(bgBundles, ["ui"], "Assets.backgroundLoadBundle name"); + console.log("pixi_smoke.harness.mjs OK"); diff --git a/tests/codegen-deno/pixisound_smoke.affine b/tests/codegen-deno/pixisound_smoke.affine index 03ea282d..dd30e0a5 100644 --- a/tests/codegen-deno/pixisound_smoke.affine +++ b/tests/codegen-deno/pixisound_smoke.affine @@ -5,7 +5,8 @@ // importing the generated module; we re-export thin wrappers so the // harness can drive each extern via stable names. -use PixiSound::{Sound, pixiSoundFrom, pixiSoundPlay, pixiSoundStop, pixiSoundPause, pixiSoundResume, pixiSoundSetVolume, pixiSoundSetLoop}; +use Deno::{Json}; +use PixiSound::{Sound, pixiSoundFrom, pixiSoundPlay, pixiSoundStop, pixiSoundPause, pixiSoundResume, pixiSoundSetVolume, pixiSoundSetLoop, pixiSoundIsPlaying, pixiSoundDuration, pixiSoundPlayOptions, pixiSoundPauseAll, pixiSoundResumeAll, pixiSoundMuteAll, pixiSoundUnmuteAll, pixiSoundVolumeAll, pixiSoundSetVolumeAll, pixiSoundFind, pixiSoundPlayName}; pub fn smokeFrom(url: String) -> Sound = pixiSoundFrom(url); @@ -33,3 +34,15 @@ pub fn smokeIsPlaying(s: Sound) -> Bool = pub fn smokeDuration(s: Sound) -> Float = pixiSoundDuration(s); + +pub fn smokePlayOptions(s: Sound, options: Json) -> Int = + pixiSoundPlayOptions(s, options); + +pub fn smokePauseAll() -> Int = pixiSoundPauseAll(); +pub fn smokeResumeAll() -> Int = pixiSoundResumeAll(); +pub fn smokeMuteAll() -> Int = pixiSoundMuteAll(); +pub fn smokeUnmuteAll() -> Int = pixiSoundUnmuteAll(); +pub fn smokeVolumeAll() -> Float = pixiSoundVolumeAll(); +pub fn smokeSetVolumeAll(vol: Float) -> Int = pixiSoundSetVolumeAll(vol); +pub fn smokeFind(name: String) -> Sound = pixiSoundFind(name); +pub fn smokePlayName(name: String, options: Json) -> Int = pixiSoundPlayName(name, options); diff --git a/tests/codegen-deno/pixisound_smoke.harness.mjs b/tests/codegen-deno/pixisound_smoke.harness.mjs index 08a1c2d5..7580be74 100644 --- a/tests/codegen-deno/pixisound_smoke.harness.mjs +++ b/tests/codegen-deno/pixisound_smoke.harness.mjs @@ -20,24 +20,47 @@ class MockSound { this.isPlaying = false; this.duration = 12.5; } - play() { this.playCount += 1; } + play(opts) { + this.playCount += 1; + this.lastPlayOpts = opts; + } stop() { this.stopCount += 1; } pause() { this.pauseCount += 1; } resume() { this.resumeCount += 1; } } let lastFromUrl = null; +const singleton = { + volumeAll: 1, + pauseAllCount: 0, + resumeAllCount: 0, + muteAllCount: 0, + unmuteAllCount: 0, + played: [], + sounds: {}, + pauseAll() { this.pauseAllCount += 1; }, + resumeAll() { this.resumeAllCount += 1; }, + muteAll() { this.muteAllCount += 1; }, + unmuteAll() { this.unmuteAllCount += 1; }, + find(name) { return this.sounds[name]; }, + play(name, opts) { this.played.push({ name, opts }); }, +}; globalThis.__as_pixi_sound = { from(url) { lastFromUrl = url; - return new MockSound(url); + const s = new MockSound(url); + singleton.sounds[url] = s; + return s; }, }; +globalThis.__as_pixi_sound_lib = singleton; const { smokeFrom, smokePlay, smokeStop, smokePause, smokeResume, - smokeSetVolume, smokeSetLoop, + smokeSetVolume, smokeSetLoop, smokeIsPlaying, smokeDuration, + smokePlayOptions, smokePauseAll, smokeResumeAll, smokeMuteAll, + smokeUnmuteAll, smokeVolumeAll, smokeSetVolumeAll, smokeFind, smokePlayName, } = await import("./pixisound_smoke.bun.js"); const s = smokeFrom("assets/bgm.mp3"); diff --git a/tests/codegen-deno/pixiui_smoke.affine b/tests/codegen-deno/pixiui_smoke.affine index 0bba9422..fa85948f 100644 --- a/tests/codegen-deno/pixiui_smoke.affine +++ b/tests/codegen-deno/pixiui_smoke.affine @@ -6,7 +6,7 @@ // registration, and upcast → Container for each MVP component. use Deno::{Json}; -use PixiUI::{Button, FancyButton, Slider, Switch, Container, pixiUiButtonNew, pixiUiButtonOnPress, pixiUiButtonAsContainer, pixiUiFancyButtonNew, pixiUiFancyButtonAsContainer, pixiUiSliderNew, pixiUiSliderOnUpdate, pixiUiSliderAsContainer, pixiUiSwitchNew, pixiUiSwitchOnChange, pixiUiSwitchAsContainer, pixiUiProgressBarNew, pixiUiProgressBarAsContainer, pixiUiListNew, pixiUiListAsContainer, pixiUiInputNew, pixiUiInputAsContainer}; +use PixiUI::{Button, FancyButton, Slider, Switch, Container, Input, List, ProgressBar, pixiUiButtonNew, pixiUiButtonOnPress, pixiUiButtonAsContainer, pixiUiFancyButtonNew, pixiUiFancyButtonAsContainer, pixiUiFancyButtonOnPress, pixiUiFancyButtonOnDown, pixiUiFancyButtonOnHover, pixiUiSliderNew, pixiUiSliderOnUpdate, pixiUiSliderAsContainer, pixiUiSliderSetValue, pixiUiSliderValue, pixiUiSliderAddChild, pixiUiSwitchNew, pixiUiSwitchOnChange, pixiUiSwitchAsContainer, pixiUiProgressBarNew, pixiUiProgressBarAsContainer, pixiUiProgressBarSetProgress, pixiUiProgressBarProgress, pixiUiListNew, pixiUiListAsContainer, pixiUiListAddChild, pixiUiInputNew, pixiUiInputAsContainer, pixiUiInputValue, pixiUiInputSetValue, pixiUiInputOnEnter, pixiUiInputOnChange}; pub fn smokeButton(options: Json, callback: Json) -> Container { let b = pixiUiButtonNew(options); @@ -42,3 +42,41 @@ pub fn smokeList(options: Json) -> Container { pub fn smokeInput(options: Json) -> Container { pixiUiInputAsContainer(pixiUiInputNew(options)) } + +/// #760 idaptik-reached FancyButton signals + Slider value + Input +/// value/enter/change + List.addChild. +pub fn smokeFancyButtonEvents(options: Json, onPress: Json, onDown: Json, onHover: Json) -> Container { + let fb = pixiUiFancyButtonNew(options); + pixiUiFancyButtonOnPress(fb, onPress); + pixiUiFancyButtonOnDown(fb, onDown); + pixiUiFancyButtonOnHover(fb, onHover); + pixiUiFancyButtonAsContainer(fb) +} + +pub fn smokeSliderValue(options: Json, v: Float) -> Float { + let s = pixiUiSliderNew(options); + pixiUiSliderSetValue(s, v); + pixiUiSliderValue(s) +} + +pub fn smokeSliderAdd(options: Json, child: Json) -> Int { + pixiUiSliderAddChild(pixiUiSliderNew(options), child) +} + +pub fn smokeListAdd(options: Json, child: Json) -> Int { + pixiUiListAddChild(pixiUiListNew(options), child) +} + +pub fn smokeInputValue(options: Json, v: String, onEnter: Json, onChange: Json) -> String { + let i = pixiUiInputNew(options); + pixiUiInputSetValue(i, v); + pixiUiInputOnEnter(i, onEnter); + pixiUiInputOnChange(i, onChange); + pixiUiInputValue(i) +} + +pub fn smokeProgress(options: Json, v: Float) -> Float { + let p = pixiUiProgressBarNew(options); + pixiUiProgressBarSetProgress(p, v); + pixiUiProgressBarProgress(p) +} diff --git a/tests/codegen-deno/pixiui_smoke.harness.mjs b/tests/codegen-deno/pixiui_smoke.harness.mjs index 1eee8d40..6953941d 100644 --- a/tests/codegen-deno/pixiui_smoke.harness.mjs +++ b/tests/codegen-deno/pixiui_smoke.harness.mjs @@ -13,6 +13,13 @@ const ctorCalls = { Button: [], FancyButton: [], Slider: [], Switch: [], Progres const onPressRegs = []; const onUpdateRegs = []; const onChangeRegs = []; +const fancyPressRegs = []; +const fancyDownRegs = []; +const fancyHoverRegs = []; +const inputEnterRegs = []; +const inputChangeRegs = []; +const listChildren = []; +const sliderChildren = []; // `Signal`-ish stub matching the @pixi/ui event surface // (`button.onPress.connect(cb)`). Records every registered callback @@ -32,6 +39,9 @@ class MockButton { class MockFancyButton { constructor(options) { ctorCalls.FancyButton.push(options); + this.onPress = new MockSignal(fancyPressRegs); + this.onDown = new MockSignal(fancyDownRegs); + this.onHover = new MockSignal(fancyHoverRegs); } } @@ -39,7 +49,9 @@ class MockSlider { constructor(options) { ctorCalls.Slider.push(options); this.onUpdate = new MockSignal(onUpdateRegs); + this.value = options?.value ?? 0; } + addChild(child) { sliderChildren.push(child); return child; } } class MockSwitch { @@ -49,9 +61,24 @@ class MockSwitch { } } -class MockProgressBar { constructor(options) { ctorCalls.ProgressBar.push(options); } } -class MockList { constructor(options) { ctorCalls.List.push(options); } } -class MockInput { constructor(options) { ctorCalls.Input.push(options); } } +class MockProgressBar { + constructor(options) { + ctorCalls.ProgressBar.push(options); + this.progress = options?.progress ?? 0; + } +} +class MockList { + constructor(options) { ctorCalls.List.push(options); } + addChild(child) { listChildren.push(child); return child; } +} +class MockInput { + constructor(options) { + ctorCalls.Input.push(options); + this.value = options?.value ?? ""; + this.onEnter = new MockSignal(inputEnterRegs); + this.onChange = new MockSignal(inputChangeRegs); + } +} globalThis.__as_pixi_ui = { Button: MockButton, @@ -63,7 +90,7 @@ globalThis.__as_pixi_ui = { Input: MockInput, }; -const { smokeButton, smokeFancyButton, smokeSlider, smokeSwitch, smokeProgressBar, smokeList, smokeInput } = +const { smokeButton, smokeFancyButton, smokeSlider, smokeSwitch, smokeProgressBar, smokeList, smokeInput, smokeFancyButtonEvents, smokeSliderValue, smokeSliderAdd, smokeListAdd, smokeInputValue, smokeProgress } = await import("./pixiui_smoke.bun.js"); // ── Button: ctor + onPress + upcast ────────────────────────────────