@energy8platform/game-engine 0.10.10 → 0.11.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 (49) hide show
  1. package/README.md +185 -74
  2. package/dist/index.cjs.js +1280 -296
  3. package/dist/index.cjs.js.map +1 -1
  4. package/dist/index.d.ts +362 -46
  5. package/dist/index.esm.js +1281 -298
  6. package/dist/index.esm.js.map +1 -1
  7. package/dist/lua.cjs.js +16 -21
  8. package/dist/lua.cjs.js.map +1 -1
  9. package/dist/lua.d.ts +0 -2
  10. package/dist/lua.esm.js +16 -21
  11. package/dist/lua.esm.js.map +1 -1
  12. package/dist/react.cjs.js +2372 -11
  13. package/dist/react.cjs.js.map +1 -1
  14. package/dist/react.d.ts +17 -6
  15. package/dist/react.esm.js +2372 -12
  16. package/dist/react.esm.js.map +1 -1
  17. package/dist/ui.cjs.js +1553 -632
  18. package/dist/ui.cjs.js.map +1 -1
  19. package/dist/ui.d.ts +374 -46
  20. package/dist/ui.esm.js +1553 -634
  21. package/dist/ui.esm.js.map +1 -1
  22. package/dist/vite.cjs.js +1 -11
  23. package/dist/vite.cjs.js.map +1 -1
  24. package/dist/vite.d.ts +1 -1
  25. package/dist/vite.esm.js +1 -11
  26. package/dist/vite.esm.js.map +1 -1
  27. package/package.json +3 -18
  28. package/src/index.ts +3 -3
  29. package/src/lua/LuaEngine.ts +8 -18
  30. package/src/lua/SimulationRunner.ts +7 -3
  31. package/src/react/applyProps.ts +86 -0
  32. package/src/react/extendAll.ts +27 -6
  33. package/src/react/index.ts +1 -1
  34. package/src/react/jsx.d.ts +222 -0
  35. package/src/react/reconciler.ts +22 -5
  36. package/src/ui/BalanceDisplay.ts +31 -38
  37. package/src/ui/Button.ts +217 -53
  38. package/src/ui/FlexContainer.ts +479 -0
  39. package/src/ui/Label.ts +13 -0
  40. package/src/ui/Layout.ts +86 -87
  41. package/src/ui/Modal.ts +11 -1
  42. package/src/ui/Panel.ts +108 -36
  43. package/src/ui/ProgressBar.ts +85 -31
  44. package/src/ui/ScrollContainer.ts +397 -45
  45. package/src/ui/Toast.ts +47 -17
  46. package/src/ui/WinDisplay.ts +51 -39
  47. package/src/ui/index.ts +5 -11
  48. package/src/ui/view.ts +28 -0
  49. package/src/vite/index.ts +1 -11
package/src/ui/Button.ts CHANGED
@@ -1,13 +1,22 @@
1
- import { Graphics, Texture } from 'pixi.js';
2
- import { FancyButton } from '@pixi/ui';
3
- import type { ButtonOptions } from '@pixi/ui';
1
+ import { Container, Graphics, Text } from 'pixi.js';
2
+ import { Tween } from '../animation/Tween';
3
+ import { Easing } from '../animation/Easing';
4
+ import { resolveView } from './view';
5
+ import type { ViewInput } from './view';
4
6
 
5
7
  export type ButtonState = 'default' | 'hover' | 'pressed' | 'disabled';
6
8
 
