canvasengine 2.0.0-beta.26 → 2.0.0-beta.28

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 (33) hide show
  1. package/dist/{DebugRenderer-C8qYAVLT.js → DebugRenderer-VHjOerIu.js} +2 -2
  2. package/dist/{DebugRenderer-C8qYAVLT.js.map → DebugRenderer-VHjOerIu.js.map} +1 -1
  3. package/dist/components/Button.d.ts +136 -0
  4. package/dist/components/Button.d.ts.map +1 -0
  5. package/dist/components/Container.d.ts +1 -0
  6. package/dist/components/Container.d.ts.map +1 -1
  7. package/dist/components/DOMContainer.d.ts +1 -0
  8. package/dist/components/DOMContainer.d.ts.map +1 -1
  9. package/dist/components/DisplayObject.d.ts +1 -0
  10. package/dist/components/DisplayObject.d.ts.map +1 -1
  11. package/dist/components/Mesh.d.ts +1 -0
  12. package/dist/components/Mesh.d.ts.map +1 -1
  13. package/dist/components/Sprite.d.ts +1 -0
  14. package/dist/components/Sprite.d.ts.map +1 -1
  15. package/dist/components/Viewport.d.ts +1 -0
  16. package/dist/components/Viewport.d.ts.map +1 -1
  17. package/dist/components/index.d.ts +1 -0
  18. package/dist/components/index.d.ts.map +1 -1
  19. package/dist/engine/reactive.d.ts +31 -4
  20. package/dist/engine/reactive.d.ts.map +1 -1
  21. package/dist/{index-6NvvNj5_.js → index-Cif_Fy_t.js} +2577 -2431
  22. package/dist/index-Cif_Fy_t.js.map +1 -0
  23. package/dist/index.global.js +6 -6
  24. package/dist/index.global.js.map +1 -1
  25. package/dist/index.js +58 -56
  26. package/package.json +1 -1
  27. package/src/components/Button.ts +269 -0
  28. package/src/components/DisplayObject.ts +21 -2
  29. package/src/components/Graphic.ts +1 -1
  30. package/src/components/Sprite.ts +16 -9
  31. package/src/components/index.ts +2 -1
  32. package/src/engine/reactive.ts +169 -50
  33. package/dist/index-6NvvNj5_.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,62 +1,64 @@
1
- import { h as a } from "./index-6NvvNj5_.js";
2
- import { A as r, C as n, m as o, l as c, D as l, v as p, a3 as m, a2 as S, a0 as g, n as u, G as b, c as d, M as C, N as j, O as T, P as E, a1 as O, R as f, o as v, p as w, S as h, q as D, r as P, T as k, b as x, V as y, t as A, $ as H, _ as M, Y as V, d as G, H as N, z as R, I as q, e as U, Q as z, Z as B, f as I, g as J, w as K, j as L, i as Q, x as W, k as X, U as Y, B as Z, K as _, J as $, X as F, y as aa, s as sa, L as ea, W as ia, a as ta, u as ra } from "./index-6NvvNj5_.js";
1
+ import { h as a } from "./index-Cif_Fy_t.js";
2
+ import { A as r, B as n, w as o, C as c, m as l, l as p, D as S, v as m, a5 as u, a4 as g, a2 as b, n as d, G as C, c as j, M as T, N as E, O, P as f, a3 as v, R as w, o as h, p as D, S as P, q as k, r as x, T as y, b as A, V as H, t as M, a1 as V, a0 as B, _ as G, d as N, J as R, H as q, K as U, e as z, W as I, $ as J, f as K, g as L, x as Q, j as W, i as X, y as Y, k as Z, X as _, I as $, Q as F, L as aa, Z as sa, z as ea, s as ta, U as ia, Y as ra, a as na, u as oa } from "./index-Cif_Fy_t.js";
3
3
  const e = a.Howler;
