bard-legends-framework 1.11.4 → 1.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md CHANGED
@@ -223,24 +223,71 @@ export class TitleScene extends Scene<void, TitleSceneOutputDTO> { // <Input, O
223
223
 
224
224
  ---
225
225
 
226
- ## Bootstrap
226
+ ## Bootstrap (Electron)
227
+
228
+ The framework owns the whole Electron shell. A game has four one-liner entry files:
229
+
230
+ ```ts
231
+ // src/main.ts — Electron main process
232
+ import { ElectronApp } from 'bard-legends-framework/main';
233
+ ElectronApp.start({ defaultWindowSize, minWindowSize, maxWindowSize });
234
+
235
+ // src/preload.ts — context bridge (exposes window.api)
236
+ import { ElectronPreload } from 'bard-legends-framework/preload';
237
+ ElectronPreload.expose();
238
+ ```
239
+
240
+ ```js
241
+ // forge.config.mjs — electron-forge build config
242
+ import { ElectronForge } from 'bard-legends-framework/forge';
243
+ export default ElectronForge.createForgeConfig();
244
+ ```
227
245
 
228
246
  ```ts
229
- import { Game, Service } from 'bard-legends-framework';
247
+ // src/renderer.ts the game
248
+ import { ElectronGame, Service } from 'bard-legends-framework';
230
249
 
231
250
  // MANDATORY: register every controller and view via side-effecting import (Vite glob).
251
+ // Globs resolve relative to the importing file, so they must stay in the game's renderer entry.
232
252
  import.meta.glob('./**/*.controller.ts', { eager: true });
233
253
  import.meta.glob('./**/*.view.ts', { eager: true });
234
254
 
235
- let game = new Game({ devMode, resolution: 2, maxScreenResolution });
236
- await game.setup({ assetDefinitions: [...SpriteAssets, ...FontAssets], spriteDefinitions: SpriteDefinitions });
255
+ await ElectronGame.start({ resolution: 2 }, { spriteAssetDefinitions, spriteDefinitions, /* fonts, sounds */ });
237
256
  Service.get(GameCycleGateway).startGame(); // kick off the first scene via a gateway
238
257
  ```
239
258
 
259
+ - `ElectronApp.start` runs window management, fullscreen, focus events, encrypted save-file IO and
260
+ quit over IPC. `ElectronPreload.expose` publishes the typed `window.api` bridge (`ElectronBridge`).
261
+ `ElectronGame.start` fetches `devMode`/`maxScreenResolution` from the main process, creates the
262
+ `Game`, awaits `setup` (asset loading), wires window focus → `Game.pause` and tab visibility →
263
+ `Game.freeze`, and reports the renderer ready.
264
+ - **Custom IPC** (both directions):
265
+ - renderer → main: pass `ipcHandlers: { channelName: handler }` to `ElectronApp.start` and list
266
+ the channel in `ElectronPreload.expose({ invokes: ['channelName'] })`; it appears on
267
+ `window.api` as an async method.
268
+ - main → renderer: pass `onWindowCreated: mainWindow => { ... }` to `ElectronApp.start` and push
269
+ with `mainWindow.webContents.send(channel, data)`; list the channel in
270
+ `ElectronPreload.expose({ events: ['channelName'] })` and it appears on `window.api` as a
271
+ subscription method `(callback) => unsubscribe`.
272
+ - Type both by augmenting the `ElectronBridge` interface from any game module:
273
+ `declare module 'bard-legends-framework' { interface ElectronBridge { channelName(...): ...; } }`
274
+ - The `Game-Boilerplate` repo demonstrates both (`sendMessageToMain` invoke, `screenSize` event).
275
+ - The `@electron-forge/*` packages (cli, makers, plugin-vite) are **dependencies of the framework**
276
+ — game projects keep only `electron` and `vite` as devDependencies (forge requires `electron` in
277
+ the app's own devDependencies).
278
+ - Games never invoke `electron-forge` directly. The framework ships a CLI at `bin/cli.mjs` that
279
+ forwards to its own `@electron-forge/cli` (so the forge version always matches the framework's,
280
+ linked or published). Game scripts call it by path, which resolves in both modes:
281
+ ```json
282
+ "start": "set \"ENVIRONMENT=dev\" && node node_modules/bard-legends-framework/bin/cli.mjs start",
283
+ "build": "npm run test-full && node node_modules/bard-legends-framework/bin/cli.mjs package",
284
+ "build-with-installer": "npm run test-full && node node_modules/bard-legends-framework/bin/cli.mjs make",
285
+ "deploy": "npm run test-full && node node_modules/bard-legends-framework/bin/cli.mjs publish"
286
+ ```
240
287
  - `Game` is the global singleton: `Game.instance`, `Game.camera`, `Game.time`, `Game.pause`
241
288
  (a `Reducer<boolean>` — open an effect to pause), `Game.instance.screenSize`/`screenSizeCenter`
242
289
  (`PersistentNotifier<Vector>`), `setResolution`, `screenPositonToStagePosition`.
243
- - `setup` loads assets and must be awaited before any scene opens.
290
+ - `setup` loads assets and must be awaited before any scene opens (`ElectronGame.start` does both).
244
291
  - If you add a module, no manual registration is needed **as long as** files keep the
245
292
  `*.controller.ts` / `*.view.ts` suffixes so the globs pick them up.
246
293
 
package/bin/cli.mjs ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+ import { spawn } from 'child_process';
3
+ import { createRequire } from 'module';
4
+ import path from 'path';
5
+
6
+ let require = createRequire(import.meta.url);
7
+ let forgePackageJsonPath = require.resolve('@electron-forge/cli/package.json');
8
+ let forgeBinPath = path.join(path.dirname(forgePackageJsonPath), require(forgePackageJsonPath).bin['electron-forge']);
9
+
10
+ spawn(process.execPath, [forgeBinPath, ...process.argv.slice(2)], { stdio: 'inherit' }).on('exit', code =>
11
+ process.exit(code ?? 0)
12
+ );
@@ -0,0 +1,18 @@
1
+ import { BrowserWindow } from 'electron';
2
+
3
+ interface ElectronWindowSize {
4
+ readonly width: number;
5
+ readonly height: number;
6
+ }
7
+ interface ElectronAppConfiguration {
8
+ readonly defaultWindowSize: ElectronWindowSize;
9
+ readonly minWindowSize: ElectronWindowSize;
10
+ readonly maxWindowSize: ElectronWindowSize;
11
+ readonly ipcHandlers?: Readonly<Record<string, (...args: never[]) => unknown>>;
12
+ readonly onWindowCreated?: (mainWindow: BrowserWindow) => void;
13
+ }
14
+ declare class ElectronApp {
15
+ static start(configuration: ElectronAppConfiguration): void;
16
+ }
17
+
18
+ export { ElectronApp, type ElectronAppConfiguration, type ElectronWindowSize };
@@ -0,0 +1,18 @@
1
+ import { BrowserWindow } from 'electron';
2
+
3
+ interface ElectronWindowSize {
4
+ readonly width: number;
5
+ readonly height: number;
6
+ }
7
+ interface ElectronAppConfiguration {
8
+ readonly defaultWindowSize: ElectronWindowSize;
9
+ readonly minWindowSize: ElectronWindowSize;
10
+ readonly maxWindowSize: ElectronWindowSize;
11
+ readonly ipcHandlers?: Readonly<Record<string, (...args: never[]) => unknown>>;
12
+ readonly onWindowCreated?: (mainWindow: BrowserWindow) => void;
13
+ }
14
+ declare class ElectronApp {
15
+ static start(configuration: ElectronAppConfiguration): void;
16
+ }
17
+
18
+ export { ElectronApp, type ElectronAppConfiguration, type ElectronWindowSize };
@@ -0,0 +1 @@
1
+ 'use strict';var electron=require('electron'),T=require('electron-squirrel-startup'),w=require('path'),child_process=require('child_process'),p=require('crypto'),y=require('fs/promises'),V=require('write-file-atomic'),helpersLib=require('helpers-lib');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var T__default=/*#__PURE__*/_interopDefault(T);var w__default=/*#__PURE__*/_interopDefault(w);var p__default=/*#__PURE__*/_interopDefault(p);var y__default=/*#__PURE__*/_interopDefault(y);var V__default=/*#__PURE__*/_interopDefault(V);var v=Object.defineProperty;var n=(d,e)=>v(d,"name",{value:e,configurable:true});var h=class{static{n(this,"SingleAsyncQueue");}constructor(){this.ƀkt=false,this.ƀjf=new helpersLib.Queue;}async enqueue(e){let t=this.ƀjf.empty,a=new Promise((i,r)=>this.ƀjf.add({operation:e,resolve:i,reject:r}));return t&&this.ƀjt(),a}ƀjt(){if(!this.ƀkt){let e=[];for(;this.ƀjf.notEmpty;){let t=this.ƀjf.pop();t&&e.push(t);}e.length>0&&this.ƀju(e);}}ƀju(e){let t=e.pop();if(!t)throw new Error("No executing entry found");this.ƀkt=true,t.operation().then(()=>{e.forEach(a=>a.resolve()),t.resolve(),this.ƀkt=false,this.ƀjt();}).catch(a=>{t.reject(a),e.length>0?this.ƀju(e):(this.ƀkt=false,this.ƀjt());});}};var g=w__default.default.join(electron.app.getPath("userData"),"saves"),_=1,f=p__default.default.scryptSync("gdfgmhur%+/&ty@ertbdf423-secret","ga!sgnwq6^/&(roofvafnl_salt",32),c=class{static{n(this,"MainSaveLoad");}static{this.ƀdq=new Map;}static ƀev(e){let t=this.ƀdq.get(e);return t||(t=new h,this.ƀdq.set(e,t)),t}static async save(e,t){return this.ƀev(t).enqueue(()=>this.ƀlm(e,t))}static async load(e){return this.ƀjj(e)}static async ƀlm(e,t){let a=this.ƀep(),i=w__default.default.join(g,t);if(await y__default.default.mkdir(g,{recursive:true}),!e)await y__default.default.rm(i,{force:true});else {let r=this.ƀdf(e,f),s=this.ƀdf(a,f),u={version:_,data:{initializationVector:r.initializationVector,encrypted:r.encrypted,authTag:r.authTag},machineId:{initializationVector:s.initializationVector,encrypted:s.encrypted,authTag:s.authTag}};await V__default.default(i,JSON.stringify(u),{encoding:"utf-8"});}}static async ƀjj(e){let t=w__default.default.join(g,e),a;try{a=await y__default.default.readFile(t,"utf-8");}catch{return}let i=JSON.parse(a),r={version:i.version,data:{initializationVector:Buffer.from(i.data.initializationVector),encrypted:Buffer.from(i.data.encrypted),authTag:Buffer.from(i.data.authTag)},machineId:{initializationVector:Buffer.from(i.machineId.initializationVector),encrypted:Buffer.from(i.machineId.encrypted),authTag:Buffer.from(i.machineId.authTag)}},s=this.ƀct(r.data.encrypted,f,r.data.initializationVector,r.data.authTag);if(this.ƀct(r.machineId.encrypted,f,r.machineId.initializationVector,r.machineId.authTag)===this.ƀep())return s}static ƀdf(e,t){let a=p__default.default.randomBytes(12),i=p__default.default.createCipheriv("aes-256-gcm",t,a),r=Buffer.concat([i.update(e,"utf8"),i.final()]),s=i.getAuthTag();return {initializationVector:a,encrypted:r,authTag:s}}static ƀct(e,t,a,i){let r=p__default.default.createDecipheriv("aes-256-gcm",t,a);return r.setAuthTag(i),Buffer.concat([r.update(e),r.final()]).toString("utf8")}static ƀep(){return process.platform==="win32"?child_process.execSync("wmic csproduct get uuid /format:value",{encoding:"utf8"}).split("=")[1].trim():process.platform==="darwin"?child_process.execSync("ioreg -rd1 -c IOPlatformExpertDevice | awk '/IOPlatformUUID/{print $3}'",{encoding:"utf8"}).trim().replace(/"/g,""):child_process.execSync("cat /sys/class/dmi/id/product_uuid",{encoding:"utf8"}).trim()}};var I=class{static{n(this,"ElectronApp");}static start(e){process.env.ELECTRON_DISABLE_SECURITY_WARNINGS="true",T__default.default&&electron.app.quit();let t=process.env.ENVIRONMENT==="dev",a=n(()=>{let i=new electron.BrowserWindow({width:e.defaultWindowSize.width,height:e.defaultWindowSize.height,webPreferences:{preload:w__default.default.join(__dirname,"preload.js")},backgroundColor:"#000",show:false,autoHideMenuBar:true});i.once("ready-to-show",()=>{i.show();}),i.setMenu(null),MAIN_WINDOW_VITE_DEV_SERVER_URL?i.loadURL(MAIN_WINDOW_VITE_DEV_SERVER_URL):i.loadFile(w__default.default.join(__dirname,`../renderer/${MAIN_WINDOW_VITE_NAME}/index.html`)),this.ƀkm(i,e,t),e.onWindowCreated?.(i),t&&(i.setSize(1616,1616),i.setPosition(-7,0),i.webContents.openDevTools());},"createWindow");electron.app.on("ready",a),electron.app.on("window-all-closed",()=>{process.platform!=="darwin"&&electron.app.quit();}),electron.app.on("activate",()=>{electron.BrowserWindow.getAllWindows().length===0&&a();});}static ƀkm(e,t,a){electron.ipcMain.handle("save",async(i,r,s)=>{await c.save(r,s);}),electron.ipcMain.handle("load",async(i,r)=>c.load(r)),electron.ipcMain.handle("getOptions",async()=>({devMode:a,maxScreenResolution:t.maxWindowSize})),Object.entries(t.ipcHandlers??{}).forEach(([i,r])=>{electron.ipcMain.handle(i,(s,...u)=>r(...u));}),e.on("focus",()=>{e.webContents.send("onWindowFocus",true);}),e.on("blur",()=>{e.webContents.send("onWindowFocus",false);}),electron.ipcMain.on("rendererReady",()=>{e.webContents.send("onWindowFocus",e.isFocused());}),electron.ipcMain.on("setFullScreen",(i,r)=>{r?(e.setResizable(false),e.setMaximumSize(0,0),e.setFullScreen(true),e.setMovable(false),e.setMaximizable(false),e.setMinimizable(false),e.setMenuBarVisibility(false)):(e.setFullScreen(false),e.setResizable(true),a||(e.setMaximumSize(t.maxWindowSize.width,t.maxWindowSize.height),e.setMinimumSize(t.minWindowSize.width,t.minWindowSize.height),e.setSize(t.defaultWindowSize.width,t.defaultWindowSize.height),e.center()),e.setMovable(true),e.setMaximizable(true),e.setMinimizable(true),e.setMenuBarVisibility(true));}),electron.ipcMain.on("quit",()=>{electron.app.quit();});}};exports.ElectronApp=I;
@@ -0,0 +1 @@
1
+ import {app,BrowserWindow,ipcMain}from'electron';import T from'electron-squirrel-startup';import w from'path';import {execSync}from'child_process';import p from'crypto';import y from'fs/promises';import V from'write-file-atomic';import {Queue}from'helpers-lib';var v=Object.defineProperty;var n=(d,e)=>v(d,"name",{value:e,configurable:true});var h=class{static{n(this,"SingleAsyncQueue");}constructor(){this.ƀkt=false,this.ƀjf=new Queue;}async enqueue(e){let t=this.ƀjf.empty,a=new Promise((i,r)=>this.ƀjf.add({operation:e,resolve:i,reject:r}));return t&&this.ƀjt(),a}ƀjt(){if(!this.ƀkt){let e=[];for(;this.ƀjf.notEmpty;){let t=this.ƀjf.pop();t&&e.push(t);}e.length>0&&this.ƀju(e);}}ƀju(e){let t=e.pop();if(!t)throw new Error("No executing entry found");this.ƀkt=true,t.operation().then(()=>{e.forEach(a=>a.resolve()),t.resolve(),this.ƀkt=false,this.ƀjt();}).catch(a=>{t.reject(a),e.length>0?this.ƀju(e):(this.ƀkt=false,this.ƀjt());});}};var g=w.join(app.getPath("userData"),"saves"),_=1,f=p.scryptSync("gdfgmhur%+/&ty@ertbdf423-secret","ga!sgnwq6^/&(roofvafnl_salt",32),c=class{static{n(this,"MainSaveLoad");}static{this.ƀdq=new Map;}static ƀev(e){let t=this.ƀdq.get(e);return t||(t=new h,this.ƀdq.set(e,t)),t}static async save(e,t){return this.ƀev(t).enqueue(()=>this.ƀlm(e,t))}static async load(e){return this.ƀjj(e)}static async ƀlm(e,t){let a=this.ƀep(),i=w.join(g,t);if(await y.mkdir(g,{recursive:true}),!e)await y.rm(i,{force:true});else {let r=this.ƀdf(e,f),s=this.ƀdf(a,f),u={version:_,data:{initializationVector:r.initializationVector,encrypted:r.encrypted,authTag:r.authTag},machineId:{initializationVector:s.initializationVector,encrypted:s.encrypted,authTag:s.authTag}};await V(i,JSON.stringify(u),{encoding:"utf-8"});}}static async ƀjj(e){let t=w.join(g,e),a;try{a=await y.readFile(t,"utf-8");}catch{return}let i=JSON.parse(a),r={version:i.version,data:{initializationVector:Buffer.from(i.data.initializationVector),encrypted:Buffer.from(i.data.encrypted),authTag:Buffer.from(i.data.authTag)},machineId:{initializationVector:Buffer.from(i.machineId.initializationVector),encrypted:Buffer.from(i.machineId.encrypted),authTag:Buffer.from(i.machineId.authTag)}},s=this.ƀct(r.data.encrypted,f,r.data.initializationVector,r.data.authTag);if(this.ƀct(r.machineId.encrypted,f,r.machineId.initializationVector,r.machineId.authTag)===this.ƀep())return s}static ƀdf(e,t){let a=p.randomBytes(12),i=p.createCipheriv("aes-256-gcm",t,a),r=Buffer.concat([i.update(e,"utf8"),i.final()]),s=i.getAuthTag();return {initializationVector:a,encrypted:r,authTag:s}}static ƀct(e,t,a,i){let r=p.createDecipheriv("aes-256-gcm",t,a);return r.setAuthTag(i),Buffer.concat([r.update(e),r.final()]).toString("utf8")}static ƀep(){return process.platform==="win32"?execSync("wmic csproduct get uuid /format:value",{encoding:"utf8"}).split("=")[1].trim():process.platform==="darwin"?execSync("ioreg -rd1 -c IOPlatformExpertDevice | awk '/IOPlatformUUID/{print $3}'",{encoding:"utf8"}).trim().replace(/"/g,""):execSync("cat /sys/class/dmi/id/product_uuid",{encoding:"utf8"}).trim()}};var I=class{static{n(this,"ElectronApp");}static start(e){process.env.ELECTRON_DISABLE_SECURITY_WARNINGS="true",T&&app.quit();let t=process.env.ENVIRONMENT==="dev",a=n(()=>{let i=new BrowserWindow({width:e.defaultWindowSize.width,height:e.defaultWindowSize.height,webPreferences:{preload:w.join(__dirname,"preload.js")},backgroundColor:"#000",show:false,autoHideMenuBar:true});i.once("ready-to-show",()=>{i.show();}),i.setMenu(null),MAIN_WINDOW_VITE_DEV_SERVER_URL?i.loadURL(MAIN_WINDOW_VITE_DEV_SERVER_URL):i.loadFile(w.join(__dirname,`../renderer/${MAIN_WINDOW_VITE_NAME}/index.html`)),this.ƀkm(i,e,t),e.onWindowCreated?.(i),t&&(i.setSize(1616,1616),i.setPosition(-7,0),i.webContents.openDevTools());},"createWindow");app.on("ready",a),app.on("window-all-closed",()=>{process.platform!=="darwin"&&app.quit();}),app.on("activate",()=>{BrowserWindow.getAllWindows().length===0&&a();});}static ƀkm(e,t,a){ipcMain.handle("save",async(i,r,s)=>{await c.save(r,s);}),ipcMain.handle("load",async(i,r)=>c.load(r)),ipcMain.handle("getOptions",async()=>({devMode:a,maxScreenResolution:t.maxWindowSize})),Object.entries(t.ipcHandlers??{}).forEach(([i,r])=>{ipcMain.handle(i,(s,...u)=>r(...u));}),e.on("focus",()=>{e.webContents.send("onWindowFocus",true);}),e.on("blur",()=>{e.webContents.send("onWindowFocus",false);}),ipcMain.on("rendererReady",()=>{e.webContents.send("onWindowFocus",e.isFocused());}),ipcMain.on("setFullScreen",(i,r)=>{r?(e.setResizable(false),e.setMaximumSize(0,0),e.setFullScreen(true),e.setMovable(false),e.setMaximizable(false),e.setMinimizable(false),e.setMenuBarVisibility(false)):(e.setFullScreen(false),e.setResizable(true),a||(e.setMaximumSize(t.maxWindowSize.width,t.maxWindowSize.height),e.setMinimumSize(t.minWindowSize.width,t.minWindowSize.height),e.setSize(t.defaultWindowSize.width,t.defaultWindowSize.height),e.center()),e.setMovable(true),e.setMaximizable(true),e.setMinimizable(true),e.setMenuBarVisibility(true));}),ipcMain.on("quit",()=>{app.quit();});}};export{I as ElectronApp};
@@ -0,0 +1,9 @@
1
+ interface ElectronCustomChannels {
2
+ readonly invokes?: readonly string[];
3
+ readonly events?: readonly string[];
4
+ }
5
+ declare class ElectronPreload {
6
+ static expose(customChannels?: ElectronCustomChannels): void;
7
+ }
8
+
9
+ export { type ElectronCustomChannels, ElectronPreload };
@@ -0,0 +1,9 @@
1
+ interface ElectronCustomChannels {
2
+ readonly invokes?: readonly string[];
3
+ readonly events?: readonly string[];
4
+ }
5
+ declare class ElectronPreload {
6
+ static expose(customChannels?: ElectronCustomChannels): void;
7
+ }
8
+
9
+ export { type ElectronCustomChannels, ElectronPreload };
@@ -0,0 +1 @@
1
+ 'use strict';var electron=require('electron');var p=Object.defineProperty;var n=(s,o)=>p(s,"name",{value:o,configurable:true});var u=class{static{n(this,"ElectronPreload");}static expose(o={}){let v=Object.fromEntries((o.invokes??[]).map(e=>[e,(...r)=>electron.ipcRenderer.invoke(e,...r)])),a=Object.fromEntries((o.events??[]).map(e=>[e,r=>{let i=n((d,c)=>r(c),"handler");return electron.ipcRenderer.on(e,i),()=>{electron.ipcRenderer.removeListener(e,i);}}]));electron.contextBridge.exposeInMainWorld("api",{...v,...a,save:n(async function(e,r){return electron.ipcRenderer.invoke("save",e,r)},"save"),load:n(async function(e){return electron.ipcRenderer.invoke("load",e)},"load"),getOptions:n(async function(){return electron.ipcRenderer.invoke("getOptions")},"getOptions"),setFullScreen:n(e=>electron.ipcRenderer.send("setFullScreen",e),"setFullScreen"),rendererReady:n(()=>electron.ipcRenderer.send("rendererReady"),"rendererReady"),onWindowFocus:n(e=>{let r=n((i,d)=>e(d),"handler");return electron.ipcRenderer.on("onWindowFocus",r),()=>{electron.ipcRenderer.removeListener("onWindowFocus",r);}},"onWindowFocus"),quit:n(()=>electron.ipcRenderer.send("quit"),"quit")});}};exports.ElectronPreload=u;
@@ -0,0 +1 @@
1
+ import {ipcRenderer,contextBridge}from'electron';var p=Object.defineProperty;var n=(s,o)=>p(s,"name",{value:o,configurable:true});var u=class{static{n(this,"ElectronPreload");}static expose(o={}){let v=Object.fromEntries((o.invokes??[]).map(e=>[e,(...r)=>ipcRenderer.invoke(e,...r)])),a=Object.fromEntries((o.events??[]).map(e=>[e,r=>{let i=n((d,c)=>r(c),"handler");return ipcRenderer.on(e,i),()=>{ipcRenderer.removeListener(e,i);}}]));contextBridge.exposeInMainWorld("api",{...v,...a,save:n(async function(e,r){return ipcRenderer.invoke("save",e,r)},"save"),load:n(async function(e){return ipcRenderer.invoke("load",e)},"load"),getOptions:n(async function(){return ipcRenderer.invoke("getOptions")},"getOptions"),setFullScreen:n(e=>ipcRenderer.send("setFullScreen",e),"setFullScreen"),rendererReady:n(()=>ipcRenderer.send("rendererReady"),"rendererReady"),onWindowFocus:n(e=>{let r=n((i,d)=>e(d),"handler");return ipcRenderer.on("onWindowFocus",r),()=>{ipcRenderer.removeListener("onWindowFocus",r);}},"onWindowFocus"),quit:n(()=>ipcRenderer.send("quit"),"quit")});}};export{u as ElectronPreload};
package/dist/index.d.mts CHANGED
@@ -1,8 +1,21 @@
1
1
  import * as actions_lib from 'actions-lib';
2
- import { IDAttachable, AttachmentID, Attachable, IdleSingleEvent, IdleSequence, Variable, Reducer, Sequence, SingleNotifier, Notifier, SingleEvent, Action, ClassID, NotifierCallbackFunction, IAttachment } from 'actions-lib';
2
+ import { IdleSingleEvent, IDAttachable, AttachmentID, Attachable, IdleSequence, Variable, Reducer, Sequence, SingleNotifier, Notifier, SingleEvent, Action, ClassID, NotifierCallbackFunction, IAttachment } from 'actions-lib';
3
3
  import { Vector, DynamicID, Vec2, RGBColor, Radian, Rectangle, Grid, GridNeighborType, Line } from 'helpers-lib';
4
- import p2$1 from 'p2';
5
4
  import * as Pixi from 'pixi.js';
5
+ import p2$1 from 'p2';
6
+ import { LayoutStyles } from '@pixi/layout';
7
+
8
+ type AudioAppearTransitionType = 'ease' | 'instant';
9
+ declare const SOUND_TRANSITION_DURATION = 1000;
10
+ declare class Audio {
11
+ private constructor();
12
+ setVolumes(volumes: {
13
+ master: number;
14
+ music: number;
15
+ sound: number;
16
+ }): void;
17
+ appear(on: boolean, type?: AudioAppearTransitionType): IdleSingleEvent;
18
+ }
6
19
 
7
20
  /**
8
21
  * Binds a controller to its gateway (the class produced by `Gateway<T>()`). The controller must
@@ -158,18 +171,6 @@ declare abstract class View extends IDAttachable {
158
171
  update: (time: number, delta: number) => void;
159
172
  }
160
173
 
161
- type AudioAppearTransitionType = 'ease' | 'instant';
162
- declare const SOUND_TRANSITION_DURATION = 1000;
163
- declare class Audio {
164
- private constructor();
165
- setVolumes(volumes: {
166
- master: number;
167
- music: number;
168
- sound: number;
169
- }): void;
170
- appear(on: boolean, type?: AudioAppearTransitionType): IdleSingleEvent;
171
- }
172
-
173
174
  interface FocusingNewTargetInfo {
174
175
  time: number;
175
176
  duration: number;
@@ -322,9 +323,14 @@ declare class Game {
322
323
  readonly devMode: boolean;
323
324
  constructor(partialConfiguration?: Partial<GameConfiguration>);
324
325
  setup(setupOptions: GameSetupOptions): Promise<void>;
326
+ generateTexture(options: Pixi.GenerateTextureOptions | Pixi.Container): Pixi.Texture;
325
327
  setResolution(resolution: number): void;
326
328
  }
327
329
 
330
+ declare class ElectronGame {
331
+ static start(configuration: Partial<Omit<GameConfiguration, 'devMode' | 'maxScreenResolution'>>, setupOptions: GameSetupOptions): Promise<Game>;
332
+ }
333
+
328
334
  interface SoundOptions {
329
335
  readonly time?: number;
330
336
  readonly volume?: number;
@@ -407,40 +413,17 @@ declare class MusicPlayer extends IDAttachable {
407
413
  close(): IdleSingleEvent;
408
414
  }
409
415
 
410
- declare enum ContainerEventType {
411
- Click = "click",
412
- MouseOver = "mouseover",
413
- MouseOut = "mouseout",
414
- PointerDown = "pointerdown",
415
- PointerUp = "pointerup",
416
- PointerUpOutside = "pointerupoutside",
417
- PointerMove = "pointermove",
418
- PointerCancel = "pointercancel",
419
- PointerOver = "pointerover",
420
- PointerOut = "pointerout",
421
- PointerEnter = "pointerenter",
422
- PointerLeave = "pointerleave",
423
- Added = "added",
424
- Removed = "removed",
425
- Update = "update",
426
- Change = "change",
427
- Wheel = "wheel"
416
+ interface PixiDisplayObject {
417
+ x: number;
418
+ y: number;
419
+ rotation: number;
420
+ alpha: number;
428
421
  }
429
- type ContainerEventTypeToResultType = {
430
- [K in ContainerEventType]: K extends ContainerEventType.Wheel ? {
431
- deltaY: number;
432
- } : undefined;
433
- };
434
- declare enum Cursor {
435
- Default = "default",
436
- Pointer = "pointer"
437
- }
438
- declare abstract class ContainerAttributes extends IDAttachable {
439
- protected constructor();
440
- get size(): Vector;
441
- set size(value: Vector);
442
- getSize(): Vector;
443
- setSize(value: Vector): this;
422
+ declare abstract class DisplayObjectAttributes extends IDAttachable {
423
+ protected readonly _pixiDisplayObject: PixiDisplayObject;
424
+ protected constructor(_pixiDisplayObject: PixiDisplayObject);
425
+ protected abstract _applyScale(x: number, y: number): void;
426
+ protected abstract _getHoldFromModifier(holdFrom: Vector): Vector;
444
427
  /**
445
428
  * @param value The position to set
446
429
  * @param options.holdFrom An anchor point to hold the position from
@@ -457,7 +440,6 @@ declare abstract class ContainerAttributes extends IDAttachable {
457
440
  set x(value: number);
458
441
  get y(): number;
459
442
  set y(value: number);
460
- on<T extends ContainerEventType>(eventType: T): Sequence<ContainerEventTypeToResultType[T]>;
461
443
  setRotation(value: Radian): this;
462
444
  get rotation(): Radian;
463
445
  set rotation(value: Radian);
@@ -466,38 +448,9 @@ declare abstract class ContainerAttributes extends IDAttachable {
466
448
  */
467
449
  get rotationValue(): number;
468
450
  set rotationValue(value: number);
469
- setZIndex(value: number): this;
470
- get zIndex(): number;
471
- set zIndex(value: number);
472
- setSkewX(radian: Radian): this;
473
- get skewX(): Radian;
474
- set skewX(radian: Radian);
475
- setSkewY(radian: Radian): this;
476
- get skewY(): Radian;
477
- set skewY(radian: Radian);
478
- setSortableChildren(value: boolean): this;
479
- get sortableChildren(): boolean;
480
- set sortableChildren(value: boolean);
481
451
  setAlpha(value: number): this;
482
452
  get alpha(): number;
483
453
  set alpha(value: number);
484
- setInteractive(value: boolean): this;
485
- get interactive(): boolean;
486
- set interactive(value: boolean);
487
- /**
488
- * Renders this subtree as an isolated render group: its cached render instructions are not
489
- * rebuilt when other parts of the scene change, and moving the group only updates one transform.
490
- * Use for subtrees that move as a whole (world under a camera) or rarely change (HUD vs world).
491
- */
492
- setRenderGroup(value?: boolean): this;
493
- setCursor(value: Cursor): this;
494
- get cursor(): Cursor;
495
- set cursor(value: Cursor);
496
- protected getMask(): Pixi.Container;
497
- setMask(mask: ContainerAttributes | undefined): this;
498
- hitTest(stagePosition: Vector): boolean;
499
- get boundingBox(): Rectangle;
500
- ignoreBounds(value?: boolean): this;
501
454
  setMirror(mirror?: boolean): this;
502
455
  get mirror(): boolean;
503
456
  set mirror(value: boolean);
@@ -572,11 +525,40 @@ declare class Sprite extends Container {
572
525
  download(): this;
573
526
  }
574
527
 
528
+ type LayoutStyle = LayoutStyles;
529
+ declare enum ContainerEventType {
530
+ Click = "click",
531
+ MouseOver = "mouseover",
532
+ MouseOut = "mouseout",
533
+ PointerDown = "pointerdown",
534
+ PointerUp = "pointerup",
535
+ PointerUpOutside = "pointerupoutside",
536
+ PointerMove = "pointermove",
537
+ PointerCancel = "pointercancel",
538
+ PointerOver = "pointerover",
539
+ PointerOut = "pointerout",
540
+ PointerEnter = "pointerenter",
541
+ PointerLeave = "pointerleave",
542
+ Added = "added",
543
+ Removed = "removed",
544
+ Update = "update",
545
+ Change = "change",
546
+ Wheel = "wheel"
547
+ }
548
+ type ContainerEventTypeToResultType = {
549
+ [K in ContainerEventType]: K extends ContainerEventType.Wheel ? {
550
+ deltaY: number;
551
+ } : undefined;
552
+ };
553
+ declare enum Cursor {
554
+ Default = "default",
555
+ Pointer = "pointer"
556
+ }
575
557
  interface SetParentOptions {
576
558
  addUnder: boolean;
577
559
  cropOverflowingParts: boolean;
578
560
  }
579
- declare class Container extends ContainerAttributes {
561
+ declare class Container extends DisplayObjectAttributes {
580
562
  protected static allContainers: Map<number, Container>;
581
563
  readonly filters: Filters;
582
564
  protected addChildTo: Container;
@@ -584,8 +566,50 @@ declare class Container extends ContainerAttributes {
584
566
  addUnder: boolean;
585
567
  cropOverflowingParts: boolean;
586
568
  };
587
- constructor();
569
+ constructor(pixiContainer?: Pixi.Container);
588
570
  destroy(): void;
571
+ protected _applyScale(x: number, y: number): void;
572
+ protected _getHoldFromModifier(holdFrom: Vector): Vector;
573
+ get size(): Vector;
574
+ set size(value: Vector);
575
+ getSize(): Vector;
576
+ setSize(value: Vector): this;
577
+ /**
578
+ * Makes this object a flex item of a LayoutContainer parent, treated as a box measured by its bounds.
579
+ * @param style Optional per-item overrides (alignSelf, margins, width, height...)
580
+ * @returns this
581
+ */
582
+ setLayout(style?: LayoutStyle): this;
583
+ on<T extends ContainerEventType>(eventType: T): Sequence<ContainerEventTypeToResultType[T]>;
584
+ setZIndex(value: number): this;
585
+ get zIndex(): number;
586
+ set zIndex(value: number);
587
+ setSkewX(radian: Radian): this;
588
+ get skewX(): Radian;
589
+ set skewX(radian: Radian);
590
+ setSkewY(radian: Radian): this;
591
+ get skewY(): Radian;
592
+ set skewY(radian: Radian);
593
+ setSortableChildren(value: boolean): this;
594
+ get sortableChildren(): boolean;
595
+ set sortableChildren(value: boolean);
596
+ setInteractive(value: boolean): this;
597
+ get interactive(): boolean;
598
+ set interactive(value: boolean);
599
+ /**
600
+ * Renders this subtree as an isolated render group: its cached render instructions are not
601
+ * rebuilt when other parts of the scene change, and moving the group only updates one transform.
602
+ * Use for subtrees that move as a whole (world under a camera) or rarely change (HUD vs world).
603
+ */
604
+ setRenderGroup(value?: boolean): this;
605
+ setCursor(value: Cursor): this;
606
+ get cursor(): Cursor;
607
+ set cursor(value: Cursor);
608
+ protected getMask(): Pixi.Container;
609
+ setMask(mask: Container | undefined): this;
610
+ hitTest(stagePosition: Vector): boolean;
611
+ get boundingBox(): Rectangle;
612
+ ignoreBounds(value?: boolean): this;
589
613
  getBoundingMask(): Sprite;
590
614
  displayParent(parent: Container | number, partialOptions?: Partial<SetParentOptions>): this;
591
615
  }
@@ -982,6 +1006,16 @@ declare class ScrollMaskUI extends Container {
982
1006
  constructor(container: Container, size: Vector, padding: number, direction?: ScrollDirection);
983
1007
  }
984
1008
 
1009
+ /**
1010
+ * A flexbox container.
1011
+ */
1012
+ declare class LayoutContainer extends Container {
1013
+ constructor(style?: LayoutStyle);
1014
+ setLayout(style: LayoutStyle): this;
1015
+ debug(): this;
1016
+ debugBackground(): this;
1017
+ }
1018
+
985
1019
  interface KeyboardKeyInfo {
986
1020
  readonly key: string;
987
1021
  readonly code: string;
@@ -1342,4 +1376,26 @@ declare abstract class MovablePhysicsEntity extends PhysicsEntity {
1342
1376
  convertToDTO(): PhysicsBodyDTO;
1343
1377
  }
1344
1378
 
1345
- export { AdvancedSound, type AdvancedSoundOptions, AnimationFlicker, AnimationInterpolationFunctions, type AnimationOptions, Animations, AnimationsCompletionHandlingType, Animator, type AnimatorAnimation, type AssetDefinition, type AudioAppearTransitionType, BORDER_MATERIAL_NAME, BardLegendsTestingHelper, type BlendMode, type BorderProperties, Camera, CameraGateway, type CircleShapeData, ClosestAvailableSpaceHelper, type CollisionDetails, type CollisionReport, Container, ContainerEventType, ControllerDecorator, type CreateDashedLineOptions, type CreatePhysicsWorldRequestDTO, Cursor, DEFAULT_SHADER_RESOLUTION, DeltaTime, DisplayObjectArray, type DisplayObjectArrayOptions, type DropShadowOptions, Entity, EntityDecorator, type EntityID, type ExplosionHit, FadeInContent, type FadeInContentOptions, FocusingAnimation, Game, Gateway, type GlowEffectOptions, type GlowOptions, type GlowingShapeDefinition, Graphics, type HitTestOptions, ImmovablePhysicsEntity, type InteractingBodyGroups, type KeyboardKeyInfo, KeyboardService, type MapSizeDTO, type MaterialContactDefinition, type MaterialDefinition, MenuEntity, type MenuOptions, MenuUI, MenuView, MouseService, MouseTargetFocusService, MovableEntity, MovablePhysicsEntity, Music, MusicPlayer, type MusicPlayerOptions, type PartialTextOptions, PathFinder, type PathFinderResult, type PhysicsBodyDTO, PhysicsEntity, type PhysicsEntityDefinition, type PhysicsExplosionOptions, PhysicsGateway, PhysicsShapeType, type PhysicsWorldID, Placeholder, type PolygonDefinition, type PolygonShapeData, ROTATIONAL_SPEED_LIMIT, type RayCast, type RayCastHit, ReAnimateHandlingType, type RectangleShapeData, RichText, type RichTextOptions, type RichTextRectangleCut, type RichTextStyles, SOUND_TRANSITION_DURATION, SPEED_LIMIT, Scene, SceneDecorator, ScrollAreaUI, ScrollDirection, type ScrollInContentOptions, ScrollMaskUI, Service, ServiceDecorator, type SetParentOptions, type ShapeDefinition, SingletonEntity, SlideInContent, SlideInContentByIndex, SlideStateAnimation, SlideStateAnimationState, Sound, type SoundDefinition, type SoundID, type SoundIDRegistry, type SoundOptions, Sprite, type SpriteDefinition, type SpriteID, type SpriteIDRegistry, StateAnimation, type StateAnimationOptions, type StateAnimationState, Text, type TextAlignment, type TextOptions, ThroughEmptyStateAnimation, UpdatableContainer, UpdateCycle, VectorFieldPathFinder, VectorSet, View, ViewDecorator, type ViewDecoratorMeta };
1379
+ interface ElectronAppOptions {
1380
+ readonly devMode: boolean;
1381
+ readonly maxScreenResolution: {
1382
+ readonly width: number;
1383
+ readonly height: number;
1384
+ } | undefined;
1385
+ }
1386
+ interface ElectronBridge {
1387
+ save(data: string | undefined, fileName: string): Promise<void>;
1388
+ load(fileName: string): Promise<string | undefined>;
1389
+ getOptions(): Promise<ElectronAppOptions>;
1390
+ setFullScreen(fullScreen: boolean): void;
1391
+ rendererReady(): void;
1392
+ onWindowFocus(callback: (focused: boolean) => void): () => void;
1393
+ quit(): void;
1394
+ }
1395
+ declare global {
1396
+ interface Window {
1397
+ api: ElectronBridge;
1398
+ }
1399
+ }
1400
+
1401
+ export { AdvancedSound, type AdvancedSoundOptions, AnimationFlicker, AnimationInterpolationFunctions, type AnimationOptions, Animations, AnimationsCompletionHandlingType, Animator, type AnimatorAnimation, type AssetDefinition, type AudioAppearTransitionType, BORDER_MATERIAL_NAME, BardLegendsTestingHelper, type BlendMode, type BorderProperties, Camera, CameraGateway, type CircleShapeData, ClosestAvailableSpaceHelper, type CollisionDetails, type CollisionReport, Container, ContainerEventType, ControllerDecorator, type CreateDashedLineOptions, type CreatePhysicsWorldRequestDTO, Cursor, DEFAULT_SHADER_RESOLUTION, DeltaTime, DisplayObjectArray, type DisplayObjectArrayOptions, type DropShadowOptions, type ElectronAppOptions, type ElectronBridge, ElectronGame, Entity, EntityDecorator, type EntityID, type ExplosionHit, FadeInContent, type FadeInContentOptions, FocusingAnimation, Game, Gateway, type GlowEffectOptions, type GlowOptions, type GlowingShapeDefinition, Graphics, type HitTestOptions, ImmovablePhysicsEntity, type InteractingBodyGroups, type KeyboardKeyInfo, KeyboardService, LayoutContainer, type LayoutStyle, type MapSizeDTO, type MaterialContactDefinition, type MaterialDefinition, MenuEntity, type MenuOptions, MenuUI, MenuView, MouseService, MouseTargetFocusService, MovableEntity, MovablePhysicsEntity, Music, MusicPlayer, type MusicPlayerOptions, type PartialTextOptions, PathFinder, type PathFinderResult, type PhysicsBodyDTO, PhysicsEntity, type PhysicsEntityDefinition, type PhysicsExplosionOptions, PhysicsGateway, PhysicsShapeType, type PhysicsWorldID, Placeholder, type PolygonDefinition, type PolygonShapeData, ROTATIONAL_SPEED_LIMIT, type RayCast, type RayCastHit, ReAnimateHandlingType, type RectangleShapeData, RichText, type RichTextOptions, type RichTextRectangleCut, type RichTextStyles, SOUND_TRANSITION_DURATION, SPEED_LIMIT, Scene, SceneDecorator, ScrollAreaUI, ScrollDirection, type ScrollInContentOptions, ScrollMaskUI, Service, ServiceDecorator, type SetParentOptions, type ShapeDefinition, SingletonEntity, SlideInContent, SlideInContentByIndex, SlideStateAnimation, SlideStateAnimationState, Sound, type SoundDefinition, type SoundID, type SoundIDRegistry, type SoundOptions, Sprite, type SpriteDefinition, type SpriteID, type SpriteIDRegistry, StateAnimation, type StateAnimationOptions, type StateAnimationState, Text, type TextAlignment, type TextOptions, ThroughEmptyStateAnimation, UpdatableContainer, UpdateCycle, VectorFieldPathFinder, VectorSet, View, ViewDecorator, type ViewDecoratorMeta };