@energy8platform/game-engine 0.10.11 → 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.
- package/README.md +185 -74
- package/dist/index.cjs.js +1280 -296
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +362 -46
- package/dist/index.esm.js +1281 -298
- package/dist/index.esm.js.map +1 -1
- package/dist/lua.cjs.js +8 -18
- package/dist/lua.cjs.js.map +1 -1
- package/dist/lua.d.ts +0 -2
- package/dist/lua.esm.js +8 -18
- package/dist/lua.esm.js.map +1 -1
- package/dist/react.cjs.js +2372 -11
- package/dist/react.cjs.js.map +1 -1
- package/dist/react.d.ts +17 -6
- package/dist/react.esm.js +2372 -12
- package/dist/react.esm.js.map +1 -1
- package/dist/ui.cjs.js +1553 -632
- package/dist/ui.cjs.js.map +1 -1
- package/dist/ui.d.ts +374 -46
- package/dist/ui.esm.js +1553 -634
- package/dist/ui.esm.js.map +1 -1
- package/dist/vite.cjs.js +1 -11
- package/dist/vite.cjs.js.map +1 -1
- package/dist/vite.d.ts +1 -1
- package/dist/vite.esm.js +1 -11
- package/dist/vite.esm.js.map +1 -1
- package/package.json +3 -18
- package/src/index.ts +3 -3
- package/src/lua/LuaEngine.ts +8 -18
- package/src/react/applyProps.ts +86 -0
- package/src/react/extendAll.ts +27 -6
- package/src/react/index.ts +1 -1
- package/src/react/jsx.d.ts +222 -0
- package/src/react/reconciler.ts +22 -5
- package/src/ui/BalanceDisplay.ts +31 -38
- package/src/ui/Button.ts +217 -53
- package/src/ui/FlexContainer.ts +479 -0
- package/src/ui/Label.ts +13 -0
- package/src/ui/Layout.ts +86 -87
- package/src/ui/Modal.ts +11 -1
- package/src/ui/Panel.ts +108 -36
- package/src/ui/ProgressBar.ts +85 -31
- package/src/ui/ScrollContainer.ts +397 -45
- package/src/ui/Toast.ts +47 -17
- package/src/ui/WinDisplay.ts +51 -39
- package/src/ui/index.ts +5 -11
- package/src/ui/view.ts +28 -0
- package/src/vite/index.ts +1 -11
package/src/ui/Button.ts
CHANGED
|
@@ -1,13 +1,22 @@
|
|
|
1
|
-
import { Graphics,
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
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
|
-
/**
|
|
9
|
-
|
|
10
|
-
/**
|
|
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(
|
|
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
|
|
58
|
+
* Interactive button with per-state custom views and animations.
|
|
49
59
|
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
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
|
-
*
|
|
62
|
-
*
|
|
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
|
|
66
|
-
|
|
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
|
-
|
|
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
|
-
|
|
81
|
-
|
|
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
|
-
|
|
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
|
-
|
|
136
|
+
// Interaction
|
|
137
|
+
this.eventMode = 'static';
|
|
138
|
+
this.cursor = 'pointer';
|
|
118
139
|
|
|
119
|
-
this.
|
|
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.
|
|
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
|
}
|