@energy8platform/game-engine 0.10.11 → 0.12.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 +272 -74
- package/dist/index.cjs.js +1322 -296
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +369 -46
- package/dist/index.esm.js +1323 -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 +2848 -35
- package/dist/react.cjs.js.map +1 -1
- package/dist/react.d.ts +17 -6
- package/dist/react.esm.js +2848 -36
- package/dist/react.esm.js.map +1 -1
- package/dist/ui.cjs.js +1913 -592
- package/dist/ui.cjs.js.map +1 -1
- package/dist/ui.d.ts +528 -46
- package/dist/ui.esm.js +1911 -594
- 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 +90 -2
- package/src/react/extendAll.ts +29 -6
- package/src/react/index.ts +1 -1
- package/src/react/jsx.d.ts +249 -0
- package/src/react/reconciler.ts +80 -7
- package/src/ui/BalanceDisplay.ts +31 -38
- package/src/ui/Button.ts +217 -53
- package/src/ui/FlexContainer.ts +529 -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/Slider.ts +241 -0
- package/src/ui/Toast.ts +47 -17
- package/src/ui/Toggle.ts +201 -0
- package/src/ui/WinDisplay.ts +51 -39
- package/src/ui/index.ts +9 -11
- package/src/ui/view.ts +28 -0
- package/src/vite/index.ts +1 -11
package/src/react/applyProps.ts
CHANGED
|
@@ -1,4 +1,92 @@
|
|
|
1
1
|
const RESERVED = new Set(['children', 'key', 'ref']);
|
|
2
|
+
/** Props handled by the reconciler as flex item config, not forwarded to components */
|
|
3
|
+
const FLEX_ITEM_PROPS = new Set(['flexGrow', 'flexShrink', 'layoutWidth', 'layoutHeight', 'alignSelf', 'flexExclude']);
|
|
4
|
+
|
|
5
|
+
// ─── UI Component helpers ────────────────────────────────
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Extract a config object from React props.
|
|
9
|
+
* - Strips reserved keys (children, key, ref) and event props
|
|
10
|
+
* - Unfolds dash-notation into nested objects: `colors-default` → `{ colors: { default: ... } }`
|
|
11
|
+
*/
|
|
12
|
+
export function extractConfig(props: Record<string, any>): Record<string, any> {
|
|
13
|
+
const config: Record<string, any> = {};
|
|
14
|
+
|
|
15
|
+
for (const key in props) {
|
|
16
|
+
if (RESERVED.has(key) || FLEX_ITEM_PROPS.has(key) || isEventProp(key)) continue;
|
|
17
|
+
|
|
18
|
+
if (key.includes('-')) {
|
|
19
|
+
const parts = key.split('-');
|
|
20
|
+
const root = parts[0];
|
|
21
|
+
const nested = parts.slice(1).join('-');
|
|
22
|
+
if (!config[root] || typeof config[root] !== 'object') {
|
|
23
|
+
config[root] = {};
|
|
24
|
+
}
|
|
25
|
+
config[root][nested] = props[key];
|
|
26
|
+
} else {
|
|
27
|
+
config[key] = props[key];
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return config;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Diff two prop sets and return a config object with only changed values.
|
|
36
|
+
* Uses extractConfig format (dash-notation unfolded).
|
|
37
|
+
*/
|
|
38
|
+
export function diffConfig(
|
|
39
|
+
newProps: Record<string, any>,
|
|
40
|
+
oldProps: Record<string, any>,
|
|
41
|
+
): Record<string, any> {
|
|
42
|
+
const changed: Record<string, any> = {};
|
|
43
|
+
|
|
44
|
+
// New or changed props
|
|
45
|
+
for (const key in newProps) {
|
|
46
|
+
if (RESERVED.has(key) || FLEX_ITEM_PROPS.has(key) || isEventProp(key)) continue;
|
|
47
|
+
if (newProps[key] !== oldProps[key]) {
|
|
48
|
+
if (key.includes('-')) {
|
|
49
|
+
const parts = key.split('-');
|
|
50
|
+
const root = parts[0];
|
|
51
|
+
const nested = parts.slice(1).join('-');
|
|
52
|
+
if (!changed[root] || typeof changed[root] !== 'object') {
|
|
53
|
+
changed[root] = {};
|
|
54
|
+
}
|
|
55
|
+
changed[root][nested] = newProps[key];
|
|
56
|
+
} else {
|
|
57
|
+
changed[key] = newProps[key];
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return changed;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Apply only event props from React props to a PixiJS instance.
|
|
67
|
+
*/
|
|
68
|
+
export function applyEventProps(
|
|
69
|
+
instance: any,
|
|
70
|
+
newProps: Record<string, any>,
|
|
71
|
+
oldProps: Record<string, any> = {},
|
|
72
|
+
): void {
|
|
73
|
+
// Remove old event handlers
|
|
74
|
+
for (const key in oldProps) {
|
|
75
|
+
if (!isEventProp(key) || key in newProps) continue;
|
|
76
|
+
instance[REACT_TO_PIXI_EVENTS[key]] = null;
|
|
77
|
+
}
|
|
78
|
+
// Apply new/changed event handlers + onPress (component-level callback)
|
|
79
|
+
for (const key in newProps) {
|
|
80
|
+
if (key === 'onPress') {
|
|
81
|
+
instance.onPress = newProps[key];
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (!isEventProp(key)) continue;
|
|
85
|
+
if (newProps[key] !== oldProps[key]) {
|
|
86
|
+
instance[REACT_TO_PIXI_EVENTS[key]] = newProps[key];
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
2
90
|
|
|
3
91
|
const REACT_TO_PIXI_EVENTS: Record<string, string> = {
|
|
4
92
|
onClick: 'onclick',
|
|
@@ -63,7 +151,7 @@ export function applyProps(
|
|
|
63
151
|
): void {
|
|
64
152
|
// Remove old props not in newProps
|
|
65
153
|
for (const key in oldProps) {
|
|
66
|
-
if (RESERVED.has(key) || key in newProps) continue;
|
|
154
|
+
if (RESERVED.has(key) || FLEX_ITEM_PROPS.has(key) || key in newProps) continue;
|
|
67
155
|
|
|
68
156
|
const pixiEvent = REACT_TO_PIXI_EVENTS[key];
|
|
69
157
|
if (pixiEvent) {
|
|
@@ -83,7 +171,7 @@ export function applyProps(
|
|
|
83
171
|
|
|
84
172
|
// Apply new props
|
|
85
173
|
for (const key in newProps) {
|
|
86
|
-
if (RESERVED.has(key)) continue;
|
|
174
|
+
if (RESERVED.has(key) || FLEX_ITEM_PROPS.has(key)) continue;
|
|
87
175
|
|
|
88
176
|
const value = newProps[key];
|
|
89
177
|
const pixiEvent = REACT_TO_PIXI_EVENTS[key];
|
package/src/react/extendAll.ts
CHANGED
|
@@ -14,6 +14,11 @@ import {
|
|
|
14
14
|
HTMLText,
|
|
15
15
|
} from 'pixi.js';
|
|
16
16
|
import { extend } from './catalogue';
|
|
17
|
+
import {
|
|
18
|
+
Button, Label, Panel, FlexContainer, ProgressBar,
|
|
19
|
+
ScrollContainer, Modal, Toast, BalanceDisplay, WinDisplay, Layout,
|
|
20
|
+
Slider, Toggle,
|
|
21
|
+
} from '../ui';
|
|
17
22
|
|
|
18
23
|
/**
|
|
19
24
|
* Register all standard PixiJS display objects for JSX use.
|
|
@@ -38,14 +43,32 @@ export function extendPixiElements(): void {
|
|
|
38
43
|
}
|
|
39
44
|
|
|
40
45
|
/**
|
|
41
|
-
* Register
|
|
42
|
-
*
|
|
46
|
+
* Register all engine UI components for JSX use.
|
|
47
|
+
* Call once at app startup before rendering React scenes that use UI components.
|
|
43
48
|
*
|
|
49
|
+
* @example
|
|
44
50
|
* ```ts
|
|
45
|
-
*
|
|
46
|
-
*
|
|
51
|
+
* extendPixiElements();
|
|
52
|
+
* extendUIElements();
|
|
53
|
+
*
|
|
54
|
+
* // Now you can use:
|
|
55
|
+
* // <button text="SPIN" onPress={handler} />
|
|
56
|
+
* // <flexContainer direction="row" gap={16}>...</flexContainer>
|
|
57
|
+
* // <label text="Hello" style-fontSize={24} />
|
|
47
58
|
* ```
|
|
48
59
|
*/
|
|
49
|
-
export function
|
|
50
|
-
extend(
|
|
60
|
+
export function extendUIElements(): void {
|
|
61
|
+
extend({
|
|
62
|
+
Button, Label, Panel, FlexContainer, ProgressBar,
|
|
63
|
+
ScrollContainer, Modal, Toast, BalanceDisplay, WinDisplay, Layout,
|
|
64
|
+
Slider, Toggle,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Register additional custom components for JSX use.
|
|
70
|
+
* Pass an object mapping component names to their constructors.
|
|
71
|
+
*/
|
|
72
|
+
export function extendCustomElements(components: Record<string, any>): void {
|
|
73
|
+
extend(components);
|
|
51
74
|
}
|
package/src/react/index.ts
CHANGED
|
@@ -4,7 +4,7 @@ export type { PixiRoot } from './createPixiRoot';
|
|
|
4
4
|
|
|
5
5
|
// Catalogue
|
|
6
6
|
export { extend } from './catalogue';
|
|
7
|
-
export { extendPixiElements,
|
|
7
|
+
export { extendPixiElements, extendUIElements, extendCustomElements } from './extendAll';
|
|
8
8
|
|
|
9
9
|
// Scene
|
|
10
10
|
export { ReactScene } from './ReactScene';
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSX IntrinsicElements type declarations for the PixiJS React reconciler.
|
|
3
|
+
*
|
|
4
|
+
* Provides TypeScript autocompletion and type safety for:
|
|
5
|
+
* - Standard PixiJS elements: <container>, <sprite>, <text>, <graphics>
|
|
6
|
+
* - Engine UI components: <button>, <label>, <panel>, <flexContainer>, etc.
|
|
7
|
+
*
|
|
8
|
+
* Dash-notation is supported for nested config objects:
|
|
9
|
+
* <button colors-default={0xff0000} colors-hover={0x00ff00} />
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { Container, Texture, TextStyle } from 'pixi.js';
|
|
13
|
+
import type { ViewInput } from '../ui/view';
|
|
14
|
+
import type { ButtonConfig, ButtonState } from '../ui/Button';
|
|
15
|
+
import type { LabelConfig } from '../ui/Label';
|
|
16
|
+
import type { PanelConfig } from '../ui/Panel';
|
|
17
|
+
import type { FlexContainerConfig, FlexItemConfig, AlignSelf } from '../ui/FlexContainer';
|
|
18
|
+
import type { ProgressBarConfig } from '../ui/ProgressBar';
|
|
19
|
+
import type { ScrollContainerConfig } from '../ui/ScrollContainer';
|
|
20
|
+
import type { ModalConfig } from '../ui/Modal';
|
|
21
|
+
import type { ToastConfig } from '../ui/Toast';
|
|
22
|
+
import type { BalanceDisplayConfig } from '../ui/BalanceDisplay';
|
|
23
|
+
import type { WinDisplayConfig } from '../ui/WinDisplay';
|
|
24
|
+
import type { LayoutConfig } from '../ui/Layout';
|
|
25
|
+
import type { SliderConfig } from '../ui/Slider';
|
|
26
|
+
import type { ToggleConfig } from '../ui/Toggle';
|
|
27
|
+
|
|
28
|
+
// ─── Event props ─────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
interface PixiEventProps {
|
|
31
|
+
onClick?: (e: any) => void;
|
|
32
|
+
onPointerDown?: (e: any) => void;
|
|
33
|
+
onPointerUp?: (e: any) => void;
|
|
34
|
+
onPointerMove?: (e: any) => void;
|
|
35
|
+
onPointerOver?: (e: any) => void;
|
|
36
|
+
onPointerOut?: (e: any) => void;
|
|
37
|
+
onPointerEnter?: (e: any) => void;
|
|
38
|
+
onPointerLeave?: (e: any) => void;
|
|
39
|
+
onPointerCancel?: (e: any) => void;
|
|
40
|
+
onPointerTap?: (e: any) => void;
|
|
41
|
+
onPointerUpOutside?: (e: any) => void;
|
|
42
|
+
onMouseDown?: (e: any) => void;
|
|
43
|
+
onMouseUp?: (e: any) => void;
|
|
44
|
+
onMouseMove?: (e: any) => void;
|
|
45
|
+
onMouseOver?: (e: any) => void;
|
|
46
|
+
onMouseOut?: (e: any) => void;
|
|
47
|
+
onWheel?: (e: any) => void;
|
|
48
|
+
onTap?: (e: any) => void;
|
|
49
|
+
onRightClick?: (e: any) => void;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ─── Base container props (shared by all elements) ───────
|
|
53
|
+
|
|
54
|
+
interface BaseProps extends PixiEventProps {
|
|
55
|
+
key?: React.Key;
|
|
56
|
+
ref?: React.Ref<any>;
|
|
57
|
+
children?: React.ReactNode;
|
|
58
|
+
|
|
59
|
+
// Container props
|
|
60
|
+
x?: number;
|
|
61
|
+
y?: number;
|
|
62
|
+
width?: number;
|
|
63
|
+
height?: number;
|
|
64
|
+
alpha?: number;
|
|
65
|
+
visible?: boolean;
|
|
66
|
+
rotation?: number;
|
|
67
|
+
angle?: number;
|
|
68
|
+
zIndex?: number;
|
|
69
|
+
label?: string;
|
|
70
|
+
cursor?: string;
|
|
71
|
+
eventMode?: 'auto' | 'none' | 'passive' | 'static' | 'dynamic';
|
|
72
|
+
|
|
73
|
+
// Nested via dash-notation
|
|
74
|
+
'scale-x'?: number;
|
|
75
|
+
'scale-y'?: number;
|
|
76
|
+
'pivot-x'?: number;
|
|
77
|
+
'pivot-y'?: number;
|
|
78
|
+
'position-x'?: number;
|
|
79
|
+
'position-y'?: number;
|
|
80
|
+
'anchor-x'?: number;
|
|
81
|
+
'anchor-y'?: number;
|
|
82
|
+
|
|
83
|
+
// Allow scale as number (uniform)
|
|
84
|
+
scale?: number | { x: number; y: number };
|
|
85
|
+
|
|
86
|
+
// Flex item props (used when child of <flexContainer>)
|
|
87
|
+
flexGrow?: number;
|
|
88
|
+
flexShrink?: number;
|
|
89
|
+
layoutWidth?: number;
|
|
90
|
+
layoutHeight?: number;
|
|
91
|
+
alignSelf?: AlignSelf;
|
|
92
|
+
flexExclude?: boolean;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ─── PixiJS primitive elements ───────────────────────────
|
|
96
|
+
|
|
97
|
+
interface SpriteProps extends BaseProps {
|
|
98
|
+
texture?: string | Texture;
|
|
99
|
+
anchor?: number | { x: number; y: number };
|
|
100
|
+
tint?: number;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
interface TextProps extends BaseProps {
|
|
104
|
+
text?: string;
|
|
105
|
+
style?: Partial<TextStyle>;
|
|
106
|
+
anchor?: number | { x: number; y: number };
|
|
107
|
+
// Dash-notation for style
|
|
108
|
+
'style-fontSize'?: number;
|
|
109
|
+
'style-fill'?: number | string;
|
|
110
|
+
'style-fontFamily'?: string;
|
|
111
|
+
'style-fontWeight'?: string;
|
|
112
|
+
'style-letterSpacing'?: number;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
interface GraphicsProps extends BaseProps {
|
|
116
|
+
/** Draw function: receives the Graphics instance, called on mount and updates */
|
|
117
|
+
draw?: (g: any) => void;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ─── Engine UI component props ───────────────────────────
|
|
121
|
+
|
|
122
|
+
interface ButtonComponentProps extends BaseProps, Omit<ButtonConfig, 'colors' | 'textStyle'> {
|
|
123
|
+
// Full object form
|
|
124
|
+
colors?: Partial<Record<ButtonState, number>>;
|
|
125
|
+
textStyle?: Record<string, unknown>;
|
|
126
|
+
// Custom views (ViewInput: string texture name, Texture, or Container)
|
|
127
|
+
defaultView?: ViewInput;
|
|
128
|
+
hoverView?: ViewInput;
|
|
129
|
+
pressedView?: ViewInput;
|
|
130
|
+
disabledView?: ViewInput;
|
|
131
|
+
// Dash-notation for nested objects
|
|
132
|
+
'colors-default'?: number;
|
|
133
|
+
'colors-hover'?: number;
|
|
134
|
+
'colors-pressed'?: number;
|
|
135
|
+
'colors-disabled'?: number;
|
|
136
|
+
'textStyle-fontSize'?: number;
|
|
137
|
+
'textStyle-fill'?: number | string;
|
|
138
|
+
'textStyle-fontFamily'?: string;
|
|
139
|
+
'textStyle-fontWeight'?: string;
|
|
140
|
+
'textStyle-fontStyle'?: string;
|
|
141
|
+
'textStyle-letterSpacing'?: number;
|
|
142
|
+
// Component callbacks
|
|
143
|
+
onPress?: () => void;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
interface LabelComponentProps extends BaseProps, Omit<LabelConfig, 'style'> {
|
|
147
|
+
style?: Partial<TextStyle>;
|
|
148
|
+
'style-fontSize'?: number;
|
|
149
|
+
'style-fill'?: number | string;
|
|
150
|
+
'style-fontFamily'?: string;
|
|
151
|
+
'style-fontWeight'?: string;
|
|
152
|
+
'style-letterSpacing'?: number;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
interface PanelComponentProps extends BaseProps, Omit<PanelConfig, 'layout'> {
|
|
156
|
+
layout?: Partial<FlexContainerConfig>;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
interface FlexContainerComponentProps extends BaseProps, FlexContainerConfig {
|
|
160
|
+
padding?: number | [number, number, number, number];
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
interface ProgressBarComponentProps extends BaseProps, ProgressBarConfig {
|
|
164
|
+
progress?: number;
|
|
165
|
+
/** Custom track background (string texture name, Texture, or Container) */
|
|
166
|
+
trackView?: ViewInput;
|
|
167
|
+
/** Custom fill bar */
|
|
168
|
+
fillView?: ViewInput;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
interface ScrollContainerComponentProps extends BaseProps, Omit<ScrollContainerConfig, 'width' | 'height'> {
|
|
172
|
+
width: number;
|
|
173
|
+
height: number;
|
|
174
|
+
/** Show scrollbar indicator */
|
|
175
|
+
scrollbar?: boolean;
|
|
176
|
+
/** Custom scrollbar thumb view */
|
|
177
|
+
thumbView?: ViewInput;
|
|
178
|
+
scrollbarWidth?: number;
|
|
179
|
+
scrollbarPadding?: number;
|
|
180
|
+
scrollbarColor?: number;
|
|
181
|
+
scrollbarAlpha?: number;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
interface ModalComponentProps extends BaseProps, ModalConfig {
|
|
185
|
+
onClose?: () => void;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
interface ToastComponentProps extends BaseProps, ToastConfig {
|
|
189
|
+
/** Custom background view */
|
|
190
|
+
backgroundView?: ViewInput;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
interface BalanceDisplayComponentProps extends BaseProps, BalanceDisplayConfig {
|
|
194
|
+
/** Current balance value */
|
|
195
|
+
value?: number;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
interface WinDisplayComponentProps extends BaseProps, WinDisplayConfig {}
|
|
199
|
+
|
|
200
|
+
interface LayoutComponentProps extends BaseProps, LayoutConfig {}
|
|
201
|
+
|
|
202
|
+
interface SliderComponentProps extends BaseProps, Omit<SliderConfig, 'width' | 'height'> {
|
|
203
|
+
width?: number;
|
|
204
|
+
height?: number;
|
|
205
|
+
onUpdate?: (value: number) => void;
|
|
206
|
+
onChange?: (value: number) => void;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
interface ToggleComponentProps extends BaseProps, Omit<ToggleConfig, 'width' | 'height'> {
|
|
210
|
+
width?: number;
|
|
211
|
+
height?: number;
|
|
212
|
+
onView?: ViewInput;
|
|
213
|
+
offView?: ViewInput;
|
|
214
|
+
onChange?: (value: boolean) => void;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// ─── JSX IntrinsicElements ───────────────────────────────
|
|
218
|
+
|
|
219
|
+
declare global {
|
|
220
|
+
namespace JSX {
|
|
221
|
+
interface IntrinsicElements {
|
|
222
|
+
// PixiJS primitives
|
|
223
|
+
container: BaseProps;
|
|
224
|
+
sprite: SpriteProps;
|
|
225
|
+
text: TextProps;
|
|
226
|
+
graphics: GraphicsProps;
|
|
227
|
+
animatedSprite: BaseProps & { textures?: Texture[]; animationSpeed?: number; loop?: boolean; playing?: boolean };
|
|
228
|
+
nineSliceSprite: BaseProps & { texture?: string | Texture; leftWidth?: number; topHeight?: number; rightWidth?: number; bottomHeight?: number };
|
|
229
|
+
tilingSprite: BaseProps & { texture?: string | Texture; tilePosition?: { x: number; y: number } };
|
|
230
|
+
|
|
231
|
+
// Engine UI components
|
|
232
|
+
button: ButtonComponentProps;
|
|
233
|
+
label: LabelComponentProps;
|
|
234
|
+
panel: PanelComponentProps;
|
|
235
|
+
flexContainer: FlexContainerComponentProps;
|
|
236
|
+
progressBar: ProgressBarComponentProps;
|
|
237
|
+
scrollContainer: ScrollContainerComponentProps;
|
|
238
|
+
modal: ModalComponentProps;
|
|
239
|
+
toast: ToastComponentProps;
|
|
240
|
+
balanceDisplay: BalanceDisplayComponentProps;
|
|
241
|
+
winDisplay: WinDisplayComponentProps;
|
|
242
|
+
layout: LayoutComponentProps;
|
|
243
|
+
slider: SliderComponentProps;
|
|
244
|
+
toggle: ToggleComponentProps;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export {};
|
package/src/react/reconciler.ts
CHANGED
|
@@ -2,7 +2,34 @@ import Reconciler from 'react-reconciler';
|
|
|
2
2
|
import { DefaultEventPriority } from 'react-reconciler/constants';
|
|
3
3
|
import { Container } from 'pixi.js';
|
|
4
4
|
import { catalogue } from './catalogue';
|
|
5
|
-
import { applyProps, hasEventProps } from './applyProps';
|
|
5
|
+
import { applyProps, hasEventProps, extractConfig, diffConfig, applyEventProps } from './applyProps';
|
|
6
|
+
import { FlexContainer } from '../ui/FlexContainer';
|
|
7
|
+
import type { FlexItemConfig } from '../ui/FlexContainer';
|
|
8
|
+
|
|
9
|
+
/** Flex item prop names that should be forwarded to _flexConfig on the child */
|
|
10
|
+
const FLEX_ITEM_PROPS = ['flexGrow', 'flexShrink', 'layoutWidth', 'layoutHeight', 'alignSelf', 'flexExclude'] as const;
|
|
11
|
+
|
|
12
|
+
/** Extract FlexItemConfig from props if any flex item props are present */
|
|
13
|
+
function extractFlexItemConfig(props: Record<string, any>): FlexItemConfig | undefined {
|
|
14
|
+
let config: FlexItemConfig | undefined;
|
|
15
|
+
for (const key of FLEX_ITEM_PROPS) {
|
|
16
|
+
if (key in props) {
|
|
17
|
+
if (!config) config = {};
|
|
18
|
+
(config as any)[key] = props[key];
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return config;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Apply flex item config to a child being added to a FlexContainer */
|
|
25
|
+
function addChildToFlex(parent: FlexContainer, child: Container & { _flexConfig?: FlexItemConfig }): void {
|
|
26
|
+
const flexConfig = child._flexConfig;
|
|
27
|
+
if (flexConfig && Object.keys(flexConfig).length > 0) {
|
|
28
|
+
parent.addFlexChild(child, flexConfig);
|
|
29
|
+
} else {
|
|
30
|
+
parent.addChild(child);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
6
33
|
|
|
7
34
|
function toPascalCase(str: string): string {
|
|
8
35
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
@@ -37,14 +64,29 @@ const hostConfig: Reconciler.HostConfig<
|
|
|
37
64
|
`Call extend({ ${name} }) before rendering.`,
|
|
38
65
|
);
|
|
39
66
|
}
|
|
40
|
-
const instance = new Ctor();
|
|
41
|
-
applyProps(instance, props);
|
|
42
67
|
|
|
43
|
-
|
|
68
|
+
let instance;
|
|
69
|
+
if (Ctor.prototype.__uiComponent) {
|
|
70
|
+
// Config-based UI component: pass props as constructor config
|
|
71
|
+
const config = extractConfig(props);
|
|
72
|
+
instance = new Ctor(config);
|
|
73
|
+
applyEventProps(instance, props);
|
|
74
|
+
} else {
|
|
75
|
+
// Standard PixiJS element
|
|
76
|
+
instance = new Ctor();
|
|
77
|
+
applyProps(instance, props);
|
|
78
|
+
}
|
|
79
|
+
|
|
44
80
|
if (hasEventProps(props) && instance.eventMode === 'auto') {
|
|
45
81
|
instance.eventMode = 'static';
|
|
46
82
|
}
|
|
47
83
|
|
|
84
|
+
// Store flex item config for when this child is added to a FlexContainer parent
|
|
85
|
+
const flexItemConfig = extractFlexItemConfig(props);
|
|
86
|
+
if (flexItemConfig) {
|
|
87
|
+
instance._flexConfig = { ...instance._flexConfig, ...flexItemConfig };
|
|
88
|
+
}
|
|
89
|
+
|
|
48
90
|
return instance;
|
|
49
91
|
},
|
|
50
92
|
|
|
@@ -55,11 +97,23 @@ const hostConfig: Reconciler.HostConfig<
|
|
|
55
97
|
},
|
|
56
98
|
|
|
57
99
|
appendInitialChild(parent, child) {
|
|
58
|
-
if (child instanceof Container)
|
|
100
|
+
if (child instanceof Container) {
|
|
101
|
+
if (parent instanceof FlexContainer) {
|
|
102
|
+
addChildToFlex(parent, child);
|
|
103
|
+
} else {
|
|
104
|
+
parent.addChild(child);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
59
107
|
},
|
|
60
108
|
|
|
61
109
|
appendChild(parent, child) {
|
|
62
|
-
if (child instanceof Container)
|
|
110
|
+
if (child instanceof Container) {
|
|
111
|
+
if (parent instanceof FlexContainer) {
|
|
112
|
+
addChildToFlex(parent, child);
|
|
113
|
+
} else {
|
|
114
|
+
parent.addChild(child);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
63
117
|
},
|
|
64
118
|
|
|
65
119
|
appendChildToContainer(container, child) {
|
|
@@ -97,7 +151,26 @@ const hostConfig: Reconciler.HostConfig<
|
|
|
97
151
|
},
|
|
98
152
|
|
|
99
153
|
commitUpdate(instance, _updatePayload, _type, oldProps, newProps) {
|
|
100
|
-
|
|
154
|
+
if (instance.__uiComponent && typeof instance.updateConfig === 'function') {
|
|
155
|
+
const changed = diffConfig(newProps, oldProps);
|
|
156
|
+
if (Object.keys(changed).length > 0) {
|
|
157
|
+
instance.updateConfig(changed);
|
|
158
|
+
}
|
|
159
|
+
applyEventProps(instance, newProps, oldProps);
|
|
160
|
+
} else {
|
|
161
|
+
applyProps(instance, newProps, oldProps);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Update flex item config if parent is FlexContainer
|
|
165
|
+
const newFlexConfig = extractFlexItemConfig(newProps);
|
|
166
|
+
const oldFlexConfig = extractFlexItemConfig(oldProps);
|
|
167
|
+
if (newFlexConfig || oldFlexConfig) {
|
|
168
|
+
instance._flexConfig = { ...instance._flexConfig, ...newFlexConfig };
|
|
169
|
+
// Trigger parent relayout
|
|
170
|
+
if (instance.parent instanceof FlexContainer) {
|
|
171
|
+
instance.parent.updateLayout();
|
|
172
|
+
}
|
|
173
|
+
}
|
|
101
174
|
|
|
102
175
|
if (hasEventProps(newProps) && instance.eventMode === 'auto') {
|
|
103
176
|
instance.eventMode = 'static';
|
package/src/ui/BalanceDisplay.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Container } from 'pixi.js';
|
|
2
2
|
import { Label } from './Label';
|
|
3
|
+
import { Tween } from '../animation/Tween';
|
|
3
4
|
import { Easing } from '../animation/Easing';
|
|
4
5
|
|
|
5
6
|
export interface BalanceDisplayConfig {
|
|
@@ -23,7 +24,7 @@ export interface BalanceDisplayConfig {
|
|
|
23
24
|
* Reactive balance display component.
|
|
24
25
|
*
|
|
25
26
|
* Automatically formats currency and can animate value changes
|
|
26
|
-
* with a smooth countup/countdown effect.
|
|
27
|
+
* with a smooth countup/countdown effect using engine Tween.
|
|
27
28
|
*
|
|
28
29
|
* @example
|
|
29
30
|
* ```ts
|
|
@@ -35,13 +36,15 @@ export interface BalanceDisplayConfig {
|
|
|
35
36
|
* ```
|
|
36
37
|
*/
|
|
37
38
|
export class BalanceDisplay extends Container {
|
|
39
|
+
readonly __uiComponent = true as const;
|
|
40
|
+
|
|
38
41
|
private _prefixLabel: Label | null = null;
|
|
39
42
|
private _valueLabel: Label;
|
|
40
43
|
private _config: Required<Pick<BalanceDisplayConfig, 'currency' | 'locale' | 'animated' | 'animationDuration'>>;
|
|
41
44
|
private _currentValue = 0;
|
|
42
45
|
private _displayedValue = 0;
|
|
43
|
-
|
|
44
|
-
private
|
|
46
|
+
/** Internal target for Tween animation */
|
|
47
|
+
private _tweenTarget = { value: 0 };
|
|
45
48
|
|
|
46
49
|
constructor(config: BalanceDisplayConfig = {}) {
|
|
47
50
|
super();
|
|
@@ -111,42 +114,21 @@ export class BalanceDisplay extends Container {
|
|
|
111
114
|
this.updateDisplay();
|
|
112
115
|
}
|
|
113
116
|
|
|
114
|
-
private
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
if (this._animationCancelled) {
|
|
127
|
-
this._animating = false;
|
|
128
|
-
resolve();
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const elapsed = Date.now() - startTime;
|
|
133
|
-
const t = Math.min(elapsed / duration, 1);
|
|
134
|
-
const eased = Easing.easeOutCubic(t);
|
|
135
|
-
|
|
136
|
-
this._displayedValue = from + (to - from) * eased;
|
|
117
|
+
private animateValue(from: number, to: number): void {
|
|
118
|
+
// Cancel any running animation
|
|
119
|
+
Tween.killTweensOf(this._tweenTarget);
|
|
120
|
+
|
|
121
|
+
this._tweenTarget.value = from;
|
|
122
|
+
Tween.to(
|
|
123
|
+
this._tweenTarget,
|
|
124
|
+
{ value: to },
|
|
125
|
+
this._config.animationDuration,
|
|
126
|
+
Easing.easeOutCubic,
|
|
127
|
+
() => {
|
|
128
|
+
this._displayedValue = this._tweenTarget.value;
|
|
137
129
|
this.updateDisplay();
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
requestAnimationFrame(tick);
|
|
141
|
-
} else {
|
|
142
|
-
this._displayedValue = to;
|
|
143
|
-
this.updateDisplay();
|
|
144
|
-
this._animating = false;
|
|
145
|
-
resolve();
|
|
146
|
-
}
|
|
147
|
-
};
|
|
148
|
-
requestAnimationFrame(tick);
|
|
149
|
-
});
|
|
130
|
+
},
|
|
131
|
+
);
|
|
150
132
|
}
|
|
151
133
|
|
|
152
134
|
private updateDisplay(): void {
|
|
@@ -163,4 +145,15 @@ export class BalanceDisplay extends Container {
|
|
|
163
145
|
this._valueLabel.y = 14;
|
|
164
146
|
}
|
|
165
147
|
}
|
|
148
|
+
|
|
149
|
+
/** React reconciler update hook */
|
|
150
|
+
updateConfig(changed: Record<string, any>): void {
|
|
151
|
+
if ('value' in changed) this.setValue(changed.value);
|
|
152
|
+
if ('currency' in changed) this.setCurrency(changed.currency);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
override destroy(options?: boolean | { children?: boolean; texture?: boolean; textureSource?: boolean }): void {
|
|
156
|
+
Tween.killTweensOf(this._tweenTarget);
|
|
157
|
+
super.destroy(options);
|
|
158
|
+
}
|
|
166
159
|
}
|