canvasengine 2.0.1-beta.1 → 2.0.1

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 (212) hide show
  1. package/dist/DebugRenderer-DkjTAc48.js +1384 -0
  2. package/dist/DebugRenderer-DkjTAc48.js.map +1 -0
  3. package/dist/components/Button.d.ts +185 -0
  4. package/dist/components/Button.d.ts.map +1 -0
  5. package/dist/components/Canvas.d.ts +17 -0
  6. package/dist/components/Canvas.d.ts.map +1 -0
  7. package/dist/components/Container.d.ts +25 -0
  8. package/dist/components/Container.d.ts.map +1 -0
  9. package/dist/components/DOMContainer.d.ts +28 -0
  10. package/dist/components/DOMContainer.d.ts.map +1 -0
  11. package/dist/components/DOMElement.d.ts +54 -0
  12. package/dist/components/DOMElement.d.ts.map +1 -0
  13. package/dist/components/DOMSprite.d.ts +127 -0
  14. package/dist/components/DOMSprite.d.ts.map +1 -0
  15. package/dist/components/DisplayObject.d.ts +18 -0
  16. package/dist/components/DisplayObject.d.ts.map +1 -0
  17. package/dist/components/FocusContainer.d.ts +129 -0
  18. package/dist/components/FocusContainer.d.ts.map +1 -0
  19. package/dist/components/Graphic.d.ts +64 -0
  20. package/dist/components/Graphic.d.ts.map +1 -0
  21. package/dist/components/Joystick.d.ts +37 -0
  22. package/dist/components/Joystick.d.ts.map +1 -0
  23. package/dist/components/Mesh.d.ts +138 -0
  24. package/dist/components/Mesh.d.ts.map +1 -0
  25. package/dist/components/NineSliceSprite.d.ts +16 -0
  26. package/dist/components/NineSliceSprite.d.ts.map +1 -0
  27. package/dist/components/ParticleEmitter.d.ts +4 -0
  28. package/dist/components/ParticleEmitter.d.ts.map +1 -0
  29. package/dist/components/Scene.d.ts +2 -0
  30. package/dist/components/Scene.d.ts.map +1 -0
  31. package/dist/components/Sprite.d.ts +182 -0
  32. package/dist/components/Sprite.d.ts.map +1 -0
  33. package/dist/components/Text.d.ts +24 -0
  34. package/dist/components/Text.d.ts.map +1 -0
  35. package/dist/components/TilingSprite.d.ts +17 -0
  36. package/dist/components/TilingSprite.d.ts.map +1 -0
  37. package/dist/components/Video.d.ts +14 -0
  38. package/dist/components/Video.d.ts.map +1 -0
  39. package/dist/components/Viewport.d.ts +52 -0
  40. package/dist/components/Viewport.d.ts.map +1 -0
  41. package/dist/components/index.d.ts +20 -0
  42. package/dist/components/index.d.ts.map +1 -0
  43. package/dist/components/types/DisplayObject.d.ts +127 -0
  44. package/dist/components/types/DisplayObject.d.ts.map +1 -0
  45. package/dist/components/types/MouseEvent.d.ts +4 -0
  46. package/dist/components/types/MouseEvent.d.ts.map +1 -0
  47. package/dist/components/types/Spritesheet.d.ts +248 -0
  48. package/dist/components/types/Spritesheet.d.ts.map +1 -0
  49. package/dist/components/types/index.d.ts +4 -0
  50. package/dist/components/types/index.d.ts.map +1 -0
  51. package/dist/directives/Controls.d.ts +112 -0
  52. package/dist/directives/Controls.d.ts.map +1 -0
  53. package/dist/directives/ControlsBase.d.ts +199 -0
  54. package/dist/directives/ControlsBase.d.ts.map +1 -0
  55. package/dist/directives/Drag.d.ts +69 -0
  56. package/dist/directives/Drag.d.ts.map +1 -0
  57. package/dist/directives/Flash.d.ts +116 -0
  58. package/dist/directives/Flash.d.ts.map +1 -0
  59. package/dist/directives/FocusNavigation.d.ts +52 -0
  60. package/dist/directives/FocusNavigation.d.ts.map +1 -0
  61. package/dist/directives/FogVisibility.d.ts +47 -0
  62. package/dist/directives/FogVisibility.d.ts.map +1 -0
  63. package/dist/directives/GamepadControls.d.ts +224 -0
  64. package/dist/directives/GamepadControls.d.ts.map +1 -0
  65. package/dist/directives/JoystickControls.d.ts +171 -0
  66. package/dist/directives/JoystickControls.d.ts.map +1 -0
  67. package/dist/directives/KeyboardControls.d.ts +219 -0
  68. package/dist/directives/KeyboardControls.d.ts.map +1 -0
  69. package/dist/directives/Scheduler.d.ts +36 -0
  70. package/dist/directives/Scheduler.d.ts.map +1 -0
  71. package/dist/directives/Shake.d.ts +98 -0
  72. package/dist/directives/Shake.d.ts.map +1 -0
  73. package/dist/directives/Sound.d.ts +25 -0
  74. package/dist/directives/Sound.d.ts.map +1 -0
  75. package/dist/directives/SpriteEffects.d.ts +70 -0
  76. package/dist/directives/SpriteEffects.d.ts.map +1 -0
  77. package/dist/directives/Transition.d.ts +10 -0
  78. package/dist/directives/Transition.d.ts.map +1 -0
  79. package/dist/directives/ViewportCull.d.ts +11 -0
  80. package/dist/directives/ViewportCull.d.ts.map +1 -0
  81. package/dist/directives/ViewportFollow.d.ts +18 -0
  82. package/dist/directives/ViewportFollow.d.ts.map +1 -0
  83. package/dist/directives/index.d.ts +15 -0
  84. package/dist/directives/index.d.ts.map +1 -0
  85. package/dist/dist-BOOc43Qm.js +778 -0
  86. package/dist/dist-BOOc43Qm.js.map +1 -0
  87. package/dist/engine/FocusManager.d.ts +174 -0
  88. package/dist/engine/FocusManager.d.ts.map +1 -0
  89. package/dist/engine/animation.d.ts +72 -0
  90. package/dist/engine/animation.d.ts.map +1 -0
  91. package/dist/engine/bootstrap.d.ts +52 -0
  92. package/dist/engine/bootstrap.d.ts.map +1 -0
  93. package/dist/engine/directive.d.ts +13 -0
  94. package/dist/engine/directive.d.ts.map +1 -0
  95. package/dist/engine/reactive.d.ts +139 -0
  96. package/dist/engine/reactive.d.ts.map +1 -0
  97. package/dist/engine/signal.d.ts +74 -0
  98. package/dist/engine/signal.d.ts.map +1 -0
  99. package/dist/engine/trigger.d.ts +54 -0
  100. package/dist/engine/trigger.d.ts.map +1 -0
  101. package/dist/engine/utils.d.ts +89 -0
  102. package/dist/engine/utils.d.ts.map +1 -0
  103. package/dist/hooks/addContext.d.ts +2 -0
  104. package/dist/hooks/addContext.d.ts.map +1 -0
  105. package/dist/hooks/useFocus.d.ts +60 -0
  106. package/dist/hooks/useFocus.d.ts.map +1 -0
  107. package/dist/hooks/useProps.d.ts +53 -0
  108. package/dist/hooks/useProps.d.ts.map +1 -0
  109. package/dist/hooks/useRef.d.ts +4 -0
  110. package/dist/hooks/useRef.d.ts.map +1 -0
  111. package/dist/index.d.ts +19 -844
  112. package/dist/index.d.ts.map +1 -0
  113. package/dist/index.global.js +189 -0
  114. package/dist/index.global.js.map +1 -0
  115. package/dist/index.js +15156 -3287
  116. package/dist/index.js.map +1 -1
  117. package/dist/utils/Ease.d.ts +17 -0
  118. package/dist/utils/Ease.d.ts.map +1 -0
  119. package/dist/utils/GlobalAssetLoader.d.ts +141 -0
  120. package/dist/utils/GlobalAssetLoader.d.ts.map +1 -0
  121. package/dist/utils/RadialGradient.d.ts +57 -0
  122. package/dist/utils/RadialGradient.d.ts.map +1 -0
  123. package/dist/utils/functions.d.ts +2 -0
  124. package/dist/utils/functions.d.ts.map +1 -0
  125. package/dist/utils/tabindex.d.ts +16 -0
  126. package/dist/utils/tabindex.d.ts.map +1 -0
  127. package/index.d.ts +1 -3
  128. package/package.json +19 -52
  129. package/src/components/Button.ts +406 -0
  130. package/src/components/Canvas.ts +82 -51
  131. package/src/components/Container.ts +31 -4
  132. package/src/components/DOMContainer.ts +387 -0
  133. package/src/components/DOMElement.ts +556 -0
  134. package/src/components/DOMSprite.ts +1044 -0
  135. package/src/components/DisplayObject.ts +423 -202
  136. package/src/components/FocusContainer.ts +368 -0
  137. package/src/components/Graphic.ts +255 -68
  138. package/src/components/Joystick.ts +269 -0
  139. package/src/components/Mesh.ts +239 -0
  140. package/src/components/NineSliceSprite.ts +4 -1
  141. package/src/components/ParticleEmitter.ts +12 -8
  142. package/src/components/Sprite.ts +441 -54
  143. package/src/components/Text.ts +270 -26
  144. package/src/components/Video.ts +110 -0
  145. package/src/components/Viewport.ts +122 -63
  146. package/src/components/index.ts +12 -5
  147. package/src/components/types/DisplayObject.ts +64 -5
  148. package/src/components/types/Spritesheet.ts +0 -118
  149. package/src/directives/Controls.ts +254 -0
  150. package/src/directives/ControlsBase.ts +267 -0
  151. package/src/directives/Drag.ts +357 -52
  152. package/src/directives/Flash.ts +419 -0
  153. package/src/directives/FocusNavigation.ts +113 -0
  154. package/src/directives/FogVisibility.ts +273 -0
  155. package/src/directives/GamepadControls.ts +537 -0
  156. package/src/directives/JoystickControls.ts +396 -0
  157. package/src/directives/KeyboardControls.ts +86 -431
  158. package/src/directives/Scheduler.ts +21 -16
  159. package/src/directives/Shake.ts +298 -0
  160. package/src/directives/Sound.ts +94 -31
  161. package/src/directives/SpriteEffects.ts +461 -0
  162. package/src/directives/ViewportFollow.ts +40 -9
  163. package/src/directives/index.ts +14 -6
  164. package/src/engine/FocusManager.ts +510 -0
  165. package/src/engine/animation.ts +175 -21
  166. package/src/engine/bootstrap.ts +140 -6
  167. package/src/engine/directive.ts +4 -4
  168. package/src/engine/reactive.ts +1266 -167
  169. package/src/engine/signal.ts +335 -47
  170. package/src/engine/trigger.ts +95 -12
  171. package/src/engine/utils.ts +101 -9
  172. package/src/hooks/useFocus.ts +91 -0
  173. package/src/hooks/useProps.ts +92 -7
  174. package/src/index.ts +13 -6
  175. package/src/types/pixi-cull.d.ts +7 -0
  176. package/src/utils/GlobalAssetLoader.ts +257 -0
  177. package/src/utils/RadialGradient.ts +29 -0
  178. package/src/utils/functions.ts +7 -0
  179. package/src/utils/tabindex.ts +70 -0
  180. package/testing/index.ts +42 -0
  181. package/tsconfig.json +15 -25
  182. package/vite.config.ts +39 -0
  183. package/.cursorrules +0 -0
  184. package/.github/workflows/ci.yml +0 -29
  185. package/README.md +0 -79
  186. package/dist/compiler/vite.js +0 -119
  187. package/dist/compiler/vite.js.map +0 -1
  188. package/logo.png +0 -0
  189. package/src/compiler/grammar.pegjs +0 -180
  190. package/src/compiler/vite.ts +0 -166
  191. package/src/components/DrawMap/index.ts +0 -65
  192. package/src/components/Tilemap/Tile.ts +0 -79
  193. package/src/components/Tilemap/TileGroup.ts +0 -207
  194. package/src/components/Tilemap/TileLayer.ts +0 -163
  195. package/src/components/Tilemap/TileSet.ts +0 -41
  196. package/src/components/Tilemap/index.ts +0 -80
  197. package/src/presets/Bar.ts +0 -89
  198. package/src/presets/Button.ts +0 -0
  199. package/src/presets/Joystick.ts +0 -286
  200. package/src/presets/NightAmbiant.ts +0 -122
  201. package/src/presets/Particle.ts +0 -53
  202. package/starter/assets/logo.png +0 -0
  203. package/starter/components/app.ce +0 -18
  204. package/starter/components/hello.ce +0 -35
  205. package/starter/index.html +0 -21
  206. package/starter/main.ts +0 -6
  207. package/starter/package.json +0 -20
  208. package/starter/tsconfig.json +0 -32
  209. package/starter/vite.config.ts +0 -12
  210. package/tsconfig.node.json +0 -10
  211. package/tsup.config.ts +0 -28
  212. package/vitest.config.ts +0 -12
