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