@energy8platform/game-engine 0.3.0 → 0.5.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.
@@ -1,4 +1,6 @@
1
- import { Container, Graphics } from 'pixi.js';
1
+ import { Container, type ColorSource } from 'pixi.js';
2
+ import { ScrollBox as PixiScrollBox } from '@pixi/ui';
3
+ import type { ScrollBoxOptions } from '@pixi/ui';
2
4
 
3
5
  // ─── Types ───────────────────────────────────────────────
4
6
 
@@ -14,42 +16,39 @@ export interface ScrollContainerConfig {
14
16
  /** Scroll direction (default: 'vertical') */
15
17
  direction?: ScrollDirection;
16
18
 
17
- /** Show scrollbar(s) (default: true) */
18
- showScrollbar?: boolean;
19
-
20
- /** Scrollbar width in pixels (default: 6) */
21
- scrollbarWidth?: number;
22
-
23
- /** Scrollbar color (default: 0xffffff) */
24
- scrollbarColor?: number;
25
-
26
- /** Scrollbar opacity (default: 0.4) */
27
- scrollbarAlpha?: number;
19
+ /** Background color (undefined = transparent) */
20
+ backgroundColor?: ColorSource;
28
21
 
29
- /** Elasticity factor for overscroll bounce (0 = none, 1 = infinite, default: 0.3) */
30
- elasticity?: number;
22
+ /** Border radius for the mask (default: 0) */
23
+ borderRadius?: number;
31
24
 
32
- /** Inertia deceleration factor (0 = instant stop, 1 = infinite drift, default: 0.92) */
33
- inertia?: number;
25
+ /** Gap between items (default: 0) */
26
+ elementsMargin?: number;
34
27
 
35
- /** Snap to items of fixed height/width (0 = no snap) */
36
- snapSize?: number;
28
+ /** Padding */
29
+ padding?: number;
37
30
 
38
- /** Background color (undefined = transparent) */
39
- backgroundColor?: number;
31
+ /** Disable dynamic rendering (render all items even when offscreen) */
32
+ disableDynamicRendering?: boolean;
40
33
 
41
- /** Background alpha (default: 1) */
42
- backgroundAlpha?: number;
34
+ /** Disable easing/inertia */
35
+ disableEasing?: boolean;
43
36
 
44
- /** Border radius for the mask (default: 0) */
45
- borderRadius?: number;
37
+ /** Global scroll scroll even when mouse is not over the component */
38
+ globalScroll?: boolean;
46
39
  }
47
40
 
41
+ const DIRECTION_MAP: Record<ScrollDirection, 'vertical' | 'horizontal' | 'bidirectional'> = {
42
+ vertical: 'vertical',
43
+ horizontal: 'horizontal',
44
+ both: 'bidirectional',
45
+ };
46
+
48
47
  /**
49
- * Scrollable container with touch/drag, mouse wheel, inertia, and optional scrollbar.
48
+ * Scrollable container powered by `@pixi/ui` ScrollBox.
50
49
  *
51
- * Perfect for paytables, settings panels, bet history, and any scrollable content
52
- * that doesn't fit on screen.
50
+ * Provides touch/drag scrolling, mouse wheel support, inertia, and
51
+ * dynamic rendering optimization for off-screen items.
53
52
  *
54
53
  * @example
55
54
  * ```ts
@@ -57,501 +56,71 @@ export interface ScrollContainerConfig {
57
56
  * width: 600,
58
57
  * height: 400,
59
58
  * direction: 'vertical',
60
- * showScrollbar: true,
61
- * elasticity: 0.3,
59
+ * elementsMargin: 8,
62
60
  * });
63
61
  *
64
- * // Add content taller than 400px
65
- * const list = new Container();
66
62
  * for (let i = 0; i < 50; i++) {
67
- * const row = createRow(i);
68
- * row.y = i * 40;
69
- * list.addChild(row);
63
+ * scroll.addItem(createRow(i));
70
64
  * }
71
- * scroll.setContent(list);
72
65
  *
73
66
  * scene.container.addChild(scroll);
74
67
  * ```
75
68
  */
