@energy8platform/game-engine 0.3.0 → 0.4.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/src/ui/Button.ts CHANGED
@@ -1,8 +1,8 @@
1
- import { Container, Graphics, Sprite, Texture } from 'pixi.js';
2
- import { Tween } from '../animation/Tween';
3
- import { Easing } from '../animation/Easing';
1
+ import { Graphics, Texture } from 'pixi.js';
2
+ import { FancyButton } from '@pixi/ui';
3
+ import type { ButtonOptions } from '@pixi/ui';
4
4
 
5
- export type ButtonState = 'normal' | 'hover' | 'pressed' | 'disabled';
5
+ export type ButtonState = 'default' | 'hover' | 'pressed' | 'disabled';
6
6
 
7
7
  export interface ButtonConfig {
8
8
  /** Default texture/sprite for each state (optional — uses Graphics if not provided) */
@@ -21,179 +21,120 @@ export interface ButtonConfig {
21
21
  animationDuration?: number;
22
22
  /** Start disabled */
23
23
  disabled?: boolean;
24
+ /** Button text */
25
+ text?: string;
26
+ /** Button text style */
27
+ textStyle?: Record<string, unknown>;
24
28
  }
25
29
 
26
30
  const DEFAULT_COLORS: Record<ButtonState, number> = {
27
- normal: 0xffd700,
31
+ default: 0xffd700,
28
32
  hover: 0xffe44d,
29
33
  pressed: 0xccac00,
30
34
  disabled: 0x666666,
31
35
  };
32
36
 
37
+ function makeGraphicsView(
38
+ w: number, h: number, radius: number, color: number,
39
+ ): Graphics {
40
+ const g = new Graphics();
41
+ g.roundRect(0, 0, w, h, radius).fill(color);
42
+ // Highlight overlay
43
+ g.roundRect(2, 2, w - 4, h * 0.45, radius).fill({ color: 0xffffff, alpha: 0.1 });
44
+ return g;
45
+ }
46
+
33
47
  /**
34
- * Interactive button component with state management and animation.
48
+ * Interactive button component powered by `@pixi/ui` FancyButton.
35
49
  *
36
- * Supports both texture-based and Graphics-based rendering.
50
+ * Supports both texture-based and Graphics-based rendering with
51
+ * per-state views, press animation, and text.
37
52
  *
38
53
  * @example
39
54
  * ```ts
40
55
  * const btn = new Button({
41
56
  * width: 200, height: 60, borderRadius: 12,
42
- * colors: { normal: 0x22aa22, hover: 0x33cc33 },
57
+ * colors: { default: 0x22aa22, hover: 0x33cc33 },
58
+ * text: 'SPIN',
43
59
  * });
44
60
  *
45
- * btn.onTap = () => console.log('Clicked!');
61
+ * btn.onPress.connect(() => console.log('Clicked!'));
46
62
  * scene.container.addChild(btn);
47
63
  * ```
48
64
  */
49
- export class Button extends Container {
50
- private _state: ButtonState = 'normal';
51
- private _bg: Graphics;
52
- private _sprites: Partial<Record<ButtonState, Sprite>> = {};
53
- private _config: Required<
65
+ export class Button extends FancyButton {
66
+ private _buttonConfig: Required<
54
67
  Pick<ButtonConfig, 'width' | 'height' | 'borderRadius' | 'pressScale' | 'animationDuration'>
55
68
  > & ButtonConfig;
56
69
 
57
- /** Called when the button is tapped/clicked */
58
- public onTap?: () => void;
59
-
60
- /** Called when the button state changes */
61
- public onStateChange?: (state: ButtonState) => void;
62
-
63
70
  constructor(config: ButtonConfig = {}) {
64
- super();
65
-
66
- this._config = {
67
- width: 200,
68
- height: 60,
69
- borderRadius: 8,
70
- pressScale: 0.95,
71
- animationDuration: 100,
71
+ const resolvedConfig = {
72
+ width: config.width ?? 200,
73
+ height: config.height ?? 60,
74
+ borderRadius: config.borderRadius ?? 8,
75
+ pressScale: config.pressScale ?? 0.95,
76
+ animationDuration: config.animationDuration ?? 100,
72
77
  ...config,
73
78
  };
74
79
 
75
- // Create Graphics background
76
- this._bg = new Graphics();
77
- this.addChild(this._bg);
80
+ const colorMap = { ...DEFAULT_COLORS, ...config.colors };
81
+ const { width, height, borderRadius } = resolvedConfig;
82
+
83
+ // Build FancyButton options
84
+ const options: ButtonOptions = {
85
+ anchor: 0.5,
86
+ animations: {
87
+ hover: {
88
+ props: { scale: { x: 1.03, y: 1.03 } },
89
+ duration: resolvedConfig.animationDuration,
90
+ },
91
+ pressed: {
92
+ props: { scale: { x: resolvedConfig.pressScale, y: resolvedConfig.pressScale } },
93
+ duration: resolvedConfig.animationDuration,
94
+ },
95
+ },
96
+ };
78
97
 
79
- // Create texture sprites if provided
98
+ // Texture-based views
80
99
  if (config.textures) {
81
- for (const [state, tex] of Object.entries(config.textures)) {
82
- const texture = typeof tex === 'string' ? Texture.from(tex) : tex;
83
- const sprite = new Sprite(texture);
84
- sprite.anchor.set(0.5);
85
- sprite.visible = state === 'normal';
86
- this._sprites[state as ButtonState] = sprite;
87
- this.addChild(sprite);
88
- }
100
+ if (config.textures.default) options.defaultView = config.textures.default as any;
101
+ if (config.textures.hover) options.hoverView = config.textures.hover as any;
102
+ if (config.textures.pressed) options.pressedView = config.textures.pressed as any;
103
+ if (config.textures.disabled) options.disabledView = config.textures.disabled as any;
104
+ } else {
105
+ // Graphics-based views
106
+ options.defaultView = makeGraphicsView(width, height, borderRadius, colorMap.default);
107
+ options.hoverView = makeGraphicsView(width, height, borderRadius, colorMap.hover);
108
+ options.pressedView = makeGraphicsView(width, height, borderRadius, colorMap.pressed);
109
+ options.disabledView = makeGraphicsView(width, height, borderRadius, colorMap.disabled);
89
110
  }
90
111
 
91
- // Make interactive
92
- this.eventMode = 'static';
93
- this.cursor = 'pointer';
94
-
95
- // Set up hit area for Graphics-based
96
- this.pivot.set(this._config.width / 2, this._config.height / 2);
112
+ // Text
113
+ if (config.text) {
114
+ options.text = config.text;
115
+ }
97
116
 
98
- // Bind events
99
- this.on('pointerover', this.onPointerOver);
100
- this.on('pointerout', this.onPointerOut);
101
- this.on('pointerdown', this.onPointerDown);
102
- this.on('pointerup', this.onPointerUp);
103
- this.on('pointertap', this.onPointerTap);
117
+ super(options);
104
118
 
105
- // Initial render
106
- this.setState('normal');
119
+ this._buttonConfig = resolvedConfig;
107
120
 
108
121
  if (config.disabled) {
109
- this.disable();
122
+ this.enabled = false;
110
123
  }
111
124
  }
112
125
 
113
- /** Current button state */
114
- get state(): ButtonState {
115
- return this._state;
116
- }
117
-
118
126
  /** Enable the button */
119
127
  enable(): void {
120
- if (this._state === 'disabled') {
121
- this.setState('normal');
122
- this.eventMode = 'static';
123
- this.cursor = 'pointer';
124
- }
128
+ this.enabled = true;
125
129
  }
126
130
 
127
131
  /** Disable the button */
128
132
  disable(): void {
129
- this.setState('disabled');
130
- this.eventMode = 'none';
131
- this.cursor = 'default';
133
+ this.enabled = false;
132
134
  }
133
135
 
134
136
  /** Whether the button is disabled */
135
137
  get disabled(): boolean {
136
- return this._state === 'disabled';
138
+ return !this.enabled;
137
139
  }
138
-
139
- private setState(state: ButtonState): void {
140
- if (this._state === state) return;
141
- this._state = state;
142
- this.render();
143
- this.onStateChange?.(state);
144
- }
145
-
146
- private render(): void {
147
- const { width, height, borderRadius, colors } = this._config;
148
- const colorMap = { ...DEFAULT_COLORS, ...colors };
149
-
150
- // Update Graphics
151
- this._bg.clear();
152
- this._bg.roundRect(0, 0, width, height, borderRadius).fill(colorMap[this._state]);
153
-
154
- // Add highlight for normal/hover
155
- if (this._state === 'normal' || this._state === 'hover') {
156
- this._bg
157
- .roundRect(2, 2, width - 4, height * 0.45, borderRadius)
158
- .fill({ color: 0xffffff, alpha: 0.1 });
159
- }
160
-
161
- // Update sprite visibility
162
- for (const [state, sprite] of Object.entries(this._sprites)) {
163
- if (sprite) sprite.visible = state === this._state;
164
- }
165
- // Fall back to normal sprite if state sprite doesn't exist
166
- if (!this._sprites[this._state] && this._sprites.normal) {
167
- this._sprites.normal.visible = true;
168
- }
169
- }
170
-
171
- private onPointerOver = (): void => {
172
- if (this._state === 'disabled') return;
173
- this.setState('hover');
174
- };
175
-
176
- private onPointerOut = (): void => {
177
- if (this._state === 'disabled') return;
178
- this.setState('normal');
179
- Tween.to(this.scale, { x: 1, y: 1 }, this._config.animationDuration);
180
- };
181
-
182
- private onPointerDown = (): void => {
183
- if (this._state === 'disabled') return;
184
- this.setState('pressed');
185
- const s = this._config.pressScale;
186
- Tween.to(this.scale, { x: s, y: s }, this._config.animationDuration, Easing.easeOutQuad);
187
- };
188
-
189
- private onPointerUp = (): void => {
190
- if (this._state === 'disabled') return;
191
- this.setState('hover');
192
- Tween.to(this.scale, { x: 1, y: 1 }, this._config.animationDuration, Easing.easeOutBack);
193
- };
194
-
195
- private onPointerTap = (): void => {
196
- if (this._state === 'disabled') return;
197
- this.onTap?.();
198
- };
199
140
  }
package/src/ui/Layout.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  import { Container } from 'pixi.js';
2
+ import '@pixi/layout';
3
+ import type { LayoutStyles } from '@pixi/layout';
2
4
 
3
5
  // ─── Types ───────────────────────────────────────────────
4
6
 
@@ -38,8 +40,65 @@ export interface LayoutConfig {
38
40
  breakpoints?: Record<number, Partial<LayoutConfig>>;
39
41
  }
40
42
 
43
+ // ─── Helpers ─────────────────────────────────────────────
44
+
45
+ const ALIGNMENT_MAP: Record<LayoutAlignment, LayoutStyles['alignItems']> = {
46
+ start: 'flex-start',
47
+ center: 'center',
48
+ end: 'flex-end',
49
+ stretch: 'stretch',
50
+ };
51
+
52
+ function normalizePadding(
53
+ padding: number | [number, number, number, number],
54
+ ): [number, number, number, number] {
55
+ if (typeof padding === 'number') return [padding, padding, padding, padding];
56
+ return padding;
57
+ }
58
+
59
+ function directionToFlexStyles(
60
+ direction: LayoutDirection,
61
+ maxWidth: number,
62
+ ): Partial<LayoutStyles> {
63
+ switch (direction) {
64
+ case 'horizontal':
65
+ return { flexDirection: 'row', flexWrap: 'nowrap' };
66
+ case 'vertical':
67
+ return { flexDirection: 'column', flexWrap: 'nowrap' };
68
+ case 'grid':
69
+ return { flexDirection: 'row', flexWrap: 'wrap' };
70
+ case 'wrap':
71
+ return {
72
+ flexDirection: 'row',
73
+ flexWrap: 'wrap',
74
+ ...(maxWidth < Infinity ? { maxWidth } : {}),
75
+ };
76
+ }
77
+ }
78
+
79
+ function buildLayoutStyles(config: {
80
+ direction: LayoutDirection;
81
+ gap: number;
82
+ alignment: LayoutAlignment;
83
+ columns: number;
84
+ padding: [number, number, number, number];
85
+ maxWidth: number;
86
+ }): LayoutStyles {
87
+ const [pt, pr, pb, pl] = config.padding;
88
+
89
+ return {
90
+ ...directionToFlexStyles(config.direction, config.maxWidth),
91
+ gap: config.gap,
92
+ alignItems: ALIGNMENT_MAP[config.alignment],
93
+ paddingTop: pt,
94
+ paddingRight: pr,
95
+ paddingBottom: pb,
96
+ paddingLeft: pl,
97
+ };
98
+ }
99
+
41
100
  /**
42
- * Responsive layout container that automatically arranges its children.
101
+ * Responsive layout container powered by `@pixi/layout` (Yoga flexbox engine).
43
102
  *
44
103
  * Supports horizontal, vertical, grid, and wrap layout modes with
45
104
  * alignment, padding, gap, and viewport-anchor positioning.
@@ -62,17 +121,15 @@ export interface LayoutConfig {
62
121
  * toolbar.addItem(betLabel);
63
122
  * scene.container.addChild(toolbar);
64
123
  *
65
- * // On resize, update layout position relative to viewport
66
124
  * toolbar.updateViewport(width, height);
67
125
  * ```
68
126
  */
69
127
  export class Layout extends Container {
70
- private _config: Required<Pick<LayoutConfig, 'direction' | 'gap' | 'alignment' | 'autoLayout' | 'columns'>>;
128
+ private _layoutConfig: Required<Pick<LayoutConfig, 'direction' | 'gap' | 'alignment' | 'autoLayout' | 'columns'>>;
71
129
  private _padding: [number, number, number, number];
72
130
  private _anchor: LayoutAnchor;
73
131
  private _maxWidth: number;
74
132
  private _breakpoints: [number, Partial<LayoutConfig>][];
75
- private _content: Container;
76
133
  private _items: Container[] = [];
77
134
  private _viewportWidth = 0;
78
135
  private _viewportHeight = 0;
@@ -80,7 +137,7 @@ export class Layout extends Container {
80
137
  constructor(config: LayoutConfig = {}) {
81
138
  super();
82
139
 
83
- this._config = {
140
+ this._layoutConfig = {
84
141
  direction: config.direction ?? 'vertical',
85
142
  gap: config.gap ?? 0,
86
143
  alignment: config.alignment ?? 'start',
@@ -88,26 +145,28 @@ export class Layout extends Container {
88
145
  columns: config.columns ?? 2,
89
146
  };
90
147
 
91
- this._padding = Layout.normalizePadding(config.padding ?? 0);
148
+ this._padding = normalizePadding(config.padding ?? 0);
92
149
  this._anchor = config.anchor ?? 'top-left';
93
150
  this._maxWidth = config.maxWidth ?? Infinity;
94
151
 
95
- // Sort breakpoints by width ascending for correct resolution
96
152
  this._breakpoints = config.breakpoints
97
153
  ? Object.entries(config.breakpoints)
98
154
  .map(([w, cfg]) => [Number(w), cfg] as [number, Partial<LayoutConfig>])
99
155
  .sort((a, b) => a[0] - b[0])
100
156
  : [];
101
157
 
102
- this._content = new Container();
103
- this.addChild(this._content);
158
+ this.applyLayoutStyles();
104
159
  }
105
160
 
106
161
  /** Add an item to the layout */
107
162
  addItem(child: Container): this {
108
163
  this._items.push(child);
109
- this._content.addChild(child);
110
- if (this._config.autoLayout) this.layout();
164
+ this.addChild(child);
165
+
166
+ if (this._layoutConfig.direction === 'grid') {
167
+ this.applyGridChildWidth(child);
168
+ }
169
+
111
170
  return this;
112
171
  }
113
172
 
@@ -116,8 +175,7 @@ export class Layout extends Container {
116
175
  const idx = this._items.indexOf(child);
117
176
  if (idx !== -1) {
118
177
  this._items.splice(idx, 1);
119
- this._content.removeChild(child);
120
- if (this._config.autoLayout) this.layout();
178
+ this.removeChild(child);
121
179
  }
122
180
  return this;
123
181
  }
@@ -125,10 +183,9 @@ export class Layout extends Container {
125
183
  /** Remove all items */
126
184
  clearItems(): this {
127
185
  for (const item of this._items) {
128
- this._content.removeChild(item);
186
+ this.removeChild(item);
129
187
  }
130
188
  this._items.length = 0;
131
- if (this._config.autoLayout) this.layout();
132
189
  return this;
133
190
  }
134
191
 
@@ -144,192 +201,74 @@ export class Layout extends Container {
144
201
  updateViewport(width: number, height: number): void {
145
202
  this._viewportWidth = width;
146
203
  this._viewportHeight = height;
147
- this.layout();
204
+ this.applyLayoutStyles();
205
+ this.applyAnchor();
148
206
  }
149
207
 
150
- /**
151
- * Recalculate layout positions of all children.
152
- */
153
- layout(): void {
154
- if (this._items.length === 0) return;
155
-
156
- // Resolve effective config (apply breakpoint overrides)
208
+ private applyLayoutStyles(): void {
157
209
  const effective = this.resolveConfig();
158
- const gap = effective.gap ?? this._config.gap;
159
- const direction = effective.direction ?? this._config.direction;
160
- const alignment = effective.alignment ?? this._config.alignment;
161
- const columns = effective.columns ?? this._config.columns;
210
+ const direction = effective.direction ?? this._layoutConfig.direction;
211
+ const gap = effective.gap ?? this._layoutConfig.gap;
212
+ const alignment = effective.alignment ?? this._layoutConfig.alignment;
213
+ const columns = effective.columns ?? this._layoutConfig.columns;
162
214
  const padding = effective.padding !== undefined
163
- ? Layout.normalizePadding(effective.padding)
215
+ ? normalizePadding(effective.padding)
164
216
  : this._padding;
165
217
  const maxWidth = effective.maxWidth ?? this._maxWidth;
166
218
 
167
- const [pt, pr, pb, pl] = padding;
168
-
169
- switch (direction) {
170
- case 'horizontal':
171
- this.layoutLinear('x', 'y', gap, alignment, pl, pt);
172
- break;
173
- case 'vertical':
174
- this.layoutLinear('y', 'x', gap, alignment, pt, pl);
175
- break;
176
- case 'grid':
177
- this.layoutGrid(columns, gap, alignment, pl, pt);
178
- break;
179
- case 'wrap':
180
- this.layoutWrap(maxWidth - pl - pr, gap, alignment, pl, pt);
181
- break;
182
- }
219
+ const styles = buildLayoutStyles({ direction, gap, alignment, columns, padding, maxWidth });
220
+ this.layout = styles;
183
221
 
184
- // Apply anchor positioning relative to viewport
185
- this.applyAnchor(effective.anchor ?? this._anchor);
186
- }
187
-
188
- // ─── Private layout helpers ────────────────────────────
189
-
190
- private layoutLinear(
191
- mainAxis: 'x' | 'y',
192
- crossAxis: 'x' | 'y',
193
- gap: number,
194
- alignment: LayoutAlignment,
195
- mainOffset: number,
196
- crossOffset: number,
197
- ): void {
198
- let pos = mainOffset;
199
- const sizes = this._items.map(item => this.getItemSize(item));
200
- const maxCross = Math.max(...sizes.map(s => (crossAxis === 'x' ? s.width : s.height)));
201
-
202
- for (let i = 0; i < this._items.length; i++) {
203
- const item = this._items[i];
204
- const size = sizes[i];
205
-
206
- item[mainAxis] = pos;
207
-
208
- // Cross-axis alignment
209
- const itemCross = crossAxis === 'x' ? size.width : size.height;
210
- switch (alignment) {
211
- case 'start':
212
- item[crossAxis] = crossOffset;
213
- break;
214
- case 'center':
215
- item[crossAxis] = crossOffset + (maxCross - itemCross) / 2;
216
- break;
217
- case 'end':
218
- item[crossAxis] = crossOffset + maxCross - itemCross;
219
- break;
220
- case 'stretch':
221
- item[crossAxis] = crossOffset;
222
- // Note: stretch doesn't resize children — that's up to the item
223
- break;
224
- }
225
-
226
- const mainSize = mainAxis === 'x' ? size.width : size.height;
227
- pos += mainSize + gap;
228
- }
229
- }
230
-
231
- private layoutGrid(
232
- columns: number,
233
- gap: number,
234
- alignment: LayoutAlignment,
235
- offsetX: number,
236
- offsetY: number,
237
- ): void {
238
- const sizes = this._items.map(item => this.getItemSize(item));
239
- const maxItemWidth = Math.max(...sizes.map(s => s.width));
240
- const maxItemHeight = Math.max(...sizes.map(s => s.height));
241
- const cellW = maxItemWidth + gap;
242
- const cellH = maxItemHeight + gap;
243
-
244
- for (let i = 0; i < this._items.length; i++) {
245
- const item = this._items[i];
246
- const col = i % columns;
247
- const row = Math.floor(i / columns);
248
- const size = sizes[i];
249
-
250
- // X alignment within cell
251
- switch (alignment) {
252
- case 'center':
253
- item.x = offsetX + col * cellW + (maxItemWidth - size.width) / 2;
254
- break;
255
- case 'end':
256
- item.x = offsetX + col * cellW + maxItemWidth - size.width;
257
- break;
258
- default:
259
- item.x = offsetX + col * cellW;
222
+ if (direction === 'grid') {
223
+ for (const item of this._items) {
224
+ this.applyGridChildWidth(item);
260
225
  }
261
-
262
- item.y = offsetY + row * cellH;
263
226
  }
264
227
  }
265
228
 
266
- private layoutWrap(
267
- maxWidth: number,
268
- gap: number,
269
- alignment: LayoutAlignment,
270
- offsetX: number,
271
- offsetY: number,
272
- ): void {
273
- let x = offsetX;
274
- let y = offsetY;
275
- let rowHeight = 0;
276
- const sizes = this._items.map(item => this.getItemSize(item));
277
-
278
- for (let i = 0; i < this._items.length; i++) {
279
- const item = this._items[i];
280
- const size = sizes[i];
281
-
282
- // Check if item fits in current row
283
- if (x + size.width > maxWidth + offsetX && x > offsetX) {
284
- // Wrap to next row
285
- x = offsetX;
286
- y += rowHeight + gap;
287
- rowHeight = 0;
288
- }
289
-
290
- item.x = x;
291
- item.y = y;
229
+ private applyGridChildWidth(child: Container): void {
230
+ const effective = this.resolveConfig();
231
+ const columns = effective.columns ?? this._layoutConfig.columns;
292
232
 
293
- x += size.width + gap;
294
- rowHeight = Math.max(rowHeight, size.height);
233
+ const pct = `${(100 / columns).toFixed(2)}%` as `${number}%`;
234
+ if (child._layout) {
235
+ child._layout.setStyle({ width: pct });
236
+ } else {
237
+ child.layout = { width: pct };
295
238
  }
296
239
  }
297
240
 
298
- private applyAnchor(anchor: LayoutAnchor): void {
241
+ private applyAnchor(): void {
242
+ const anchor = this.resolveConfig().anchor ?? this._anchor;
299
243
  if (this._viewportWidth === 0 || this._viewportHeight === 0) return;
300
244
 
301
- const bounds = this._content.getBounds();
302
- const contentW = bounds.width;
303
- const contentH = bounds.height;
245
+ const bounds = this.getLocalBounds();
246
+ const contentW = bounds.width * this.scale.x;
247
+ const contentH = bounds.height * this.scale.y;
304
248
  const vw = this._viewportWidth;
305
249
  const vh = this._viewportHeight;
306
250
 
307
251
  let anchorX = 0;
308
252
  let anchorY = 0;
309
253
 
310
- // Horizontal
311
254
  if (anchor.includes('left')) {
312
255
  anchorX = 0;
313
256
  } else if (anchor.includes('right')) {
314
257
  anchorX = vw - contentW;
315
258
  } else {
316
- // center
317
259
  anchorX = (vw - contentW) / 2;
318
260
  }
319
261
 
320
- // Vertical
321
262
  if (anchor.startsWith('top')) {
322
263
  anchorY = 0;
323
264
  } else if (anchor.startsWith('bottom')) {
324
265
  anchorY = vh - contentH;
325
266
  } else {
326
- // center
327
267
  anchorY = (vh - contentH) / 2;
328
268
  }
329
269
 
330
- // Compensate for content's local bounds offset
331
- this.x = anchorX - bounds.x;
332
- this.y = anchorY - bounds.y;
270
+ this.x = anchorX - bounds.x * this.scale.x;
271
+ this.y = anchorY - bounds.y * this.scale.y;
333
272
  }
334
273
 
335
274
  private resolveConfig(): Partial<LayoutConfig> {
@@ -337,28 +276,11 @@ export class Layout extends Container {
337
276
  return {};
338
277
  }
339
278
 
340
- // Find the largest breakpoint that's ≤ current viewport width
341
- let resolved: Partial<LayoutConfig> = {};
342
279
  for (const [maxWidth, overrides] of this._breakpoints) {
343
280
  if (this._viewportWidth <= maxWidth) {
344
- resolved = overrides;
345
- break;
281
+ return overrides;
346
282
  }
347
283
  }
348
- return resolved;
349
- }
350
-
351
- private getItemSize(item: Container): { width: number; height: number } {
352
- const bounds = item.getBounds();
353
- return { width: bounds.width, height: bounds.height };
354
- }
355
-
356
- private static normalizePadding(
357
- padding: number | [number, number, number, number],
358
- ): [number, number, number, number] {
359
- if (typeof padding === 'number') {
360
- return [padding, padding, padding, padding];
361
- }
362
- return padding;
284
+ return {};
363
285
  }
364
286
  }
package/src/ui/Modal.ts CHANGED
@@ -17,6 +17,8 @@ export interface ModalConfig {
17
17
  * Modal overlay component.
18
18
  * Shows content on top of a dark overlay with enter/exit animations.
19
19
  *
20
+ * The content container uses `@pixi/layout` for automatic centering.
21
+ *
20
22
  * @example
21
23
  * ```ts
22
24
  * const modal = new Modal({ closeOnOverlay: true });
@@ -38,11 +40,10 @@ export class Modal extends Container {
38
40
  super();
39
41
 
40
42
  this._config = {
41
- overlayColor: 0x000000,
42
- overlayAlpha: 0.7,
43
- closeOnOverlay: true,
44
- animationDuration: 300,
45
- ...config,
43
+ overlayColor: config.overlayColor ?? 0x000000,
44
+ overlayAlpha: config.overlayAlpha ?? 0.7,
45
+ closeOnOverlay: config.closeOnOverlay ?? true,
46
+ animationDuration: config.animationDuration ?? 300,
46
47
  };
47
48
 
48
49
  // Overlay