@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/src/ui/FlexContainer.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { Container } from 'pixi.js';
|
|
|
5
5
|
export type FlexDirection = 'row' | 'column';
|
|
6
6
|
export type JustifyContent = 'start' | 'center' | 'end' | 'space-between' | 'space-around';
|
|
7
7
|
export type AlignItems = 'start' | 'center' | 'end' | 'stretch';
|
|
8
|
+
export type AlignContent = 'start' | 'center' | 'end' | 'space-between' | 'stretch';
|
|
8
9
|
|
|
9
10
|
export type AlignSelf = 'auto' | 'start' | 'center' | 'end' | 'stretch';
|
|
10
11
|
|
|
@@ -14,13 +15,21 @@ export interface FlexItemConfig {
|
|
|
14
15
|
/** Flex shrink factor (0 = don't shrink, default: 1) */
|
|
15
16
|
flexShrink?: number;
|
|
16
17
|
/** Explicit width override for layout calculations */
|
|
17
|
-
layoutWidth?: number;
|
|
18
|
+
layoutWidth?: number | string;
|
|
18
19
|
/** Explicit height override for layout calculations */
|
|
19
|
-
layoutHeight?: number;
|
|
20
|
+
layoutHeight?: number | string;
|
|
20
21
|
/** Override parent's alignItems for this child */
|
|
21
22
|
alignSelf?: AlignSelf;
|
|
22
23
|
/** Exclude from flex layout (acts like position: absolute) */
|
|
23
24
|
flexExclude?: boolean;
|
|
25
|
+
/** Absolute positioning for flexExclude children (distance from top edge) */
|
|
26
|
+
top?: number;
|
|
27
|
+
/** Absolute positioning for flexExclude children (distance from right edge) */
|
|
28
|
+
right?: number;
|
|
29
|
+
/** Absolute positioning for flexExclude children (distance from bottom edge) */
|
|
30
|
+
bottom?: number;
|
|
31
|
+
/** Absolute positioning for flexExclude children (distance from left edge) */
|
|
32
|
+
left?: number;
|
|
24
33
|
}
|
|
25
34
|
|
|
26
35
|
export interface FlexContainerConfig {
|
|
@@ -34,16 +43,23 @@ export interface FlexContainerConfig {
|
|
|
34
43
|
gap?: number;
|
|
35
44
|
/** Padding [top, right, bottom, left] or single number (default: 0) */
|
|
36
45
|
padding?: number | [number, number, number, number];
|
|
46
|
+
/** Individual padding overrides (take priority over `padding`) */
|
|
47
|
+
paddingTop?: number;
|
|
48
|
+
paddingRight?: number;
|
|
49
|
+
paddingBottom?: number;
|
|
50
|
+
paddingLeft?: number;
|
|
37
51
|
/** Enable wrapping to next line (default: false) */
|
|
38
52
|
flexWrap?: boolean;
|
|
53
|
+
/** Distribution of lines along the cross axis when wrapping (default: 'start') */
|
|
54
|
+
alignContent?: AlignContent;
|
|
39
55
|
/** Maximum width before wrapping (only with flexWrap) */
|
|
40
56
|
maxWidth?: number;
|
|
41
57
|
/** Maximum height before wrapping (only with flexWrap + column) */
|
|
42
58
|
maxHeight?: number;
|
|
43
|
-
/** Explicit container width
|
|
44
|
-
width?: number;
|
|
45
|
-
/** Explicit container height
|
|
46
|
-
height?: number;
|
|
59
|
+
/** Explicit container width — number in pixels, or string percentage (e.g. "50%") resolved against parent */
|
|
60
|
+
width?: number | string;
|
|
61
|
+
/** Explicit container height — number in pixels, or string percentage (e.g. "50%") resolved against parent */
|
|
62
|
+
height?: number | string;
|
|
47
63
|
}
|
|
48
64
|
|
|
49
65
|
// ─── Helpers ─────────────────────────────────────────────
|
|
@@ -52,26 +68,56 @@ function normalizePadding(p: number | [number, number, number, number]): [number
|
|
|
52
68
|
return typeof p === 'number' ? [p, p, p, p] : p;
|
|
53
69
|
}
|
|
54
70
|
|
|
71
|
+
/** Resolve padding from config: individual props override the base `padding` value */
|
|
72
|
+
function resolvePadding(config: FlexContainerConfig): [number, number, number, number] {
|
|
73
|
+
const base = normalizePadding(config.padding ?? 0);
|
|
74
|
+
return [
|
|
75
|
+
config.paddingTop ?? base[0],
|
|
76
|
+
config.paddingRight ?? base[1],
|
|
77
|
+
config.paddingBottom ?? base[2],
|
|
78
|
+
config.paddingLeft ?? base[3],
|
|
79
|
+
];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Resolve a dimension value — number passes through, "50%" resolves against reference */
|
|
83
|
+
function resolveDimension(value: number | string | undefined, reference: number): number | undefined {
|
|
84
|
+
if (value === undefined) return undefined;
|
|
85
|
+
if (typeof value === 'number') return value;
|
|
86
|
+
if (typeof value === 'string' && value.endsWith('%')) {
|
|
87
|
+
const pct = parseFloat(value);
|
|
88
|
+
if (!isNaN(pct) && reference > 0 && isFinite(reference)) return (pct / 100) * reference;
|
|
89
|
+
}
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
|
|
55
93
|
/** Measure a child's size and bounds offset for layout purposes */
|
|
56
|
-
function measureChild(
|
|
94
|
+
function measureChild(
|
|
95
|
+
child: Container & { _flexConfig?: FlexItemConfig },
|
|
96
|
+
parentContentW = 0,
|
|
97
|
+
parentContentH = 0,
|
|
98
|
+
): { w: number; h: number; ox: number; oy: number } {
|
|
57
99
|
const cfg = child._flexConfig;
|
|
58
|
-
|
|
59
|
-
|
|
100
|
+
const resolvedLW = resolveDimension(cfg?.layoutWidth, parentContentW);
|
|
101
|
+
const resolvedLH = resolveDimension(cfg?.layoutHeight, parentContentH);
|
|
102
|
+
if (resolvedLW !== undefined && resolvedLH !== undefined) {
|
|
103
|
+
return { w: resolvedLW, h: resolvedLH, ox: 0, oy: 0 };
|
|
60
104
|
}
|
|
61
105
|
|
|
62
|
-
// For FlexContainers, use their explicit
|
|
106
|
+
// For FlexContainers, use their explicit or computed size
|
|
63
107
|
if (child instanceof FlexContainer) {
|
|
64
108
|
const fc = child;
|
|
65
|
-
|
|
66
|
-
|
|
109
|
+
const w = fc._explicitWidth > 0 ? fc._explicitWidth : (fc._computedWidth > 0 ? fc._computedWidth : undefined);
|
|
110
|
+
const h = fc._explicitHeight > 0 ? fc._explicitHeight : (fc._computedHeight > 0 ? fc._computedHeight : undefined);
|
|
111
|
+
if (w !== undefined && h !== undefined) {
|
|
112
|
+
return { w, h, ox: 0, oy: 0 };
|
|
67
113
|
}
|
|
68
114
|
}
|
|
69
115
|
|
|
70
116
|
// Use localBounds to get the true visual extent and origin offset.
|
|
71
117
|
// This handles children with non-zero anchors (e.g. Button, Label with centered text).
|
|
72
118
|
const bounds = child.getLocalBounds();
|
|
73
|
-
const w =
|
|
74
|
-
const h =
|
|
119
|
+
const w = resolvedLW ?? bounds.width;
|
|
120
|
+
const h = resolvedLH ?? bounds.height;
|
|
75
121
|
return { w, h, ox: bounds.x, oy: bounds.y };
|
|
76
122
|
}
|
|
77
123
|
|
|
@@ -87,6 +133,45 @@ interface LineItem {
|
|
|
87
133
|
oy: number;
|
|
88
134
|
}
|
|
89
135
|
|
|
136
|
+
/**
|
|
137
|
+
* Set a child's main or cross dimension.
|
|
138
|
+
* For FlexContainer children, calls resize() to trigger internal relayout
|
|
139
|
+
* instead of the PixiJS scale setter.
|
|
140
|
+
*/
|
|
141
|
+
function setChildMainSize(child: Container, isRow: boolean, mainSize: number, item: LineItem): void {
|
|
142
|
+
if (child instanceof FlexContainer) {
|
|
143
|
+
const fc = child;
|
|
144
|
+
fc.resize(
|
|
145
|
+
isRow ? mainSize : fc._explicitWidth || fc._computedWidth,
|
|
146
|
+
isRow ? fc._explicitHeight || fc._computedHeight : mainSize,
|
|
147
|
+
);
|
|
148
|
+
} else {
|
|
149
|
+
if (isRow) {
|
|
150
|
+
child.width = mainSize;
|
|
151
|
+
} else {
|
|
152
|
+
child.height = mainSize;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (isRow) item.w = mainSize;
|
|
156
|
+
else item.h = mainSize;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function setChildCrossSize(child: Container, isRow: boolean, crossSize: number): void {
|
|
160
|
+
if (child instanceof FlexContainer) {
|
|
161
|
+
const fc = child;
|
|
162
|
+
fc.resize(
|
|
163
|
+
isRow ? fc._explicitWidth || fc._computedWidth : crossSize,
|
|
164
|
+
isRow ? crossSize : fc._explicitHeight || fc._computedHeight,
|
|
165
|
+
);
|
|
166
|
+
} else {
|
|
167
|
+
if (isRow) {
|
|
168
|
+
child.height = crossSize;
|
|
169
|
+
} else {
|
|
170
|
+
child.width = crossSize;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
90
175
|
function layoutLine(
|
|
91
176
|
items: LineItem[],
|
|
92
177
|
isRow: boolean,
|
|
@@ -120,13 +205,7 @@ function layoutLine(
|
|
|
120
205
|
const grow = item.child._flexConfig?.flexGrow ?? 0;
|
|
121
206
|
if (grow > 0) {
|
|
122
207
|
const flexSize = (grow / totalGrow) * availableForFlex;
|
|
123
|
-
|
|
124
|
-
item.w = flexSize;
|
|
125
|
-
item.child.width = flexSize;
|
|
126
|
-
} else {
|
|
127
|
-
item.h = flexSize;
|
|
128
|
-
item.child.height = flexSize;
|
|
129
|
-
}
|
|
208
|
+
setChildMainSize(item.child, isRow, flexSize, item);
|
|
130
209
|
}
|
|
131
210
|
}
|
|
132
211
|
}
|
|
@@ -149,13 +228,7 @@ function layoutLine(
|
|
|
149
228
|
const itemMain = isRow ? item.w : item.h;
|
|
150
229
|
const reduction = overflow * (itemMain / totalShrinkable);
|
|
151
230
|
const newSize = Math.max(0, itemMain - reduction);
|
|
152
|
-
|
|
153
|
-
item.w = newSize;
|
|
154
|
-
item.child.width = newSize;
|
|
155
|
-
} else {
|
|
156
|
-
item.h = newSize;
|
|
157
|
-
item.child.height = newSize;
|
|
158
|
-
}
|
|
231
|
+
setChildMainSize(item.child, isRow, newSize, item);
|
|
159
232
|
}
|
|
160
233
|
}
|
|
161
234
|
}
|
|
@@ -217,11 +290,7 @@ function layoutLine(
|
|
|
217
290
|
crossPos += crossSize - crossDim;
|
|
218
291
|
break;
|
|
219
292
|
case 'stretch':
|
|
220
|
-
|
|
221
|
-
item.child.height = crossSize;
|
|
222
|
-
} else {
|
|
223
|
-
item.child.width = crossSize;
|
|
224
|
-
}
|
|
293
|
+
setChildCrossSize(item.child, isRow, crossSize);
|
|
225
294
|
break;
|
|
226
295
|
}
|
|
227
296
|
|
|
@@ -264,12 +333,18 @@ function layoutLine(
|
|
|
264
333
|
export class FlexContainer extends Container {
|
|
265
334
|
readonly __uiComponent = true as const;
|
|
266
335
|
|
|
267
|
-
private _config: Required<Pick<FlexContainerConfig, 'direction' | 'justifyContent' | 'alignItems' | 'gap' | 'flexWrap'>>;
|
|
336
|
+
private _config: Required<Pick<FlexContainerConfig, 'direction' | 'justifyContent' | 'alignItems' | 'gap' | 'flexWrap' | 'alignContent'>>;
|
|
268
337
|
private _padding: [number, number, number, number];
|
|
269
338
|
private _maxWidth: number;
|
|
270
339
|
private _maxHeight: number;
|
|
271
340
|
/** @internal */ _explicitWidth: number;
|
|
272
341
|
/** @internal */ _explicitHeight: number;
|
|
342
|
+
/** @internal */ _computedWidth = 0;
|
|
343
|
+
/** @internal */ _computedHeight = 0;
|
|
344
|
+
/** @internal */ _availableWidth = 0;
|
|
345
|
+
/** @internal */ _availableHeight = 0;
|
|
346
|
+
/** @internal */ _rawWidth: number | string;
|
|
347
|
+
/** @internal */ _rawHeight: number | string;
|
|
273
348
|
private _layoutChildren: (Container & { _flexConfig?: FlexItemConfig })[] = [];
|
|
274
349
|
private _layoutDirty = true;
|
|
275
350
|
|
|
@@ -282,13 +357,16 @@ export class FlexContainer extends Container {
|
|
|
282
357
|
alignItems: config.alignItems ?? 'start',
|
|
283
358
|
gap: config.gap ?? 0,
|
|
284
359
|
flexWrap: config.flexWrap ?? false,
|
|
360
|
+
alignContent: config.alignContent ?? 'start',
|
|
285
361
|
};
|
|
286
362
|
|
|
287
|
-
this._padding =
|
|
363
|
+
this._padding = resolvePadding(config);
|
|
288
364
|
this._maxWidth = config.maxWidth ?? Infinity;
|
|
289
365
|
this._maxHeight = config.maxHeight ?? Infinity;
|
|
290
|
-
this.
|
|
291
|
-
this.
|
|
366
|
+
this._rawWidth = config.width ?? 0;
|
|
367
|
+
this._rawHeight = config.height ?? 0;
|
|
368
|
+
this._explicitWidth = typeof this._rawWidth === 'number' ? this._rawWidth : 0;
|
|
369
|
+
this._explicitHeight = typeof this._rawHeight === 'number' ? this._rawHeight : 0;
|
|
292
370
|
}
|
|
293
371
|
|
|
294
372
|
// ─── Public API ──────────────────────────────────────
|
|
@@ -402,20 +480,45 @@ export class FlexContainer extends Container {
|
|
|
402
480
|
*/
|
|
403
481
|
updateLayout(): void {
|
|
404
482
|
this._layoutDirty = false;
|
|
405
|
-
const { direction, justifyContent, alignItems, gap, flexWrap } = this._config;
|
|
483
|
+
const { direction, justifyContent, alignItems, gap, flexWrap, alignContent } = this._config;
|
|
406
484
|
const [pt, pr, pb, pl] = this._padding;
|
|
407
485
|
const isRow = direction === 'row';
|
|
408
486
|
|
|
487
|
+
// Resolve percentage width/height against parent's available space
|
|
488
|
+
if (typeof this._rawWidth === 'string') {
|
|
489
|
+
this._explicitWidth = resolveDimension(this._rawWidth, this._availableWidth) ?? 0;
|
|
490
|
+
}
|
|
491
|
+
if (typeof this._rawHeight === 'string') {
|
|
492
|
+
this._explicitHeight = resolveDimension(this._rawHeight, this._availableHeight) ?? 0;
|
|
493
|
+
}
|
|
494
|
+
|
|
409
495
|
const contentW = this._explicitWidth > 0 ? this._explicitWidth - pl - pr : Infinity;
|
|
410
496
|
const contentH = this._explicitHeight > 0 ? this._explicitHeight - pt - pb : Infinity;
|
|
411
497
|
const mainLimit = isRow ? contentW : contentH;
|
|
412
498
|
const crossLimit = isRow ? contentH : contentW;
|
|
413
499
|
|
|
500
|
+
// Pass content area to measureChild for percentage resolution
|
|
501
|
+
const pctRefW = contentW < Infinity ? contentW : 0;
|
|
502
|
+
const pctRefH = contentH < Infinity ? contentH : 0;
|
|
503
|
+
|
|
504
|
+
// Propagate available size to child FlexContainers and resolve their percentages
|
|
505
|
+
for (const child of this._layoutChildren) {
|
|
506
|
+
if (child instanceof FlexContainer) {
|
|
507
|
+
const fc = child;
|
|
508
|
+
fc._availableWidth = pctRefW;
|
|
509
|
+
fc._availableHeight = pctRefH;
|
|
510
|
+
// If child has percentage dimensions, trigger its layout to resolve them
|
|
511
|
+
if (typeof fc._rawWidth === 'string' || typeof fc._rawHeight === 'string') {
|
|
512
|
+
fc.updateLayout();
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
414
517
|
// Measure children (skip flexExclude — they position themselves)
|
|
415
518
|
const measured: LineItem[] = [];
|
|
416
519
|
for (const child of this._layoutChildren) {
|
|
417
520
|
if (child._flexConfig?.flexExclude) continue;
|
|
418
|
-
const { w, h, ox, oy } = measureChild(child);
|
|
521
|
+
const { w, h, ox, oy } = measureChild(child, pctRefW, pctRefH);
|
|
419
522
|
measured.push({ child, w, h, ox, oy });
|
|
420
523
|
}
|
|
421
524
|
|
|
@@ -453,8 +556,54 @@ export class FlexContainer extends Container {
|
|
|
453
556
|
return maxCross;
|
|
454
557
|
});
|
|
455
558
|
|
|
559
|
+
// Compute natural main size (for auto-sizing when no explicit size given)
|
|
560
|
+
let naturalMainSize = 0;
|
|
561
|
+
if (mainLimit === Infinity) {
|
|
562
|
+
for (const line of lines) {
|
|
563
|
+
let lineMain = 0;
|
|
564
|
+
for (const item of line) {
|
|
565
|
+
lineMain += isRow ? item.w : item.h;
|
|
566
|
+
}
|
|
567
|
+
lineMain += gap * Math.max(0, line.length - 1);
|
|
568
|
+
naturalMainSize = Math.max(naturalMainSize, lineMain);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// Effective main size: explicit if set, otherwise natural content size
|
|
573
|
+
const effectiveMainSize = mainLimit < Infinity ? mainLimit : naturalMainSize;
|
|
574
|
+
|
|
575
|
+
// Compute alignContent offsets for multi-line layouts
|
|
576
|
+
const totalLinesCross = lineCrossSizes.reduce((s, v) => s + v, 0) + gap * Math.max(0, lines.length - 1);
|
|
577
|
+
let acOffset = 0;
|
|
578
|
+
let acExtraGap = 0;
|
|
579
|
+
if (lines.length > 1 && crossLimit < Infinity) {
|
|
580
|
+
const freeSpace = Math.max(0, crossLimit - totalLinesCross);
|
|
581
|
+
switch (alignContent) {
|
|
582
|
+
case 'center':
|
|
583
|
+
acOffset = freeSpace / 2;
|
|
584
|
+
break;
|
|
585
|
+
case 'end':
|
|
586
|
+
acOffset = freeSpace;
|
|
587
|
+
break;
|
|
588
|
+
case 'space-between':
|
|
589
|
+
if (lines.length > 1) {
|
|
590
|
+
acExtraGap = freeSpace / (lines.length - 1);
|
|
591
|
+
}
|
|
592
|
+
break;
|
|
593
|
+
case 'stretch':
|
|
594
|
+
if (lines.length > 0) {
|
|
595
|
+
const extra = freeSpace / lines.length;
|
|
596
|
+
for (let i = 0; i < lineCrossSizes.length; i++) {
|
|
597
|
+
lineCrossSizes[i] += extra;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
break;
|
|
601
|
+
// 'start' — no adjustment
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
456
605
|
// Layout each line
|
|
457
|
-
let crossOffset = isRow ? pt : pl;
|
|
606
|
+
let crossOffset = (isRow ? pt : pl) + acOffset;
|
|
458
607
|
for (let i = 0; i < lines.length; i++) {
|
|
459
608
|
const line = lines[i];
|
|
460
609
|
const lineCross = lineCrossSizes[i];
|
|
@@ -463,16 +612,13 @@ export class FlexContainer extends Container {
|
|
|
463
612
|
// Offset items by padding
|
|
464
613
|
const tempItems = line.map((item) => ({ ...item }));
|
|
465
614
|
|
|
466
|
-
//
|
|
467
|
-
|
|
468
|
-
const effectiveCross = lines.length === 1 && crossLimit < Infinity
|
|
469
|
-
? crossLimit
|
|
470
|
-
: (crossLimit < Infinity ? Math.min(lineCross, crossLimit) : lineCross);
|
|
615
|
+
// Cross size for alignment: use container cross size for single-line, line cross for multi-line
|
|
616
|
+
const effectiveCross = lines.length === 1 && crossLimit < Infinity ? crossLimit : lineCross;
|
|
471
617
|
|
|
472
618
|
layoutLine(
|
|
473
619
|
tempItems,
|
|
474
620
|
isRow,
|
|
475
|
-
|
|
621
|
+
effectiveMainSize,
|
|
476
622
|
mainLimit < Infinity ? justifyContent : 'start',
|
|
477
623
|
alignItems,
|
|
478
624
|
gap,
|
|
@@ -487,24 +633,42 @@ export class FlexContainer extends Container {
|
|
|
487
633
|
origChild.child.y = item.child.y + (isRow ? 0 : mainStart);
|
|
488
634
|
}
|
|
489
635
|
|
|
490
|
-
crossOffset += lineCross + gap;
|
|
636
|
+
crossOffset += lineCross + gap + acExtraGap;
|
|
491
637
|
}
|
|
492
|
-
}
|
|
493
638
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
639
|
+
// Compute and store actual dimensions for measureChild() and getContentSize()
|
|
640
|
+
let totalCrossNatural = 0;
|
|
641
|
+
for (let i = 0; i < lineCrossSizes.length; i++) {
|
|
642
|
+
totalCrossNatural += lineCrossSizes[i];
|
|
643
|
+
if (i < lineCrossSizes.length - 1) totalCrossNatural += gap;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
if (isRow) {
|
|
647
|
+
this._computedWidth = this._explicitWidth > 0 ? this._explicitWidth : (pl + naturalMainSize + pr);
|
|
648
|
+
this._computedHeight = this._explicitHeight > 0 ? this._explicitHeight : (pt + totalCrossNatural + pb);
|
|
649
|
+
} else {
|
|
650
|
+
this._computedWidth = this._explicitWidth > 0 ? this._explicitWidth : (pl + totalCrossNatural + pr);
|
|
651
|
+
this._computedHeight = this._explicitHeight > 0 ? this._explicitHeight : (pt + naturalMainSize + pb);
|
|
652
|
+
}
|
|
497
653
|
|
|
498
|
-
|
|
499
|
-
let maxY = 0;
|
|
654
|
+
// Position flexExclude children (absolute positioning)
|
|
500
655
|
for (const child of this._layoutChildren) {
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
656
|
+
if (!child._flexConfig?.flexExclude) continue;
|
|
657
|
+
const cfg = child._flexConfig;
|
|
658
|
+
const { w, h } = measureChild(child, pctRefW, pctRefH);
|
|
659
|
+
const cw = this._computedWidth;
|
|
660
|
+
const ch = this._computedHeight;
|
|
661
|
+
if (cfg.left !== undefined) child.x = cfg.left;
|
|
662
|
+
else if (cfg.right !== undefined) child.x = cw - w - cfg.right;
|
|
663
|
+
if (cfg.top !== undefined) child.y = cfg.top;
|
|
664
|
+
else if (cfg.bottom !== undefined) child.y = ch - h - cfg.bottom;
|
|
504
665
|
}
|
|
666
|
+
}
|
|
505
667
|
|
|
506
|
-
|
|
507
|
-
|
|
668
|
+
/** Computed content size (after layout) */
|
|
669
|
+
getContentSize(): { width: number; height: number } {
|
|
670
|
+
if (this._layoutDirty) this.updateLayout();
|
|
671
|
+
return { width: this._computedWidth, height: this._computedHeight };
|
|
508
672
|
}
|
|
509
673
|
|
|
510
674
|
/** React reconciler update hook — applies changed config props */
|
|
@@ -513,11 +677,27 @@ export class FlexContainer extends Container {
|
|
|
513
677
|
if ('justifyContent' in changed) this.setJustifyContent(changed.justifyContent);
|
|
514
678
|
if ('alignItems' in changed) this.setAlignItems(changed.alignItems);
|
|
515
679
|
if ('gap' in changed) this.setGap(changed.gap);
|
|
516
|
-
if ('padding' in changed
|
|
680
|
+
if ('padding' in changed || 'paddingTop' in changed || 'paddingRight' in changed || 'paddingBottom' in changed || 'paddingLeft' in changed) {
|
|
681
|
+
this._padding = resolvePadding(changed as FlexContainerConfig);
|
|
682
|
+
this._layoutDirty = true;
|
|
683
|
+
}
|
|
517
684
|
if ('flexWrap' in changed) { this._config.flexWrap = changed.flexWrap; this._layoutDirty = true; }
|
|
685
|
+
if ('alignContent' in changed) { this._config.alignContent = changed.alignContent; this._layoutDirty = true; }
|
|
518
686
|
if ('width' in changed || 'height' in changed) {
|
|
519
|
-
|
|
520
|
-
|
|
687
|
+
const w = changed.width ?? this._rawWidth;
|
|
688
|
+
const h = changed.height ?? this._rawHeight;
|
|
689
|
+
this._rawWidth = w;
|
|
690
|
+
this._rawHeight = h;
|
|
691
|
+
if (typeof w === 'number' && typeof h === 'number') {
|
|
692
|
+
this.resize(w, h);
|
|
693
|
+
} else {
|
|
694
|
+
// Percentage — will resolve in updateLayout
|
|
695
|
+
this._explicitWidth = typeof w === 'number' ? w : 0;
|
|
696
|
+
this._explicitHeight = typeof h === 'number' ? h : 0;
|
|
697
|
+
this._layoutDirty = true;
|
|
698
|
+
this.updateLayout();
|
|
699
|
+
}
|
|
700
|
+
return;
|
|
521
701
|
}
|
|
522
702
|
if (this._layoutDirty) this.updateLayout();
|
|
523
703
|
}
|
package/src/ui/index.ts
CHANGED
|
@@ -4,7 +4,7 @@ export type { ViewInput } from './view';
|
|
|
4
4
|
|
|
5
5
|
// ─── Engine UI Components ─────────────────────────────────
|
|
6
6
|
export { FlexContainer } from './FlexContainer';
|
|
7
|
-
export type { FlexContainerConfig, FlexDirection, JustifyContent, AlignItems, AlignSelf, FlexItemConfig } from './FlexContainer';
|
|
7
|
+
export type { FlexContainerConfig, FlexDirection, JustifyContent, AlignItems, AlignSelf, AlignContent, FlexItemConfig } from './FlexContainer';
|
|
8
8
|
export { Button } from './Button';
|
|
9
9
|
export type { ButtonConfig, ButtonState } from './Button';
|
|
10
10
|
export { ProgressBar } from './ProgressBar';
|