canvasengine 2.0.0-beta.4 → 2.0.0-beta.41

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 (142) hide show
  1. package/dist/DebugRenderer-BxfW34YG.js +172 -0
  2. package/dist/DebugRenderer-BxfW34YG.js.map +1 -0
  3. package/dist/components/Button.d.ts +183 -0
  4. package/dist/components/Button.d.ts.map +1 -0
  5. package/dist/components/Canvas.d.ts +18 -0
  6. package/dist/components/Canvas.d.ts.map +1 -0
  7. package/dist/components/DOMElement.d.ts +44 -0
  8. package/dist/components/DOMElement.d.ts.map +1 -0
  9. package/dist/components/Graphic.d.ts +65 -0
  10. package/dist/components/Graphic.d.ts.map +1 -0
  11. package/dist/components/Joystick.d.ts +36 -0
  12. package/dist/components/Joystick.d.ts.map +1 -0
  13. package/dist/components/NineSliceSprite.d.ts +17 -0
  14. package/dist/components/NineSliceSprite.d.ts.map +1 -0
  15. package/dist/components/ParticleEmitter.d.ts +5 -0
  16. package/dist/components/ParticleEmitter.d.ts.map +1 -0
  17. package/dist/components/Scene.d.ts +2 -0
  18. package/dist/components/Scene.d.ts.map +1 -0
  19. package/dist/components/Text.d.ts +26 -0
  20. package/dist/components/Text.d.ts.map +1 -0
  21. package/dist/components/TilingSprite.d.ts +18 -0
  22. package/dist/components/TilingSprite.d.ts.map +1 -0
  23. package/dist/components/Video.d.ts +15 -0
  24. package/dist/components/Video.d.ts.map +1 -0
  25. package/dist/components/index.d.ts +18 -0
  26. package/dist/components/index.d.ts.map +1 -0
  27. package/dist/components/types/DisplayObject.d.ts +110 -0
  28. package/dist/components/types/DisplayObject.d.ts.map +1 -0
  29. package/dist/components/types/MouseEvent.d.ts +4 -0
  30. package/dist/components/types/MouseEvent.d.ts.map +1 -0
  31. package/dist/components/types/Spritesheet.d.ts +248 -0
  32. package/dist/components/types/Spritesheet.d.ts.map +1 -0
  33. package/dist/components/types/index.d.ts +5 -0
  34. package/dist/components/types/index.d.ts.map +1 -0
  35. package/dist/directives/Controls.d.ts +113 -0
  36. package/dist/directives/Controls.d.ts.map +1 -0
  37. package/dist/directives/ControlsBase.d.ts +198 -0
  38. package/dist/directives/ControlsBase.d.ts.map +1 -0
  39. package/dist/directives/Drag.d.ts +70 -0
  40. package/dist/directives/Drag.d.ts.map +1 -0
  41. package/dist/directives/Flash.d.ts +117 -0
  42. package/dist/directives/Flash.d.ts.map +1 -0
  43. package/dist/directives/GamepadControls.d.ts +225 -0
  44. package/dist/directives/GamepadControls.d.ts.map +1 -0
  45. package/dist/directives/JoystickControls.d.ts +172 -0
  46. package/dist/directives/JoystickControls.d.ts.map +1 -0
  47. package/dist/directives/KeyboardControls.d.ts +219 -0
  48. package/dist/directives/KeyboardControls.d.ts.map +1 -0
  49. package/dist/directives/Scheduler.d.ts +36 -0
  50. package/dist/directives/Scheduler.d.ts.map +1 -0
  51. package/dist/directives/Shake.d.ts +98 -0
  52. package/dist/directives/Shake.d.ts.map +1 -0
  53. package/dist/directives/Sound.d.ts +26 -0
  54. package/dist/directives/Sound.d.ts.map +1 -0
  55. package/dist/directives/Transition.d.ts +11 -0
  56. package/dist/directives/Transition.d.ts.map +1 -0
  57. package/dist/directives/ViewportCull.d.ts +12 -0
  58. package/dist/directives/ViewportCull.d.ts.map +1 -0
  59. package/dist/directives/ViewportFollow.d.ts +19 -0
  60. package/dist/directives/ViewportFollow.d.ts.map +1 -0
  61. package/dist/directives/index.d.ts +13 -0
  62. package/dist/directives/index.d.ts.map +1 -0
  63. package/dist/engine/animation.d.ts +73 -0
  64. package/dist/engine/animation.d.ts.map +1 -0
  65. package/dist/engine/bootstrap.d.ts +16 -0
  66. package/dist/engine/bootstrap.d.ts.map +1 -0
  67. package/dist/engine/directive.d.ts +14 -0
  68. package/dist/engine/directive.d.ts.map +1 -0
  69. package/dist/engine/reactive.d.ts +105 -0
  70. package/dist/engine/reactive.d.ts.map +1 -0
  71. package/dist/engine/signal.d.ts +72 -0
  72. package/dist/engine/signal.d.ts.map +1 -0
  73. package/dist/engine/trigger.d.ts +54 -0
  74. package/dist/engine/trigger.d.ts.map +1 -0
  75. package/dist/engine/utils.d.ts +90 -0
  76. package/dist/engine/utils.d.ts.map +1 -0
  77. package/dist/hooks/addContext.d.ts +2 -0
  78. package/dist/hooks/addContext.d.ts.map +1 -0
  79. package/dist/hooks/useProps.d.ts +42 -0
  80. package/dist/hooks/useProps.d.ts.map +1 -0
  81. package/dist/hooks/useRef.d.ts +5 -0
  82. package/dist/hooks/useRef.d.ts.map +1 -0
  83. package/dist/index-BnuKipxl.js +12568 -0
  84. package/dist/index-BnuKipxl.js.map +1 -0
  85. package/dist/index.d.ts +15 -1083
  86. package/dist/index.d.ts.map +1 -0
  87. package/dist/index.global.js +29 -0
  88. package/dist/index.global.js.map +1 -0
  89. package/dist/index.js +81 -3041
  90. package/dist/index.js.map +1 -1
  91. package/dist/utils/Ease.d.ts +17 -0
  92. package/dist/utils/Ease.d.ts.map +1 -0
  93. package/dist/utils/GlobalAssetLoader.d.ts +141 -0
  94. package/dist/utils/GlobalAssetLoader.d.ts.map +1 -0
  95. package/dist/utils/RadialGradient.d.ts +58 -0
  96. package/dist/utils/RadialGradient.d.ts.map +1 -0
  97. package/dist/utils/functions.d.ts +2 -0
  98. package/dist/utils/functions.d.ts.map +1 -0
  99. package/package.json +13 -7
  100. package/src/components/Button.ts +396 -0
  101. package/src/components/Canvas.ts +61 -45
  102. package/src/components/Container.ts +21 -2
  103. package/src/components/DOMContainer.ts +123 -0
  104. package/src/components/DOMElement.ts +421 -0
  105. package/src/components/DisplayObject.ts +350 -197
  106. package/src/components/Graphic.ts +200 -34
  107. package/src/components/Joystick.ts +363 -0
  108. package/src/components/Mesh.ts +222 -0
  109. package/src/components/NineSliceSprite.ts +4 -1
  110. package/src/components/ParticleEmitter.ts +12 -8
  111. package/src/components/Sprite.ts +306 -30
  112. package/src/components/Text.ts +125 -18
  113. package/src/components/Video.ts +110 -0
  114. package/src/components/Viewport.ts +59 -43
  115. package/src/components/index.ts +8 -2
  116. package/src/components/types/DisplayObject.ts +34 -0
  117. package/src/components/types/Spritesheet.ts +0 -118
  118. package/src/directives/Controls.ts +254 -0
  119. package/src/directives/ControlsBase.ts +266 -0
  120. package/src/directives/Drag.ts +357 -52
  121. package/src/directives/Flash.ts +419 -0
  122. package/src/directives/GamepadControls.ts +537 -0
  123. package/src/directives/JoystickControls.ts +396 -0
  124. package/src/directives/KeyboardControls.ts +66 -424
  125. package/src/directives/Shake.ts +295 -0
  126. package/src/directives/Sound.ts +94 -31
  127. package/src/directives/ViewportFollow.ts +35 -7
  128. package/src/directives/index.ts +12 -6
  129. package/src/engine/animation.ts +175 -21
  130. package/src/engine/bootstrap.ts +23 -3
  131. package/src/engine/directive.ts +2 -2
  132. package/src/engine/reactive.ts +780 -177
  133. package/src/engine/signal.ts +35 -4
  134. package/src/engine/trigger.ts +34 -7
  135. package/src/engine/utils.ts +19 -3
  136. package/src/hooks/useProps.ts +1 -1
  137. package/src/index.ts +4 -2
  138. package/src/utils/GlobalAssetLoader.ts +257 -0
  139. package/src/utils/functions.ts +7 -0
  140. package/testing/index.ts +12 -0
  141. package/tsconfig.json +17 -0
  142. package/vite.config.ts +39 -0