7
9
  export interface ButtonConfig {
8
- /** Default texture/sprite for each state (optional uses Graphics if not provided) */
9
- textures?: Partial<Record<ButtonState, string | Texture>>;
10
- /** Width (for Graphics-based button) */
10
+ /** Custom view for default state (string texture name, Texture, or Container) */
11
+ defaultView?: ViewInput;
12
+ /** Custom view for hover state */
13
+ hoverView?: ViewInput;
14
+ /** Custom view for pressed state */
15
+ pressedView?: ViewInput;
16
+ /** Custom view for disabled state */
17
+ disabledView?: ViewInput;
18
+
19
+ /** Width (for Graphics-based button, ignored when custom views provided) */
11
20
  width?: number;
12
21
  /** Height (for Graphics-based button) */
13
22
  height?: number;
@@ -15,16 +24,19 @@ export interface ButtonConfig {
15
24
  borderRadius?: number;
16
25
  /** Colors for each state (for Graphics-based button) */
17
26
  colors?: Partial<Record<ButtonState, number>>;
27
+
18
28
  /** Scale on press */
19
29
  pressScale?: number;
20
30
  /** Scale animation duration (ms) */
21
31
  animationDuration?: number;
22
32
  /** Start disabled */
23
33
  disabled?: boolean;
24
- /** Button text */
34
+ /** Button text (rendered on top of the view) */
25
35
  text?: string;
26
36
  /** Button text style */
27
37
  textStyle?: Record<string, unknown>;
38
+ /** Press callback */
39
+ onPress?: () => void;
28
40
  }
29
41
 
30
42
  const DEFAULT_COLORS: Record<ButtonState, number> = {
@@ -38,37 +50,62 @@ function makeGraphicsView(
38
50
  w: number, h: number, radius: number, color: number,
39
51
  ): Graphics {
40
52
  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 });
53
+ g.roundRect(-w / 2, -h / 2, w, h, radius).fill(color);
44
54
  return g;
45
55
  }
46
56
 
47
57
  /**
48
- * Interactive button component powered by `@pixi/ui` FancyButton.
58
+ * Interactive button with per-state custom views and animations.
49
59
  *
50
- * Supports both texture-based and Graphics-based rendering with
51
- * per-state views, press animation, and text.
60
+ * Each visual state accepts a `ViewInput`: texture name, Texture, or any Container
61
+ * (Sprite, NineSliceSprite, AnimatedSprite, custom artwork, etc).
62
+ * Falls back to colored Graphics when no custom view is provided.
52
63
  *
53
64
  * @example
54
65
  * ```ts
66
+ * // Graphics-based (quick prototyping)
55
67
  * const btn = new Button({
56
68
  * width: 200, height: 60, borderRadius: 12,
57
69
  * colors: { default: 0x22aa22, hover: 0x33cc33 },
58
70
  * text: 'SPIN',
71
+ * onPress: () => spin(),
72
+ * });
73
+ *
74
+ * // Asset-based (production art)
75
+ * const btn = new Button({
76
+ * defaultView: 'btn-idle',
77
+ * hoverView: 'btn-hover',
78
+ * pressedView: 'btn-pressed',
79
+ * disabledView: 'btn-disabled',
80
+ * text: 'SPIN',
81
+ * onPress: () => spin(),
59
82
  * });
60
83
  *
61
- * btn.onPress.connect(() => console.log('Clicked!'));
62
- * scene.container.addChild(btn);
84
+ * // Custom Container view
85
+ * const btn = new Button({
86
+ * defaultView: myAnimatedSprite,
87
+ * text: 'SPIN',
88
+ * });
63
89
  * ```
64
90
  */