@@ -1,54 +1,179 @@
1
- import { effect, Signal } from "@signe/reactive";
2
- import { Graphics as PixiGraphics } from "pixi.js";
3
- import { createComponent, registerComponent } from "../engine/reactive";
4
- import { DisplayObject } from "./DisplayObject";
1
+ import { Effect, effect, isSignal, signal, Signal, WritableSignal } from "@signe/reactive";
2
+ import { Assets, ObservablePoint, Graphics as PixiGraphics } from "pixi.js";
3
+ import { createComponent, Element, registerComponent } from "../engine/reactive";
4
+ import { ComponentInstance, DisplayObject } from "./DisplayObject";
5
5
  import { DisplayObjectProps } from "./types/DisplayObject";
6
6
  import { useProps } from "../hooks/useProps";
7
+ import { SignalOrPrimitive } from "./types";
8
+ import { isPercent } from "../utils/functions";
9
+ import { setObservablePoint } from "../engine/utils";
7
10
 
8
11
  interface GraphicsProps extends DisplayObjectProps {
9
- draw?: (graphics: PixiGraphics) => void;
12
+ draw?: (graphics: PixiGraphics, width: number, height: number, anchor?: [number, number]) => void;
10
13
  }
11
14
 
12
15
  interface RectProps extends DisplayObjectProps {
13
- width: number;
14
- height: number;
15
- color: string;
16
+ color: SignalOrPrimitive<string>;
16
17
  }