@@ -0,0 +1,58 @@
1
+ import { Texture, Matrix } from 'pixi.js';
2
+
3
+ /**
4
+ * Creates a radial gradient texture that can be used in PixiJS.
5
+ * @example
6
+ * const gradient = new RadialGradient(size, size, 0, size, size, 0);
7
+ * gradient.addColorStop(0, "rgba(255, 255, 0, 1)");
8
+ * gradient.addColorStop(0.5, "rgba(255, 255, 0, 0.3)");
9
+ * gradient.addColorStop(0.8, "rgba(255, 255, 0, 0)");
10
+ */
11
+ export declare class RadialGradient {
12
+ private x0;
13
+ private y0;
14
+ private x1;
15
+ private y1;
16
+ private x2;
17
+ private y2;
18
+ private focalPoint;
19
+ private canvas;
20
+ private ctx;
21
+ private gradient;
22
+ private texture;
23
+ transform: Matrix;
24
+ size: number;
25
+ /**
26
+ * Creates a new RadialGradient instance
27
+ * @param x0 - The x-coordinate of the starting circle
28
+ * @param y0 - The y-coordinate of the starting circle
29
+ * @param x1 - The x-coordinate of the ending circle
30
+ * @param y1 - The y-coordinate of the ending circle
31
+ * @param x2 - The x-coordinate for gradient transformation
32
+ * @param y2 - The y-coordinate for gradient transformation
33
+ * @param focalPoint - The focal point of the gradient (0-1), defaults to 0
34
+ */
35
+ constructor(x0: number, y0: number, x1: number, y1: number, x2: number, y2: number, focalPoint?: number);
36
+ /**
37
+ * Adds a color stop to the gradient
38
+ * @param offset - The position of the color stop (0-1)
39
+ * @param color - The color value (any valid CSS color string)
40
+ */
41
+ addColorStop(offset: number, color: string): void;
42
+ /**
43
+ * Renders the gradient and returns the texture with its transformation matrix
44
+ * @param options - Render options
45
+ * @param options.translate - Optional translation coordinates
46
+ * @returns Object containing the texture and transformation matrix
47
+ */
48
+ render({ translate }?: {
49
+ translate?: {
50
+ x: number;
51
+ y: number;
52
+ };
53
+ }): {
54
+ texture: Texture<import('pixi.js').TextureSource<any>>;
55
+ matrix: Matrix;
56
+ };
57
+ }
58
+ //# sourceMappingURL=RadialGradient.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RadialGradient.d.ts","sourceRoot":"","sources":["../../src/utils/RadialGradient.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAA2B,MAAM,EAAE,MAAM,SAAS,CAAC;AAEnE;;;;;;;GAOG;AACH,qBAAa,cAAc;IAoBvB,OAAO,CAAC,EAAE;IACV,OAAO,CAAC,EAAE;IACV,OAAO,CAAC,EAAE;IACV,OAAO,CAAC,EAAE;IACV,OAAO,CAAC,EAAE;IACV,OAAO,CAAC,EAAE;IACV,OAAO,CAAC,UAAU;IAzBpB,OAAO,CAAC,MAAM,CAAoB;IAClC,OAAO,CAAC,GAAG,CAAkC;IAC7C,OAAO,CAAC,QAAQ,CAA+B;IAC/C,OAAO,CAAC,OAAO,CAAwB;IAChC,SAAS,EAAE,MAAM,CAAC;IAElB,IAAI,SAAO;IAElB;;;;;;;;;OASG;gBAEO,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,EACV,UAAU,GAAE,MAAU;IAsBhC;;;;OAIG;IACH,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;IAM1C;;;;;OAKG;IACH,MAAM,CAAC,EAAE,SAAS,EAAE,GAAE;QAAE,SAAS,CAAC,EAAE;YAAE,CAAC,EAAE,MAAM,CAAC;YAAC,CAAC,EAAE,MAAM,CAAA;SAAE,CAAA;KAAO;;;;CAuCpE"}
@@ -0,0 +1,2 @@
1
+ export declare function isPercent(value?: string | number): boolean;
2
+ //# sourceMappingURL=functions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"functions.d.ts","sourceRoot":"","sources":["../../src/utils/functions.ts"],"names":[],"mappings":"AAAA,wBAAgB,SAAS,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,WAMhD"}
package/package.json CHANGED
@@ -1,22 +1,28 @@
1
1
  {
2
2
  "name": "canvasengine",
3
- "version": "2.0.0-beta.4",
3
+ "version": "2.0.0-beta.41",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
+ "peerDependencies": {
8
+ "pixi.js": "^8.9.2"
9
+ },
7
10
  "dependencies": {
8
11
  "@barvynkoa/particle-emitter": "^0.0.1",
9
- "@signe/reactive": "^1.0.1",
12
+ "@pixi/layout": "^3.0.2",
13
+ "@signe/reactive": "^2.6.0",
10
14
  "howler": "^2.2.4",
15
+ "joypad.js": "^2.3.5",
11
16
  "pixi-filters": "^6.0.5",
12
17
  "pixi-viewport": "^6.0.3",
13
- "pixi.js": "^8.6.4",
14
18
  "popmotion": "^11.0.5",
15
19
  "rxjs": "^7.8.1",
16
- "yoga-layout": "^2.0.1"
20
+ "yoga-layout": "^3.2.1"
17
21
  },
18
22
  "devDependencies": {
19
- "@types/howler": "^2.2.11"
23
+ "@types/howler": "^2.2.11",
24
+ "vite": "^5.0.0",
25
+ "vite-plugin-dts": "^3.0.0"
20
26
  },
21
27
  "author": "Samuel Ronce",
22
28
  "license": "MIT",
@@ -38,7 +44,7 @@
38
44
  "access": "public"
39
45
  },
