@energy8platform/game-engine 0.20.0 → 0.22.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.
Files changed (93) hide show
  1. package/README.md +121 -0
  2. package/dist/animation.cjs.js +18 -3
  3. package/dist/animation.cjs.js.map +1 -1
  4. package/dist/animation.esm.js +18 -3
  5. package/dist/animation.esm.js.map +1 -1
  6. package/dist/core.cjs.js +18 -3
  7. package/dist/core.cjs.js.map +1 -1
  8. package/dist/core.esm.js +18 -3
  9. package/dist/core.esm.js.map +1 -1
  10. package/dist/host.cjs.js +18 -3
  11. package/dist/host.cjs.js.map +1 -1
  12. package/dist/host.d.ts +3 -5
  13. package/dist/host.esm.js +18 -3
  14. package/dist/host.esm.js.map +1 -1
  15. package/dist/index.cjs.js +18 -2535
  16. package/dist/index.cjs.js.map +1 -1
  17. package/dist/index.d.ts +3 -1020
  18. package/dist/index.esm.js +20 -2525
  19. package/dist/index.esm.js.map +1 -1
  20. package/dist/slot.cjs.js +1872 -61
  21. package/dist/slot.cjs.js.map +1 -1
  22. package/dist/slot.d.ts +555 -28
  23. package/dist/slot.esm.js +1857 -62
  24. package/dist/slot.esm.js.map +1 -1
  25. package/dist/vite.cjs.js +0 -5
  26. package/dist/vite.cjs.js.map +1 -1
  27. package/dist/vite.esm.js +0 -5
  28. package/dist/vite.esm.js.map +1 -1
  29. package/package.json +2 -35
  30. package/src/animation/Tween.ts +14 -3
  31. package/src/host/index.ts +0 -1
  32. package/src/host/types.ts +0 -3
  33. package/src/index.ts +0 -28
  34. package/src/slot/anim/easing-map.ts +16 -2
  35. package/src/slot/cascade/TumbleController.ts +230 -0
  36. package/src/slot/config/ReelSystemConfig.ts +559 -0
  37. package/src/slot/config/presets.ts +222 -0
  38. package/src/slot/features/extra.ts +189 -0
  39. package/src/slot/features/index.ts +44 -0
  40. package/src/slot/features/symbols.ts +183 -0
  41. package/src/slot/features/types.ts +136 -0
  42. package/src/slot/features/wilds.ts +151 -0
  43. package/src/slot/grid/ReelGrid.ts +93 -24
  44. package/src/slot/grid/SymbolCell.ts +45 -11
  45. package/src/slot/index.ts +61 -2
  46. package/src/slot/motion/AnticipationController.ts +96 -0
  47. package/src/slot/motion/SpinEngine.ts +384 -0
  48. package/src/slot/system/ReelSystem.ts +295 -0
  49. package/src/vite/index.ts +0 -5
  50. package/dist/react-jsx.cjs.js +0 -3
  51. package/dist/react-jsx.cjs.js.map +0 -1
  52. package/dist/react-jsx.d.ts +0 -602
  53. package/dist/react-jsx.esm.js +0 -2
  54. package/dist/react-jsx.esm.js.map +0 -1
  55. package/dist/react.cjs.js +0 -3787
  56. package/dist/react.cjs.js.map +0 -1
  57. package/dist/react.d.ts +0 -1467
  58. package/dist/react.esm.js +0 -3771
  59. package/dist/react.esm.js.map +0 -1
  60. package/dist/ui.cjs.js +0 -2999
  61. package/dist/ui.cjs.js.map +0 -1
  62. package/dist/ui.d.ts +0 -1116
  63. package/dist/ui.esm.js +0 -2983
  64. package/dist/ui.esm.js.map +0 -1
  65. package/src/react/EngineContext.ts +0 -26
  66. package/src/react/ReactScene.ts +0 -88
  67. package/src/react/applyProps.ts +0 -271
  68. package/src/react/catalogue.ts +0 -17
  69. package/src/react/createPixiRoot.ts +0 -31
  70. package/src/react/extendAll.ts +0 -74
  71. package/src/react/hooks.ts +0 -46
  72. package/src/react/index.ts +0 -29
  73. package/src/react/jsx-runtime.ts +0 -346
  74. package/src/react/reconciler.ts +0 -338
  75. package/src/slot/freeSpins/FreeSpinsSession.ts +0 -40
  76. package/src/state/StateMachine.ts +0 -231
  77. package/src/state/index.ts +0 -1
  78. package/src/ui/BalanceDisplay.ts +0 -159
  79. package/src/ui/Button.ts +0 -304
  80. package/src/ui/FlexContainer.ts +0 -775
  81. package/src/ui/Label.ts +0 -124
  82. package/src/ui/LabelValue.ts +0 -122
  83. package/src/ui/Layout.ts +0 -291
  84. package/src/ui/Modal.ts +0 -170
  85. package/src/ui/Panel.ts +0 -204
  86. package/src/ui/ProgressBar.ts +0 -170
  87. package/src/ui/ScrollContainer.ts +0 -478
  88. package/src/ui/Slider.ts +0 -241
  89. package/src/ui/Toast.ts +0 -150
  90. package/src/ui/Toggle.ts +0 -201
  91. package/src/ui/WinDisplay.ts +0 -145
  92. package/src/ui/index.ts +0 -33
  93. package/src/ui/view.ts +0 -28