4
4
  export {
5
5
  r as ArraySubject,
6
- n as Canvas,
7
- o as Circle,
8
- c as Container,
9
- l as DOMContainer,
10
- p as DOMElement,
11
- m as DisplayObject,
12
- S as EVENTS,
13
- g as Easing,
14
- u as Ellipse,
15
- b as Graphics,
16
- d as Howl,
6
+ n as Button,
7
+ o as ButtonState,
8
+ c as Canvas,
9
+ l as Circle,
10
+ p as Container,
11
+ S as DOMContainer,
12
+ m as DOMElement,
13
+ u as DisplayObject,
14
+ g as EVENTS,
15
+ b as Easing,
16
+ d as Ellipse,
17
+ C as Graphics,
18
+ j as Howl,
17
19
  e as Howler,
18
- C as Mesh,
19
- j as NineSliceSprite,
20
- T as ObjectSubject,
21
- E as ParticlesEmitter,
22
- O as RadialGradient,
23
- f as Rect,
24
- v as Scene,
25
- w as Sprite,
26
- h as Svg,
27
- D as Text,
28
- P as TilingSprite,
29
- k as Triangle,
30
- x as Utils,
31
- y as Video,
32
- A as Viewport,
33
- H as animatedSequence,
34
- M as animatedSignal,
35
- V as bootstrapCanvas,
36
- G as computed,
37
- N as cond,
38
- R as createComponent,
39
- q as currentSubscriptionsTracker,
40
- U as effect,
41
- z as h,
42
- B as isAnimatedSignal,
43
- I as isArraySubject,
44
- J as isComputed,
45
- K as isElement,
46
- L as isObjectSubject,
47
- Q as isObservable,
48
- W as isPrimitive,
49
- X as isSignal,
50
- Y as isTrigger,
51
- Z as loop,
52
- _ as mount,
53
- $ as mountTracker,
54
- F as on,
55
- aa as registerComponent,
56
- sa as signal,
57
- ea as tick,
58
- ia as trigger,
59
- ta as useDefineProps,
60
- ra as useProps
20
+ T as Mesh,
21
+ E as NineSliceSprite,
22
+ O as ObjectSubject,
23
+ f as ParticlesEmitter,
24
+ v as RadialGradient,
25
+ w as Rect,
26
+ h as Scene,
27
+ D as Sprite,
28
+ P as Svg,
29
+ k as Text,
30
+ x as TilingSprite,
31
+ y as Triangle,
32
+ A as Utils,
33
+ H as Video,
34
+ M as Viewport,
35
+ V as animatedSequence,
36
+ B as animatedSignal,
37
+ G as bootstrapCanvas,
38
+ N as computed,
39
+ R as cond,
40
+ q as createComponent,
41
+ U as currentSubscriptionsTracker,
42
+ z as effect,
43
+ I as h,
44
+ J as isAnimatedSignal,
45
+ K as isArraySubject,
46
+ L as isComputed,
47
+ Q as isElement,
48
+ W as isObjectSubject,
49
+ X as isObservable,
50
+ Y as isPrimitive,
51
+ Z as isSignal,
52
+ _ as isTrigger,
53
+ $ as loop,
54
+ F as mount,
55
+ aa as mountTracker,
56
+ sa as on,
57
+ ea as registerComponent,
58
+ ta as signal,
59
+ ia as tick,
60
+ ra as trigger,
61
+ na as useDefineProps,
62
+ oa as useProps
61
63
  };
62
64
  //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "canvasengine",
