@energy8platform/game-engine 0.11.0 → 0.13.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 +88 -1
- package/bin/simulate.ts +45 -5
- package/dist/index.cjs.js +49 -7
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +7 -0
- package/dist/index.esm.js +49 -7
- 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 +667 -215
- package/dist/react.cjs.js.map +1 -1
- package/dist/react.esm.js +667 -215
- package/dist/react.esm.js.map +1 -1
- package/dist/ui.cjs.js +407 -7
- package/dist/ui.cjs.js.map +1 -1
- package/dist/ui.d.ts +156 -2
- package/dist/ui.esm.js +406 -8
- 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/applyProps.ts +6 -4
- package/src/react/extendAll.ts +2 -0
- package/src/react/jsx.d.ts +28 -1
- package/src/react/reconciler.ts +58 -2
- package/src/ui/FlexContainer.ts +57 -7
- package/src/ui/Slider.ts +241 -0
- package/src/ui/Toggle.ts +201 -0
- package/src/ui/index.ts +5 -1
package/dist/react.cjs.js
CHANGED
|
@@ -23,6 +23,8 @@ function extend(components) {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
const RESERVED = new Set(['children', 'key', 'ref']);
|
|
26
|
+
/** Props handled by the reconciler as flex item config, not forwarded to components */
|
|
27
|
+
const FLEX_ITEM_PROPS$1 = new Set(['flexGrow', 'flexShrink', 'layoutWidth', 'layoutHeight', 'alignSelf', 'flexExclude']);
|
|
26
28
|
// ─── UI Component helpers ────────────────────────────────
|
|
27
29
|
/**
|
|
28
30
|
* Extract a config object from React props.
|
|
@@ -32,7 +34,7 @@ const RESERVED = new Set(['children', 'key', 'ref']);
|
|
|
32
34
|
function extractConfig(props) {
|
|
33
35
|
const config = {};
|
|
34
36
|
for (const key in props) {
|
|
35
|
-
if (RESERVED.has(key) || isEventProp(key))
|
|
37
|
+
if (RESERVED.has(key) || FLEX_ITEM_PROPS$1.has(key) || isEventProp(key))
|
|
36
38
|
continue;
|
|
37
39
|
if (key.includes('-')) {
|
|
38
40
|
const parts = key.split('-');
|
|
@@ -57,7 +59,7 @@ function diffConfig(newProps, oldProps) {
|
|
|
57
59
|
const changed = {};
|
|
58
60
|
// New or changed props
|
|
59
61
|
for (const key in newProps) {
|
|
60
|
-
if (RESERVED.has(key) || isEventProp(key))
|
|
62
|
+
if (RESERVED.has(key) || FLEX_ITEM_PROPS$1.has(key) || isEventProp(key))
|
|
61
63
|
continue;
|
|
62
64
|
if (newProps[key] !== oldProps[key]) {
|
|
63
65
|
if (key.includes('-')) {
|
|
@@ -156,7 +158,7 @@ function setNestedValue(target, path, value) {
|
|
|
156
158
|
function applyProps(instance, newProps, oldProps = {}) {
|
|
157
159
|
// Remove old props not in newProps
|
|
158
160
|
for (const key in oldProps) {
|
|
159
|
-
if (RESERVED.has(key) || key in newProps)
|
|
161
|
+
if (RESERVED.has(key) || FLEX_ITEM_PROPS$1.has(key) || key in newProps)
|
|
160
162
|
continue;
|
|
161
163
|
const pixiEvent = REACT_TO_PIXI_EVENTS[key];
|
|
162
164
|
if (pixiEvent) {
|
|
@@ -175,7 +177,7 @@ function applyProps(instance, newProps, oldProps = {}) {
|
|
|
175
177
|
}
|
|
176
178
|
// Apply new props
|
|
177
179
|
for (const key in newProps) {
|
|
178
|
-
if (RESERVED.has(key))
|
|
180
|
+
if (RESERVED.has(key) || FLEX_ITEM_PROPS$1.has(key))
|
|
179
181
|
continue;
|
|
180
182
|
const value = newProps[key];
|
|
181
183
|
const pixiEvent = REACT_TO_PIXI_EVENTS[key];
|
|
@@ -201,182 +203,6 @@ function applyProps(instance, newProps, oldProps = {}) {
|
|
|
201
203
|
}
|
|
202
204
|
}
|
|
203
205
|
|
|
204
|
-
function toPascalCase(str) {
|
|
205
|
-
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
206
|
-
}
|
|
207
|
-
const hostConfig = {
|
|
208
|
-
isPrimaryRenderer: false,
|
|
209
|
-
supportsMutation: true,
|
|
210
|
-
supportsPersistence: false,
|
|
211
|
-
supportsHydration: false,
|
|
212
|
-
createInstance(type, props) {
|
|
213
|
-
const name = toPascalCase(type);
|
|
214
|
-
const Ctor = catalogue[name];
|
|
215
|
-
if (!Ctor) {
|
|
216
|
-
throw new Error(`[PixiReconciler] Unknown element "<${type}>". ` +
|
|
217
|
-
`Call extend({ ${name} }) before rendering.`);
|
|
218
|
-
}
|
|
219
|
-
let instance;
|
|
220
|
-
if (Ctor.prototype.__uiComponent) {
|
|
221
|
-
// Config-based UI component: pass props as constructor config
|
|
222
|
-
const config = extractConfig(props);
|
|
223
|
-
instance = new Ctor(config);
|
|
224
|
-
applyEventProps(instance, props);
|
|
225
|
-
}
|
|
226
|
-
else {
|
|
227
|
-
// Standard PixiJS element
|
|
228
|
-
instance = new Ctor();
|
|
229
|
-
applyProps(instance, props);
|
|
230
|
-
}
|
|
231
|
-
if (hasEventProps(props) && instance.eventMode === 'auto') {
|
|
232
|
-
instance.eventMode = 'static';
|
|
233
|
-
}
|
|
234
|
-
return instance;
|
|
235
|
-
},
|
|
236
|
-
createTextInstance() {
|
|
237
|
-
throw new Error('[PixiReconciler] Text strings are not supported. Use a <text> element.');
|
|
238
|
-
},
|
|
239
|
-
appendInitialChild(parent, child) {
|
|
240
|
-
if (child instanceof pixi_js.Container)
|
|
241
|
-
parent.addChild(child);
|
|
242
|
-
},
|
|
243
|
-
appendChild(parent, child) {
|
|
244
|
-
if (child instanceof pixi_js.Container)
|
|
245
|
-
parent.addChild(child);
|
|
246
|
-
},
|
|
247
|
-
appendChildToContainer(container, child) {
|
|
248
|
-
if (child instanceof pixi_js.Container)
|
|
249
|
-
container.addChild(child);
|
|
250
|
-
},
|
|
251
|
-
removeChild(parent, child) {
|
|
252
|
-
if (child instanceof pixi_js.Container) {
|
|
253
|
-
parent.removeChild(child);
|
|
254
|
-
child.destroy({ children: true });
|
|
255
|
-
}
|
|
256
|
-
},
|
|
257
|
-
removeChildFromContainer(container, child) {
|
|
258
|
-
if (child instanceof pixi_js.Container) {
|
|
259
|
-
container.removeChild(child);
|
|
260
|
-
child.destroy({ children: true });
|
|
261
|
-
}
|
|
262
|
-
},
|
|
263
|
-
insertBefore(parent, child, beforeChild) {
|
|
264
|
-
if (child instanceof pixi_js.Container && beforeChild instanceof pixi_js.Container) {
|
|
265
|
-
if (child.parent)
|
|
266
|
-
child.parent.removeChild(child);
|
|
267
|
-
const index = parent.getChildIndex(beforeChild);
|
|
268
|
-
parent.addChildAt(child, index);
|
|
269
|
-
}
|
|
270
|
-
},
|
|
271
|
-
insertInContainerBefore(container, child, beforeChild) {
|
|
272
|
-
if (child instanceof pixi_js.Container && beforeChild instanceof pixi_js.Container) {
|
|
273
|
-
if (child.parent)
|
|
274
|
-
child.parent.removeChild(child);
|
|
275
|
-
const index = container.getChildIndex(beforeChild);
|
|
276
|
-
container.addChildAt(child, index);
|
|
277
|
-
}
|
|
278
|
-
},
|
|
279
|
-
commitUpdate(instance, _updatePayload, _type, oldProps, newProps) {
|
|
280
|
-
if (instance.__uiComponent && typeof instance.updateConfig === 'function') {
|
|
281
|
-
const changed = diffConfig(newProps, oldProps);
|
|
282
|
-
if (Object.keys(changed).length > 0) {
|
|
283
|
-
instance.updateConfig(changed);
|
|
284
|
-
}
|
|
285
|
-
applyEventProps(instance, newProps, oldProps);
|
|
286
|
-
}
|
|
287
|
-
else {
|
|
288
|
-
applyProps(instance, newProps, oldProps);
|
|
289
|
-
}
|
|
290
|
-
if (hasEventProps(newProps) && instance.eventMode === 'auto') {
|
|
291
|
-
instance.eventMode = 'static';
|
|
292
|
-
}
|
|
293
|
-
},
|
|
294
|
-
finalizeInitialChildren() {
|
|
295
|
-
return false;
|
|
296
|
-
},
|
|
297
|
-
prepareUpdate() {
|
|
298
|
-
return true;
|
|
299
|
-
},
|
|
300
|
-
shouldSetTextContent() {
|
|
301
|
-
return false;
|
|
302
|
-
},
|
|
303
|
-
getRootHostContext() {
|
|
304
|
-
return null;
|
|
305
|
-
},
|
|
306
|
-
getChildHostContext(parentHostContext) {
|
|
307
|
-
return parentHostContext;
|
|
308
|
-
},
|
|
309
|
-
getPublicInstance(instance) {
|
|
310
|
-
return instance;
|
|
311
|
-
},
|
|
312
|
-
prepareForCommit() {
|
|
313
|
-
return null;
|
|
314
|
-
},
|
|
315
|
-
resetAfterCommit() { },
|
|
316
|
-
preparePortalMount() { },
|
|
317
|
-
scheduleTimeout: setTimeout,
|
|
318
|
-
cancelTimeout: clearTimeout,
|
|
319
|
-
noTimeout: -1,
|
|
320
|
-
getCurrentEventPriority() {
|
|
321
|
-
return constants.DefaultEventPriority;
|
|
322
|
-
},
|
|
323
|
-
hideInstance(instance) {
|
|
324
|
-
instance.visible = false;
|
|
325
|
-
},
|
|
326
|
-
unhideInstance(instance) {
|
|
327
|
-
instance.visible = true;
|
|
328
|
-
},
|
|
329
|
-
hideTextInstance() { },
|
|
330
|
-
unhideTextInstance() { },
|
|
331
|
-
clearContainer() { },
|
|
332
|
-
detachDeletedInstance() { },
|
|
333
|
-
prepareScopeUpdate() { },
|
|
334
|
-
getInstanceFromNode() { return null; },
|
|
335
|
-
getInstanceFromScope() { return null; },
|
|
336
|
-
beforeActiveInstanceBlur() { },
|
|
337
|
-
afterActiveInstanceBlur() { },
|
|
338
|
-
};
|
|
339
|
-
const reconciler = Reconciler(hostConfig);
|
|
340
|
-
|
|
341
|
-
function createPixiRoot(container) {
|
|
342
|
-
const fiberRoot = reconciler.createContainer(container, // containerInfo
|
|
343
|
-
constants.ConcurrentRoot, // tag
|
|
344
|
-
null, // hydrationCallbacks
|
|
345
|
-
false, // isStrictMode
|
|
346
|
-
null, // concurrentUpdatesByDefaultOverride
|
|
347
|
-
'', // identifierPrefix
|
|
348
|
-
(err) => console.error('[PixiRoot]', err), null);
|
|
349
|
-
return {
|
|
350
|
-
render(element) {
|
|
351
|
-
reconciler.updateContainer(element, fiberRoot, null, () => { });
|
|
352
|
-
},
|
|
353
|
-
unmount() {
|
|
354
|
-
reconciler.updateContainer(null, fiberRoot, null, () => { });
|
|
355
|
-
},
|
|
356
|
-
};
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
/**
|
|
360
|
-
* Resolve a ViewInput to a Container instance.
|
|
361
|
-
*
|
|
362
|
-
* @example
|
|
363
|
-
* ```ts
|
|
364
|
-
* resolveView('btn-idle') // → Sprite.from('btn-idle')
|
|
365
|
-
* resolveView(someTexture) // → new Sprite(someTexture)
|
|
366
|
-
* resolveView(myCustomContainer) // → myCustomContainer (as-is)
|
|
367
|
-
* resolveView(undefined) // → null
|
|
368
|
-
* ```
|
|
369
|
-
*/
|
|
370
|
-
function resolveView(input) {
|
|
371
|
-
if (input == null)
|
|
372
|
-
return null;
|
|
373
|
-
if (typeof input === 'string')
|
|
374
|
-
return pixi_js.Sprite.from(input);
|
|
375
|
-
if (input instanceof pixi_js.Texture)
|
|
376
|
-
return new pixi_js.Sprite(input);
|
|
377
|
-
return input;
|
|
378
|
-
}
|
|
379
|
-
|
|
380
206
|
// ─── Helpers ─────────────────────────────────────────────
|
|
381
207
|
function normalizePadding(p) {
|
|
382
208
|
return typeof p === 'number' ? [p, p, p, p] : p;
|
|
@@ -435,6 +261,37 @@ function layoutLine(items, isRow, mainSize, justify, align, gap, crossOffset, cr
|
|
|
435
261
|
}
|
|
436
262
|
}
|
|
437
263
|
}
|
|
264
|
+
// Shrink: if content overflows and mainSize is finite, shrink eligible items
|
|
265
|
+
if (totalGrow === 0 && mainSize > 0) {
|
|
266
|
+
const overflow = totalFixed + totalGap - mainSize;
|
|
267
|
+
if (overflow > 0) {
|
|
268
|
+
let totalShrinkable = 0;
|
|
269
|
+
for (const item of items) {
|
|
270
|
+
const shrink = item.child._flexConfig?.flexShrink ?? 1;
|
|
271
|
+
if (shrink > 0) {
|
|
272
|
+
totalShrinkable += isRow ? item.w : item.h;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
if (totalShrinkable > 0) {
|
|
276
|
+
for (const item of items) {
|
|
277
|
+
const shrink = item.child._flexConfig?.flexShrink ?? 1;
|
|
278
|
+
if (shrink > 0) {
|
|
279
|
+
const itemMain = isRow ? item.w : item.h;
|
|
280
|
+
const reduction = overflow * (itemMain / totalShrinkable);
|
|
281
|
+
const newSize = Math.max(0, itemMain - reduction);
|
|
282
|
+
if (isRow) {
|
|
283
|
+
item.w = newSize;
|
|
284
|
+
item.child.width = newSize;
|
|
285
|
+
}
|
|
286
|
+
else {
|
|
287
|
+
item.h = newSize;
|
|
288
|
+
item.child.height = newSize;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
438
295
|
// Calculate total main size after flex
|
|
439
296
|
let totalMain = totalGap;
|
|
440
297
|
for (const item of items) {
|
|
@@ -471,9 +328,12 @@ function layoutLine(items, isRow, mainSize, justify, align, gap, crossOffset, cr
|
|
|
471
328
|
for (const item of items) {
|
|
472
329
|
const mainDim = isRow ? item.w : item.h;
|
|
473
330
|
const crossDim = isRow ? item.h : item.w;
|
|
474
|
-
// Cross-axis alignment
|
|
331
|
+
// Cross-axis alignment (alignSelf overrides align)
|
|
332
|
+
const effectiveAlign = (item.child._flexConfig?.alignSelf && item.child._flexConfig.alignSelf !== 'auto')
|
|
333
|
+
? item.child._flexConfig.alignSelf
|
|
334
|
+
: align;
|
|
475
335
|
let crossPos = crossOffset;
|
|
476
|
-
switch (
|
|
336
|
+
switch (effectiveAlign) {
|
|
477
337
|
case 'start':
|
|
478
338
|
break;
|
|
479
339
|
case 'center':
|
|
@@ -657,11 +517,14 @@ class FlexContainer extends pixi_js.Container {
|
|
|
657
517
|
const contentH = this._explicitHeight > 0 ? this._explicitHeight - pt - pb : Infinity;
|
|
658
518
|
const mainLimit = isRow ? contentW : contentH;
|
|
659
519
|
const crossLimit = isRow ? contentH : contentW;
|
|
660
|
-
// Measure children
|
|
661
|
-
const measured =
|
|
520
|
+
// Measure children (skip flexExclude — they position themselves)
|
|
521
|
+
const measured = [];
|
|
522
|
+
for (const child of this._layoutChildren) {
|
|
523
|
+
if (child._flexConfig?.flexExclude)
|
|
524
|
+
continue;
|
|
662
525
|
const { w, h, ox, oy } = measureChild(child);
|
|
663
|
-
|
|
664
|
-
}
|
|
526
|
+
measured.push({ child, w, h, ox, oy });
|
|
527
|
+
}
|
|
665
528
|
// Split into lines (if wrapping)
|
|
666
529
|
const lines = [];
|
|
667
530
|
if (flexWrap && mainLimit < Infinity) {
|
|
@@ -704,7 +567,12 @@ class FlexContainer extends pixi_js.Container {
|
|
|
704
567
|
const mainStart = isRow ? pl : pt;
|
|
705
568
|
// Offset items by padding
|
|
706
569
|
const tempItems = line.map((item) => ({ ...item }));
|
|
707
|
-
|
|
570
|
+
// For single-line layouts, use the full available cross space for alignment;
|
|
571
|
+
// for multi-line (wrapping), each line gets its own measured cross size.
|
|
572
|
+
const effectiveCross = lines.length === 1 && crossLimit < Infinity
|
|
573
|
+
? crossLimit
|
|
574
|
+
: (crossLimit < Infinity ? Math.min(lineCross, crossLimit) : lineCross);
|
|
575
|
+
layoutLine(tempItems, isRow, mainLimit < Infinity ? mainLimit : 0, mainLimit < Infinity ? justifyContent : 'start', alignItems, gap, crossOffset, effectiveCross);
|
|
708
576
|
// Apply main-axis padding offset
|
|
709
577
|
for (const item of tempItems) {
|
|
710
578
|
const origChild = line.find((l) => l.child === item.child);
|
|
@@ -725,36 +593,263 @@ class FlexContainer extends pixi_js.Container {
|
|
|
725
593
|
maxX = Math.max(maxX, child.x + w);
|
|
726
594
|
maxY = Math.max(maxY, child.y + h);
|
|
727
595
|
}
|
|
728
|
-
const [, pr, pb] = this._padding;
|
|
729
|
-
return { width: maxX + pr, height: maxY + pb };
|
|
730
|
-
}
|
|
731
|
-
/** React reconciler update hook — applies changed config props */
|
|
732
|
-
updateConfig(changed) {
|
|
733
|
-
if ('direction' in changed)
|
|
734
|
-
this.setDirection(changed.direction);
|
|
735
|
-
if ('justifyContent' in changed)
|
|
736
|
-
this.setJustifyContent(changed.justifyContent);
|
|
737
|
-
if ('alignItems' in changed)
|
|
738
|
-
this.setAlignItems(changed.alignItems);
|
|
739
|
-
if ('gap' in changed)
|
|
740
|
-
this.setGap(changed.gap);
|
|
741
|
-
if ('padding' in changed)
|
|
742
|
-
this.setPadding(changed.padding);
|
|
743
|
-
if ('flexWrap' in changed) {
|
|
744
|
-
this._config.flexWrap = changed.flexWrap;
|
|
745
|
-
this._layoutDirty = true;
|
|
596
|
+
const [, pr, pb] = this._padding;
|
|
597
|
+
return { width: maxX + pr, height: maxY + pb };
|
|
598
|
+
}
|
|
599
|
+
/** React reconciler update hook — applies changed config props */
|
|
600
|
+
updateConfig(changed) {
|
|
601
|
+
if ('direction' in changed)
|
|
602
|
+
this.setDirection(changed.direction);
|
|
603
|
+
if ('justifyContent' in changed)
|
|
604
|
+
this.setJustifyContent(changed.justifyContent);
|
|
605
|
+
if ('alignItems' in changed)
|
|
606
|
+
this.setAlignItems(changed.alignItems);
|
|
607
|
+
if ('gap' in changed)
|
|
608
|
+
this.setGap(changed.gap);
|
|
609
|
+
if ('padding' in changed)
|
|
610
|
+
this.setPadding(changed.padding);
|
|
611
|
+
if ('flexWrap' in changed) {
|
|
612
|
+
this._config.flexWrap = changed.flexWrap;
|
|
613
|
+
this._layoutDirty = true;
|
|
614
|
+
}
|
|
615
|
+
if ('width' in changed || 'height' in changed) {
|
|
616
|
+
this.resize(changed.width ?? this._explicitWidth, changed.height ?? this._explicitHeight);
|
|
617
|
+
return; // resize calls updateLayout
|
|
618
|
+
}
|
|
619
|
+
if (this._layoutDirty)
|
|
620
|
+
this.updateLayout();
|
|
621
|
+
}
|
|
622
|
+
destroy(options) {
|
|
623
|
+
this._layoutChildren.length = 0;
|
|
624
|
+
super.destroy(options);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
/** Flex item prop names that should be forwarded to _flexConfig on the child */
|
|
629
|
+
const FLEX_ITEM_PROPS = ['flexGrow', 'flexShrink', 'layoutWidth', 'layoutHeight', 'alignSelf', 'flexExclude'];
|
|
630
|
+
/** Extract FlexItemConfig from props if any flex item props are present */
|
|
631
|
+
function extractFlexItemConfig(props) {
|
|
632
|
+
let config;
|
|
633
|
+
for (const key of FLEX_ITEM_PROPS) {
|
|
634
|
+
if (key in props) {
|
|
635
|
+
if (!config)
|
|
636
|
+
config = {};
|
|
637
|
+
config[key] = props[key];
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
return config;
|
|
641
|
+
}
|
|
642
|
+
/** Apply flex item config to a child being added to a FlexContainer */
|
|
643
|
+
function addChildToFlex(parent, child) {
|
|
644
|
+
const flexConfig = child._flexConfig;
|
|
645
|
+
if (flexConfig && Object.keys(flexConfig).length > 0) {
|
|
646
|
+
parent.addFlexChild(child, flexConfig);
|
|
647
|
+
}
|
|
648
|
+
else {
|
|
649
|
+
parent.addChild(child);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
function toPascalCase(str) {
|
|
653
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
654
|
+
}
|
|
655
|
+
const hostConfig = {
|
|
656
|
+
isPrimaryRenderer: false,
|
|
657
|
+
supportsMutation: true,
|
|
658
|
+
supportsPersistence: false,
|
|
659
|
+
supportsHydration: false,
|
|
660
|
+
createInstance(type, props) {
|
|
661
|
+
const name = toPascalCase(type);
|
|
662
|
+
const Ctor = catalogue[name];
|
|
663
|
+
if (!Ctor) {
|
|
664
|
+
throw new Error(`[PixiReconciler] Unknown element "<${type}>". ` +
|
|
665
|
+
`Call extend({ ${name} }) before rendering.`);
|
|
666
|
+
}
|
|
667
|
+
let instance;
|
|
668
|
+
if (Ctor.prototype.__uiComponent) {
|
|
669
|
+
// Config-based UI component: pass props as constructor config
|
|
670
|
+
const config = extractConfig(props);
|
|
671
|
+
instance = new Ctor(config);
|
|
672
|
+
applyEventProps(instance, props);
|
|
673
|
+
}
|
|
674
|
+
else {
|
|
675
|
+
// Standard PixiJS element
|
|
676
|
+
instance = new Ctor();
|
|
677
|
+
applyProps(instance, props);
|
|
678
|
+
}
|
|
679
|
+
if (hasEventProps(props) && instance.eventMode === 'auto') {
|
|
680
|
+
instance.eventMode = 'static';
|
|
681
|
+
}
|
|
682
|
+
// Store flex item config for when this child is added to a FlexContainer parent
|
|
683
|
+
const flexItemConfig = extractFlexItemConfig(props);
|
|
684
|
+
if (flexItemConfig) {
|
|
685
|
+
instance._flexConfig = { ...instance._flexConfig, ...flexItemConfig };
|
|
686
|
+
}
|
|
687
|
+
return instance;
|
|
688
|
+
},
|
|
689
|
+
createTextInstance() {
|
|
690
|
+
throw new Error('[PixiReconciler] Text strings are not supported. Use a <text> element.');
|
|
691
|
+
},
|
|
692
|
+
appendInitialChild(parent, child) {
|
|
693
|
+
if (child instanceof pixi_js.Container) {
|
|
694
|
+
if (parent instanceof FlexContainer) {
|
|
695
|
+
addChildToFlex(parent, child);
|
|
696
|
+
}
|
|
697
|
+
else {
|
|
698
|
+
parent.addChild(child);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
},
|
|
702
|
+
appendChild(parent, child) {
|
|
703
|
+
if (child instanceof pixi_js.Container) {
|
|
704
|
+
if (parent instanceof FlexContainer) {
|
|
705
|
+
addChildToFlex(parent, child);
|
|
706
|
+
}
|
|
707
|
+
else {
|
|
708
|
+
parent.addChild(child);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
},
|
|
712
|
+
appendChildToContainer(container, child) {
|
|
713
|
+
if (child instanceof pixi_js.Container)
|
|
714
|
+
container.addChild(child);
|
|
715
|
+
},
|
|
716
|
+
removeChild(parent, child) {
|
|
717
|
+
if (child instanceof pixi_js.Container) {
|
|
718
|
+
parent.removeChild(child);
|
|
719
|
+
child.destroy({ children: true });
|
|
720
|
+
}
|
|
721
|
+
},
|
|
722
|
+
removeChildFromContainer(container, child) {
|
|
723
|
+
if (child instanceof pixi_js.Container) {
|
|
724
|
+
container.removeChild(child);
|
|
725
|
+
child.destroy({ children: true });
|
|
726
|
+
}
|
|
727
|
+
},
|
|
728
|
+
insertBefore(parent, child, beforeChild) {
|
|
729
|
+
if (child instanceof pixi_js.Container && beforeChild instanceof pixi_js.Container) {
|
|
730
|
+
if (child.parent)
|
|
731
|
+
child.parent.removeChild(child);
|
|
732
|
+
const index = parent.getChildIndex(beforeChild);
|
|
733
|
+
parent.addChildAt(child, index);
|
|
734
|
+
}
|
|
735
|
+
},
|
|
736
|
+
insertInContainerBefore(container, child, beforeChild) {
|
|
737
|
+
if (child instanceof pixi_js.Container && beforeChild instanceof pixi_js.Container) {
|
|
738
|
+
if (child.parent)
|
|
739
|
+
child.parent.removeChild(child);
|
|
740
|
+
const index = container.getChildIndex(beforeChild);
|
|
741
|
+
container.addChildAt(child, index);
|
|
742
|
+
}
|
|
743
|
+
},
|
|
744
|
+
commitUpdate(instance, _updatePayload, _type, oldProps, newProps) {
|
|
745
|
+
if (instance.__uiComponent && typeof instance.updateConfig === 'function') {
|
|
746
|
+
const changed = diffConfig(newProps, oldProps);
|
|
747
|
+
if (Object.keys(changed).length > 0) {
|
|
748
|
+
instance.updateConfig(changed);
|
|
749
|
+
}
|
|
750
|
+
applyEventProps(instance, newProps, oldProps);
|
|
751
|
+
}
|
|
752
|
+
else {
|
|
753
|
+
applyProps(instance, newProps, oldProps);
|
|
754
|
+
}
|
|
755
|
+
// Update flex item config if parent is FlexContainer
|
|
756
|
+
const newFlexConfig = extractFlexItemConfig(newProps);
|
|
757
|
+
const oldFlexConfig = extractFlexItemConfig(oldProps);
|
|
758
|
+
if (newFlexConfig || oldFlexConfig) {
|
|
759
|
+
instance._flexConfig = { ...instance._flexConfig, ...newFlexConfig };
|
|
760
|
+
// Trigger parent relayout
|
|
761
|
+
if (instance.parent instanceof FlexContainer) {
|
|
762
|
+
instance.parent.updateLayout();
|
|
763
|
+
}
|
|
746
764
|
}
|
|
747
|
-
if (
|
|
748
|
-
|
|
749
|
-
return; // resize calls updateLayout
|
|
765
|
+
if (hasEventProps(newProps) && instance.eventMode === 'auto') {
|
|
766
|
+
instance.eventMode = 'static';
|
|
750
767
|
}
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
}
|
|
768
|
+
},
|
|
769
|
+
finalizeInitialChildren() {
|
|
770
|
+
return false;
|
|
771
|
+
},
|
|
772
|
+
prepareUpdate() {
|
|
773
|
+
return true;
|
|
774
|
+
},
|
|
775
|
+
shouldSetTextContent() {
|
|
776
|
+
return false;
|
|
777
|
+
},
|
|
778
|
+
getRootHostContext() {
|
|
779
|
+
return null;
|
|
780
|
+
},
|
|
781
|
+
getChildHostContext(parentHostContext) {
|
|
782
|
+
return parentHostContext;
|
|
783
|
+
},
|
|
784
|
+
getPublicInstance(instance) {
|
|
785
|
+
return instance;
|
|
786
|
+
},
|
|
787
|
+
prepareForCommit() {
|
|
788
|
+
return null;
|
|
789
|
+
},
|
|
790
|
+
resetAfterCommit() { },
|
|
791
|
+
preparePortalMount() { },
|
|
792
|
+
scheduleTimeout: setTimeout,
|
|
793
|
+
cancelTimeout: clearTimeout,
|
|
794
|
+
noTimeout: -1,
|
|
795
|
+
getCurrentEventPriority() {
|
|
796
|
+
return constants.DefaultEventPriority;
|
|
797
|
+
},
|
|
798
|
+
hideInstance(instance) {
|
|
799
|
+
instance.visible = false;
|
|
800
|
+
},
|
|
801
|
+
unhideInstance(instance) {
|
|
802
|
+
instance.visible = true;
|
|
803
|
+
},
|
|
804
|
+
hideTextInstance() { },
|
|
805
|
+
unhideTextInstance() { },
|
|
806
|
+
clearContainer() { },
|
|
807
|
+
detachDeletedInstance() { },
|
|
808
|
+
prepareScopeUpdate() { },
|
|
809
|
+
getInstanceFromNode() { return null; },
|
|
810
|
+
getInstanceFromScope() { return null; },
|
|
811
|
+
beforeActiveInstanceBlur() { },
|
|
812
|
+
afterActiveInstanceBlur() { },
|
|
813
|
+
};
|
|
814
|
+
const reconciler = Reconciler(hostConfig);
|
|
815
|
+
|
|
816
|
+
function createPixiRoot(container) {
|
|
817
|
+
const fiberRoot = reconciler.createContainer(container, // containerInfo
|
|
818
|
+
constants.ConcurrentRoot, // tag
|
|
819
|
+
null, // hydrationCallbacks
|
|
820
|
+
false, // isStrictMode
|
|
821
|
+
null, // concurrentUpdatesByDefaultOverride
|
|
822
|
+
'', // identifierPrefix
|
|
823
|
+
(err) => console.error('[PixiRoot]', err), null);
|
|
824
|
+
return {
|
|
825
|
+
render(element) {
|
|
826
|
+
reconciler.updateContainer(element, fiberRoot, null, () => { });
|
|
827
|
+
},
|
|
828
|
+
unmount() {
|
|
829
|
+
reconciler.updateContainer(null, fiberRoot, null, () => { });
|
|
830
|
+
},
|
|
831
|
+
};
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
/**
|
|
835
|
+
* Resolve a ViewInput to a Container instance.
|
|
836
|
+
*
|
|
837
|
+
* @example
|
|
838
|
+
* ```ts
|
|
839
|
+
* resolveView('btn-idle') // → Sprite.from('btn-idle')
|
|
840
|
+
* resolveView(someTexture) // → new Sprite(someTexture)
|
|
841
|
+
* resolveView(myCustomContainer) // → myCustomContainer (as-is)
|
|
842
|
+
* resolveView(undefined) // → null
|
|
843
|
+
* ```
|
|
844
|
+
*/
|
|
845
|
+
function resolveView(input) {
|
|
846
|
+
if (input == null)
|
|
847
|
+
return null;
|
|
848
|
+
if (typeof input === 'string')
|
|
849
|
+
return pixi_js.Sprite.from(input);
|
|
850
|
+
if (input instanceof pixi_js.Texture)
|
|
851
|
+
return new pixi_js.Sprite(input);
|
|
852
|
+
return input;
|
|
758
853
|
}
|
|
759
854
|
|
|
760
855
|
/**
|
|
@@ -2606,6 +2701,362 @@ class ScrollContainer extends pixi_js.Container {
|
|
|
2606
2701
|
}
|
|
2607
2702
|
}
|
|
2608
2703
|
|
|
2704
|
+
/**
|
|
2705
|
+
* Draggable slider with customizable track, fill, and handle views.
|
|
2706
|
+
*
|
|
2707
|
+
* @example
|
|
2708
|
+
* ```ts
|
|
2709
|
+
* const volume = new Slider({
|
|
2710
|
+
* min: 0, max: 1, value: 0.5,
|
|
2711
|
+
* width: 200, height: 8,
|
|
2712
|
+
* fillColor: 0xffd700,
|
|
2713
|
+
* onUpdate: (v) => console.log('Volume:', v),
|
|
2714
|
+
* });
|
|
2715
|
+
* ```
|
|
2716
|
+
*/
|
|
2717
|
+
class Slider extends pixi_js.Container {
|
|
2718
|
+
__uiComponent = true;
|
|
2719
|
+
_track;
|
|
2720
|
+
_fill;
|
|
2721
|
+
_fillMask;
|
|
2722
|
+
_handle;
|
|
2723
|
+
_config;
|
|
2724
|
+
_value;
|
|
2725
|
+
_dragging = false;
|
|
2726
|
+
onUpdate = null;
|
|
2727
|
+
onChange = null;
|
|
2728
|
+
constructor(config = {}) {
|
|
2729
|
+
super();
|
|
2730
|
+
this._config = {
|
|
2731
|
+
min: config.min ?? 0,
|
|
2732
|
+
max: config.max ?? 1,
|
|
2733
|
+
step: config.step ?? 0,
|
|
2734
|
+
width: config.width ?? 200,
|
|
2735
|
+
height: config.height ?? 8,
|
|
2736
|
+
borderRadius: config.borderRadius ?? 4,
|
|
2737
|
+
trackColor: config.trackColor ?? 0x333333,
|
|
2738
|
+
fillColor: config.fillColor ?? 0xffd700,
|
|
2739
|
+
handleRadius: config.handleRadius ?? 12,
|
|
2740
|
+
handleColor: config.handleColor ?? 0xffffff,
|
|
2741
|
+
};
|
|
2742
|
+
this._value = config.value ?? this._config.min;
|
|
2743
|
+
this.onUpdate = config.onUpdate ?? null;
|
|
2744
|
+
this.onChange = config.onChange ?? null;
|
|
2745
|
+
const { width, height, borderRadius, trackColor, fillColor, handleRadius, handleColor } = this._config;
|
|
2746
|
+
// Track
|
|
2747
|
+
const customTrack = resolveView(config.trackView);
|
|
2748
|
+
if (customTrack) {
|
|
2749
|
+
customTrack.width = width;
|
|
2750
|
+
customTrack.height = height;
|
|
2751
|
+
this._track = customTrack;
|
|
2752
|
+
}
|
|
2753
|
+
else {
|
|
2754
|
+
const g = new pixi_js.Graphics();
|
|
2755
|
+
g.roundRect(0, 0, width, height, borderRadius).fill(trackColor);
|
|
2756
|
+
this._track = g;
|
|
2757
|
+
}
|
|
2758
|
+
this.addChild(this._track);
|
|
2759
|
+
// Fill
|
|
2760
|
+
const customFill = resolveView(config.fillView);
|
|
2761
|
+
if (customFill) {
|
|
2762
|
+
customFill.width = width;
|
|
2763
|
+
customFill.height = height;
|
|
2764
|
+
this._fill = customFill;
|
|
2765
|
+
}
|
|
2766
|
+
else {
|
|
2767
|
+
const g = new pixi_js.Graphics();
|
|
2768
|
+
g.roundRect(0, 0, width, height, borderRadius).fill(fillColor);
|
|
2769
|
+
this._fill = g;
|
|
2770
|
+
}
|
|
2771
|
+
this.addChild(this._fill);
|
|
2772
|
+
// Fill mask
|
|
2773
|
+
this._fillMask = new pixi_js.Graphics();
|
|
2774
|
+
this.addChild(this._fillMask);
|
|
2775
|
+
this._fill.mask = this._fillMask;
|
|
2776
|
+
// Handle
|
|
2777
|
+
const customHandle = resolveView(config.handleView);
|
|
2778
|
+
if (customHandle) {
|
|
2779
|
+
this._handle = customHandle;
|
|
2780
|
+
}
|
|
2781
|
+
else {
|
|
2782
|
+
const g = new pixi_js.Graphics();
|
|
2783
|
+
g.circle(0, 0, handleRadius).fill(handleColor);
|
|
2784
|
+
this._handle = g;
|
|
2785
|
+
}
|
|
2786
|
+
this._handle.y = height / 2;
|
|
2787
|
+
this.addChild(this._handle);
|
|
2788
|
+
// Interaction
|
|
2789
|
+
this.eventMode = 'static';
|
|
2790
|
+
this.cursor = 'pointer';
|
|
2791
|
+
// Hit area covers track + handle overflow
|
|
2792
|
+
const hitPad = Math.max(handleRadius - height / 2, 0);
|
|
2793
|
+
this.hitArea = { contains: (x, y) => x >= -hitPad && x <= width + hitPad && y >= -hitPad && y <= height + hitPad };
|
|
2794
|
+
this.on('pointerdown', this._onPointerDown, this);
|
|
2795
|
+
this.on('globalpointermove', this._onPointerMove, this);
|
|
2796
|
+
this.on('pointerup', this._onPointerUp, this);
|
|
2797
|
+
this.on('pointerupoutside', this._onPointerUp, this);
|
|
2798
|
+
this._updateVisuals();
|
|
2799
|
+
}
|
|
2800
|
+
/** Current value */
|
|
2801
|
+
get value() {
|
|
2802
|
+
return this._value;
|
|
2803
|
+
}
|
|
2804
|
+
set value(v) {
|
|
2805
|
+
const clamped = this._applyStep(Math.max(this._config.min, Math.min(this._config.max, v)));
|
|
2806
|
+
if (clamped === this._value)
|
|
2807
|
+
return;
|
|
2808
|
+
this._value = clamped;
|
|
2809
|
+
this._updateVisuals();
|
|
2810
|
+
}
|
|
2811
|
+
get min() { return this._config.min; }
|
|
2812
|
+
get max() { return this._config.max; }
|
|
2813
|
+
/** React reconciler update hook */
|
|
2814
|
+
updateConfig(changed) {
|
|
2815
|
+
if ('value' in changed)
|
|
2816
|
+
this.value = changed.value;
|
|
2817
|
+
if ('min' in changed) {
|
|
2818
|
+
this._config.min = changed.min;
|
|
2819
|
+
this._updateVisuals();
|
|
2820
|
+
}
|
|
2821
|
+
if ('max' in changed) {
|
|
2822
|
+
this._config.max = changed.max;
|
|
2823
|
+
this._updateVisuals();
|
|
2824
|
+
}
|
|
2825
|
+
if ('step' in changed)
|
|
2826
|
+
this._config.step = changed.step;
|
|
2827
|
+
if ('onUpdate' in changed)
|
|
2828
|
+
this.onUpdate = changed.onUpdate;
|
|
2829
|
+
if ('onChange' in changed)
|
|
2830
|
+
this.onChange = changed.onChange;
|
|
2831
|
+
}
|
|
2832
|
+
_fraction() {
|
|
2833
|
+
const { min, max } = this._config;
|
|
2834
|
+
return max === min ? 0 : (this._value - min) / (max - min);
|
|
2835
|
+
}
|
|
2836
|
+
_applyStep(v) {
|
|
2837
|
+
const { step, min } = this._config;
|
|
2838
|
+
if (step <= 0)
|
|
2839
|
+
return v;
|
|
2840
|
+
return min + Math.round((v - min) / step) * step;
|
|
2841
|
+
}
|
|
2842
|
+
_updateVisuals() {
|
|
2843
|
+
const frac = this._fraction();
|
|
2844
|
+
const w = this._config.width;
|
|
2845
|
+
const h = this._config.height;
|
|
2846
|
+
// Update fill mask
|
|
2847
|
+
this._fillMask.clear();
|
|
2848
|
+
this._fillMask.rect(0, 0, w * frac, h).fill(0xffffff);
|
|
2849
|
+
// Update handle position
|
|
2850
|
+
this._handle.x = w * frac;
|
|
2851
|
+
}
|
|
2852
|
+
_valueFromPointer(e) {
|
|
2853
|
+
const local = this.toLocal(e.global);
|
|
2854
|
+
const frac = Math.max(0, Math.min(1, local.x / this._config.width));
|
|
2855
|
+
const { min, max } = this._config;
|
|
2856
|
+
return this._applyStep(min + frac * (max - min));
|
|
2857
|
+
}
|
|
2858
|
+
_onPointerDown(e) {
|
|
2859
|
+
this._dragging = true;
|
|
2860
|
+
const newValue = this._valueFromPointer(e);
|
|
2861
|
+
if (newValue !== this._value) {
|
|
2862
|
+
this._value = newValue;
|
|
2863
|
+
this._updateVisuals();
|
|
2864
|
+
this.onUpdate?.(this._value);
|
|
2865
|
+
}
|
|
2866
|
+
}
|
|
2867
|
+
_onPointerMove(e) {
|
|
2868
|
+
if (!this._dragging)
|
|
2869
|
+
return;
|
|
2870
|
+
const newValue = this._valueFromPointer(e);
|
|
2871
|
+
if (newValue !== this._value) {
|
|
2872
|
+
this._value = newValue;
|
|
2873
|
+
this._updateVisuals();
|
|
2874
|
+
this.onUpdate?.(this._value);
|
|
2875
|
+
}
|
|
2876
|
+
}
|
|
2877
|
+
_onPointerUp(_e) {
|
|
2878
|
+
if (!this._dragging)
|
|
2879
|
+
return;
|
|
2880
|
+
this._dragging = false;
|
|
2881
|
+
this.onChange?.(this._value);
|
|
2882
|
+
}
|
|
2883
|
+
destroy(options) {
|
|
2884
|
+
this.off('pointerdown', this._onPointerDown, this);
|
|
2885
|
+
this.off('globalpointermove', this._onPointerMove, this);
|
|
2886
|
+
this.off('pointerup', this._onPointerUp, this);
|
|
2887
|
+
this.off('pointerupoutside', this._onPointerUp, this);
|
|
2888
|
+
this.onUpdate = null;
|
|
2889
|
+
this.onChange = null;
|
|
2890
|
+
super.destroy(options);
|
|
2891
|
+
}
|
|
2892
|
+
}
|
|
2893
|
+
|
|
2894
|
+
/**
|
|
2895
|
+
* Toggle switch with two states.
|
|
2896
|
+
*
|
|
2897
|
+
* Supports custom ON/OFF views or auto-generated Graphics-based toggle.
|
|
2898
|
+
* Click to toggle, or use `forceSwitch(value)` programmatically.
|
|
2899
|
+
*
|
|
2900
|
+
* @example
|
|
2901
|
+
* ```ts
|
|
2902
|
+
* const mute = new Toggle({
|
|
2903
|
+
* value: false,
|
|
2904
|
+
* onColor: 0x22cc22,
|
|
2905
|
+
* onChange: (on) => audioManager.mute(!on),
|
|
2906
|
+
* });
|
|
2907
|
+
* ```
|
|
2908
|
+
*/
|
|
2909
|
+
class Toggle extends pixi_js.Container {
|
|
2910
|
+
__uiComponent = true;
|
|
2911
|
+
_value;
|
|
2912
|
+
_onView = null;
|
|
2913
|
+
_offView = null;
|
|
2914
|
+
_handle = null;
|
|
2915
|
+
_trackGfx = null;
|
|
2916
|
+
_config;
|
|
2917
|
+
_useCustomViews;
|
|
2918
|
+
onChange = null;
|
|
2919
|
+
constructor(config = {}) {
|
|
2920
|
+
super();
|
|
2921
|
+
this._config = {
|
|
2922
|
+
width: config.width ?? 52,
|
|
2923
|
+
height: config.height ?? 28,
|
|
2924
|
+
onColor: config.onColor ?? 0x22cc22,
|
|
2925
|
+
offColor: config.offColor ?? 0x666666,
|
|
2926
|
+
handleColor: config.handleColor ?? 0xffffff,
|
|
2927
|
+
handleRadius: config.handleRadius ?? 0, // 0 = auto
|
|
2928
|
+
animationDuration: config.animationDuration ?? 200,
|
|
2929
|
+
};
|
|
2930
|
+
this._value = config.value ?? false;
|
|
2931
|
+
this.onChange = config.onChange ?? null;
|
|
2932
|
+
const customOn = resolveView(config.onView);
|
|
2933
|
+
const customOff = resolveView(config.offView);
|
|
2934
|
+
this._useCustomViews = !!(customOn || customOff);
|
|
2935
|
+
if (this._useCustomViews) {
|
|
2936
|
+
// Custom view mode: show/hide ON and OFF views
|
|
2937
|
+
if (customOn) {
|
|
2938
|
+
this._onView = customOn;
|
|
2939
|
+
this._onView.visible = this._value;
|
|
2940
|
+
this.addChild(this._onView);
|
|
2941
|
+
}
|
|
2942
|
+
if (customOff) {
|
|
2943
|
+
this._offView = customOff;
|
|
2944
|
+
this._offView.visible = !this._value;
|
|
2945
|
+
this.addChild(this._offView);
|
|
2946
|
+
}
|
|
2947
|
+
}
|
|
2948
|
+
else {
|
|
2949
|
+
// Graphics mode: track + sliding handle
|
|
2950
|
+
const { width, height, handleColor } = this._config;
|
|
2951
|
+
const handleRadius = this._config.handleRadius || (height / 2 - 3);
|
|
2952
|
+
this._config.handleRadius = handleRadius;
|
|
2953
|
+
this._trackGfx = new pixi_js.Graphics();
|
|
2954
|
+
this.addChild(this._trackGfx);
|
|
2955
|
+
this._drawTrack();
|
|
2956
|
+
const handle = new pixi_js.Graphics();
|
|
2957
|
+
handle.circle(0, 0, handleRadius).fill(handleColor);
|
|
2958
|
+
handle.y = height / 2;
|
|
2959
|
+
handle.x = this._value ? width - handleRadius - 3 : handleRadius + 3;
|
|
2960
|
+
this._handle = handle;
|
|
2961
|
+
this.addChild(handle);
|
|
2962
|
+
}
|
|
2963
|
+
// Interaction
|
|
2964
|
+
this.eventMode = 'static';
|
|
2965
|
+
this.cursor = 'pointer';
|
|
2966
|
+
this.on('pointertap', this._onTap, this);
|
|
2967
|
+
}
|
|
2968
|
+
/** Current toggle state */
|
|
2969
|
+
get value() {
|
|
2970
|
+
return this._value;
|
|
2971
|
+
}
|
|
2972
|
+
set value(v) {
|
|
2973
|
+
if (v === this._value)
|
|
2974
|
+
return;
|
|
2975
|
+
this.forceSwitch(v);
|
|
2976
|
+
}
|
|
2977
|
+
/** Programmatically switch to a specific state with animation */
|
|
2978
|
+
forceSwitch(value) {
|
|
2979
|
+
this._value = value;
|
|
2980
|
+
this._animateToState();
|
|
2981
|
+
}
|
|
2982
|
+
/** React reconciler update hook */
|
|
2983
|
+
updateConfig(changed) {
|
|
2984
|
+
if ('value' in changed)
|
|
2985
|
+
this.value = changed.value;
|
|
2986
|
+
if ('onChange' in changed)
|
|
2987
|
+
this.onChange = changed.onChange;
|
|
2988
|
+
if ('animationDuration' in changed)
|
|
2989
|
+
this._config.animationDuration = changed.animationDuration;
|
|
2990
|
+
}
|
|
2991
|
+
_onTap() {
|
|
2992
|
+
this._value = !this._value;
|
|
2993
|
+
this._animateToState();
|
|
2994
|
+
this.onChange?.(this._value);
|
|
2995
|
+
}
|
|
2996
|
+
_animateToState() {
|
|
2997
|
+
const duration = this._config.animationDuration;
|
|
2998
|
+
if (this._useCustomViews) {
|
|
2999
|
+
// Custom views: crossfade
|
|
3000
|
+
if (this._onView) {
|
|
3001
|
+
Tween.killTweensOf(this._onView);
|
|
3002
|
+
if (this._value) {
|
|
3003
|
+
this._onView.visible = true;
|
|
3004
|
+
Tween.to(this._onView, { alpha: 1 }, duration);
|
|
3005
|
+
}
|
|
3006
|
+
else {
|
|
3007
|
+
Tween.to(this._onView, { alpha: 0 }, duration).then(() => {
|
|
3008
|
+
if (this._onView)
|
|
3009
|
+
this._onView.visible = false;
|
|
3010
|
+
});
|
|
3011
|
+
}
|
|
3012
|
+
}
|
|
3013
|
+
if (this._offView) {
|
|
3014
|
+
Tween.killTweensOf(this._offView);
|
|
3015
|
+
if (!this._value) {
|
|
3016
|
+
this._offView.visible = true;
|
|
3017
|
+
Tween.to(this._offView, { alpha: 1 }, duration);
|
|
3018
|
+
}
|
|
3019
|
+
else {
|
|
3020
|
+
Tween.to(this._offView, { alpha: 0 }, duration).then(() => {
|
|
3021
|
+
if (this._offView)
|
|
3022
|
+
this._offView.visible = false;
|
|
3023
|
+
});
|
|
3024
|
+
}
|
|
3025
|
+
}
|
|
3026
|
+
}
|
|
3027
|
+
else {
|
|
3028
|
+
// Graphics mode: slide handle + recolor track
|
|
3029
|
+
this._drawTrack();
|
|
3030
|
+
if (this._handle) {
|
|
3031
|
+
const { width } = this._config;
|
|
3032
|
+
const handleRadius = this._config.handleRadius;
|
|
3033
|
+
const targetX = this._value ? width - handleRadius - 3 : handleRadius + 3;
|
|
3034
|
+
Tween.killTweensOf(this._handle);
|
|
3035
|
+
Tween.to(this._handle, { x: targetX }, duration);
|
|
3036
|
+
}
|
|
3037
|
+
}
|
|
3038
|
+
}
|
|
3039
|
+
_drawTrack() {
|
|
3040
|
+
if (!this._trackGfx)
|
|
3041
|
+
return;
|
|
3042
|
+
const { width, height, onColor, offColor } = this._config;
|
|
3043
|
+
const radius = height / 2;
|
|
3044
|
+
this._trackGfx.clear();
|
|
3045
|
+
this._trackGfx.roundRect(0, 0, width, height, radius).fill(this._value ? onColor : offColor);
|
|
3046
|
+
}
|
|
3047
|
+
destroy(options) {
|
|
3048
|
+
this.off('pointertap', this._onTap, this);
|
|
3049
|
+
if (this._handle)
|
|
3050
|
+
Tween.killTweensOf(this._handle);
|
|
3051
|
+
if (this._onView)
|
|
3052
|
+
Tween.killTweensOf(this._onView);
|
|
3053
|
+
if (this._offView)
|
|
3054
|
+
Tween.killTweensOf(this._offView);
|
|
3055
|
+
this.onChange = null;
|
|
3056
|
+
super.destroy(options);
|
|
3057
|
+
}
|
|
3058
|
+
}
|
|
3059
|
+
|
|
2609
3060
|
/**
|
|
2610
3061
|
* Register all standard PixiJS display objects for JSX use.
|
|
2611
3062
|
* Call once at app startup before rendering any React scenes.
|
|
@@ -2646,6 +3097,7 @@ function extendUIElements() {
|
|
|
2646
3097
|
extend({
|
|
2647
3098
|
Button, Label, Panel, FlexContainer, ProgressBar,
|
|
2648
3099
|
ScrollContainer, Modal, Toast, BalanceDisplay, WinDisplay, Layout,
|
|
3100
|
+
Slider, Toggle,
|
|
2649
3101
|
});
|
|
2650
3102
|
}
|
|
2651
3103
|
/**
|