@energy8platform/game-engine 0.14.10 → 0.15.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,20 +1,27 @@
1
1
  /**
2
- * JSX IntrinsicElements type declarations for the PixiJS React reconciler.
2
+ * Ambient JSX IntrinsicElements declarations for the PixiJS React reconciler.
3
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.
4
+ * Consumer apps opt in once (e.g. from an app-level `vite-env.d.ts`):
5
+ *
6
+ * import '@energy8platform/game-engine/react-jsx';
7
+ *
8
+ * After that, TypeScript recognises `<flexContainer>`, `<button>`, `<label>` etc.
9
+ * with fully-typed props — no need to hand-maintain a local `pixi-jsx.d.ts`.
10
+ *
11
+ * The JS entry for this subpath is intentionally empty: this module exists
12
+ * solely to carry type declarations that side-effect the global `JSX` namespace.
7
13
  *
8
14
  * Dash-notation is supported for nested config objects:
9
15
  * <button colors-default={0xff0000} colors-hover={0x00ff00} />
10
16
  */
11
17
 
12
- import type { Container, Texture, TextStyle } from 'pixi.js';
18
+ import type { Texture, TextStyle } from 'pixi.js';
13
19
  import type { ViewInput } from '../ui/view';
14
20
  import type { ButtonConfig, ButtonState } from '../ui/Button';
15
21
  import type { LabelConfig } from '../ui/Label';
22
+ import type { LabelValueConfig } from '../ui/LabelValue';
16
23
  import type { PanelConfig } from '../ui/Panel';
17
- import type { FlexContainerConfig, FlexItemConfig, AlignSelf, AlignContent } from '../ui/FlexContainer';
24
+ import type { FlexContainerConfig, AlignSelf, AlignContent } from '../ui/FlexContainer';
18
25
  import type { ProgressBarConfig } from '../ui/ProgressBar';
19
26
  import type { ScrollContainerConfig } from '../ui/ScrollContainer';
20
27
  import type { ModalConfig } from '../ui/Modal';
@@ -90,11 +97,18 @@ interface BaseProps extends PixiEventProps {
90
97
  layoutHeight?: number | string;
91
98
  alignSelf?: AlignSelf;
92
99
  flexExclude?: boolean;
93
- // Absolute positioning for flexExclude children
100
+ // Absolute positioning for flexExclude children.
101
+ // Values describe the visual bounding rectangle (top-left of the rendered
102
+ // frame), so elements with centered origin like Button/Label are positioned
103
+ // intuitively — (left: 100, top: 50) puts the visible corner at (100, 50).
94
104
  top?: number;
95
105
  right?: number;
96
106
  bottom?: number;
97
107
  left?: number;
108
+ /** X coordinate of the visual center within parent (px or `'50%'`). Overrides left/right. */
109
+ centerX?: number | string;
110
+ /** Y coordinate of the visual center within parent (px or `'50%'`). Overrides top/bottom. */
111
+ centerY?: number | string;
98
112
  }
99
113
 
100
114
  // ─── PixiJS primitive elements ───────────────────────────