17
18
 
18
19
  interface CircleProps extends DisplayObjectProps {
19
- radius: number;
20
- color: string;
20
+ radius: SignalOrPrimitive<number>;
21
+ color: SignalOrPrimitive<string>;
21
22
  }
22
23
 
23
24
  interface EllipseProps extends DisplayObjectProps {
24
- width: number;
25
- height: number;
26
- color: string;
25
+ color: SignalOrPrimitive<string>;
27
26
  }
28
27
 
29
28
  interface TriangleProps extends DisplayObjectProps {
30
- base: number;
31
- height: number;
32
- color: string;
29
+ base: SignalOrPrimitive<number>;
30
+ color: SignalOrPrimitive<string>;
33
31
  }
34
32
 
35
33
  interface SvgProps extends DisplayObjectProps {
36
- svg: string;
34
+ /** SVG content as string (legacy prop) */
35
+ svg?: string;
36
+ /** URL source of the SVG file to load */
37
+ src?: string;
38
+ /** Direct SVG content as string */
39
+ content?: string;
37
40
  }
38
41
 
39
42
  class CanvasGraphics extends DisplayObject(PixiGraphics) {
40
- onInit(props: GraphicsProps) {
41
- super.onInit(props);
43
+ clearEffect: Effect;
44
+ _width: WritableSignal<number>;
45
+ _height: WritableSignal<number>;
46
+
47
+ isCustomAnchor = true;
48
+
49
+ /**
50
+ * Initializes the graphics component with reactive width and height handling.
51
+ *
52
+ * This method handles different types of width and height props:
53
+ * - **Numbers**: Direct pixel values
54
+ * - **Strings with %**: Percentage values that trigger flex layout and use layout box dimensions
55
+ * - **Signals**: Reactive values that update automatically
56
+ *
57
+ * When percentage values are detected, the component:
58
+ * 1. Sets `display: 'flex'` to enable layout calculations
59
+ * 2. Listens to layout events to get computed dimensions
60
+ * 3. Updates internal width/height signals with layout box values
61
+ *
62
+ * The draw function receives the reactive width and height signals as parameters.
63
+ *
64
+ * @param props - Component properties including width, height, and draw function
65
+ * @example
66
+ * ```typescript
67
+ * // With pixel values
68
+ * Graphics({ width: 100, height: 50, draw: (g, w, h) => g.rect(0, 0, w(), h()) });
69
+ *
70
+ * // With percentage values (uses layout box)
71
+ * Graphics({ width: "50%", height: "100%", draw: (g, w, h) => g.rect(0, 0, w(), h()) });
72
+ *
73
+ * // With signals
74
+ * const width = signal(100);
75
+ * Graphics({ width, height: 50, draw: (g, w, h) => g.rect(0, 0, w(), h()) });
76
+ * ```
77
+ */
78
+ async onInit(props) {
79
+ await super.onInit(props);
80
+ this.setObjectFit('none');
81
+ }
82
+
83
+ /**
84
+ * Called when the component is mounted to the scene graph.
85
+ * Creates the reactive effect for drawing using the original signals from propObservables.
86
+ * @param {Element<DisplayObject>} element - The element being mounted with props and propObservables.
87
+ * @param {number} [index] - The index of the component among its siblings.
88
+ */
89
+ async onMount(element: Element<any>, index?: number): Promise<void> {
90
+ await super.onMount(element, index);
91
+ if (this.destroyed || !this.parent) {
92
+ return;
93
+ }
94
+ const { props, propObservables } = element;
95
+
96
+ // Use original signals from propObservables if available, otherwise create new ones
97
+ const width = (isSignal(propObservables?.width) ? propObservables.width : signal(props.width || 0)) as WritableSignal<number>;
98
+ const height = (isSignal(propObservables?.height) ? propObservables.height : signal(props.height || 0)) as WritableSignal<number>;
99
+ const anchor = (isSignal(propObservables?.anchor) ? propObservables.anchor : signal(props.anchor || [0, 0])) as WritableSignal<[number, number]>;
100
+
101
+ // Store as class properties for access in other methods
102
+ this._width = width;
103
+ this._height = height;
104
+
105
+ // Check if width or height are percentages to set display flex
106
+ const isWidthPercentage = isPercent(width());
107
+ const isHeightPercentage = isPercent(height());
108
+
42
109
  if (props.draw) {
43
- effect(() => {
110
+ this.clearEffect = effect(() => {
111
+ const w = width();
112
+ const h = height();
113
+ const a = anchor();
114
+ if (typeof w == 'string' || typeof h == 'string') {
115
+ return
116
+ }
117
+ if (this.destroyed || !this.parent) {
118
+ return
119
+ }
44
120
  this.clear();
45
- props.draw?.(this);
121
+ props.draw?.(this, w, h, a);
122
+ this.subjectInit.next(this)
46
123
  });
47
124
  }
125
+
126
+ this.on('layout', (event) => {
127
+ const layoutBox = event.computedLayout;
128
+ // Update width if it's a percentage and value has changed
129
+ if (isWidthPercentage && isSignal(width) && width() !== layoutBox.width) {
130
+ width.set(layoutBox.width);
131
+ }
132
+
133
+ // Update height if it's a percentage and value has changed
134
+ if (isHeightPercentage && isSignal(height) && height() !== layoutBox.height) {
135
+ height.set(layoutBox.height);
136
+ }
137
+ });
48
138
  }
49
- }
50
139
 
51
- interface CanvasGraphics extends PixiGraphics {}
140
+ /**
141
+ * Called when component props are updated.
142
+ * Updates the internal width and height signals when props change.
143
+ * @param props - Updated properties
144
+ */
145
+ onUpdate(props: any) {
146
+ super.onUpdate(props);
147
+
148
+ // Update width signal if width prop changed and value is different
149
+ if (props.width !== undefined && this._width && this._width() !== props.width) {
150
+ this._width.set(props.width);
151
+ }
152
+
153
+ // Update height signal if height prop changed and value is different
154
+ if (props.height !== undefined && this._height && this._height() !== props.height) {
155
+ this._height.set(props.height);
156
+ }
157
+ }
158
+
159
+ /**
160
+ * Called when the component is about to be destroyed.
161
+ * This method should be overridden by subclasses to perform any cleanup.
162
+ * It ensures that the clearEffect subscription is unsubscribed before calling the original afterDestroy callback.
163
+ * @param parent The parent element.
164
+ * @param afterDestroy A callback function to be executed after the component's own destruction logic.
165
+ * @example
166
+ * // This method is typically called by the engine internally.
167
+ * // await component.onDestroy(parentElement, () => console.log('Component destroyed'));
168
+ */
169
+ async onDestroy(parent: Element<ComponentInstance>, afterDestroy: () => void): Promise<void> {
170
+ const _afterDestroyCallback = async () => {
171
+ this.clearEffect?.subscription.unsubscribe();
172
+ afterDestroy();
173
+ }
174
+ await super.onDestroy(parent, _afterDestroyCallback);
175
+ }
176
+ }
52
177
 
53
178
  registerComponent("Graphics", CanvasGraphics);
54
179
 
@@ -56,92 +181,154 @@ export function Graphics(props: GraphicsProps) {
56
181
  return createComponent("Graphics", props);
57
182
  }
58
183
 
184
+ const graphicsAnchor = (anchor, width, height) => {
185
+ const observableAnchor = new ObservablePoint({ _onUpdate: () => {} }, 0, 0);
186
+ setObservablePoint(observableAnchor, anchor);
187
+ const ax = observableAnchor.x;
188
+ const ay = observableAnchor.y;
189
+
190
+ return { x: -ax * width, y: -ay * height };
191
+ }
192
+
193
+ const propValue = (value: any) => isSignal(value) ? value() : value;
194
+
59
195
  export function Rect(props: RectProps) {
60
- const { width, height, color, borderRadius, border } = useProps(props, {
196
+ const { color, borderRadius, border } = useProps(props, {
61
197
  borderRadius: null,
62
198
  border: null
63
199
  })
200
+
64
201
  return Graphics({
65
- draw: (g) => {
202
+ draw: (g, width, height, anchor) => {
203
+ const { x, y } = graphicsAnchor(anchor, width, height);
66
204
  if (borderRadius()) {
67
- g.roundRect(0, 0, width(), height(), borderRadius());
205
+ g.roundRect(x, y, width, height, borderRadius());
68
206
  } else {
69
- g.rect(0, 0, width(), height());
207
+ g.rect(x, y, width, height);
70
208
  }
71
- if (border) {
72
- g.stroke(border);
209
+ const borderValue = propValue(border);
210
+ if (borderValue) {
211
+ g.stroke(borderValue);
73
212
  }
74
- g.fill(color());
213
+ g.fill(propValue(color));
75
214
  },
76
215
  ...props
77
216
  })
78
217
  }
79
218
 
80
- function drawShape(g: PixiGraphics, shape: 'circle' | 'ellipse', props: {
81
- radius: Signal<number>;
82
- color: Signal<string>;
83
- border: Signal<number>;
84
- } | {
85
- width: Signal<number>;
86
- height: Signal<number>;
87
- color: Signal<string>;
88
- border: Signal<number>;
89
- }) {
90
- const { color, border } = props;
91
- if ('radius' in props) {
92
- g.circle(0, 0, props.radius());
93
- } else {
94
- g.ellipse(0, 0, props.width() / 2, props.height() / 2);
95
- }
96
- if (border()) {
97
- g.stroke(border());
98
- }
99
- g.fill(color());
100
- }
101
-
102
219
  export function Circle(props: CircleProps) {
103
- const { radius, color, border } = useProps(props, {
104
- border: null
220
+ const { color, border, radius } = useProps(props, {
221
+ border: null,
222
+ radius: null
105
223
  })
106
224
  return Graphics({
107
- draw: (g) => drawShape(g, 'circle', { radius, color, border }),
225
+ draw: (g, width, height, anchor) => {
226
+ const { x, y } = graphicsAnchor(anchor, width, height);
227
+ if (width == height || height == 0) {
228
+ g.circle(x, y, propValue(radius) || width);
229
+ } else {
230
+ g.ellipse(x, y, width, height);
231
+ }
232
+ const borderValue = propValue(border);
233
+ if (borderValue) {
234
+ g.stroke(borderValue);
235
+ }
236
+ g.fill(propValue(color));
237
+ },
108
238
  ...props
109
239
  })
110
240
  }
111
241
 
112
242
  export function Ellipse(props: EllipseProps) {
113
- const { width, height, color, border } = useProps(props, {
243
+ const { color, border } = useProps(props, {
114
244
  border: null
115
245
  })
246
+
116
247
  return Graphics({
117
- draw: (g) => drawShape(g, 'ellipse', { width, height, color, border }),
248
+ draw: (g, width, height, anchor) => {
249
+ const { x, y } = graphicsAnchor(anchor, width, height);
250
+ g.ellipse(x + width / 2, y + height / 2, width / 2, height / 2);
251
+ const borderValue = propValue(border);
252
+ if (borderValue) {
253
+ g.stroke(borderValue);
254
+ }
255
+ g.fill(propValue(color));
256
+ },
118
257
  ...props
119
258
  })
120
259
  }
121
260
 
122
261
  export function Triangle(props: TriangleProps) {
123
- const { width, height, color, border } = useProps(props, {
262
+ const { color, border } = useProps(props, {
124
263
  border: null,
125
264
  color: '#000'
126
265
  })
127
266
  return Graphics({
128
- draw: (g) => {
129
- g.moveTo(0, height());
130
- g.lineTo(width() / 2, 0);
131
- g.lineTo(width(), height());
132
- g.lineTo(0, height());
133
- g.fill(color());
134
- if (border) {
135
- g.stroke(border);
267
+ draw: (g, gWidth, gHeight, anchor) => {
268
+ const { x, y } = graphicsAnchor(anchor, gWidth, gHeight);
269
+ g.moveTo(x, y + gHeight);
270
+ g.lineTo(x + gWidth / 2, y);
271
+ g.lineTo(x + gWidth, y + gHeight);
272
+ g.lineTo(x, y + gHeight);
273
+ g.fill(propValue(color));
274
+ const borderValue = propValue(border);
275
+ if (borderValue) {
276
+ g.stroke(borderValue);
136
277
  }
137
278
  },
138
279
  ...props
139
280
  })
140
281
  }
141
282
 
283
+ /**
284
+ * Creates an SVG component that can render SVG graphics from URL, content, or legacy svg prop.
285
+ *
286
+ * This component provides three ways to display SVG graphics:
287
+ * - **src**: Load SVG from a URL using Assets.load with parseAsGraphicsContext option
288
+ * - **content**: Render SVG directly from string content using Graphics.svg() method
289
+ * - **svg**: Legacy prop for SVG content (for backward compatibility)
290
+ *
291
+ * @param props - Component properties including src, content, or svg
292
+ * @returns A reactive SVG component
293
+ * @example
294
+ * ```typescript
295
+ * // Load from URL
296
+ * const svgFromUrl = Svg({ src: "/assets/logo.svg" });
297
+ *
298
+ * // Direct content
299
+ * const svgFromContent = Svg({
300
+ * content: `<svg viewBox="0 0 100 100">
301
+ * <circle cx="50" cy="50" r="40" fill="blue"/>
302
+ * </svg>`
303
+ * });
304
+ *
305
+ * // Legacy usage
306
+ * const svgLegacy = Svg({ svg: "<svg>...</svg>" });
307
+ * ```
308
+ */
142
309
  export function Svg(props: SvgProps) {
143
310
  return Graphics({
144
- draw: (g) => g.svg(props.svg),
311
+ draw: async (g) => {
312
+ if (props.src) {
313
+ // Load SVG from source URL with graphics context parsing
314
+ const svgData = await Assets.load({
315
+ src: props.src,
316
+ data: {
317
+ parseAsGraphicsContext: true,
318
+ },
319
+ });
320
+
321
+ // Apply the loaded graphics context
322
+ const graphics = new PixiGraphics(svgData);
323
+ g.context = graphics.context;
324
+ } else if (props.content) {
325
+ // Render SVG directly from content string
326
+ g.svg(props.content);
327
+ } else if (props.svg) {
328
+ // Legacy prop support
329
+ g.svg(props.svg);
330
+ }
331
+ },
145
332
  ...props
146
333
  })
147
- }
334
+ }
@@ -0,0 +1,269 @@
1
+ /*
2
+ * Joystick
3
+ *
4
+ * Inspired by https://github.com/endel/pixi-virtual-joystick
5
+ */
6
+
7
+ import * as PIXI from "pixi.js";
8
+ import { Container, Graphics, Sprite, h, signal, isSignal, computed } from "../";
9
+ import type { DisplayObjectProps } from "./types/DisplayObject";
10
+
11
+ export interface JoystickChangeEvent {
12
+ angle: number;
13
+ direction: Direction;
14
+ power: number;
15
+ }
16
+
17
+ export enum Direction {
18
+ LEFT = "left",
19
+ TOP = "top",
20
+ BOTTOM = "bottom",
21
+ RIGHT = "right",
22
+ TOP_LEFT = "top_left",
23
+ TOP_RIGHT = "top_right",
24
+ BOTTOM_LEFT = "bottom_left",
25
+ BOTTOM_RIGHT = "bottom_right",
26
+ }
27
+
28
+ export interface JoystickSettings extends DisplayObjectProps {
29
+ outer?: string;
30
+ inner?: string;
31
+ outerScale?: { x: number; y: number };
32
+ innerScale?: { x: number; y: number };
33
+ innerColor?: string;
34
+ outerColor?: string;
35
+ onChange?: (data: JoystickChangeEvent) => void;
36
+ onStart?: () => void;
37
+ onEnd?: () => void;
38
+ /** Controls instance to automatically apply joystick events to (e.g., JoystickControls or ControlsDirective) */
39
+ controls?: any;
40
+ }
41
+
42
+ export function Joystick(opts: JoystickSettings = {}) {
43
+ const settings = Object.assign(
44
+ {
45
+ outerScale: { x: 1, y: 1 },
46
+ innerScale: { x: 1, y: 1 },
47
+ innerColor: "black",
48
+ outerColor: "black",
49
+ },
50
+ opts
51
+ );
52
+
53
+ // Unwrap controls if it's a signal
54
+ const getControls = () => {
55
+ if (isSignal(settings.controls)) {
56
+ return settings.controls();
57
+ }
58
+ return settings.controls;
59
+ };
60
+
61
+ let outerRadius = 70;
62
+ let innerRadius = 10;
63
+ const innerVisualRadius = innerRadius * 2.5;
64
+ const movementRadius = outerRadius - innerVisualRadius;
65
+ const joystickSize = outerRadius * 2;
66
+ const centerPosition = { x: outerRadius, y: outerRadius };
67
+ const innerAlphaStandby = 0.5;
68
+
69
+ let dragging = false;
70
+ let power = 0;
71
+
72
+ const innerPositionX = signal(centerPosition.x);
73
+ const innerPositionY = signal(centerPosition.y);
74
+ const innerAlpha = signal(innerAlphaStandby);
75
+
76
+ function getPowerFromDistance(distance: number) {
77
+ return Math.min(1, distance / outerRadius);
78
+ }
79
+
80
+ function getDirection(center: PIXI.Point) {
81
+ let rad = Math.atan2(center.y, center.x); // [-PI, PI]
82
+ if ((rad >= -Math.PI / 8 && rad < 0) || (rad >= 0 && rad < Math.PI / 8)) {
83
+ return Direction.RIGHT;
84
+ } else if (rad >= Math.PI / 8 && rad < (3 * Math.PI) / 8) {
85
+ return Direction.BOTTOM_RIGHT;
86
+ } else if (rad >= (3 * Math.PI) / 8 && rad < (5 * Math.PI) / 8) {
87
+ return Direction.BOTTOM;
88
+ } else if (rad >= (5 * Math.PI) / 8 && rad < (7 * Math.PI) / 8) {
89
+ return Direction.BOTTOM_LEFT;
90
+ } else if (
91
+ (rad >= (7 * Math.PI) / 8 && rad < Math.PI) ||
92
+ (rad >= -Math.PI && rad < (-7 * Math.PI) / 8)
93
+ ) {
94
+ return Direction.LEFT;
95
+ } else if (rad >= (-7 * Math.PI) / 8 && rad < (-5 * Math.PI) / 8) {
96
+ return Direction.TOP_LEFT;
97
+ } else if (rad >= (-5 * Math.PI) / 8 && rad < (-3 * Math.PI) / 8) {
98
+ return Direction.TOP;
99
+ } else {
100
+ return Direction.TOP_RIGHT;
101
+ }
102
+ }
103
+
104
+ function handleDragStart(event: any) {
105
+ dragging = true;
106
+ innerAlpha.set(1);
107
+ settings.onStart?.();
108
+
109
+ // Notify controls if provided
110
+ const controls = getControls();
111
+ if (controls) {
112
+ // Check if it's JoystickControls instance
113
+ if (controls.handleJoystickStart) {
114
+ controls.handleJoystickStart();
115
+ }
116
+ // Check if it's ControlsDirective with joystick getter
117
+ else if (controls.joystick && controls.joystick.handleJoystickStart) {
118
+ controls.joystick.handleJoystickStart();
119
+ }
120
+ }
121
+ }
122
+
123
+ function handleDragEnd() {
124
+ if (!dragging) return;
125
+ innerPositionX.set(centerPosition.x);
126
+ innerPositionY.set(centerPosition.y);
127
+ dragging = false;
128
+ innerAlpha.set(innerAlphaStandby);
129
+ settings.onEnd?.();
130
+
131
+ // Notify controls if provided
132
+ const controls = getControls();
133
+ if (controls) {
134
+ // Check if it's JoystickControls instance
135
+ if (controls.handleJoystickEnd) {
136
+ controls.handleJoystickEnd();
137
+ }
138
+ // Check if it's ControlsDirective with joystick getter
139
+ else if (controls.joystick && controls.joystick.handleJoystickEnd) {
140
+ controls.joystick.handleJoystickEnd();
141
+ }
142
+ }
143
+ }
144
+
145
+ function handleDragMove(event: any) {
146
+ if (dragging == false) {
147
+ return;
148
+ }
149
+
150
+ const target = event.currentTarget || event.target;
151
+ let newPosition = event.getLocalPosition(target);
152
+
153
+ const sideX = newPosition.x - centerPosition.x;
154
+ const sideY = newPosition.y - centerPosition.y;
155
+ const distance = Math.sqrt(sideX * sideX + sideY * sideY);
156
+
157
+ if (distance == 0) {
158
+ return;
159
+ }
160
+
161
+ /**
162
+ * x: -1 <-> 1
163
+ * y: -1 <-> 1
164
+ * Y
165
+ * ^
166
+ * |
167
+ * 180 | 90
168
+ * ------------> X
169
+ * 270 | 360
170
+ * |
171
+ * |
172
+ */
173
+
174
+ const visualDistance = Math.min(distance, movementRadius);
175
+ const visualRatio = visualDistance / distance;
176
+ const visualX = sideX * visualRatio;
177
+ const visualY = sideY * visualRatio;
178
+ const directionPoint = new PIXI.Point(sideX, sideY);
179
+ let angle = -(Math.atan2(sideY, sideX) * 180) / Math.PI;
180
+ if (angle < 0) {
181
+ angle += 360;
182
+ }
183
+
184
+ innerPositionX.set(centerPosition.x + visualX);
185
+ innerPositionY.set(centerPosition.y + visualY);
186
+ power = getPowerFromDistance(distance);
187
+
188
+ const direction = getDirection(directionPoint);
189
+ const changeEvent = { angle, direction, power };
190
+ settings.onChange?.(changeEvent);
191
+
192
+ // Notify controls if provided
193
+ const controls = getControls();
194
+ if (controls) {
195
+ // Check if it's JoystickControls instance
196
+ if (controls.handleJoystickChange) {
197
+ controls.handleJoystickChange(changeEvent);
198
+ }
199
+ // Check if it's ControlsDirective with joystick getter
200
+ else if (controls.joystick && controls.joystick.handleJoystickChange) {
201
+ controls.joystick.handleJoystickChange(changeEvent);
202
+ }
203
+ }
204
+ }
205
+
206
+ let innerElement;
207
+ let outerElement;
208
+
209
+ if (!settings.outer) {
210
+ outerElement = h(Graphics, {
211
+ draw: (g) => {
212
+ g.circle(centerPosition.x, centerPosition.y, outerRadius).fill(settings.outerColor);
213
+ },
214
+ positionType: "absolute",
215
+ alpha: 0.5,
216
+ });
217
+ } else {
218
+ outerElement = h(Sprite, {
219
+ image: settings.outer,
220
+ x: centerPosition.x,
221
+ y: centerPosition.y,
222
+ anchor: { x: 0.5, y: 0.5 },
223
+ scale: settings.outerScale,
224
+ positionType: "absolute",
225
+ });
226
+ }
227
+
228
+ const innerOptions: any = {
229
+ scale: settings.innerScale,
230
+ alpha: innerAlpha,
231
+ positionType: "absolute",
232
+ };
233
+
234
+ if (!settings.inner) {
235
+ innerElement = h(Graphics, {
236
+ draw: (g) => {
237
+ g.circle(innerVisualRadius, innerVisualRadius, innerVisualRadius).fill(settings.innerColor);
238
+ },
239
+ width: innerVisualRadius * 2,
240
+ height: innerVisualRadius * 2,
241
+ x: computed(() => innerPositionX() - innerVisualRadius),
242
+ y: computed(() => innerPositionY() - innerVisualRadius),
243
+ ...innerOptions,
244
+ });
245
+ } else {
246
+ innerElement = h(Sprite, {
247
+ image: settings.inner,
248
+ x: innerPositionX,
249
+ y: innerPositionY,
250
+ anchor: { x: 0.5, y: 0.5 },
251
+ ...innerOptions,
252
+ });
253
+ }
254
+
255
+ return h(
256
+ Container,
257
+ {
258
+ ...opts,
259
+ width: opts.width ?? joystickSize,
260
+ height: opts.height ?? joystickSize,
261
+ pointerdown: handleDragStart,
262
+ pointerup: handleDragEnd,
263
+ pointerupoutside: handleDragEnd,
264
+ pointermove: handleDragMove,
265
+ },
266
+ outerElement,
267
+ innerElement,
268
+ );
269
+ }