40
46
  "scripts": {
41
- "build": "tsup",
42
- "dev": "tsup --watch"
47
+ "build": "vite build",
48
+ "dev": "vite build --watch"
43
49
  }
44
50
  }
@@ -0,0 +1,396 @@
1
+ import { effect, signal, computed, isSignal } from "@signe/reactive";
2
+ import { FederatedPointerEvent } from "pixi.js";
3
+ import { h } from "../engine/signal";
4
+ import { useDefineProps } from "../hooks/useProps";
5
+ import { Container } from "./Container";
6
+ import { Rect, Circle, Ellipse } from "./Graphic";
7
+ import { Text } from "./Text";
8
+ import { ControlsDirective } from "../directives/Controls";
9
+ import { JoystickControls } from "../directives/JoystickControls";
10
+ import { Element } from "../engine/reactive";
11
+
12
+ /**
13
+ * Button states for visual feedback
14
+ */
15
+ export enum ButtonState {
16
+ Normal = "normal",
17
+ Hover = "hover",
18
+ Pressed = "pressed",
19
+ Disabled = "disabled"
20
+ }
21
+
22
+ /**
23
+ * Button style configuration for different visual approaches
24
+ */
25
+ export interface ButtonStyle {
26
+ /** Background color for each state */
27
+ backgroundColor?: {
28
+ [ButtonState.Normal]?: string;
29
+ [ButtonState.Hover]?: string;
30
+ [ButtonState.Pressed]?: string;
31
+ [ButtonState.Disabled]?: string;
32
+ };
33
+ /** Border configuration */
34
+ border?: {
35
+ color?: string;
36
+ width?: number;
37
+ radius?: number;
38
+ };
39
+ /** Text styling */
40
+ text?: {
41
+ color?: string;
42
+ fontSize?: number;
43
+ fontFamily?: string;
44
+ };
45
+ /** Sprite textures for each state (alternative to backgroundColor) */
46
+ textures?: {
47
+ [ButtonState.Normal]?: string;
48
+ [ButtonState.Hover]?: string;
49
+ [ButtonState.Pressed]?: string;
50
+ [ButtonState.Disabled]?: string;
51
+ };
52
+ }
53
+
54
+ /**
55
+ * Properties for the Button component
56
+ */
57
+ export interface ButtonProps {
58
+ /** Button text content */
59
+ text?: string;
60
+ /** Button disabled state */
61
+ disabled?: boolean;
62
+ /** Click event handler */
63
+ click?: (event: FederatedPointerEvent) => void;
64
+ /** Hover enter event handler */
65
+ hoverEnter?: (event: FederatedPointerEvent) => void;
66
+ /** Hover leave event handler */
67
+ hoverLeave?: (event: FederatedPointerEvent) => void;
68
+ /** Press down event handler */
69
+ pressDown?: (event: FederatedPointerEvent) => void;
70
+ /** Press up event handler */
71
+ pressUp?: (event: FederatedPointerEvent) => void;
72
+ /** Visual style configuration */
73
+ style?: ButtonStyle;
74
+ /** Button width */
75
+ width?: number;
76
+ /** Button height */
77
+ height?: number;
78
+ /** Button position X */
79
+ x?: number;
80
+ /** Button position Y */
81
+ y?: number;
82
+ /** Button alpha/opacity */
83
+ alpha?: number;
84
+ /** Button visibility */
85
+ visible?: boolean;
86
+ /** Button cursor */
87
+ cursor?: string;
88
+ /** Controls instance to automatically apply button events to (e.g., ControlsDirective or JoystickControls) */
89
+ controls?: ControlsDirective | JoystickControls | any;
90
+ /** Name of the control to trigger with applyControl when button is clicked */
91
+ controlName?: string;
92
+ /** Shape of the button background: 'rect', 'circle', or 'ellipse' */
93
+ shape?: 'rect' | 'circle' | 'ellipse';
94
+ /** Custom background component or element (replaces default background if provided) */
95
+ background?: Element | any;
96
+ /** Custom children components for button content (takes priority over text if provided) */
97
+ children?: Element[];
98
+ }
99
+
100
+ /**
101
+ * Creates a Button component with interactive states and customizable styling.
102
+ *
103
+ * This component provides a fully interactive button with visual feedback
104
+ * for different states (normal, hover, pressed, disabled). It supports both
105
+ * sprite-based and graphics-based rendering approaches.
106
+ *
107
+ * The button is built using a Container with background and text elements,
108
+ * providing reactive state management and event handling.
109
+ *
110
+ * ## Features
111
+ *
112
+ * - **Controls Integration**: Automatically trigger controls via `applyControl` when clicked
113
+ * - **Multiple Shapes**: Support for rect, circle, and ellipse shapes
114
+ * - **Custom Content**: Use children components for custom button content
115
+ * - **Custom Background**: Provide a custom background component
116
+ *
117
+ * @param props - Button configuration including text, styling, controls, shape, and event handlers
118
+ * @returns A reactive Button component
119
+ * @example
120
+ * ```typescript
121
+ * // Simple button with text and click handler
122
+ * const simpleButton = Button({
123
+ * text: "Click Me",
124
+ * click: () => console.log("Button clicked!"),
125
+ * width: 150,
126
+ * height: 50
127
+ * });
128
+ *
129
+ * // Button with controls integration
130
+ * const jumpButton = Button({
131
+ * text: "Jump",
132
+ * controls: controlsInstance,
133
+ * controlName: "jump",
134
+ * width: 120,
135
+ * height: 40
136
+ * });
137
+ *
138
+ * // Circular button
139
+ * const circleButton = Button({
140
+ * text: "Action",
141
+ * shape: "circle",
142
+ * width: 100,
143
+ * height: 100
144
+ * });
145
+ *
146
+ * // Button with custom content (children)
147
+ * const customButton = Button({
148
+ * shape: "circle",
149
+ * width: 80,
150
+ * height: 80,
151
+ * children: [
152
+ * h(Sprite, { image: "icon.png", width: 50, height: 50 })
153
+ * ]
154
+ * });
155
+ *
156
+ * // Styled button with custom colors
157
+ * const styledButton = Button({
158
+ * text: "Styled Button",
159
+ * style: {
160
+ * backgroundColor: {
161
+ * normal: "#28a745",
162
+ * hover: "#218838",
163
+ * pressed: "#1e7e34",
164
+ * disabled: "#6c757d"
165
+ * },
166
+ * border: {
167
+ * radius: 8,
168
+ * width: 2,
169
+ * color: "#ffffff"
170
+ * },
171
+ * text: {
172
+ * fontSize: 18,
173
+ * color: "#ffffff"
174
+ * }
175
+ * }
176
+ * });
177
+ *
178
+ * // Sprite-based button
179
+ * const spriteButton = Button({
180
+ * text: "Play Game",
181
+ * style: {
182
+ * textures: {
183
+ * normal: "/assets/button-normal.png",
184
+ * hover: "/assets/button-hover.png",
185
+ * pressed: "/assets/button-pressed.png"
186
+ * }
187
+ * }
188
+ * });
189
+ * ```
190
+ */
191
+ export function Button(props: ButtonProps) {
192
+ // Internal state signals
193
+ const currentState = signal(ButtonState.Normal);
194
+ const isPressed = signal(false);
195
+ const isHovered = signal(false);
196
+
197
+ // Define reactive props with defaults
198
+ const defineProps = useDefineProps(props);
199
+ const { text, disabled, width, height, style, shape, controlName } = defineProps({
200
+ text: {
201
+ type: String,
202
+ default: ""
203
+ },
204
+ disabled: {
205
+ type: Boolean,
206
+ default: false
207
+ },
208
+ width: {
209
+ type: Number,
210
+ default: 120
211
+ },
212
+ height: {
213
+ type: Number,
214
+ default: 40
215
+ },
216
+ style: {
217
+ type: Object,
218
+ default: () => ({})
219
+ },
220
+ shape: {
221
+ type: String,
222
+ default: "rect"
223
+ },
224
+ controlName: {
225
+ type: String,
226
+ default: undefined
227
+ }
228
+ });
229
+
230
+ // Helper function to get controls instance (handles signals like Joystick)
231
+ const getControls = () => {
232
+ if (!props.controls) return null;
233
+ if (isSignal(props.controls)) {
234
+ return props.controls();
235
+ }
236
+ return props.controls;
237
+ };
238
+
239
+ // Update button state based on disabled and interaction states
240
+ effect(() => {
241
+ const isDisabled = disabled();
242
+ const pressed = isPressed();
243
+ const hovered = isHovered();
244
+
245
+ if (isDisabled) {
246
+ currentState.set(ButtonState.Disabled);
247
+ } else if (pressed) {
248
+ currentState.set(ButtonState.Pressed);
249
+ } else if (hovered) {
250
+ currentState.set(ButtonState.Hover);
251
+ } else {
252
+ currentState.set(ButtonState.Normal);
253
+ }
254
+ });
255
+
256
+ // Event handlers
257
+ const eventHandlers = {
258
+ pointerenter: (event: FederatedPointerEvent) => {
259
+ if (!disabled()) {
260
+ isHovered.set(true);
261
+ props.hoverEnter?.(event);
262
+ }
263
+ },
264
+ pointerleave: (event: FederatedPointerEvent) => {
265
+ isHovered.set(false);
266
+ isPressed.set(false);
267
+ props.hoverLeave?.(event);
268
+ },
269
+ pointerdown: async (event: FederatedPointerEvent) => {
270
+ if (!disabled()) {
271
+ isPressed.set(true);
272
+ props.pressDown?.(event);
273
+
274
+ // Apply control if controls and controlName are provided
275
+ const controls = getControls();
276
+ const name = controlName();
277
+ if (controls && name && controls.applyControl) {
278
+ await controls.applyControl(name, true);
279
+ }
280
+ }
281
+ },
282
+ pointerup: async (event: FederatedPointerEvent) => {
283
+ if (!disabled() && isPressed()) {
284
+ isPressed.set(false);
285
+ props.pressUp?.(event);
286
+
287
+ // Apply control release if controls and controlName are provided
288
+ const controls = getControls();
289
+ const name = controlName();
290
+ if (controls && name && controls.applyControl) {
291
+ await controls.applyControl(name, false);
292
+ }
293
+ }
294
+ },
295
+ pointertap: async (event: FederatedPointerEvent) => {
296
+ if (!disabled()) {
297
+ props.click?.(event);
298
+
299
+ // Apply control if controls and controlName are provided (press and release)
300
+ const controls = getControls();
301
+ const name = controlName();
302
+ if (controls && name && controls.applyControl) {
303
+ await controls.applyControl(name);
304
+ }
305
+ }
306
+ }
307
+ };
308
+
309
+ // Generate background element
310
+ const getBackgroundElement = () => {
311
+ // If custom background is provided, use it
312
+ if (props.background) {
313
+ return props.background;
314
+ }
315
+
316
+ // Otherwise, use shape-based background
317
+ const currentShape = shape();
318
+ const bgColor = computed(() => {
319
+ const currentStyle = style();
320
+ const backgroundColor = currentStyle.backgroundColor || {
321
+ [ButtonState.Normal]: "#007bff",
322
+ [ButtonState.Hover]: "#0056b3",
323
+ [ButtonState.Pressed]: "#004085",
324
+ [ButtonState.Disabled]: "#6c757d"
325
+ };
326
+ const state = currentState();
327
+ return backgroundColor[state] || backgroundColor[ButtonState.Normal];
328
+ });
329
+
330
+ if (currentShape === 'circle') {
331
+ // For circle, use the smaller dimension as radius
332
+ const radius = computed(() => Math.min(width(), height()) / 2);
333
+ return h(Circle, {
334
+ radius: radius,
335
+ x: computed(() => width() / 2),
336
+ y: computed(() => height() / 2),
337
+ color: bgColor
338
+ });
339
+ } else if (currentShape === 'ellipse') {
340
+ return h(Ellipse, {
341
+ width: width,
342
+ height: height,
343
+ color: bgColor
344
+ });
345
+ } else {
346
+ // Default: rect
347
+ return h(Rect, {
348
+ width: width,
349
+ height: height,
350
+ color: bgColor
351
+ });
352
+ }
353
+ };
354
+
355
+ // Generate content element(s)
356
+ const getContentElements = () => {
357
+ // If children are provided, use them (priority over text)
358
+ if (props.children && props.children.length > 0) {
359
+ return props.children;
360
+ }
361
+
362
+ // Otherwise, use text
363
+ return [
364
+ h(Text, {
365
+ text: text,
366
+ x: computed(() => width() / 2),
367
+ y: computed(() => height() / 2),
368
+ anchor: { x: 0.5, y: 0.5 },
369
+ style: computed(() => {
370
+ const currentStyle = style();
371
+ const textStyle = currentStyle.text || {};
372
+ return {
373
+ fontSize: textStyle.fontSize || 16,
374
+ fontFamily: textStyle.fontFamily || "Arial",
375
+ fill: textStyle.color || "#ffffff"
376
+ };
377
+ })()
378
+ })
379
+ ];
380
+ };
381
+
382
+ // Return Container with h() children
383
+ return h(Container, {
384
+ x: props.x,
385
+ y: props.y,
386
+ width: props.width,
387
+ height: props.height,
388
+ alpha: props.alpha,
389
+ visible: props.visible,
390
+ cursor: props.cursor || "pointer",
391
+ ...eventHandlers
392
+ }, [
393
+ getBackgroundElement(),
394
+ ...getContentElements()
395
+ ]);
396
+ }
@@ -1,21 +1,26 @@
1
1
  import { effect, Signal, signal } from "@signe/reactive";