@@ -1,478 +0,0 @@
1
- import { Container, Graphics, type ColorSource, Ticker } from 'pixi.js';
2
- import { resolveView } from './view';
3
- import type { ViewInput } from './view';
4
-
5
- // ─── Types ───────────────────────────────────────────────
6
-
7
- export type ScrollDirection = 'vertical' | 'horizontal' | 'both';
8
-
9
- export interface ScrollContainerConfig {
10
- /** Visible viewport width */
11
- width: number;
12
-
13
- /** Visible viewport height */
14
- height: number;
15
-
16
- /** Scroll direction (default: 'vertical') */
17
- direction?: ScrollDirection;
18
-
19
- /** Background color (undefined = transparent) */
20
- backgroundColor?: ColorSource;
21
-
22
- /** Border radius for the mask (default: 0) */
23
- borderRadius?: number;
24
-
25
- /** Gap between items (default: 0) */
26
- elementsMargin?: number;
27
-
28
- /** Padding */
29
- padding?: number;
30
-
31
- /** Disable easing/inertia */
32
- disableEasing?: boolean;
33
-
34
- /** Show scrollbar indicator (default: false) */
35
- scrollbar?: boolean;
36
-
37
- /** Custom scrollbar thumb view (string texture name, Texture, or Container) */
38
- thumbView?: ViewInput;
39
-
40
- /** Scrollbar width in px (default: 6) */
41
- scrollbarWidth?: number;
42
-
43
- /** Scrollbar padding from edge (default: 4) */
44
- scrollbarPadding?: number;
45
-
46
- /** Scrollbar color (when no thumbView, default: 0xaaaaaa) */
47
- scrollbarColor?: number;
48
-
49
- /** Scrollbar alpha (default: 0.5) */
50
- scrollbarAlpha?: number;
51
- }
52
-
53
- const DECELERATION = 0.95;
54
- const MIN_VELOCITY = 0.5;
55
-
56
- /**
57
- * Scrollable container with touch/drag, mouse wheel, and inertia.
58
- *
59
- * @example
60
- * ```ts
61
- * const scroll = new ScrollContainer({
62
- * width: 600,
63
- * height: 400,
64
- * direction: 'vertical',
65
- * elementsMargin: 8,
66
- * });
67
- *
68
- * for (let i = 0; i < 50; i++) {
69
- * scroll.addItem(createRow(i));
70
- * }
71
- *
72
- * scene.container.addChild(scroll);
73
- * ```
74
- */
75
- export class ScrollContainer extends Container {
76
- readonly __uiComponent = true as const;
77
-
78
- private _viewport: { width: number; height: number };
79
- private _internalSetup = true;
80
- private _content: Container;
81
- private _maskGfx: Graphics;
82
- private _bg: Graphics | null = null;
83
- private _scrollConfig: Required<Pick<ScrollContainerConfig, 'direction' | 'elementsMargin' | 'padding' | 'borderRadius' | 'disableEasing'>>;
84
- private _items: Container[] = [];
85
-
86
- // Scrollbar
87
- private _scrollbar: Container | null = null;
88
- private _scrollbarConfig: { width: number; padding: number };
89
-
90
- // Drag state
91
- private _dragging = false;
92
- private _dragStart = { x: 0, y: 0 };
93
- private _contentStart = { x: 0, y: 0 };
94
- private _velocity = { x: 0, y: 0 };
95
- private _lastDragPos = { x: 0, y: 0 };
96
- private _lastDragTime = 0;
97
- private _inertiaActive = false;
98
-
99
- // Bound handlers for cleanup
100
- private _onTickBound: ((ticker: Ticker) => void) | null = null;
101
- private _onWheelBound: ((e: WheelEvent) => void) | null = null;
102
-
103
- constructor(config: ScrollContainerConfig) {
104
- super();
105
-
106
- this._viewport = { width: config.width, height: config.height };
107
- this._scrollConfig = {
108
- direction: config.direction ?? 'vertical',
109
- elementsMargin: config.elementsMargin ?? 0,
110
- padding: config.padding ?? 0,
111
- borderRadius: config.borderRadius ?? 0,
112
- disableEasing: config.disableEasing ?? false,
113
- };
114
-
115
- // Background
116
- if (config.backgroundColor !== undefined) {
117
- this._bg = new Graphics();
118
- this._bg.roundRect(0, 0, config.width, config.height, this._scrollConfig.borderRadius)
119
- .fill(config.backgroundColor);
120
- this.addChild(this._bg);
121
- }
122
-
123
- // Mask
124
- this._maskGfx = new Graphics();
125
- this._maskGfx.roundRect(0, 0, config.width, config.height, this._scrollConfig.borderRadius)
126
- .fill(0xffffff);
127
- this.addChild(this._maskGfx);
128
-
129
- // Content container
130
- this._content = new Container();
131
- this._content.mask = this._maskGfx;
132
- this.addChild(this._content);
133
-
134
- // Interaction
135
- this.eventMode = 'static';
136
- this.hitArea = { contains: (x: number, y: number) => x >= 0 && x <= config.width && y >= 0 && y <= config.height };
137
-
138
- this.on('pointerdown', this._onPointerDown, this);
139
- this.on('pointermove', this._onPointerMove, this);
140
- this.on('pointerup', this._onPointerUp, this);
141
- this.on('pointerupoutside', this._onPointerUp, this);
142
-
143
- // Mouse wheel
144
- this._onWheelBound = this._onWheel.bind(this);
145
-
146
- // Scrollbar
147
- const sbWidth = config.scrollbarWidth ?? 6;
148
- const sbPadding = config.scrollbarPadding ?? 4;
149
- this._scrollbarConfig = { width: sbWidth, padding: sbPadding };
150
-
151
- if (config.scrollbar) {
152
- const customThumb = resolveView(config.thumbView);
153
- if (customThumb) {
154
- this._scrollbar = customThumb;
155
- } else {
156
- const g = new Graphics();
157
- g.roundRect(0, 0, sbWidth, 40, sbWidth / 2).fill(config.scrollbarColor ?? 0xaaaaaa);
158
- g.alpha = config.scrollbarAlpha ?? 0.5;
159
- this._scrollbar = g;
160
- }
161
- this._scrollbar.visible = false;
162
- super.addChild(this._scrollbar);
163
- }
164
-
165
- this._internalSetup = false;
166
- }
167
-
168
- /**
169
- * Override addChild so external children are routed to scroll content.
170
- * Enables `<scrollContainer><label /><panel /></scrollContainer>` in React JSX.
171
- */
172
- override addChild<T extends Container>(...children: T[]): T {
173
- if (this._internalSetup) {
174
- return super.addChild(...children);
175
- }
176
- for (const child of children) {
177
- this.addItem(child as any);
178
- }
179
- return children[0];
180
- }
181
-
182
- override removeChild<T extends Container>(...children: T[]): T {
183
- if (this._internalSetup) {
184
- return super.removeChild(...children);
185
- }
186
- for (const child of children) {
187
- const idx = this._items.indexOf(child as any);
188
- if (idx !== -1) {
189
- this._items.splice(idx, 1);
190
- this._content.removeChild(child);
191
- }
192
- }
193
- this.layoutItems();
194
- return children[0];
195
- }
196
-
197
- /** React reconciler update hook */
198
- updateConfig(changed: Record<string, any>): void {
199
- if ('width' in changed || 'height' in changed) {
200
- this.setViewportSize(
201
- changed.width ?? this._viewport.width,
202
- changed.height ?? this._viewport.height,
203
- );
204
- }
205
- }
206
-
207
- /** Enable mouse wheel scrolling (call after adding to stage) */
208
- enableWheel(canvas: HTMLCanvasElement): void {
209
- if (this._onWheelBound) {
210
- canvas.addEventListener('wheel', this._onWheelBound, { passive: false });
211
- }
212
- }
213
-
214
- /** Set scrollable content. Replaces any existing items. */
215
- setContent(content: Container): void {
216
- this.clearItems();
217
- const children = [...content.children] as Container[];
218
- for (const child of children) {
219
- this.addItem(child);
220
- }
221
- }
222
-
223
- /** Add a single item */
224
- addItem(child: Container): this {
225
- this._items.push(child);
226
- this._content.addChild(child);
227
- this.layoutItems();
228
- return this;
229
- }
230
-
231
- /** Remove all items */
232
- clearItems(): void {
233
- for (const item of this._items) {
234
- this._content.removeChild(item);
235
- }
236
- this._items.length = 0;
237
- }
238
-
239
- /** Get items */
240
- get items(): readonly Container[] {
241
- return this._items;
242
- }
243
-
244
- /** Scroll to make a specific item index visible */
245
- scrollToItem(index: number): void {
246
- if (index < 0 || index >= this._items.length) return;
247
- const item = this._items[index];
248
- const isVert = this._scrollConfig.direction !== 'horizontal';
249
-
250
- if (isVert) {
251
- this._content.y = -item.y + this._scrollConfig.padding;
252
- } else {
253
- this._content.x = -item.x + this._scrollConfig.padding;
254
- }
255
- this.clampScroll();
256
- }
257
-
258
- /** Current scroll position */
259
- get scrollPosition(): { x: number; y: number } {
260
- return { x: this._content.x, y: this._content.y };
261
- }
262
-
263
- /** Resize the scroll viewport */
264
- setViewportSize(width: number, height: number): void {
265
- this._viewport.width = width;
266
- this._viewport.height = height;
267
-
268
- this._maskGfx.clear();
269
- this._maskGfx.roundRect(0, 0, width, height, this._scrollConfig.borderRadius).fill(0xffffff);
270
-
271
- if (this._bg) {
272
- this._bg.clear();
273
- this._bg.roundRect(0, 0, width, height, this._scrollConfig.borderRadius)
274
- .fill(0xffffff); // color will be overridden if needed
275
- }
276
-
277
- this.clampScroll();
278
- }
279
-
280
- // ─── Layout ──────────────────────────────────────────
281
-
282
- private layoutItems(): void {
283
- const { direction, elementsMargin, padding } = this._scrollConfig;
284
- const isVert = direction !== 'horizontal';
285
- let pos = padding;
286
-
287
- for (const item of this._items) {
288
- if (isVert) {
289
- item.x = padding;
290
- item.y = pos;
291
- pos += item.height + elementsMargin;
292
- } else {
293
- item.x = pos;
294
- item.y = padding;
295
- pos += item.width + elementsMargin;
296
- }
297
- }
298
- }
299
-
300
- // ─── Drag handling ───────────────────────────────────
301
-
302
- private _onPointerDown(e: import('pixi.js').FederatedPointerEvent): void {
303
- this._dragging = true;
304
- this._inertiaActive = false;
305
- this._dragStart.x = e.globalX;
306
- this._dragStart.y = e.globalY;
307
- this._contentStart.x = this._content.x;
308
- this._contentStart.y = this._content.y;
309
- this._lastDragPos.x = e.globalX;
310
- this._lastDragPos.y = e.globalY;
311
- this._lastDragTime = Date.now();
312
- this._velocity.x = 0;
313
- this._velocity.y = 0;
314
-
315
- this.stopInertia();
316
- }
317
-
318
- private _onPointerMove(e: import('pixi.js').FederatedPointerEvent): void {
319
- if (!this._dragging) return;
320
-
321
- const dx = e.globalX - this._dragStart.x;
322
- const dy = e.globalY - this._dragStart.y;
323
- const { direction } = this._scrollConfig;
324
-
325
- if (direction !== 'horizontal') {
326
- this._content.y = this._contentStart.y + dy;
327
- }
328
- if (direction !== 'vertical') {
329
- this._content.x = this._contentStart.x + dx;
330
- }
331
-
332
- // Track velocity
333
- const now = Date.now();
334
- const dt = now - this._lastDragTime;
335
- if (dt > 0) {
336
- this._velocity.x = (e.globalX - this._lastDragPos.x) / dt * 16;
337
- this._velocity.y = (e.globalY - this._lastDragPos.y) / dt * 16;
338
- }
339
- this._lastDragPos.x = e.globalX;
340
- this._lastDragPos.y = e.globalY;
341
- this._lastDragTime = now;
342
-
343
- this.clampScroll();
344
- }
345
-
346
- private _onPointerUp(): void {
347
- if (!this._dragging) return;
348
- this._dragging = false;
349
-
350
- if (!this._scrollConfig.disableEasing &&
351
- (Math.abs(this._velocity.x) > MIN_VELOCITY || Math.abs(this._velocity.y) > MIN_VELOCITY)) {
352
- this.startInertia();
353
- }
354
- }
355
-
356
- // ─── Inertia ─────────────────────────────────────────
357
-
358
- private startInertia(): void {
359
- this._inertiaActive = true;
360
- this._onTickBound = this._inertiaTick.bind(this);
361
- Ticker.shared.add(this._onTickBound);
362
- }
363
-
364
- private stopInertia(): void {
365
- if (this._onTickBound && this._inertiaActive) {
366
- Ticker.shared.remove(this._onTickBound);
367
- this._inertiaActive = false;
368
- }
369
- }
370
-
371
- private _inertiaTick(): void {
372
- const { direction } = this._scrollConfig;
373
-
374
- if (direction !== 'horizontal') {
375
- this._content.y += this._velocity.y;
376
- this._velocity.y *= DECELERATION;
377
- }
378
- if (direction !== 'vertical') {
379
- this._content.x += this._velocity.x;
380
- this._velocity.x *= DECELERATION;
381
- }
382
-
383
- this.clampScroll();
384
-
385
- if (Math.abs(this._velocity.x) < MIN_VELOCITY && Math.abs(this._velocity.y) < MIN_VELOCITY) {
386
- this.stopInertia();
387
- }
388
- }
389
-
390
- // ─── Mouse wheel ─────────────────────────────────────
391
-
392
- private _onWheel(e: WheelEvent): void {
393
- const { direction } = this._scrollConfig;
394
- e.preventDefault();
395
-
396
- if (direction !== 'horizontal') {
397
- this._content.y -= e.deltaY;
398
- }
399
- if (direction !== 'vertical') {
400
- this._content.x -= e.deltaX;
401
- }
402
-
403
- this.clampScroll();
404
- }
405
-
406
- // ─── Scroll bounds ───────────────────────────────────
407
-
408
- private clampScroll(): void {
409
- const { direction } = this._scrollConfig;
410
- const bounds = this._content.getLocalBounds();
411
-
412
- if (direction !== 'horizontal') {
413
- const contentHeight = bounds.height + bounds.y;
414
- const maxScroll = Math.min(0, this._viewport.height - contentHeight);
415
- this._content.y = Math.max(maxScroll, Math.min(0, this._content.y));
416
- }
417
-
418
- if (direction !== 'vertical') {
419
- const contentWidth = bounds.width + bounds.x;
420
- const maxScroll = Math.min(0, this._viewport.width - contentWidth);
421
- this._content.x = Math.max(maxScroll, Math.min(0, this._content.x));
422
- }
423
-
424
- this.updateScrollbar();
425
- }
426
-
427
- private updateScrollbar(): void {
428
- if (!this._scrollbar) return;
429
- const { direction } = this._scrollConfig;
430
- const { width: sbW, padding: sbPad } = this._scrollbarConfig;
431
- const bounds = this._content.getLocalBounds();
432
- const isVert = direction !== 'horizontal';
433
-
434
- if (isVert) {
435
- const contentH = bounds.height + bounds.y;
436
- if (contentH <= this._viewport.height) {
437
- this._scrollbar.visible = false;
438
- return;
439
- }
440
- this._scrollbar.visible = true;
441
- const ratio = this._viewport.height / contentH;
442
- const thumbH = Math.max(20, this._viewport.height * ratio);
443
- const scrollRange = this._viewport.height - thumbH;
444
- const scrollProgress = -this._content.y / (contentH - this._viewport.height);
445
-
446
- this._scrollbar.x = this._viewport.width - sbW - sbPad;
447
- this._scrollbar.y = scrollProgress * scrollRange;
448
- this._scrollbar.height = thumbH;
449
- this._scrollbar.width = sbW;
450
- } else {
451
- const contentW = bounds.width + bounds.x;
452
- if (contentW <= this._viewport.width) {
453
- this._scrollbar.visible = false;
454
- return;
455
- }
456
- this._scrollbar.visible = true;
457
- const ratio = this._viewport.width / contentW;
458
- const thumbW = Math.max(20, this._viewport.width * ratio);
459
- const scrollRange = this._viewport.width - thumbW;
460
- const scrollProgress = -this._content.x / (contentW - this._viewport.width);
461
-
462
- this._scrollbar.y = this._viewport.height - sbW - sbPad;
463
- this._scrollbar.x = scrollProgress * scrollRange;
464
- this._scrollbar.width = thumbW;
465
- this._scrollbar.height = sbW;
466
- }
467
- }
468
-
469
- override destroy(options?: boolean | { children?: boolean; texture?: boolean; textureSource?: boolean }): void {
470
- this.stopInertia();
471
- this.off('pointerdown', this._onPointerDown, this);
472
- this.off('pointermove', this._onPointerMove, this);
473
- this.off('pointerup', this._onPointerUp, this);
474
- this.off('pointerupoutside', this._onPointerUp, this);
475
- this._items.length = 0;
476
- super.destroy(options);
477
- }
478
- }
package/src/ui/Slider.ts DELETED
@@ -1,241 +0,0 @@
1
- import { Container, Graphics, FederatedPointerEvent } from 'pixi.js';
2
- import { resolveView } from './view';
3
- import type { ViewInput } from './view';
4
-
5
- export interface SliderConfig {
6
- /** Minimum value (default: 0) */
7
- min?: number;
8
- /** Maximum value (default: 1) */
9
- max?: number;
10
- /** Step increment (0 = continuous, default: 0) */
11
- step?: number;
12
- /** Initial value (default: min) */
13
- value?: number;
14
- /** Track width in pixels (default: 200) */
15
- width?: number;
16
- /** Track height in pixels (default: 8) */
17
- height?: number;
18
- /** Corner radius for Graphics-based track/fill (default: 4) */
19
- borderRadius?: number;
20
- /** Track background color (ignored when trackView provided) */
21
- trackColor?: number;
22
- /** Fill bar color (ignored when fillView provided) */
23
- fillColor?: number;
24
- /** Handle radius (for Graphics-based handle, default: 12) */
25
- handleRadius?: number;
26
- /** Handle color (for Graphics-based handle, ignored when handleView provided) */
27
- handleColor?: number;
28
-
29
- /** Custom track background view */
30
- trackView?: ViewInput;
31
- /** Custom fill bar view */
32
- fillView?: ViewInput;
33
- /** Custom handle view */
34
- handleView?: ViewInput;
35
-
36
- /** Called when value changes during drag */
37
- onUpdate?: (value: number) => void;
38
- /** Called when drag ends */
39
- onChange?: (value: number) => void;
40
- }
41
-
42
- /**
43
- * Draggable slider with customizable track, fill, and handle views.
44
- *
45
- * @example
46
- * ```ts
47
- * const volume = new Slider({
48
- * min: 0, max: 1, value: 0.5,
49
- * width: 200, height: 8,
50
- * fillColor: 0xffd700,
51
- * onUpdate: (v) => console.log('Volume:', v),
52
- * });
53
- * ```
54
- */
55
- export class Slider extends Container {
56
- readonly __uiComponent = true as const;
57
-
58
- private _track: Container;
59
- private _fill: Container;
60
- private _fillMask: Graphics;
61
- private _handle: Container;
62
- private _config: Required<Pick<SliderConfig, 'min' | 'max' | 'step' | 'width' | 'height' | 'borderRadius' | 'trackColor' | 'fillColor' | 'handleRadius' | 'handleColor'>>;
63
- private _value: number;
64
- private _dragging = false;
65
-
66
- onUpdate: ((value: number) => void) | null = null;
67
- onChange: ((value: number) => void) | null = null;
68
-
69
- constructor(config: SliderConfig = {}) {
70
- super();
71
-
72
- this._config = {
73
- min: config.min ?? 0,
74
- max: config.max ?? 1,
75
- step: config.step ?? 0,
76
- width: config.width ?? 200,
77
- height: config.height ?? 8,
78
- borderRadius: config.borderRadius ?? 4,
79
- trackColor: config.trackColor ?? 0x333333,
80
- fillColor: config.fillColor ?? 0xffd700,
81
- handleRadius: config.handleRadius ?? 12,
82
- handleColor: config.handleColor ?? 0xffffff,
83
- };
84
-
85
- this._value = config.value ?? this._config.min;
86
- this.onUpdate = config.onUpdate ?? null;
87
- this.onChange = config.onChange ?? null;
88
-
89
- const { width, height, borderRadius, trackColor, fillColor, handleRadius, handleColor } = this._config;
90
-
91
- // Track
92
- const customTrack = resolveView(config.trackView);
93
- if (customTrack) {
94
- customTrack.width = width;
95
- customTrack.height = height;
96
- this._track = customTrack;
97
- } else {
98
- const g = new Graphics();
99
- g.roundRect(0, 0, width, height, borderRadius).fill(trackColor);
100
- this._track = g;
101
- }
102
- this.addChild(this._track);
103
-
104
- // Fill
105
- const customFill = resolveView(config.fillView);
106
- if (customFill) {
107
- customFill.width = width;
108
- customFill.height = height;
109
- this._fill = customFill;
110
- } else {
111
- const g = new Graphics();
112
- g.roundRect(0, 0, width, height, borderRadius).fill(fillColor);
113
- this._fill = g;
114
- }
115
- this.addChild(this._fill);
116
-
117
- // Fill mask
118
- this._fillMask = new Graphics();
119
- this.addChild(this._fillMask);
120
- this._fill.mask = this._fillMask;
121
-
122
- // Handle
123
- const customHandle = resolveView(config.handleView);
124
- if (customHandle) {
125
- this._handle = customHandle;
126
- } else {
127
- const g = new Graphics();
128
- g.circle(0, 0, handleRadius).fill(handleColor);
129
- this._handle = g;
130
- }
131
- this._handle.y = height / 2;
132
- this.addChild(this._handle);
133
-
134
- // Interaction
135
- this.eventMode = 'static';
136
- this.cursor = 'pointer';
137
-
138
- // Hit area covers track + handle overflow
139
- const hitPad = Math.max(handleRadius - height / 2, 0);
140
- this.hitArea = { contains: (x: number, y: number) => x >= -hitPad && x <= width + hitPad && y >= -hitPad && y <= height + hitPad };
141
-
142
- this.on('pointerdown', this._onPointerDown, this);
143
- this.on('globalpointermove', this._onPointerMove, this);
144
- this.on('pointerup', this._onPointerUp, this);
145
- this.on('pointerupoutside', this._onPointerUp, this);
146
-
147
- this._updateVisuals();
148
- }
149
-
150
- /** Current value */
151
- get value(): number {
152
- return this._value;
153
- }
154
-
155
- set value(v: number) {
156
- const clamped = this._applyStep(Math.max(this._config.min, Math.min(this._config.max, v)));
157
- if (clamped === this._value) return;
158
- this._value = clamped;
159
- this._updateVisuals();
160
- }
161
-
162
- get min(): number { return this._config.min; }
163
- get max(): number { return this._config.max; }
164
-
165
- /** React reconciler update hook */
166
- updateConfig(changed: Record<string, any>): void {
167
- if ('value' in changed) this.value = changed.value;
168
- if ('min' in changed) { this._config.min = changed.min; this._updateVisuals(); }
169
- if ('max' in changed) { this._config.max = changed.max; this._updateVisuals(); }
170
- if ('step' in changed) this._config.step = changed.step;
171
- if ('onUpdate' in changed) this.onUpdate = changed.onUpdate;
172
- if ('onChange' in changed) this.onChange = changed.onChange;
173
- }
174
-
175
- private _fraction(): number {
176
- const { min, max } = this._config;
177
- return max === min ? 0 : (this._value - min) / (max - min);
178
- }
179
-
180
- private _applyStep(v: number): number {
181
- const { step, min } = this._config;
182
- if (step <= 0) return v;
183
- return min + Math.round((v - min) / step) * step;
184
- }
185
-
186
- private _updateVisuals(): void {
187
- const frac = this._fraction();
188
- const w = this._config.width;
189
- const h = this._config.height;
190
-
191
- // Update fill mask
192
- this._fillMask.clear();
193
- this._fillMask.rect(0, 0, w * frac, h).fill(0xffffff);
194
-
195
- // Update handle position
196
- this._handle.x = w * frac;
197
- }
198
-
199
- private _valueFromPointer(e: FederatedPointerEvent): number {
200
- const local = this.toLocal(e.global);
201
- const frac = Math.max(0, Math.min(1, local.x / this._config.width));
202
- const { min, max } = this._config;
203
- return this._applyStep(min + frac * (max - min));
204
- }
205
-
206
- private _onPointerDown(e: FederatedPointerEvent): void {
207
- this._dragging = true;
208
- const newValue = this._valueFromPointer(e);
209
- if (newValue !== this._value) {
210
- this._value = newValue;
211
- this._updateVisuals();
212
- this.onUpdate?.(this._value);
213
- }
214
- }
215
-
216
- private _onPointerMove(e: FederatedPointerEvent): void {
217
- if (!this._dragging) return;
218
- const newValue = this._valueFromPointer(e);
219
- if (newValue !== this._value) {
220
- this._value = newValue;
221
- this._updateVisuals();
222
- this.onUpdate?.(this._value);
223
- }
224
- }
225
-
226
- private _onPointerUp(_e: FederatedPointerEvent): void {
227
- if (!this._dragging) return;
228
- this._dragging = false;
229
- this.onChange?.(this._value);
230
- }
231
-
232
- override destroy(options?: boolean | { children?: boolean; texture?: boolean; textureSource?: boolean }): void {
233
- this.off('pointerdown', this._onPointerDown, this);
234
- this.off('globalpointermove', this._onPointerMove, this);
235
- this.off('pointerup', this._onPointerUp, this);
236
- this.off('pointerupoutside', this._onPointerUp, this);
237
- this.onUpdate = null;
238
- this.onChange = null;
239
- super.destroy(options);
240
- }
241
- }