65
- export class Button extends FancyButton {
66
- private _buttonConfig: Required<
91
+ export class Button extends Container {
92
+ readonly __uiComponent = true as const;
93
+
94
+ private _views: Map<ButtonState, Container> = new Map();
95
+ private _state: ButtonState = 'default';
96
+ private _enabled = true;
97
+ private _config: Required<
67
98
  Pick<ButtonConfig, 'width' | 'height' | 'borderRadius' | 'pressScale' | 'animationDuration'>
68
99
  > & ButtonConfig;
100
+ private _textObj: Text | null = null;
101
+
102
+ /** Press callback */
103
+ public onPress?: () => void;
69
104
 
70
105
  constructor(config: ButtonConfig = {}) {
71
- const resolvedConfig = {
106
+ super();
107
+
108
+ this._config = {
72
109
  width: config.width ?? 200,
73
110
  height: config.height ?? 60,
74
111
  borderRadius: config.borderRadius ?? 8,
@@ -77,52 +114,45 @@ export class Button extends FancyButton {
77
114
  ...config,
78
115
  };
79
116
 
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
- };
97
-
98
- // Texture-based views
99
- if (config.textures) {
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);
110
- }
117
+ this.onPress = config.onPress;
118
+ this._buildViews(config);
111
119
 
112
120
  // Text
113
121
  if (config.text) {
114
- options.text = config.text;
122
+ this._textObj = new Text({
123
+ text: config.text,
124
+ style: {
125
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
126
+ fontSize: 20,
127
+ fill: 0xffffff,
128
+ fontWeight: 'bold',
129
+ ...config.textStyle,
130
+ },
131
+ });
132
+ this._textObj.anchor.set(0.5);
133
+ this.addChild(this._textObj);
115
134
  }
116
135
 
117
- super(options);
136
+ // Interaction
137
+ this.eventMode = 'static';
138
+ this.cursor = 'pointer';
118
139
 
119
- this._buttonConfig = resolvedConfig;
140
+ this.on('pointerover', this._onPointerOver, this);
141
+ this.on('pointerout', this._onPointerOut, this);
142
+ this.on('pointerdown', this._onPointerDown, this);
143
+ this.on('pointerup', this._onPointerUp, this);
144
+ this.on('pointerupoutside', this._onPointerUpOutside, this);
120
145
 
121
146
  if (config.disabled) {
122
147
  this.enabled = false;
123
148
  }
124
149
  }
125
150
 
151
+ /** Current button state */
152
+ get state(): ButtonState {
153
+ return this._state;
154
+ }
155
+
126
156
  /** Enable the button */
127
157
  enable(): void {
128
158
  this.enabled = true;
@@ -133,8 +163,142 @@ export class Button extends FancyButton {
133
163
  this.enabled = false;
134
164
  }
135
165
 
166
+ /** Whether the button is enabled */
167
+ get enabled(): boolean {
168
+ return this._enabled;
169
+ }
170
+
171
+ set enabled(value: boolean) {
172
+ this._enabled = value;
173
+ this.cursor = value ? 'pointer' : 'default';
174
+ this.eventMode = value ? 'static' : 'none';
175
+ this._setState(value ? 'default' : 'disabled');
176
+ }
177
+
136
178
  /** Whether the button is disabled */
137
179
  get disabled(): boolean {
138
- return !this.enabled;
180
+ return !this._enabled;
181
+ }
182
+
183
+ /** Update button text */
184
+ set text(value: string) {
185
+ if (this._textObj) {
186
+ this._textObj.text = value;
187
+ }
188
+ }
189
+
190
+ // ─── View building ──────────────────────────────────
191
+
192
+ private _buildViews(config: ButtonConfig): void {
193
+ const colorMap = { ...DEFAULT_COLORS, ...config.colors };
194
+ const { width, height, borderRadius } = this._config;
195
+ const stateViews: Record<ButtonState, ViewInput | undefined> = {
196
+ default: config.defaultView,
197
+ hover: config.hoverView,
198
+ pressed: config.pressedView,
199
+ disabled: config.disabledView,
200
+ };
201
+
202
+ const states: ButtonState[] = ['default', 'hover', 'pressed', 'disabled'];
203
+
204
+ for (const state of states) {
205
+ const customView = resolveView(stateViews[state]);
206
+ const view = customView ?? makeGraphicsView(width, height, borderRadius, colorMap[state]);
207
+ view.visible = state === 'default';
208
+ this._views.set(state, view);
209
+ this.addChild(view);
210
+ }
211
+ }
212
+
213
+ private _rebuildViews(): void {
214
+ for (const [, view] of this._views) {
215
+ this.removeChild(view);
216
+ view.destroy();
217
+ }
218
+ this._views.clear();
219
+
220
+ this._buildViews(this._config);
221
+
222
+ // Re-insert views before text
223
+ if (this._textObj && this._textObj.parent === this) {
224
+ this.setChildIndex(this._textObj, this.children.length - 1);
225
+ }
226
+ }
227
+
228
+ // ─── State management ───────────────────────────────
229
+
230
+ private _setState(state: ButtonState): void {
231
+ if (this._state === state) return;
232
+ this._state = state;
233
+
234
+ for (const [s, view] of this._views) {
235
+ view.visible = s === state;
236
+ }
237
+ }
238
+
239
+ private _onPointerOver(): void {
240
+ if (!this._enabled) return;
241
+ this._setState('hover');
242
+ Tween.killTweensOf(this);
243
+ Tween.to(this, { 'scale.x': 1.03, 'scale.y': 1.03 }, this._config.animationDuration, Easing.easeOutQuad);
244
+ }
245
+
246
+ private _onPointerOut(): void {
247
+ if (!this._enabled) return;
248
+ this._setState('default');
249
+ Tween.killTweensOf(this);
250
+ Tween.to(this, { 'scale.x': 1, 'scale.y': 1 }, this._config.animationDuration, Easing.easeOutQuad);
251
+ }
252
+
253
+ private _onPointerDown(): void {
254
+ if (!this._enabled) return;
255
+ this._setState('pressed');
256
+ Tween.killTweensOf(this);
257
+ const s = this._config.pressScale;
258
+ Tween.to(this, { 'scale.x': s, 'scale.y': s }, this._config.animationDuration, Easing.easeOutQuad);
259
+ }
260
+
261
+ private _onPointerUp(): void {
262
+ if (!this._enabled) return;
263
+ this._setState('hover');
264
+ Tween.killTweensOf(this);
265
+ Tween.to(this, { 'scale.x': 1.03, 'scale.y': 1.03 }, this._config.animationDuration, Easing.easeOutQuad);
266
+ this.onPress?.();
267
+ }
268
+
269
+ private _onPointerUpOutside(): void {
270
+ if (!this._enabled) return;
271
+ this._setState('default');
272
+ Tween.killTweensOf(this);
273
+ Tween.to(this, { 'scale.x': 1, 'scale.y': 1 }, this._config.animationDuration, Easing.easeOutQuad);
274
+ }
275
+
276
+ /** React reconciler update hook */
277
+ updateConfig(changed: Record<string, any>): void {
278
+ if ('text' in changed && this._textObj) this._textObj.text = changed.text;
279
+ if ('disabled' in changed) this.enabled = !changed.disabled;
280
+ if ('onPress' in changed) this.onPress = changed.onPress;
281
+
282
+ const structural = [
283
+ 'colors', 'width', 'height', 'borderRadius', 'textStyle',
284
+ 'defaultView', 'hoverView', 'pressedView', 'disabledView',
285
+ ];
286
+ const needsRebuild = structural.some((k) => k in changed);
287
+ if (needsRebuild) {
288
+ Object.assign(this._config, changed);
289
+ this._rebuildViews();
290
+ }
291
+ }
292
+
293
+ override destroy(options?: boolean | { children?: boolean; texture?: boolean; textureSource?: boolean }): void {
294
+ Tween.killTweensOf(this);
295
+ this.off('pointerover', this._onPointerOver, this);
296
+ this.off('pointerout', this._onPointerOut, this);
297
+ this.off('pointerdown', this._onPointerDown, this);
298
+ this.off('pointerup', this._onPointerUp, this);
299
+ this.off('pointerupoutside', this._onPointerUpOutside, this);
300
+ this._views.clear();
301
+ this._textObj = null;
302
+ super.destroy(options);
139
303
  }
140
304
  }