2
- import { Container, autoDetectRenderer } from "pixi.js";
3
- import { loadYoga } from "yoga-layout";
4
- import { Props, createComponent, registerComponent, Element } from "../engine/reactive";
2
+ import { Application, Container } from "pixi.js";
3
+ import {
4
+ Props,
5
+ createComponent,
6
+ registerComponent,
7
+ Element,
8
+ } from "../engine/reactive";
5
9
  import { useProps } from "../hooks/useProps";
6
10
  import { ComponentInstance, DisplayObject } from "./DisplayObject";
7
11
  import { ComponentFunction } from "../engine/signal";
8
12
  import { SignalOrPrimitive } from "./types";
9
13
  import { Size } from "./types/DisplayObject";
10
14
  import { Scheduler, Tick } from "../directives/Scheduler";
15
+ import { GlobalAssetLoader } from "../utils/GlobalAssetLoader";
11
16
 
12
17
  interface CanvasElement extends Element<ComponentInstance> {
13
- render: (rootElement: HTMLElement) => void;
18
+ render: (rootElement: HTMLElement, app?: Application) => void;
14
19
  directives: {
15
- tick: Scheduler
20
+ tick: Scheduler;
16
21
  };
17
22
  propObservables: {
18
- tick: Signal<Tick>
23
+ tick: Signal<Tick>;
19
24
  };
20
25
  }
