@energy8platform/game-engine 0.12.0 → 0.14.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/README.md +60 -9
- package/bin/simulate.ts +45 -5
- package/dist/index.cjs.js +215 -56
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +28 -6
- package/dist/index.esm.js +215 -56
- package/dist/index.esm.js.map +1 -1
- package/dist/lua.cjs.js +237 -0
- package/dist/lua.cjs.js.map +1 -1
- package/dist/lua.d.ts +59 -2
- package/dist/lua.esm.js +236 -2
- package/dist/lua.esm.js.map +1 -1
- package/dist/react.cjs.js +216 -57
- package/dist/react.cjs.js.map +1 -1
- package/dist/react.esm.js +216 -57
- package/dist/react.esm.js.map +1 -1
- package/dist/ui.cjs.js +215 -56
- package/dist/ui.cjs.js.map +1 -1
- package/dist/ui.d.ts +29 -7
- package/dist/ui.esm.js +215 -56
- package/dist/ui.esm.js.map +1 -1
- package/package.json +4 -2
- package/scripts/install-simulate.mjs +100 -0
- package/src/lua/NativeSimulationRunner.ts +367 -0
- package/src/lua/index.ts +7 -0
- package/src/react/jsx.d.ts +15 -3
- package/src/react/reconciler.ts +1 -1
- package/src/ui/FlexContainer.ts +241 -61
- package/src/ui/index.ts +1 -1
package/dist/ui.esm.js
CHANGED
|
@@ -25,26 +25,90 @@ function resolveView(input) {
|
|
|
25
25
|
function normalizePadding(p) {
|
|
26
26
|
return typeof p === 'number' ? [p, p, p, p] : p;
|
|
27
27
|
}
|
|
28
|
+
/** Resolve padding from config: individual props override the base `padding` value */
|
|
29
|
+
function resolvePadding(config) {
|
|
30
|
+
const base = normalizePadding(config.padding ?? 0);
|
|
31
|
+
return [
|
|
32
|
+
config.paddingTop ?? base[0],
|
|
33
|
+
config.paddingRight ?? base[1],
|
|
34
|
+
config.paddingBottom ?? base[2],
|
|
35
|
+
config.paddingLeft ?? base[3],
|
|
36
|
+
];
|
|
37
|
+
}
|
|
38
|
+
/** Resolve a dimension value — number passes through, "50%" resolves against reference */
|
|
39
|
+
function resolveDimension(value, reference) {
|
|
40
|
+
if (value === undefined)
|
|
41
|
+
return undefined;
|
|
42
|
+
if (typeof value === 'number')
|
|
43
|
+
return value;
|
|
44
|
+
if (typeof value === 'string' && value.endsWith('%')) {
|
|
45
|
+
const pct = parseFloat(value);
|
|
46
|
+
if (!isNaN(pct) && reference > 0 && isFinite(reference))
|
|
47
|
+
return (pct / 100) * reference;
|
|
48
|
+
}
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
28
51
|
/** Measure a child's size and bounds offset for layout purposes */
|
|
29
|
-
function measureChild(child) {
|
|
52
|
+
function measureChild(child, parentContentW = 0, parentContentH = 0) {
|
|
30
53
|
const cfg = child._flexConfig;
|
|
31
|
-
|
|
32
|
-
|
|
54
|
+
const resolvedLW = resolveDimension(cfg?.layoutWidth, parentContentW);
|
|
55
|
+
const resolvedLH = resolveDimension(cfg?.layoutHeight, parentContentH);
|
|
56
|
+
if (resolvedLW !== undefined && resolvedLH !== undefined) {
|
|
57
|
+
return { w: resolvedLW, h: resolvedLH, ox: 0, oy: 0 };
|
|
33
58
|
}
|
|
34
|
-
// For FlexContainers, use their explicit
|
|
59
|
+
// For FlexContainers, use their explicit or computed size
|
|
35
60
|
if (child instanceof FlexContainer) {
|
|
36
61
|
const fc = child;
|
|
37
|
-
|
|
38
|
-
|
|
62
|
+
const w = fc._explicitWidth > 0 ? fc._explicitWidth : (fc._computedWidth > 0 ? fc._computedWidth : undefined);
|
|
63
|
+
const h = fc._explicitHeight > 0 ? fc._explicitHeight : (fc._computedHeight > 0 ? fc._computedHeight : undefined);
|
|
64
|
+
if (w !== undefined && h !== undefined) {
|
|
65
|
+
return { w, h, ox: 0, oy: 0 };
|
|
39
66
|
}
|
|
40
67
|
}
|
|
41
68
|
// Use localBounds to get the true visual extent and origin offset.
|
|
42
69
|
// This handles children with non-zero anchors (e.g. Button, Label with centered text).
|
|
43
70
|
const bounds = child.getLocalBounds();
|
|
44
|
-
const w =
|
|
45
|
-
const h =
|
|
71
|
+
const w = resolvedLW ?? bounds.width;
|
|
72
|
+
const h = resolvedLH ?? bounds.height;
|
|
46
73
|
return { w, h, ox: bounds.x, oy: bounds.y };
|
|
47
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* Set a child's main or cross dimension.
|
|
77
|
+
* For FlexContainer children, calls resize() to trigger internal relayout
|
|
78
|
+
* instead of the PixiJS scale setter.
|
|
79
|
+
*/
|
|
80
|
+
function setChildMainSize(child, isRow, mainSize, item) {
|
|
81
|
+
if (child instanceof FlexContainer) {
|
|
82
|
+
const fc = child;
|
|
83
|
+
fc.resize(isRow ? mainSize : fc._explicitWidth || fc._computedWidth, isRow ? fc._explicitHeight || fc._computedHeight : mainSize);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
if (isRow) {
|
|
87
|
+
child.width = mainSize;
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
child.height = mainSize;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (isRow)
|
|
94
|
+
item.w = mainSize;
|
|
95
|
+
else
|
|
96
|
+
item.h = mainSize;
|
|
97
|
+
}
|
|
98
|
+
function setChildCrossSize(child, isRow, crossSize) {
|
|
99
|
+
if (child instanceof FlexContainer) {
|
|
100
|
+
const fc = child;
|
|
101
|
+
fc.resize(isRow ? fc._explicitWidth || fc._computedWidth : crossSize, isRow ? crossSize : fc._explicitHeight || fc._computedHeight);
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
if (isRow) {
|
|
105
|
+
child.height = crossSize;
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
child.width = crossSize;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
48
112
|
function layoutLine(items, isRow, mainSize, justify, align, gap, crossOffset, crossSize) {
|
|
49
113
|
if (items.length === 0)
|
|
50
114
|
return;
|
|
@@ -68,14 +132,7 @@ function layoutLine(items, isRow, mainSize, justify, align, gap, crossOffset, cr
|
|
|
68
132
|
const grow = item.child._flexConfig?.flexGrow ?? 0;
|
|
69
133
|
if (grow > 0) {
|
|
70
134
|
const flexSize = (grow / totalGrow) * availableForFlex;
|
|
71
|
-
|
|
72
|
-
item.w = flexSize;
|
|
73
|
-
item.child.width = flexSize;
|
|
74
|
-
}
|
|
75
|
-
else {
|
|
76
|
-
item.h = flexSize;
|
|
77
|
-
item.child.height = flexSize;
|
|
78
|
-
}
|
|
135
|
+
setChildMainSize(item.child, isRow, flexSize, item);
|
|
79
136
|
}
|
|
80
137
|
}
|
|
81
138
|
}
|
|
@@ -97,14 +154,7 @@ function layoutLine(items, isRow, mainSize, justify, align, gap, crossOffset, cr
|
|
|
97
154
|
const itemMain = isRow ? item.w : item.h;
|
|
98
155
|
const reduction = overflow * (itemMain / totalShrinkable);
|
|
99
156
|
const newSize = Math.max(0, itemMain - reduction);
|
|
100
|
-
|
|
101
|
-
item.w = newSize;
|
|
102
|
-
item.child.width = newSize;
|
|
103
|
-
}
|
|
104
|
-
else {
|
|
105
|
-
item.h = newSize;
|
|
106
|
-
item.child.height = newSize;
|
|
107
|
-
}
|
|
157
|
+
setChildMainSize(item.child, isRow, newSize, item);
|
|
108
158
|
}
|
|
109
159
|
}
|
|
110
160
|
}
|
|
@@ -161,12 +211,7 @@ function layoutLine(items, isRow, mainSize, justify, align, gap, crossOffset, cr
|
|
|
161
211
|
crossPos += crossSize - crossDim;
|
|
162
212
|
break;
|
|
163
213
|
case 'stretch':
|
|
164
|
-
|
|
165
|
-
item.child.height = crossSize;
|
|
166
|
-
}
|
|
167
|
-
else {
|
|
168
|
-
item.child.width = crossSize;
|
|
169
|
-
}
|
|
214
|
+
setChildCrossSize(item.child, isRow, crossSize);
|
|
170
215
|
break;
|
|
171
216
|
}
|
|
172
217
|
// Compensate for local bounds offset (e.g. centered anchors)
|
|
@@ -211,6 +256,12 @@ class FlexContainer extends Container {
|
|
|
211
256
|
_maxHeight;
|
|
212
257
|
/** @internal */ _explicitWidth;
|
|
213
258
|
/** @internal */ _explicitHeight;
|
|
259
|
+
/** @internal */ _computedWidth = 0;
|
|
260
|
+
/** @internal */ _computedHeight = 0;
|
|
261
|
+
/** @internal */ _availableWidth = 0;
|
|
262
|
+
/** @internal */ _availableHeight = 0;
|
|
263
|
+
/** @internal */ _rawWidth;
|
|
264
|
+
/** @internal */ _rawHeight;
|
|
214
265
|
_layoutChildren = [];
|
|
215
266
|
_layoutDirty = true;
|
|
216
267
|
constructor(config = {}) {
|
|
@@ -221,12 +272,15 @@ class FlexContainer extends Container {
|
|
|
221
272
|
alignItems: config.alignItems ?? 'start',
|
|
222
273
|
gap: config.gap ?? 0,
|
|
223
274
|
flexWrap: config.flexWrap ?? false,
|
|
275
|
+
alignContent: config.alignContent ?? 'start',
|
|
224
276
|
};
|
|
225
|
-
this._padding =
|
|
277
|
+
this._padding = resolvePadding(config);
|
|
226
278
|
this._maxWidth = config.maxWidth ?? Infinity;
|
|
227
279
|
this._maxHeight = config.maxHeight ?? Infinity;
|
|
228
|
-
this.
|
|
229
|
-
this.
|
|
280
|
+
this._rawWidth = config.width ?? 0;
|
|
281
|
+
this._rawHeight = config.height ?? 0;
|
|
282
|
+
this._explicitWidth = typeof this._rawWidth === 'number' ? this._rawWidth : 0;
|
|
283
|
+
this._explicitHeight = typeof this._rawHeight === 'number' ? this._rawHeight : 0;
|
|
230
284
|
}
|
|
231
285
|
// ─── Public API ──────────────────────────────────────
|
|
232
286
|
/** Add a child with optional flex config. Also registers in flex layout. */
|
|
@@ -328,19 +382,41 @@ class FlexContainer extends Container {
|
|
|
328
382
|
*/
|
|
329
383
|
updateLayout() {
|
|
330
384
|
this._layoutDirty = false;
|
|
331
|
-
const { direction, justifyContent, alignItems, gap, flexWrap } = this._config;
|
|
385
|
+
const { direction, justifyContent, alignItems, gap, flexWrap, alignContent } = this._config;
|
|
332
386
|
const [pt, pr, pb, pl] = this._padding;
|
|
333
387
|
const isRow = direction === 'row';
|
|
388
|
+
// Resolve percentage width/height against parent's available space
|
|
389
|
+
if (typeof this._rawWidth === 'string') {
|
|
390
|
+
this._explicitWidth = resolveDimension(this._rawWidth, this._availableWidth) ?? 0;
|
|
391
|
+
}
|
|
392
|
+
if (typeof this._rawHeight === 'string') {
|
|
393
|
+
this._explicitHeight = resolveDimension(this._rawHeight, this._availableHeight) ?? 0;
|
|
394
|
+
}
|
|
334
395
|
const contentW = this._explicitWidth > 0 ? this._explicitWidth - pl - pr : Infinity;
|
|
335
396
|
const contentH = this._explicitHeight > 0 ? this._explicitHeight - pt - pb : Infinity;
|
|
336
397
|
const mainLimit = isRow ? contentW : contentH;
|
|
337
398
|
const crossLimit = isRow ? contentH : contentW;
|
|
399
|
+
// Pass content area to measureChild for percentage resolution
|
|
400
|
+
const pctRefW = contentW < Infinity ? contentW : 0;
|
|
401
|
+
const pctRefH = contentH < Infinity ? contentH : 0;
|
|
402
|
+
// Propagate available size to child FlexContainers and resolve their percentages
|
|
403
|
+
for (const child of this._layoutChildren) {
|
|
404
|
+
if (child instanceof FlexContainer) {
|
|
405
|
+
const fc = child;
|
|
406
|
+
fc._availableWidth = pctRefW;
|
|
407
|
+
fc._availableHeight = pctRefH;
|
|
408
|
+
// If child has percentage dimensions, trigger its layout to resolve them
|
|
409
|
+
if (typeof fc._rawWidth === 'string' || typeof fc._rawHeight === 'string') {
|
|
410
|
+
fc.updateLayout();
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
338
414
|
// Measure children (skip flexExclude — they position themselves)
|
|
339
415
|
const measured = [];
|
|
340
416
|
for (const child of this._layoutChildren) {
|
|
341
417
|
if (child._flexConfig?.flexExclude)
|
|
342
418
|
continue;
|
|
343
|
-
const { w, h, ox, oy } = measureChild(child);
|
|
419
|
+
const { w, h, ox, oy } = measureChild(child, pctRefW, pctRefH);
|
|
344
420
|
measured.push({ child, w, h, ox, oy });
|
|
345
421
|
}
|
|
346
422
|
// Split into lines (if wrapping)
|
|
@@ -377,42 +453,106 @@ class FlexContainer extends Container {
|
|
|
377
453
|
}
|
|
378
454
|
return maxCross;
|
|
379
455
|
});
|
|
456
|
+
// Compute natural main size (for auto-sizing when no explicit size given)
|
|
457
|
+
let naturalMainSize = 0;
|
|
458
|
+
if (mainLimit === Infinity) {
|
|
459
|
+
for (const line of lines) {
|
|
460
|
+
let lineMain = 0;
|
|
461
|
+
for (const item of line) {
|
|
462
|
+
lineMain += isRow ? item.w : item.h;
|
|
463
|
+
}
|
|
464
|
+
lineMain += gap * Math.max(0, line.length - 1);
|
|
465
|
+
naturalMainSize = Math.max(naturalMainSize, lineMain);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
// Effective main size: explicit if set, otherwise natural content size
|
|
469
|
+
const effectiveMainSize = mainLimit < Infinity ? mainLimit : naturalMainSize;
|
|
470
|
+
// Compute alignContent offsets for multi-line layouts
|
|
471
|
+
const totalLinesCross = lineCrossSizes.reduce((s, v) => s + v, 0) + gap * Math.max(0, lines.length - 1);
|
|
472
|
+
let acOffset = 0;
|
|
473
|
+
let acExtraGap = 0;
|
|
474
|
+
if (lines.length > 1 && crossLimit < Infinity) {
|
|
475
|
+
const freeSpace = Math.max(0, crossLimit - totalLinesCross);
|
|
476
|
+
switch (alignContent) {
|
|
477
|
+
case 'center':
|
|
478
|
+
acOffset = freeSpace / 2;
|
|
479
|
+
break;
|
|
480
|
+
case 'end':
|
|
481
|
+
acOffset = freeSpace;
|
|
482
|
+
break;
|
|
483
|
+
case 'space-between':
|
|
484
|
+
if (lines.length > 1) {
|
|
485
|
+
acExtraGap = freeSpace / (lines.length - 1);
|
|
486
|
+
}
|
|
487
|
+
break;
|
|
488
|
+
case 'stretch':
|
|
489
|
+
if (lines.length > 0) {
|
|
490
|
+
const extra = freeSpace / lines.length;
|
|
491
|
+
for (let i = 0; i < lineCrossSizes.length; i++) {
|
|
492
|
+
lineCrossSizes[i] += extra;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
break;
|
|
496
|
+
// 'start' — no adjustment
|
|
497
|
+
}
|
|
498
|
+
}
|
|
380
499
|
// Layout each line
|
|
381
|
-
let crossOffset = isRow ? pt : pl;
|
|
500
|
+
let crossOffset = (isRow ? pt : pl) + acOffset;
|
|
382
501
|
for (let i = 0; i < lines.length; i++) {
|
|
383
502
|
const line = lines[i];
|
|
384
503
|
const lineCross = lineCrossSizes[i];
|
|
385
504
|
const mainStart = isRow ? pl : pt;
|
|
386
505
|
// Offset items by padding
|
|
387
506
|
const tempItems = line.map((item) => ({ ...item }));
|
|
388
|
-
//
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
? crossLimit
|
|
392
|
-
: (crossLimit < Infinity ? Math.min(lineCross, crossLimit) : lineCross);
|
|
393
|
-
layoutLine(tempItems, isRow, mainLimit < Infinity ? mainLimit : 0, mainLimit < Infinity ? justifyContent : 'start', alignItems, gap, crossOffset, effectiveCross);
|
|
507
|
+
// Cross size for alignment: use container cross size for single-line, line cross for multi-line
|
|
508
|
+
const effectiveCross = lines.length === 1 && crossLimit < Infinity ? crossLimit : lineCross;
|
|
509
|
+
layoutLine(tempItems, isRow, effectiveMainSize, mainLimit < Infinity ? justifyContent : 'start', alignItems, gap, crossOffset, effectiveCross);
|
|
394
510
|
// Apply main-axis padding offset
|
|
395
511
|
for (const item of tempItems) {
|
|
396
512
|
const origChild = line.find((l) => l.child === item.child);
|
|
397
513
|
origChild.child.x = item.child.x + (isRow ? mainStart : 0);
|
|
398
514
|
origChild.child.y = item.child.y + (isRow ? 0 : mainStart);
|
|
399
515
|
}
|
|
400
|
-
crossOffset += lineCross + gap;
|
|
516
|
+
crossOffset += lineCross + gap + acExtraGap;
|
|
517
|
+
}
|
|
518
|
+
// Compute and store actual dimensions for measureChild() and getContentSize()
|
|
519
|
+
let totalCrossNatural = 0;
|
|
520
|
+
for (let i = 0; i < lineCrossSizes.length; i++) {
|
|
521
|
+
totalCrossNatural += lineCrossSizes[i];
|
|
522
|
+
if (i < lineCrossSizes.length - 1)
|
|
523
|
+
totalCrossNatural += gap;
|
|
524
|
+
}
|
|
525
|
+
if (isRow) {
|
|
526
|
+
this._computedWidth = this._explicitWidth > 0 ? this._explicitWidth : (pl + naturalMainSize + pr);
|
|
527
|
+
this._computedHeight = this._explicitHeight > 0 ? this._explicitHeight : (pt + totalCrossNatural + pb);
|
|
528
|
+
}
|
|
529
|
+
else {
|
|
530
|
+
this._computedWidth = this._explicitWidth > 0 ? this._explicitWidth : (pl + totalCrossNatural + pr);
|
|
531
|
+
this._computedHeight = this._explicitHeight > 0 ? this._explicitHeight : (pt + naturalMainSize + pb);
|
|
532
|
+
}
|
|
533
|
+
// Position flexExclude children (absolute positioning)
|
|
534
|
+
for (const child of this._layoutChildren) {
|
|
535
|
+
if (!child._flexConfig?.flexExclude)
|
|
536
|
+
continue;
|
|
537
|
+
const cfg = child._flexConfig;
|
|
538
|
+
const { w, h } = measureChild(child, pctRefW, pctRefH);
|
|
539
|
+
const cw = this._computedWidth;
|
|
540
|
+
const ch = this._computedHeight;
|
|
541
|
+
if (cfg.left !== undefined)
|
|
542
|
+
child.x = cfg.left;
|
|
543
|
+
else if (cfg.right !== undefined)
|
|
544
|
+
child.x = cw - w - cfg.right;
|
|
545
|
+
if (cfg.top !== undefined)
|
|
546
|
+
child.y = cfg.top;
|
|
547
|
+
else if (cfg.bottom !== undefined)
|
|
548
|
+
child.y = ch - h - cfg.bottom;
|
|
401
549
|
}
|
|
402
550
|
}
|
|
403
551
|
/** Computed content size (after layout) */
|
|
404
552
|
getContentSize() {
|
|
405
553
|
if (this._layoutDirty)
|
|
406
554
|
this.updateLayout();
|
|
407
|
-
|
|
408
|
-
let maxY = 0;
|
|
409
|
-
for (const child of this._layoutChildren) {
|
|
410
|
-
const { w, h } = measureChild(child);
|
|
411
|
-
maxX = Math.max(maxX, child.x + w);
|
|
412
|
-
maxY = Math.max(maxY, child.y + h);
|
|
413
|
-
}
|
|
414
|
-
const [, pr, pb] = this._padding;
|
|
415
|
-
return { width: maxX + pr, height: maxY + pb };
|
|
555
|
+
return { width: this._computedWidth, height: this._computedHeight };
|
|
416
556
|
}
|
|
417
557
|
/** React reconciler update hook — applies changed config props */
|
|
418
558
|
updateConfig(changed) {
|
|
@@ -424,15 +564,34 @@ class FlexContainer extends Container {
|
|
|
424
564
|
this.setAlignItems(changed.alignItems);
|
|
425
565
|
if ('gap' in changed)
|
|
426
566
|
this.setGap(changed.gap);
|
|
427
|
-
if ('padding' in changed)
|
|
428
|
-
this.
|
|
567
|
+
if ('padding' in changed || 'paddingTop' in changed || 'paddingRight' in changed || 'paddingBottom' in changed || 'paddingLeft' in changed) {
|
|
568
|
+
this._padding = resolvePadding(changed);
|
|
569
|
+
this._layoutDirty = true;
|
|
570
|
+
}
|
|
429
571
|
if ('flexWrap' in changed) {
|
|
430
572
|
this._config.flexWrap = changed.flexWrap;
|
|
431
573
|
this._layoutDirty = true;
|
|
432
574
|
}
|
|
575
|
+
if ('alignContent' in changed) {
|
|
576
|
+
this._config.alignContent = changed.alignContent;
|
|
577
|
+
this._layoutDirty = true;
|
|
578
|
+
}
|
|
433
579
|
if ('width' in changed || 'height' in changed) {
|
|
434
|
-
|
|
435
|
-
|
|
580
|
+
const w = changed.width ?? this._rawWidth;
|
|
581
|
+
const h = changed.height ?? this._rawHeight;
|
|
582
|
+
this._rawWidth = w;
|
|
583
|
+
this._rawHeight = h;
|
|
584
|
+
if (typeof w === 'number' && typeof h === 'number') {
|
|
585
|
+
this.resize(w, h);
|
|
586
|
+
}
|
|
587
|
+
else {
|
|
588
|
+
// Percentage — will resolve in updateLayout
|
|
589
|
+
this._explicitWidth = typeof w === 'number' ? w : 0;
|
|
590
|
+
this._explicitHeight = typeof h === 'number' ? h : 0;
|
|
591
|
+
this._layoutDirty = true;
|
|
592
|
+
this.updateLayout();
|
|
593
|
+
}
|
|
594
|
+
return;
|
|
436
595
|
}
|
|
437
596
|
if (this._layoutDirty)
|
|
438
597
|
this.updateLayout();
|