3
- "version": "2.0.0-beta.26",
3
+ "version": "2.0.0-beta.28",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -0,0 +1,269 @@
1
+ import { effect, signal, computed } 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 } from "./Graphic";
7
+ import { Text } from "./Text";
8
+
9
+ /**
10
+ * Button states for visual feedback
11
+ */
12
+ export enum ButtonState {
13
+ Normal = "normal",
14
+ Hover = "hover",
15
+ Pressed = "pressed",
16
+ Disabled = "disabled"
17
+ }
18
+
19
+ /**
20
+ * Button style configuration for different visual approaches
21
+ */
22
+ export interface ButtonStyle {
23
+ /** Background color for each state */
24
+ backgroundColor?: {
25
+ [ButtonState.Normal]?: string;
26
+ [ButtonState.Hover]?: string;
27
+ [ButtonState.Pressed]?: string;
28
+ [ButtonState.Disabled]?: string;
29
+ };
30
+ /** Border configuration */
31
+ border?: {
32
+ color?: string;
33
+ width?: number;
34
+ radius?: number;
35
+ };
36
+ /** Text styling */
37
+ text?: {
38
+ color?: string;
39
+ fontSize?: number;
40
+ fontFamily?: string;
41
+ };
42
+ /** Sprite textures for each state (alternative to backgroundColor) */
43
+ textures?: {
44
+ [ButtonState.Normal]?: string;
45
+ [ButtonState.Hover]?: string;
46
+ [ButtonState.Pressed]?: string;
47
+ [ButtonState.Disabled]?: string;
48
+ };
49
+ }
50
+
51
+ /**
52
+ * Properties for the Button component
53
+ */
54
+ export interface ButtonProps {
55
+ /** Button text content */
56
+ text?: string;
57
+ /** Button disabled state */
58
+ disabled?: boolean;
59
+ /** Click event handler */
60
+ click?: (event: FederatedPointerEvent) => void;
61
+ /** Hover enter event handler */
62
+ hoverEnter?: (event: FederatedPointerEvent) => void;
63
+ /** Hover leave event handler */
64
+ hoverLeave?: (event: FederatedPointerEvent) => void;
65
+ /** Press down event handler */
66
+ pressDown?: (event: FederatedPointerEvent) => void;
67
+ /** Press up event handler */
68
+ pressUp?: (event: FederatedPointerEvent) => void;
69
+ /** Visual style configuration */
70
+ style?: ButtonStyle;
71
+ /** Button width */
72
+ width?: number;
73
+ /** Button height */
74
+ height?: number;
75
+ /** Button position X */
76
+ x?: number;
77
+ /** Button position Y */
78
+ y?: number;
79
+ /** Button alpha/opacity */
80
+ alpha?: number;
81
+ /** Button visibility */
82
+ visible?: boolean;
83
+ /** Button cursor */
84
+ cursor?: string;
85
+ }
86
+
87
+ /**
88
+ * Creates a Button component with interactive states and customizable styling.
89
+ *
90
+ * This component provides a fully interactive button with visual feedback
91
+ * for different states (normal, hover, pressed, disabled). It supports both
92
+ * sprite-based and graphics-based rendering approaches.
93
+ *
94
+ * The button is built using a Container with background and text elements,
95
+ * providing reactive state management and event handling.
96
+ *
97
+ * @param props - Button configuration including text, styling, and event handlers
98
+ * @returns A reactive Button component
99
+ * @example
100
+ * ```typescript
101
+ * // Simple button with text and click handler
102
+ * const simpleButton = Button({
103
+ * text: "Click Me",
104
+ * onClick: () => console.log("Button clicked!"),
105
+ * width: 150,
106
+ * height: 50
107
+ * });
108
+ *
109
+ * // Styled button with custom colors
110
+ * const styledButton = Button({
111
+ * text: "Styled Button",
112
+ * style: {
113
+ * backgroundColor: {
114
+ * normal: "#28a745",
115
+ * hover: "#218838",
116
+ * pressed: "#1e7e34",
117
+ * disabled: "#6c757d"
118
+ * },
119
+ * border: {
120
+ * radius: 8,
121
+ * width: 2,
122
+ * color: "#ffffff"
123
+ * },
124
+ * text: {
125
+ * fontSize: 18,
126
+ * color: "#ffffff"
127
+ * }
128
+ * }
129
+ * });
130
+ *
131
+ * // Sprite-based button
132
+ * const spriteButton = Button({
133
+ * text: "Play Game",
134
+ * style: {
135
+ * textures: {
136
+ * normal: "/assets/button-normal.png",
137
+ * hover: "/assets/button-hover.png",
138
+ * pressed: "/assets/button-pressed.png"
139
+ * }
140
+ * }
141
+ * });
142
+ * ```
143
+ */
144
+ export function Button(props: ButtonProps) {
145
+ // Internal state signals
146
+ const currentState = signal(ButtonState.Normal);
147
+ const isPressed = signal(false);
148
+ const isHovered = signal(false);
149
+
150
+ // Define reactive props with defaults
151
+ const defineProps = useDefineProps(props);
152
+ const { text, disabled, width, height, style } = defineProps({
153
+ text: {
154
+ type: String,
155
+ default: ""
156
+ },
157
+ disabled: {
158
+ type: Boolean,
159
+ default: false
160
+ },
161
+ width: {
162
+ type: Number,
163
+ default: 120
164
+ },
165
+ height: {
166
+ type: Number,
167
+ default: 40
168
+ },
169
+ style: {
170
+ type: Object,
171
+ default: () => ({})
172
+ }
173
+ });
174
+
175
+ // Update button state based on disabled and interaction states
176
+ effect(() => {
177
+ const isDisabled = disabled();
178
+ const pressed = isPressed();
179
+ const hovered = isHovered();
180
+
181
+ if (isDisabled) {
182
+ currentState.set(ButtonState.Disabled);
183
+ } else if (pressed) {
184
+ currentState.set(ButtonState.Pressed);
185
+ } else if (hovered) {
186
+ currentState.set(ButtonState.Hover);
187
+ } else {
188
+ currentState.set(ButtonState.Normal);
189
+ }
190
+ });
191
+
192
+ // Event handlers
193
+ const eventHandlers = {
194
+ pointerenter: (event: FederatedPointerEvent) => {
195
+ if (!disabled()) {
196
+ isHovered.set(true);
197
+ props.hoverEnter?.(event);
198
+ }
199
+ },
200
+ pointerleave: (event: FederatedPointerEvent) => {
201
+ isHovered.set(false);
202
+ isPressed.set(false);
203
+ props.hoverLeave?.(event);
204
+ },
205
+ pointerdown: (event: FederatedPointerEvent) => {
206
+ if (!disabled()) {
207
+ isPressed.set(true);
208
+ props.pressDown?.(event);
209
+ }
210
+ },
211
+ pointerup: (event: FederatedPointerEvent) => {
212
+ if (!disabled() && isPressed()) {
213
+ isPressed.set(false);
214
+ props.pressUp?.(event);
215
+ }
216
+ },
217
+ pointertap: (event: FederatedPointerEvent) => {
218
+ if (!disabled()) {
219
+ props.click?.(event);
220
+ }
221
+ }
222
+ };
223
+
224
+ // Return Container with h() children
225
+ return h(Container, {
226
+ x: props.x,
227
+ y: props.y,
228
+ width: props.width,
229
+ height: props.height,
230
+ alpha: props.alpha,
231
+ visible: props.visible,
232
+ cursor: props.cursor || "pointer",
233
+ ...eventHandlers
234
+ }, [
235
+ // Background element (either sprite or graphics)
236
+ h(Rect, {
237
+ width: width,
238
+ height: height,
239
+ color: computed(() => {
240
+ const currentStyle = style();
241
+ const backgroundColor = currentStyle.backgroundColor || {
242
+ [ButtonState.Normal]: "#007bff",
243
+ [ButtonState.Hover]: "#0056b3",
244
+ [ButtonState.Pressed]: "#004085",
245
+ [ButtonState.Disabled]: "#6c757d"
246
+ };
247
+ const state = currentState();
248
+ return backgroundColor[state] || backgroundColor[ButtonState.Normal];
249
+ })
250
+ }),
251
+
252
+ // Text element
253
+ h(Text, {
254
+ text: text,
255
+ x: computed(() => width() / 2),
256
+ y: computed(() => height() / 2),
257
+ anchor: { x: 0.5, y: 0.5 },
258
+ style: computed(() => {
259
+ const currentStyle = style();
260
+ const textStyle = currentStyle.text || {};
261
+ return {
262
+ fontSize: textStyle.fontSize || 16,
263
+ fontFamily: textStyle.fontFamily || "Arial",
264
+ fill: textStyle.color || "#ffffff"
265
+ };
266
+ })()
267
+ })
268
+ ]);
269
+ }
@@ -116,6 +116,8 @@ export function DisplayObject(extendClass) {
116
116
  onAfterMount: OnHook | null = null;
117
117
  subjectInit = new BehaviorSubject(null);
118
118
  disableLayout: boolean = false;
119
+ // Store registered event listeners for cleanup
120
+ #registeredEvents: Map<string, Function> = new Map();
119
121
 
120
122
  get deltaRatio() {
121
123
  return this.#canvasContext?.scheduler?.tick.value.deltaRatio;
@@ -131,7 +133,16 @@ export function DisplayObject(extendClass) {
131
133
  for (let event of EVENTS) {
132
134
  if (props[event] && !this.overrideProps.includes(event)) {
133
135
  this.eventMode = "static";
134
- this.on(event, props[event]);
136
+ const eventHandler = props[event];
137
+
138
+ // Store the event handler for cleanup
139
+ if (event === 'click') {
140
+ this.on('pointertap', eventHandler);
141
+ this.#registeredEvents.set('pointertap', eventHandler);
142
+ } else {
143
+ this.on(event, eventHandler);
144
+ this.#registeredEvents.set(event, eventHandler);
145
+ }
135
146
  }
136
147
  }
137
148
  if (props.onBeforeDestroy || props['on-before-destroy']) {
@@ -159,6 +170,7 @@ export function DisplayObject(extendClass) {
159
170
  }
160
171
 
161
172
  async onMount({ parent, props }: Element<DisplayObject>, index?: number) {
173
+ if (this.destroyed) return
162
174
  this.#canvasContext = props.context;
163
175
  if (parent) {
164
176
  const instance = parent.componentInstance as DisplayObject;
@@ -188,6 +200,7 @@ export function DisplayObject(extendClass) {
188
200
  ...props,
189
201
  };
190
202
 
203
+ if (this.destroyed) return
191
204
  if (!this.#canvasContext || !this.parent) return;
192
205
 
193
206
  if (props.x !== undefined) this.setX(props.x);
@@ -283,11 +296,17 @@ export function DisplayObject(extendClass) {
283
296
  }
284
297
 
285
298
  async onDestroy(parent: Element, afterDestroy?: () => void) {
299
+ // Remove all registered event listeners
300
+ for (const [eventName, eventHandler] of this.#registeredEvents) {
301
+ this.off(eventName, eventHandler);
302
+ }
303
+ this.#registeredEvents.clear();
304
+
286
305
  if (this.onBeforeDestroy) {
287
306
  await this.onBeforeDestroy();
288
307
  }
289
- super.destroy();
290
308
  if (afterDestroy) afterDestroy();
309
+ super.destroy();
291
310
  }
292
311
 
293
312
  setFlexDirection(direction: FlexDirection) {
@@ -170,7 +170,7 @@ class CanvasGraphics extends DisplayObject(PixiGraphics) {
170
170
  */
171
171
  async onDestroy(parent: Element<ComponentInstance>, afterDestroy: () => void): Promise<void> {
172
172
  const _afterDestroyCallback = async () => {
173
- this.clearEffect.subscription.unsubscribe();
173
+ this.clearEffect?.subscription.unsubscribe();
174
174
  afterDestroy();
175
175
  }
176
176
  await super.onDestroy(parent, _afterDestroyCallback);
@@ -187,6 +187,7 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
187
187
  this.onFinish = sheet.onFinish;
188
188
  }
189
189
  this.subscriptionTick = tick.observable.subscribe((value) => {
190
+ if (this.destroyed) return
190
191
  this.update(value);
191
192
  });
192
193
  if (props.sheet?.definition) {
@@ -236,11 +237,11 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
236
237
 
237
238
  if (this.spritesheet) this.play(this.sheetCurrentAnimation, [this.sheetParams]);
238
239
  });
239
-
240
240
  super.onMount(params);
241
241
  }
242
242
 
243
243
  async onUpdate(props) {
244
+ if (this.destroyed) return
244
245
  super.onUpdate(props);
245
246
 
246
247
  const setTexture = async (image: string) => {
@@ -274,7 +275,7 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
274
275
  this.play(this.sheetCurrentAnimation, [this.sheetParams]);
275
276
  }
276
277
 
277
- if (props.hitbox) this.hitbox = props.hitbox;
278
+ if (props.hitbox) this.hitbox = props.hitbox.value ?? props.hitbox;
278
279
 
279
280
  if (props.scaleMode) this.baseTexture.scaleMode = props.scaleMode;
280
281
  else if (props.image && this.fullProps.rectangle === undefined) {
@@ -307,12 +308,17 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
307
308
  }
308
309
 
309
310
  async onDestroy(parent: Element, afterDestroy: () => void): Promise<void> {
310
- await super.onDestroy(parent);
311
- this.subscriptionSheet.forEach((sub) => sub.unsubscribe());
312
- this.subscriptionTick.unsubscribe();
313
- if (this.currentAnimationContainer && this.parent instanceof Container) {
314
- this.parent.removeChild(this.currentAnimationContainer);
315
- }
311
+ const _afterDestroy = async () => {
312
+ this.subscriptionSheet.forEach((sub) => sub.unsubscribe());
313
+ this.subscriptionTick.unsubscribe();
314
+ if (this.currentAnimationContainer && this.parent instanceof Container) {
315
+ this.parent.removeChild(this.currentAnimationContainer);
316
+ }
317
+ if (afterDestroy) {
318
+ afterDestroy();
319
+ }
320
+ };
321
+ await super.onDestroy(parent, _afterDestroy);
316
322
  }
317
323
 
318
324
  has(name: string): boolean {
@@ -347,7 +353,7 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
347
353
  );
348
354
  }
349
355
 
350
- const cloneParams = structuredClone(params);
356
+ const cloneParams = (params);
351
357
 
352
358
  this.removeChildren();
353
359
  animation.sprites = [];
@@ -453,6 +459,7 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
453
459
  const widthOfSprite =
454
460
  typeof realSize == "number" ? realSize : realSize?.width;
455
461
 
462
+
456
463
  const applyAnchorBySize = () => {
457
464
  if (heightOfSprite && this.hitbox) {
458
465
  const { spriteWidth, spriteHeight } = data;
@@ -12,4 +12,5 @@ export { Viewport } from './Viewport'
12
12
  export { NineSliceSprite } from './NineSliceSprite'
13
13
  export { type ComponentInstance } from './DisplayObject'
14
14
  export { DOMContainer } from './DOMContainer'
15
- export { DOMElement } from './DOMElement'
15
+ export { DOMElement } from './DOMElement'
16
+ export { Button, ButtonState, type ButtonProps, type ButtonStyle } from './Button'