21
26
 
@@ -30,33 +35,28 @@ export interface CanvasProps extends Props {
30
35
  isRoot?: boolean;
31
36
  tick?: any;
32
37
  class?: SignalOrPrimitive<string>;
38
+ background?: string;
33
39
  }
34
40
 
35
41
  export const Canvas: ComponentFunction<CanvasProps> = async (props = {}) => {
36
42
  let { cursorStyles, width, height, class: className } = useProps(props);
37
- const Yoga = await loadYoga();
38
43
 
39
- if (!props.width) width = signal<Size>(800)
40
- if (!props.height) height = signal<Size>(600)
41
-
42
- const renderer = await autoDetectRenderer({
43
- ...props,
44
- width: width?.(),
45
- height: height?.(),
46
- });
44
+ if (!props.width) width = signal<Size>(800);
45
+ if (!props.height) height = signal<Size>(600);
47
46
 
48
47
  const canvasSize = signal({
49
- width: renderer.width,
50
- height: renderer.height,
48
+ width: 0,
49
+ height: 0,
51
50
  });
52
51
 
53
52
  props.isRoot = true;
53
+ const globalLoader = new GlobalAssetLoader();
54
54
  const options: CanvasProps = {
55
55
  ...props,
56
56
  context: {
57
- Yoga,
58
- renderer,
59
57
  canvasSize,
58
+ app: signal(null),
59
+ globalLoader,
60
60
  },
61
61
  width: width?.(),
62
62
  height: height?.(),
@@ -69,10 +69,21 @@ export const Canvas: ComponentFunction<CanvasProps> = async (props = {}) => {
69
69
  frame: 0,
70
70
  deltaRatio: 1,
71
71
  });
72
+ } else {
73
+ options.context!.tick = props.tick;
72
74
  }