76
- export class ScrollContainer extends Container {
77
- private _config: Required<
78
- Pick<ScrollContainerConfig, 'width' | 'height' | 'direction' | 'showScrollbar' |
79
- 'scrollbarWidth' | 'scrollbarColor' | 'scrollbarAlpha' | 'elasticity' | 'inertia' |
80
- 'snapSize' | 'borderRadius'>
81
- >;
82
-
83
- private _viewport: Container;
84
- private _content: Container | null = null;
85
- private _mask: Graphics;
86
- private _bg: Graphics;
87
- private _scrollbarV: Graphics | null = null;
88
- private _scrollbarH: Graphics | null = null;
89
- private _scrollbarFadeTimeout: number | null = null;
90
-
91
- // Scroll state
92
- private _scrollX = 0;
93
- private _scrollY = 0;
94
- private _velocityX = 0;
95
- private _velocityY = 0;
96
- private _isDragging = false;
97
- private _dragStart = { x: 0, y: 0 };
98
- private _scrollStart = { x: 0, y: 0 };
99
- private _lastDragPos = { x: 0, y: 0 };
100
- private _lastDragTime = 0;
101
- private _isAnimating = false;
102
- private _animationFrame: number | null = null;
69
+ export class ScrollContainer extends PixiScrollBox {
70
+ private _scrollConfig: ScrollContainerConfig;
103
71
 
104
72
  constructor(config: ScrollContainerConfig) {
105
- super();
106
-
107
- this._config = {
73
+ const options: ScrollBoxOptions = {
108
74
  width: config.width,
109
75
  height: config.height,
110
- direction: config.direction ?? 'vertical',
111
- showScrollbar: config.showScrollbar ?? true,
112
- scrollbarWidth: config.scrollbarWidth ?? 6,
113
- scrollbarColor: config.scrollbarColor ?? 0xffffff,
114
- scrollbarAlpha: config.scrollbarAlpha ?? 0.4,
115
- elasticity: config.elasticity ?? 0.3,
116
- inertia: config.inertia ?? 0.92,
117
- snapSize: config.snapSize ?? 0,
118
- borderRadius: config.borderRadius ?? 0,
76
+ type: DIRECTION_MAP[config.direction ?? 'vertical'],
77
+ radius: config.borderRadius ?? 0,
78
+ elementsMargin: config.elementsMargin ?? 0,
79
+ padding: config.padding ?? 0,
80
+ disableDynamicRendering: config.disableDynamicRendering ?? false,
81
+ disableEasing: config.disableEasing ?? false,
82
+ globalScroll: config.globalScroll ?? true,
119
83
  };
120
84
 
121
- // Background
122
- this._bg = new Graphics();
123
85
  if (config.backgroundColor !== undefined) {
124
- this._bg.roundRect(0, 0, config.width, config.height, this._config.borderRadius)
125
- .fill({ color: config.backgroundColor, alpha: config.backgroundAlpha ?? 1 });
86
+ options.background = config.backgroundColor;
126
87
  }
127
- this.addChild(this._bg);
128
88
 
129
- // Viewport (masked area)
130
- this._viewport = new Container();
131
- this.addChild(this._viewport);
89
+ super(options);
132
90
 
133
- // Mask
134
- this._mask = new Graphics();
135
- this._mask.roundRect(0, 0, config.width, config.height, this._config.borderRadius)
136
- .fill(0xffffff);
137
- this.addChild(this._mask);
138
- this._viewport.mask = this._mask;
139
-
140
- // Scrollbars
141
- if (this._config.showScrollbar) {
142
- if (this._config.direction !== 'horizontal') {
143
- this._scrollbarV = new Graphics();
144
- this._scrollbarV.alpha = 0;
145
- this.addChild(this._scrollbarV);
146
- }
147
- if (this._config.direction !== 'vertical') {
148
- this._scrollbarH = new Graphics();
149
- this._scrollbarH.alpha = 0;
150
- this.addChild(this._scrollbarH);
151
- }
152
- }
153
-
154
- // Interaction
155
- this.eventMode = 'static';
156
- this.cursor = 'grab';
157
- this.hitArea = { contains: (x: number, y: number) =>
158
- x >= 0 && x <= config.width && y >= 0 && y <= config.height };
159
-
160
- this.on('pointerdown', this.onPointerDown);
161
- this.on('pointermove', this.onPointerMove);
162
- this.on('pointerup', this.onPointerUp);
163
- this.on('pointerupoutside', this.onPointerUp);
164
- this.on('wheel', this.onWheel);
91
+ this._scrollConfig = config;
165
92
  }
166
93
 
167
94
  /** Set scrollable content. Replaces any existing content. */
168
95
  setContent(content: Container): void {
169
- if (this._content) {
170
- this._viewport.removeChild(this._content);
96
+ // Remove existing items
97
+ const existing = this.items;
98
+ if (existing.length > 0) {
99
+ for (let i = existing.length - 1; i >= 0; i--) {
100
+ this.removeItem(i);
101
+ }
171
102
  }
172
- this._content = content;
173
- this._viewport.addChild(content);
174
- this._scrollX = 0;
175
- this._scrollY = 0;
176
- this.applyScroll();
177
- }
178
103
 
179
- /** Get the content container */
180
- get content(): Container | null {
181
- return this._content;
182
- }
183
-
184
- /** Scroll to a specific position (in content coordinates) */
185
- scrollTo(x: number, y: number, animate = true): void {
186
- if (!animate) {
187
- this._scrollX = x;
188
- this._scrollY = y;
189
- this.clampScroll();
190
- this.applyScroll();
191
- return;
104
+ // Add all children from the content container
105
+ const children = [...content.children] as Container[];
106
+ if (children.length > 0) {
107
+ this.addItems(children);
192
108
  }
109
+ }
193
110
 
194
- this.animateScrollTo(x, y);
111
+ /** Add a single item */
112
+ addItem(...items: Container[]): Container {
113
+ this.addItems(items);
114
+ return items[0];
195
115
  }
196
116
 
197
117
  /** Scroll to make a specific item/child visible */
198
118
  scrollToItem(index: number): void {
199
- if (this._config.snapSize > 0) {
200
- const pos = index * this._config.snapSize;
201
- if (this._config.direction === 'horizontal') {
202
- this.scrollTo(pos, this._scrollY);
203
- } else {
204
- this.scrollTo(this._scrollX, pos);
205
- }
206
- }
119
+ this.scrollTo(index);
207
120
  }
208
121
 
209
122
  /** Current scroll position */
210
123
  get scrollPosition(): { x: number; y: number } {
211
- return { x: this._scrollX, y: this._scrollY };
212
- }
213
-
214
- /** Resize the scroll viewport */
215
- resize(width: number, height: number): void {
216
- this._config.width = width;
217
- this._config.height = height;
218
-
219
- // Redraw mask and background
220
- this._mask.clear();
221
- this._mask.roundRect(0, 0, width, height, this._config.borderRadius).fill(0xffffff);
222
-
223
- this._bg.clear();
224
-
225
- this.hitArea = { contains: (x: number, y: number) =>
226
- x >= 0 && x <= width && y >= 0 && y <= height };
227
-
228
- this.clampScroll();
229
- this.applyScroll();
230
- }
231
-
232
- /** Destroy and clean up */
233
- override destroy(options?: any): void {
234
- this.stopAnimation();
235
- if (this._scrollbarFadeTimeout !== null) {
236
- clearTimeout(this._scrollbarFadeTimeout);
237
- }
238
- this.off('pointerdown', this.onPointerDown);
239
- this.off('pointermove', this.onPointerMove);
240
- this.off('pointerup', this.onPointerUp);
241
- this.off('pointerupoutside', this.onPointerUp);
242
- this.off('wheel', this.onWheel);
243
- super.destroy(options);
244
- }
245
-
246
- // ─── Scroll mechanics ─────────────────────────────────
247
-
248
- private get contentWidth(): number {
249
- if (!this._content) return 0;
250
- const bounds = this._content.getBounds();
251
- return bounds.width;
252
- }
253
-
254
- private get contentHeight(): number {
255
- if (!this._content) return 0;
256
- const bounds = this._content.getBounds();
257
- return bounds.height;
258
- }
259
-
260
- private get maxScrollX(): number {
261
- return Math.max(0, this.contentWidth - this._config.width);
262
- }
263
-
264
- private get maxScrollY(): number {
265
- return Math.max(0, this.contentHeight - this._config.height);
266
- }
267
-
268
- private canScrollX(): boolean {
269
- return this._config.direction === 'horizontal' || this._config.direction === 'both';
270
- }
271
-
272
- private canScrollY(): boolean {
273
- return this._config.direction === 'vertical' || this._config.direction === 'both';
274
- }
275
-
276
- private clampScroll(): void {
277
- if (this.canScrollX()) {
278
- this._scrollX = Math.max(0, Math.min(this._scrollX, this.maxScrollX));
279
- } else {
280
- this._scrollX = 0;
281
- }
282
- if (this.canScrollY()) {
283
- this._scrollY = Math.max(0, Math.min(this._scrollY, this.maxScrollY));
284
- } else {
285
- this._scrollY = 0;
286
- }
287
- }
288
-
289
- private applyScroll(): void {
290
- if (!this._content) return;
291
- this._content.x = -this._scrollX;
292
- this._content.y = -this._scrollY;
293
- this.updateScrollbars();
294
- }
295
-
296
- // ─── Input handlers ────────────────────────────────────
297
-
298
- private onPointerDown = (e: any): void => {
299
- this._isDragging = true;
300
- this._isAnimating = false;
301
- this.stopAnimation();
302
- this.cursor = 'grabbing';
303
-
304
- const local = e.getLocalPosition(this);
305
- this._dragStart = { x: local.x, y: local.y };
306
- this._scrollStart = { x: this._scrollX, y: this._scrollY };
307
- this._lastDragPos = { x: local.x, y: local.y };
308
- this._lastDragTime = Date.now();
309
- this._velocityX = 0;
310
- this._velocityY = 0;
311
-
312
- this.showScrollbars();
313
- };
314
-
315
- private onPointerMove = (e: any): void => {
316
- if (!this._isDragging) return;
317
-
318
- const local = e.getLocalPosition(this);
319
- const dx = local.x - this._dragStart.x;
320
- const dy = local.y - this._dragStart.y;
321
- const now = Date.now();
322
- const dt = Math.max(1, now - this._lastDragTime);
323
-
324
- // Calculate velocity for inertia
325
- this._velocityX = (local.x - this._lastDragPos.x) / dt * 16; // normalize to ~60fps
326
- this._velocityY = (local.y - this._lastDragPos.y) / dt * 16;
327
-
328
- this._lastDragPos = { x: local.x, y: local.y };
329
- this._lastDragTime = now;
330
-
331
- // Apply scroll with elasticity for overscroll
332
- let newX = this._scrollStart.x - dx;
333
- let newY = this._scrollStart.y - dy;
334
-
335
- const elasticity = this._config.elasticity;
336
- if (this.canScrollX()) {
337
- if (newX < 0) newX *= elasticity;
338
- else if (newX > this.maxScrollX) newX = this.maxScrollX + (newX - this.maxScrollX) * elasticity;
339
- this._scrollX = newX;
340
- }
341
- if (this.canScrollY()) {
342
- if (newY < 0) newY *= elasticity;
343
- else if (newY > this.maxScrollY) newY = this.maxScrollY + (newY - this.maxScrollY) * elasticity;
344
- this._scrollY = newY;
345
- }
346
-
347
- this.applyScroll();
348
- };
349
-
350
- private onPointerUp = (): void => {
351
- if (!this._isDragging) return;
352
- this._isDragging = false;
353
- this.cursor = 'grab';
354
-
355
- // Start inertia
356
- if (Math.abs(this._velocityX) > 0.5 || Math.abs(this._velocityY) > 0.5) {
357
- this.startInertia();
358
- } else {
359
- this.snapAndBounce();
360
- }
361
- };
362
-
363
- private onWheel = (e: any): void => {
364
- e.preventDefault?.();
365
- const delta = e.deltaY ?? 0;
366
- const deltaX = e.deltaX ?? 0;
367
-
368
- if (this.canScrollY()) {
369
- this._scrollY += delta * 0.5;
370
- }
371
- if (this.canScrollX()) {
372
- this._scrollX += deltaX * 0.5;
373
- }
374
-
375
- this.clampScroll();
376
- this.applyScroll();
377
- this.showScrollbars();
378
- this.scheduleScrollbarFade();
379
- };
380
-
381
- // ─── Inertia & snap ───────────────────────────────────
382
-
383
- private startInertia(): void {
384
- this._isAnimating = true;
385
-
386
- const tick = () => {
387
- if (!this._isAnimating) return;
388
-
389
- this._velocityX *= this._config.inertia;
390
- this._velocityY *= this._config.inertia;
391
-
392
- if (this.canScrollX()) this._scrollX -= this._velocityX;
393
- if (this.canScrollY()) this._scrollY -= this._velocityY;
394
-
395
- // Bounce back if overscrolled
396
- let bounced = false;
397
- if (this.canScrollX()) {
398
- if (this._scrollX < 0) { this._scrollX *= 0.8; bounced = true; }
399
- else if (this._scrollX > this.maxScrollX) {
400
- this._scrollX = this.maxScrollX + (this._scrollX - this.maxScrollX) * 0.8;
401
- bounced = true;
402
- }
403
- }
404
- if (this.canScrollY()) {
405
- if (this._scrollY < 0) { this._scrollY *= 0.8; bounced = true; }
406
- else if (this._scrollY > this.maxScrollY) {
407
- this._scrollY = this.maxScrollY + (this._scrollY - this.maxScrollY) * 0.8;
408
- bounced = true;
409
- }
410
- }
411
-
412
- this.applyScroll();
413
-
414
- const speed = Math.abs(this._velocityX) + Math.abs(this._velocityY);
415
- if (speed < 0.1 && !bounced) {
416
- this._isAnimating = false;
417
- this.snapAndBounce();
418
- return;
419
- }
420
-
421
- this._animationFrame = requestAnimationFrame(tick);
422
- };
423
-
424
- this._animationFrame = requestAnimationFrame(tick);
425
- }
426
-
427
- private snapAndBounce(): void {
428
- // Clamp first
429
- let targetX = Math.max(0, Math.min(this._scrollX, this.maxScrollX));
430
- let targetY = Math.max(0, Math.min(this._scrollY, this.maxScrollY));
431
-
432
- // Snap
433
- if (this._config.snapSize > 0) {
434
- if (this.canScrollY()) {
435
- targetY = Math.round(targetY / this._config.snapSize) * this._config.snapSize;
436
- targetY = Math.max(0, Math.min(targetY, this.maxScrollY));
437
- }
438
- if (this.canScrollX()) {
439
- targetX = Math.round(targetX / this._config.snapSize) * this._config.snapSize;
440
- targetX = Math.max(0, Math.min(targetX, this.maxScrollX));
441
- }
442
- }
443
-
444
- if (Math.abs(targetX - this._scrollX) < 0.5 && Math.abs(targetY - this._scrollY) < 0.5) {
445
- this._scrollX = targetX;
446
- this._scrollY = targetY;
447
- this.applyScroll();
448
- this.scheduleScrollbarFade();
449
- return;
450
- }
451
-
452
- this.animateScrollTo(targetX, targetY);
453
- }
454
-
455
- private animateScrollTo(targetX: number, targetY: number): void {
456
- this._isAnimating = true;
457
- const startX = this._scrollX;
458
- const startY = this._scrollY;
459
- const startTime = Date.now();
460
- const duration = 300;
461
-
462
- const tick = () => {
463
- if (!this._isAnimating) return;
464
-
465
- const elapsed = Date.now() - startTime;
466
- const t = Math.min(elapsed / duration, 1);
467
- // easeOutCubic
468
- const eased = 1 - Math.pow(1 - t, 3);
469
-
470
- this._scrollX = startX + (targetX - startX) * eased;
471
- this._scrollY = startY + (targetY - startY) * eased;
472
- this.applyScroll();
473
-
474
- if (t < 1) {
475
- this._animationFrame = requestAnimationFrame(tick);
476
- } else {
477
- this._isAnimating = false;
478
- this.scheduleScrollbarFade();
479
- }
480
- };
481
-
482
- this._animationFrame = requestAnimationFrame(tick);
483
- }
484
-
485
- private stopAnimation(): void {
486
- this._isAnimating = false;
487
- if (this._animationFrame !== null) {
488
- cancelAnimationFrame(this._animationFrame);
489
- this._animationFrame = null;
490
- }
491
- }
492
-
493
- // ─── Scrollbars ────────────────────────────────────────
494
-
495
- private updateScrollbars(): void {
496
- const { width, height, scrollbarWidth, scrollbarColor, scrollbarAlpha } = this._config;
497
-
498
- if (this._scrollbarV && this.canScrollY() && this.contentHeight > height) {
499
- const ratio = height / this.contentHeight;
500
- const barH = Math.max(20, height * ratio);
501
- const barY = (this._scrollY / this.maxScrollY) * (height - barH);
502
-
503
- this._scrollbarV.clear();
504
- this._scrollbarV.roundRect(
505
- width - scrollbarWidth - 2,
506
- Math.max(0, barY),
507
- scrollbarWidth,
508
- barH,
509
- scrollbarWidth / 2,
510
- ).fill({ color: scrollbarColor, alpha: scrollbarAlpha });
511
- }
512
-
513
- if (this._scrollbarH && this.canScrollX() && this.contentWidth > width) {
514
- const ratio = width / this.contentWidth;
515
- const barW = Math.max(20, width * ratio);
516
- const barX = (this._scrollX / this.maxScrollX) * (width - barW);
517
-
518
- this._scrollbarH.clear();
519
- this._scrollbarH.roundRect(
520
- Math.max(0, barX),
521
- height - scrollbarWidth - 2,
522
- barW,
523
- scrollbarWidth,
524
- scrollbarWidth / 2,
525
- ).fill({ color: scrollbarColor, alpha: scrollbarAlpha });
526
- }
527
- }
528
-
529
- private showScrollbars(): void {
530
- if (this._scrollbarV) this._scrollbarV.alpha = 1;
531
- if (this._scrollbarH) this._scrollbarH.alpha = 1;
532
- }
533
-
534
- private scheduleScrollbarFade(): void {
535
- if (this._scrollbarFadeTimeout !== null) {
536
- clearTimeout(this._scrollbarFadeTimeout);
537
- }
538
- this._scrollbarFadeTimeout = window.setTimeout(() => {
539
- this.fadeScrollbars();
540
- }, 1000);
541
- }
542
-
543
- private fadeScrollbars(): void {
544
- const duration = 300;
545
- const startTime = Date.now();
546
- const startAlphaV = this._scrollbarV?.alpha ?? 0;
547
- const startAlphaH = this._scrollbarH?.alpha ?? 0;
548
-
549
- const tick = () => {
550
- const t = Math.min((Date.now() - startTime) / duration, 1);
551
- if (this._scrollbarV) this._scrollbarV.alpha = startAlphaV * (1 - t);
552
- if (this._scrollbarH) this._scrollbarH.alpha = startAlphaH * (1 - t);
553
- if (t < 1) requestAnimationFrame(tick);
554
- };
555
- requestAnimationFrame(tick);
124
+ return { x: this.scrollX, y: this.scrollY };
556
125
  }
557
126
  }
package/src/ui/Toast.ts CHANGED
@@ -38,9 +38,8 @@ export class Toast extends Container {
38
38
  super();
39
39
 
40
40
  this._config = {
41
- duration: 3000,
42
- bottomOffset: 60,
43
- ...config,
41
+ duration: config.duration ?? 3000,
42
+ bottomOffset: config.bottomOffset ?? 60,
44
43
  };
45
44
 
46
45
  this._bg = new Graphics();
@@ -69,7 +68,6 @@ export class Toast extends Container {
69
68
  viewWidth?: number,
70
69
  viewHeight?: number,
71
70
  ): Promise<void> {
72
- // Clear previous dismiss
73
71
  if (this._dismissTimeout) {
74
72
  clearTimeout(this._dismissTimeout);
75
73
  }
@@ -81,10 +79,10 @@ export class Toast extends Container {
81
79
  const height = 44;
82
80
  const radius = 8;
83
81
 
82
+ // Draw the background
84
83
  this._bg.clear();
85
- this._bg.roundRect(-width / 2, -height / 2, width, height, radius).fill(TOAST_COLORS[type]);
86
- this._bg.roundRect(-width / 2, -height / 2, width, height, radius)
87
- .fill({ color: 0x000000, alpha: 0.2 });
84
+ this._bg.roundRect(-width / 2, -height / 2, width, height, radius);
85
+ this._bg.fill(TOAST_COLORS[type]);
88
86
 
89
87
  // Position
90
88
  if (viewWidth && viewHeight) {
@@ -96,10 +94,8 @@ export class Toast extends Container {
96
94
  this.alpha = 0;
97
95
  this.y += 20;
98
96
 
99
- // Animate in
100
97
  await Tween.to(this, { alpha: 1, y: this.y - 20 }, 300, Easing.easeOutCubic);
101
98
 
102
- // Auto-dismiss
103
99
  if (this._config.duration > 0) {
104
100
  this._dismissTimeout = setTimeout(() => {
105
101
  this.dismiss();
package/src/ui/index.ts CHANGED
@@ -1,3 +1,7 @@
1
+ // ─── @pixi/layout setup (must be imported before creating containers) ────
2
+ import '@pixi/layout';
3
+
4
+ // ─── Engine UI Components ─────────────────────────────────
1
5
  export { Button } from './Button';
2
6
  export type { ButtonConfig, ButtonState } from './Button';
3
7
  export { ProgressBar } from './ProgressBar';
@@ -18,3 +22,12 @@ export { Layout } from './Layout';
18
22
  export type { LayoutConfig, LayoutDirection, LayoutAlignment, LayoutAnchor } from './Layout';
19
23
  export { ScrollContainer } from './ScrollContainer';
20
24
  export type { ScrollContainerConfig, ScrollDirection } from './ScrollContainer';
25
+
26
+ // ─── Direct access to @pixi/ui and @pixi/layout ──────────
27
+ // These packages are optional peer dependencies.
28
+ // For any classes or types not wrapped by the engine (e.g. Slider, CheckBox,
29
+ // Input, Select, RadioGroup, List, etc.), import directly:
30
+ //
31
+ // import { Slider, CheckBox } from '@pixi/ui';
32
+ // import { LayoutContainer } from '@pixi/layout/components';
33
+ //