@@ -120,6 +134,14 @@ interface TextProps extends BaseProps {
120
134
  interface GraphicsProps extends BaseProps {
121
135
  /** Draw function: receives the Graphics instance, called on mount and updates */
122
136
  draw?: (g: any) => void;
137
+ /**
138
+ * Layout size hint (pixels). For Graphics, `width`/`height` do NOT apply a scale
139
+ * transform (unlike Container.width). They're forwarded to the parent
140
+ * FlexContainer as `layoutWidth`/`layoutHeight` so layout measures the element
141
+ * at the requested size while the actual geometry stays in the `draw` callback.
142
+ */
143
+ width?: number;
144
+ height?: number;
123
145
  }
124
146
 
125
147
  // ─── Engine UI component props ───────────────────────────
@@ -157,11 +179,28 @@ interface LabelComponentProps extends BaseProps, Omit<LabelConfig, 'style'> {
157
179
  'style-letterSpacing'?: number;
158
180
  }
159
181
 
182
+ interface LabelValueComponentProps extends Omit<BaseProps, 'width' | 'height' | 'label'>,
183
+ Omit<LabelValueConfig, 'labelStyle' | 'valueStyle'> {
184
+ labelStyle?: Partial<TextStyle>;
185
+ valueStyle?: Partial<TextStyle>;
186
+ width?: number | string;
187
+ height?: number | string;
188
+ // Dash-notation for nested styles
189
+ 'labelStyle-fontSize'?: number;
190
+ 'labelStyle-fill'?: number | string;
191
+ 'labelStyle-fontFamily'?: string;
192
+ 'labelStyle-fontWeight'?: string;
193
+ 'valueStyle-fontSize'?: number;
194
+ 'valueStyle-fill'?: number | string;
195
+ 'valueStyle-fontFamily'?: string;
196
+ 'valueStyle-fontWeight'?: string;
197
+ }
198
+
160
199
  interface PanelComponentProps extends BaseProps, Omit<PanelConfig, 'layout'> {
161
200
  layout?: Partial<FlexContainerConfig>;
162
201
  }
163
202
 
164
- interface FlexContainerComponentProps extends BaseProps, FlexContainerConfig {
203
+ interface FlexContainerComponentProps extends Omit<BaseProps, 'width' | 'height'>, FlexContainerConfig {
165
204
  padding?: number | [number, number, number, number];
166
205
  paddingTop?: number;
167
206
  paddingRight?: number;
@@ -243,6 +282,7 @@ declare global {
243
282
  // Engine UI components
244
283
  button: ButtonComponentProps;
245
284
  label: LabelComponentProps;
285
+ labelValue: LabelValueComponentProps;
246
286
  panel: PanelComponentProps;
247
287
  flexContainer: FlexContainerComponentProps;
248
288
  progressBar: ProgressBarComponentProps;
@@ -7,7 +7,7 @@ import { FlexContainer } from '../ui/FlexContainer';
7
7
  import type { FlexItemConfig } from '../ui/FlexContainer';
8
8
 
9
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', 'top', 'right', 'bottom', 'left'] as const;
10
+ const FLEX_ITEM_PROPS = ['flexGrow', 'flexShrink', 'layoutWidth', 'layoutHeight', 'alignSelf', 'flexExclude', 'top', 'right', 'bottom', 'left', 'centerX', 'centerY'] as const;
11
11
 
12
12
  /** Any component with suspendLayout/resumeLayout (FlexContainer, Panel, etc.) */
13
13
  interface Suspendable {
@@ -20,16 +20,35 @@ export interface FlexItemConfig {
20
20
  layoutHeight?: number | string;
21
21
  /** Override parent's alignItems for this child */
22
22
  alignSelf?: AlignSelf;
23
- /** Exclude from flex layout (acts like position: absolute) */
23
+ /**
24
+ * Exclude from flex layout (acts like position: absolute).
25
+ *
26
+ * Positioning props (`top`/`right`/`bottom`/`left`/`centerX`/`centerY`) describe
27
+ * the **visual bounding rectangle**, so children with a centered internal
28
+ * origin (Button, Label — `anchor = 0.5`) are positioned intuitively.
29
+ * `left: 100` places the visible left edge at 100px, regardless of origin.
30
+ */
24
31
  flexExclude?: boolean;
25
- /** Absolute positioning for flexExclude children (distance from top edge) */
32
+ /** Distance from top edge of parent content area to the child's visual top */
26
33
  top?: number;
27
- /** Absolute positioning for flexExclude children (distance from right edge) */
34
+ /** Distance from right edge of parent content area to the child's visual right */
28
35
  right?: number;
29
- /** Absolute positioning for flexExclude children (distance from bottom edge) */
36
+ /** Distance from bottom edge of parent content area to the child's visual bottom */
30
37
  bottom?: number;
31
- /** Absolute positioning for flexExclude children (distance from left edge) */
38
+ /** Distance from left edge of parent content area to the child's visual left */
32
39
  left?: number;
40
+ /**
41
+ * X coordinate of the child's visual center within parent content area.
42
+ * Accepts pixels (`200`) or a percentage of parent content width (`'50%'`).
43
+ * Takes precedence over `left`/`right`.
44
+ */
45
+ centerX?: number | string;
46
+ /**
47
+ * Y coordinate of the child's visual center within parent content area.
48
+ * Accepts pixels or a percentage of parent content height (`'50%'`).
49
+ * Takes precedence over `top`/`bottom`.
50
+ */
51
+ centerY?: number | string;
33
52
  }
34
53
 
35
54
  export interface FlexContainerConfig {
@@ -679,17 +698,26 @@ export class FlexContainer extends Container {
679
698
  this._computedHeight = this._explicitHeight > 0 ? this._explicitHeight : (pt + naturalMainSize + pb);
680
699
  }
681
700
 
682
- // Position flexExclude children (absolute positioning)
701
+ // Position flexExclude children (absolute positioning).
702
+ // All position props describe the visual (bounds) rectangle, so we subtract
703
+ // `ox/oy` to compensate for children with a centered origin (Button, Label).
704
+ // `centerX/centerY` take precedence over `left/right` / `top/bottom` on each axis.
683
705
  for (const child of this._layoutChildren) {
684
706
  if (!child._flexConfig?.flexExclude) continue;
685
707
  const cfg = child._flexConfig;
686
- const { w, h } = measureChild(child, pctRefW, pctRefH);
708
+ const { w, h, ox, oy } = measureChild(child, pctRefW, pctRefH);
687
709
  const cw = this._computedWidth;
688
710
  const ch = this._computedHeight;
689
- if (cfg.left !== undefined) child.x = cfg.left;
690
- else if (cfg.right !== undefined) child.x = cw - w - cfg.right;
691
- if (cfg.top !== undefined) child.y = cfg.top;
692
- else if (cfg.bottom !== undefined) child.y = ch - h - cfg.bottom;
711
+
712
+ const centerX = resolveDimension(cfg.centerX, pctRefW);
713
+ if (centerX !== undefined) child.x = centerX - w / 2 - ox;
714
+ else if (cfg.left !== undefined) child.x = cfg.left - ox;
715
+ else if (cfg.right !== undefined) child.x = cw - w - cfg.right - ox;
716
+
717
+ const centerY = resolveDimension(cfg.centerY, pctRefH);
718
+ if (centerY !== undefined) child.y = centerY - h / 2 - oy;
719
+ else if (cfg.top !== undefined) child.y = cfg.top - oy;
720
+ else if (cfg.bottom !== undefined) child.y = ch - h - cfg.bottom - oy;
693
721
  }
694
722
  }
695
723
 
@@ -0,0 +1,122 @@
1
+ import type { TextStyle } from 'pixi.js';
2
+ import { FlexContainer, type FlexContainerConfig } from './FlexContainer';
3
+ import { Label } from './Label';
4
+
5
+ export type LabelValueAlign = 'start' | 'center' | 'end';
6
+
7
+ export interface LabelValueConfig {
8
+ /** Caption text (top row, typically smaller/muted) */
9
+ label?: string;
10
+ /** Value text (bottom row, typically larger/prominent) */
11
+ value?: string;
12
+ /** Style for the caption Label */
13
+ labelStyle?: Partial<TextStyle>;
14
+ /** Style for the value Label */
15
+ valueStyle?: Partial<TextStyle>;
16
+ /** Gap in pixels between caption and value (default: 4) */
17
+ gap?: number;
18
+ /** Horizontal alignment of both rows (default: 'center') */
19
+ align?: LabelValueAlign;
20
+ /** Maximum width — value will be auto-scaled to fit when exceeded */
21
+ maxWidth?: number;
22
+ /** Optional padding (forwarded to the underlying FlexContainer) */
23
+ padding?: FlexContainerConfig['padding'];
24
+ }
25
+
26
+ /**
27
+ * Two-row text cell with a muted caption above and a prominent value below —
28
+ * the archetypal casino UI pattern (BALANCE/€500, BET/€1, WIN/€0.00).
29
+ *
30
+ * Extends FlexContainer, so it participates in parent flex layouts directly
31
+ * (you can pass `flexGrow`, `alignSelf`, etc. when it's a child of another
32
+ * FlexContainer) and auto-sizes to its contents when no explicit size is set.
33
+ *
34
+ * @example
35
+ * ```tsx
36
+ * <labelValue
37
+ * label="BALANCE"
38
+ * value={`€${balance.toFixed(2)}`}
39
+ * labelStyle={{ fontSize: 12, fill: 0x888888 }}
40
+ * valueStyle={{ fontSize: 22, fill: 0xffffff, fontWeight: 'bold' }}
41
+ * gap={6}
42
+ * align="center"
43
+ * />
44
+ * ```
45
+ */
46
+ export class LabelValue extends FlexContainer {
47
+ private readonly _labelEl: Label;
48
+ private readonly _valueEl: Label;
49
+ private _valueMaxWidth: number;
50
+
51
+ constructor(config: LabelValueConfig = {}) {
52
+ super({
53
+ direction: 'column',
54
+ alignItems: toAlignItems(config.align ?? 'center'),
55
+ gap: config.gap ?? 4,
56
+ padding: config.padding,
57
+ });
58
+
59
+ this._valueMaxWidth = config.maxWidth ?? Infinity;
60
+
61
+ this._labelEl = new Label({
62
+ text: config.label ?? '',
63
+ style: config.labelStyle,
64
+ });
65
+ this._valueEl = new Label({
66
+ text: config.value ?? '',
67
+ style: config.valueStyle,
68
+ maxWidth: config.maxWidth,
69
+ autoFit: config.maxWidth !== undefined,
70
+ });
71
+
72
+ this.addFlexChild(this._labelEl);
73
+ this.addFlexChild(this._valueEl);
74
+ }
75
+
76
+ /** Get the caption Label (for advanced styling / animation) */
77
+ get labelElement(): Label {
78
+ return this._labelEl;
79
+ }
80
+
81
+ /** Get the value Label */
82
+ get valueElement(): Label {
83
+ return this._valueEl;
84
+ }
85
+
86
+ /** Update caption text */
87
+ setLabel(text: string): void {
88
+ this._labelEl.text = text;
89
+ this.updateLayout();
90
+ }
91
+
92
+ /** Update value text */
93
+ setValue(text: string): void {
94
+ this._valueEl.text = text;
95
+ this.updateLayout();
96
+ }
97
+
98
+ /** React reconciler update hook */
99
+ override updateConfig(changed: Record<string, any>): void {
100
+ if ('label' in changed) this._labelEl.text = changed.label ?? '';
101
+ if ('value' in changed) this._valueEl.text = changed.value ?? '';
102
+ if ('labelStyle' in changed && typeof changed.labelStyle === 'object') {
103
+ Object.assign(this._labelEl.style, changed.labelStyle);
104
+ }
105
+ if ('valueStyle' in changed && typeof changed.valueStyle === 'object') {
106
+ Object.assign(this._valueEl.style, changed.valueStyle);
107
+ }
108
+ if ('maxWidth' in changed) {
109
+ this._valueMaxWidth = changed.maxWidth ?? Infinity;
110
+ this._valueEl.maxWidth = this._valueMaxWidth;
111
+ }
112
+ if ('gap' in changed) this.setGap(changed.gap);
113
+ if ('align' in changed) this.setAlignItems(toAlignItems(changed.align));
114
+
115
+ // Forward remaining FlexContainer props (e.g. padding, width, height)
116
+ super.updateConfig(changed);
117
+ }
118
+ }
119
+
120
+ function toAlignItems(align: LabelValueAlign): 'start' | 'center' | 'end' {
121
+ return align;
122
+ }
package/src/ui/index.ts CHANGED
@@ -11,6 +11,8 @@ export { ProgressBar } from './ProgressBar';
11
11
  export type { ProgressBarConfig } from './ProgressBar';
12
12
  export { Label } from './Label';
13
13
  export type { LabelConfig } from './Label';
14
+ export { LabelValue } from './LabelValue';
15
+ export type { LabelValueConfig, LabelValueAlign } from './LabelValue';
14
16
  export { Panel } from './Panel';
15
17
  export type { PanelConfig } from './Panel';
16
18
  export { BalanceDisplay } from './BalanceDisplay';