75
+
76
+ // Register the tick signal globally so animatedSignal can use it by default
77
+ (globalThis as any).__CANVAS_ENGINE_TICK__ = options.context!.tick;
78
+
73
79
  const canvasElement = createComponent("Canvas", options) as CanvasElement;
74
80
 
75
- canvasElement.render = (rootElement: HTMLElement) => {
81
+ canvasElement.render = (rootElement: HTMLElement, app?: Application) => {
82
+ if (!app) {
83
+ return;
84
+ }
85
+
86
+ const renderer = app.renderer;
76
87
  const canvasEl = renderer.view.canvas as HTMLCanvasElement;
77
88
 
78
89
  (globalThis as any).__PIXI_STAGE__ = canvasElement.componentInstance;
@@ -85,6 +96,34 @@ export const Canvas: ComponentFunction<CanvasProps> = async (props = {}) => {
85
96
  renderer.render(canvasElement.componentInstance as any);
86
97
  });
87
98
 
99
+ app.stage = canvasElement.componentInstance as any;
100
+
101
+ app.stage.layout = {
102
+ width: app.screen.width,
103
+ height: app.screen.height,
104
+ justifyContent: props.justifyContent,
105
+ alignItems: props.alignItems,
106
+ };
107
+
108
+ canvasSize.set({ width: app.screen.width, height: app.screen.height })
109
+
110
+ app.renderer.on('resize', (width: number, height: number) => {
111
+ canvasSize.set({ width, height });
112
+
113
+ if (app.stage.layout) {
114
+ app.stage.layout = {
115
+ width,
116
+ height
117
+ }
118
+ }
119
+ });
120
+
121
+ if (props.tickStart !== false) canvasElement.directives.tick.start();
122
+
123
+ app.ticker.add(() => {
124
+ canvasElement.propObservables!.tick();
125
+ });
126
+
88
127
  if (cursorStyles) {
89
128
  effect(() => {
90
129
  renderer.events.cursorStyles = cursorStyles();
@@ -97,37 +136,14 @@ export const Canvas: ComponentFunction<CanvasProps> = async (props = {}) => {
97
136
  });
98
137
  }
99
138
 
100
- const resizeCanvas = async () => {
101
- let w, h;
102
- if (width?.() === "100%" && height?.() === "100%") {
103
- const parent = canvasEl.parentElement;
104
- w = parent ? parent.clientWidth : window.innerWidth;
105
- h = parent ? parent.clientHeight : window.innerHeight;
106
- } else {
107
- w = width?.() ?? canvasEl.offsetWidth;
108
- h = height?.() ?? canvasEl.offsetHeight;
109
- }
110
- renderer.resize(w, h);
111
- canvasSize.set({ width: w, height: h });
112
- canvasElement.componentInstance.setWidth(w)
113
- canvasElement.componentInstance.setHeight(h)
114
- };
115
-
116
- // Listen for window resize events
117
- window.addEventListener("resize", resizeCanvas);
118
-
119
- // Check if a canvas already exists in the rootElement
120
- const existingCanvas = rootElement.querySelector('canvas');
139
+ const existingCanvas = rootElement.querySelector("canvas");
121
140
  if (existingCanvas) {
122
- // If it exists, replace it with the new canvas
123
141
  rootElement.replaceChild(canvasEl, existingCanvas);
124
142
  } else {
125
- // If it doesn't exist, append the new canvas
126
143
  rootElement.appendChild(canvasEl);
127
144
  }
128
145
 
129
- // Initial resize
130
- resizeCanvas();
146
+ options.context!.app.set(app)
131
147
  };
132
148
 
133
149
  return canvasElement;