canvasengine 2.0.0-beta.2 → 2.0.0-beta.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1285 -0
- package/dist/index.js +4150 -0
- package/dist/index.js.map +1 -0
- package/index.d.ts +4 -0
- package/package.json +5 -12
- package/src/components/Canvas.ts +53 -45
- package/src/components/Container.ts +2 -2
- package/src/components/DOMContainer.ts +229 -0
- package/src/components/DisplayObject.ts +263 -189
- package/src/components/Graphic.ts +213 -36
- package/src/components/Mesh.ts +222 -0
- package/src/components/NineSliceSprite.ts +4 -1
- package/src/components/ParticleEmitter.ts +12 -8
- package/src/components/Sprite.ts +77 -14
- package/src/components/Text.ts +34 -14
- package/src/components/Video.ts +110 -0
- package/src/components/Viewport.ts +59 -43
- package/src/components/index.ts +5 -4
- package/src/components/types/DisplayObject.ts +30 -0
- package/src/directives/Drag.ts +357 -52
- package/src/directives/KeyboardControls.ts +3 -1
- package/src/directives/Sound.ts +94 -31
- package/src/directives/ViewportFollow.ts +35 -7
- package/src/engine/animation.ts +41 -5
- package/src/engine/bootstrap.ts +13 -2
- package/src/engine/directive.ts +2 -2
- package/src/engine/reactive.ts +336 -168
- package/src/engine/trigger.ts +65 -9
- package/src/engine/utils.ts +92 -9
- package/src/hooks/useProps.ts +1 -1
- package/src/index.ts +5 -1
- package/src/utils/RadialGradient.ts +29 -0
- package/src/utils/functions.ts +7 -0
- package/testing/index.ts +12 -0
- package/src/components/DrawMap/index.ts +0 -65
- package/src/components/Tilemap/Tile.ts +0 -79
- package/src/components/Tilemap/TileGroup.ts +0 -207
- package/src/components/Tilemap/TileLayer.ts +0 -163
- package/src/components/Tilemap/TileSet.ts +0 -41
- package/src/components/Tilemap/index.ts +0 -80
|
@@ -1,54 +1,184 @@
|
|
|
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, 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";
|
|
7
9
|
|
|
8
10
|
interface GraphicsProps extends DisplayObjectProps {
|
|
9
|
-
draw?: (graphics: PixiGraphics) => void;
|
|
11
|
+
draw?: (graphics: PixiGraphics, width: number, height: number) => void;
|
|
10
12
|
}
|
|
11
13
|
|
|
12
14
|
interface RectProps extends DisplayObjectProps {
|
|
13
|
-
|
|
14
|
-
height: number;
|
|
15
|
-
color: string;
|
|
15
|
+
color: SignalOrPrimitive<string>;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
interface CircleProps extends DisplayObjectProps {
|
|
19
|
-
radius: number
|
|
20
|
-
color: string
|
|
19
|
+
radius: SignalOrPrimitive<number>;
|
|
20
|
+
color: SignalOrPrimitive<string>;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
interface EllipseProps extends DisplayObjectProps {
|
|
24
|
-
|
|
25
|
-
height: number;
|
|
26
|
-
color: string;
|
|
24
|
+
color: SignalOrPrimitive<string>;
|
|
27
25
|
}
|
|
28
26
|
|
|
29
27
|
interface TriangleProps extends DisplayObjectProps {
|
|
30
|
-
base: number
|
|
31
|
-
|
|
32
|
-
color: string;
|
|
28
|
+
base: SignalOrPrimitive<number>;
|
|
29
|
+
color: SignalOrPrimitive<string>;
|
|
33
30
|
}
|
|
34
31
|
|
|
35
32
|
interface SvgProps extends DisplayObjectProps {
|
|
36
|
-
|
|
33
|
+
/** SVG content as string (legacy prop) */
|
|
34
|
+
svg?: string;
|
|
35
|
+
/** URL source of the SVG file to load */
|
|
36
|
+
src?: string;
|
|
37
|
+
/** Direct SVG content as string */
|
|
38
|
+
content?: string;
|
|
37
39
|
}
|
|
38
40
|
|
|
39
41
|
class CanvasGraphics extends DisplayObject(PixiGraphics) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
+
clearEffect: Effect;
|
|
43
|
+
width: WritableSignal<number>;
|
|
44
|
+
height: WritableSignal<number>;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Initializes the graphics component with reactive width and height handling.
|
|
48
|
+
*
|
|
49
|
+
* This method handles different types of width and height props:
|
|
50
|
+
* - **Numbers**: Direct pixel values
|
|
51
|
+
* - **Strings with %**: Percentage values that trigger flex layout and use layout box dimensions
|
|
52
|
+
* - **Signals**: Reactive values that update automatically
|
|
53
|
+
*
|
|
54
|
+
* When percentage values are detected, the component:
|
|
55
|
+
* 1. Sets `display: 'flex'` to enable layout calculations
|
|
56
|
+
* 2. Listens to layout events to get computed dimensions
|
|
57
|
+
* 3. Updates internal width/height signals with layout box values
|
|
58
|
+
*
|
|
59
|
+
* The draw function receives the reactive width and height signals as parameters.
|
|
60
|
+
*
|
|
61
|
+
* @param props - Component properties including width, height, and draw function
|
|
62
|
+
* @example
|
|
63
|
+
* ```typescript
|
|
64
|
+
* // With pixel values
|
|
65
|
+
* Graphics({ width: 100, height: 50, draw: (g, w, h) => g.rect(0, 0, w(), h()) });
|
|
66
|
+
*
|
|
67
|
+
* // With percentage values (uses layout box)
|
|
68
|
+
* Graphics({ width: "50%", height: "100%", draw: (g, w, h) => g.rect(0, 0, w(), h()) });
|
|
69
|
+
*
|
|
70
|
+
* // With signals
|
|
71
|
+
* const width = signal(100);
|
|
72
|
+
* Graphics({ width, height: 50, draw: (g, w, h) => g.rect(0, 0, w(), h()) });
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
async onInit(props) {
|
|
76
|
+
await super.onInit(props);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Called when the component is mounted to the scene graph.
|
|
81
|
+
* Creates the reactive effect for drawing using the original signals from propObservables.
|
|
82
|
+
* @param {Element<DisplayObject>} element - The element being mounted with props and propObservables.
|
|
83
|
+
* @param {number} [index] - The index of the component among its siblings.
|
|
84
|
+
*/
|
|
85
|
+
async onMount(element: Element<any>, index?: number): Promise<void> {
|
|
86
|
+
await super.onMount(element, index);
|
|
87
|
+
const { props, propObservables } = element;
|
|
88
|
+
|
|
89
|
+
// Use original signals from propObservables if available, otherwise create new ones
|
|
90
|
+
const width = (isSignal(propObservables?.width) ? propObservables.width : signal(props.width || 0)) as WritableSignal<number>;
|
|
91
|
+
const height = (isSignal(propObservables?.height) ? propObservables.height : signal(props.height || 0)) as WritableSignal<number>;
|
|
92
|
+
|
|
93
|
+
// Store as class properties for access in other methods
|
|
94
|
+
this.width = width;
|
|
95
|
+
this.height = height;
|
|
96
|
+
|
|
97
|
+
// Check if width or height are percentages to set display flex
|
|
98
|
+
const isWidthPercentage = isPercent(width());
|
|
99
|
+
const isHeightPercentage = isPercent(height());
|
|
100
|
+
|
|
42
101
|
if (props.draw) {
|
|
43
|
-
effect(() => {
|
|
102
|
+
this.clearEffect = effect(() => {
|
|
103
|
+
const w = width();
|
|
104
|
+
const h = height();
|
|
105
|
+
if (typeof w == 'string' || typeof h == 'string') {
|
|
106
|
+
return
|
|
107
|
+
}
|
|
108
|
+
if (w == 0 || h == 0) {
|
|
109
|
+
return
|
|
110
|
+
}
|
|
44
111
|
this.clear();
|
|
45
|
-
props.draw?.(this);
|
|
112
|
+
props.draw?.(this, w, h);
|
|
113
|
+
this.subjectInit.next(this)
|
|
46
114
|
});
|
|
47
115
|
}
|
|
116
|
+
|
|
117
|
+
this.on('layout', (event) => {
|
|
118
|
+
const layoutBox = event.computedLayout;
|
|
119
|
+
// Update width if it's a percentage
|
|
120
|
+
if (isWidthPercentage && isSignal(width)) {
|
|
121
|
+
width.set(layoutBox.width);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Update height if it's a percentage
|
|
125
|
+
if (isHeightPercentage && isSignal(height)) {
|
|
126
|
+
height.set(layoutBox.height);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Called when component props are updated.
|
|
133
|
+
* Updates the internal width and height signals when props change.
|
|
134
|
+
* @param props - Updated properties
|
|
135
|
+
*/
|
|
136
|
+
onUpdate(props: any) {
|
|
137
|
+
super.onUpdate(props);
|
|
138
|
+
|
|
139
|
+
// Update width signal if width prop changed
|
|
140
|
+
if (props.width !== undefined && this.width) {
|
|
141
|
+
if (isSignal(props.width)) {
|
|
142
|
+
// If the new prop is a signal, we need to replace our local signal
|
|
143
|
+
// This shouldn't happen in normal usage, but handle it just in case
|
|
144
|
+
this.width = props.width;
|
|
145
|
+
} else {
|
|
146
|
+
// Update our local signal with the new value
|
|
147
|
+
this.width.set(props.width);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Update height signal if height prop changed
|
|
152
|
+
if (props.height !== undefined && this.height) {
|
|
153
|
+
if (isSignal(props.height)) {
|
|
154
|
+
// If the new prop is a signal, we need to replace our local signal
|
|
155
|
+
// This shouldn't happen in normal usage, but handle it just in case
|
|
156
|
+
this.height = props.height;
|
|
157
|
+
} else {
|
|
158
|
+
// Update our local signal with the new value
|
|
159
|
+
this.height.set(props.height);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
48
162
|
}
|
|
49
|
-
}
|
|
50
163
|
|
|
51
|
-
|
|
164
|
+
/**
|
|
165
|
+
* Called when the component is about to be destroyed.
|
|
166
|
+
* This method should be overridden by subclasses to perform any cleanup.
|
|
167
|
+
* It ensures that the clearEffect subscription is unsubscribed before calling the original afterDestroy callback.
|
|
168
|
+
* @param parent The parent element.
|
|
169
|
+
* @param afterDestroy A callback function to be executed after the component's own destruction logic.
|
|
170
|
+
* @example
|
|
171
|
+
* // This method is typically called by the engine internally.
|
|
172
|
+
* // await component.onDestroy(parentElement, () => console.log('Component destroyed'));
|
|
173
|
+
*/
|
|
174
|
+
async onDestroy(parent: Element<ComponentInstance>, afterDestroy: () => void): Promise<void> {
|
|
175
|
+
const _afterDestroyCallback = async () => {
|
|
176
|
+
this.clearEffect.subscription.unsubscribe();
|
|
177
|
+
afterDestroy();
|
|
178
|
+
}
|
|
179
|
+
await super.onDestroy(parent, _afterDestroyCallback);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
52
182
|
|
|
53
183
|
registerComponent("Graphics", CanvasGraphics);
|
|
54
184
|
|
|
@@ -57,16 +187,17 @@ export function Graphics(props: GraphicsProps) {
|
|
|
57
187
|
}
|
|
58
188
|
|
|
59
189
|
export function Rect(props: RectProps) {
|
|
60
|
-
const {
|
|
190
|
+
const { color, borderRadius, border } = useProps(props, {
|
|
61
191
|
borderRadius: null,
|
|
62
192
|
border: null
|
|
63
193
|
})
|
|
194
|
+
|
|
64
195
|
return Graphics({
|
|
65
|
-
draw: (g) => {
|
|
196
|
+
draw: (g, width, height) => {
|
|
66
197
|
if (borderRadius()) {
|
|
67
|
-
g.roundRect(0, 0, width
|
|
198
|
+
g.roundRect(0, 0, width, height, borderRadius());
|
|
68
199
|
} else {
|
|
69
|
-
g.rect(0, 0, width
|
|
200
|
+
g.rect(0, 0, width, height);
|
|
70
201
|
}
|
|
71
202
|
if (border) {
|
|
72
203
|
g.stroke(border);
|
|
@@ -82,8 +213,8 @@ function drawShape(g: PixiGraphics, shape: 'circle' | 'ellipse', props: {
|
|
|
82
213
|
color: Signal<string>;
|
|
83
214
|
border: Signal<number>;
|
|
84
215
|
} | {
|
|
85
|
-
width:
|
|
86
|
-
height:
|
|
216
|
+
width: WritableSignal<number>;
|
|
217
|
+
height: WritableSignal<number>;
|
|
87
218
|
color: Signal<string>;
|
|
88
219
|
border: Signal<number>;
|
|
89
220
|
}) {
|
|
@@ -114,7 +245,7 @@ export function Ellipse(props: EllipseProps) {
|
|
|
114
245
|
border: null
|
|
115
246
|
})
|
|
116
247
|
return Graphics({
|
|
117
|
-
draw: (g) => drawShape(g, 'ellipse', { width, height, color, border }),
|
|
248
|
+
draw: (g, gWidth, gHeight) => drawShape(g, 'ellipse', { width: signal(gWidth), height: signal(gHeight), color, border }),
|
|
118
249
|
...props
|
|
119
250
|
})
|
|
120
251
|
}
|
|
@@ -125,11 +256,11 @@ export function Triangle(props: TriangleProps) {
|
|
|
125
256
|
color: '#000'
|
|
126
257
|
})
|
|
127
258
|
return Graphics({
|
|
128
|
-
draw: (g) => {
|
|
129
|
-
g.moveTo(0,
|
|
130
|
-
g.lineTo(
|
|
131
|
-
g.lineTo(
|
|
132
|
-
g.lineTo(0,
|
|
259
|
+
draw: (g, gWidth, gHeight) => {
|
|
260
|
+
g.moveTo(0, gHeight);
|
|
261
|
+
g.lineTo(gWidth / 2, 0);
|
|
262
|
+
g.lineTo(gWidth, gHeight);
|
|
263
|
+
g.lineTo(0, gHeight);
|
|
133
264
|
g.fill(color());
|
|
134
265
|
if (border) {
|
|
135
266
|
g.stroke(border);
|
|
@@ -139,9 +270,55 @@ export function Triangle(props: TriangleProps) {
|
|
|
139
270
|
})
|
|
140
271
|
}
|
|
141
272
|
|
|
273
|
+
/**
|
|
274
|
+
* Creates an SVG component that can render SVG graphics from URL, content, or legacy svg prop.
|
|
275
|
+
*
|
|
276
|
+
* This component provides three ways to display SVG graphics:
|
|
277
|
+
* - **src**: Load SVG from a URL using Assets.load with parseAsGraphicsContext option
|
|
278
|
+
* - **content**: Render SVG directly from string content using Graphics.svg() method
|
|
279
|
+
* - **svg**: Legacy prop for SVG content (for backward compatibility)
|
|
280
|
+
*
|
|
281
|
+
* @param props - Component properties including src, content, or svg
|
|
282
|
+
* @returns A reactive SVG component
|
|
283
|
+
* @example
|
|
284
|
+
* ```typescript
|
|
285
|
+
* // Load from URL
|
|
286
|
+
* const svgFromUrl = Svg({ src: "/assets/logo.svg" });
|
|
287
|
+
*
|
|
288
|
+
* // Direct content
|
|
289
|
+
* const svgFromContent = Svg({
|
|
290
|
+
* content: `<svg viewBox="0 0 100 100">
|
|
291
|
+
* <circle cx="50" cy="50" r="40" fill="blue"/>
|
|
292
|
+
* </svg>`
|
|
293
|
+
* });
|
|
294
|
+
*
|
|
295
|
+
* // Legacy usage
|
|
296
|
+
* const svgLegacy = Svg({ svg: "<svg>...</svg>" });
|
|
297
|
+
* ```
|
|
298
|
+
*/
|
|
142
299
|
export function Svg(props: SvgProps) {
|
|
143
300
|
return Graphics({
|
|
144
|
-
draw: (g) =>
|
|
301
|
+
draw: async (g) => {
|
|
302
|
+
if (props.src) {
|
|
303
|
+
// Load SVG from source URL with graphics context parsing
|
|
304
|
+
const svgData = await Assets.load({
|
|
305
|
+
src: props.src,
|
|
306
|
+
data: {
|
|
307
|
+
parseAsGraphicsContext: true,
|
|
308
|
+
},
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
// Apply the loaded graphics context
|
|
312
|
+
const graphics = new PixiGraphics(svgData);
|
|
313
|
+
g.context = graphics.context;
|
|
314
|
+
} else if (props.content) {
|
|
315
|
+
// Render SVG directly from content string
|
|
316
|
+
g.svg(props.content);
|
|
317
|
+
} else if (props.svg) {
|
|
318
|
+
// Legacy prop support
|
|
319
|
+
g.svg(props.svg);
|
|
320
|
+
}
|
|
321
|
+
},
|
|
145
322
|
...props
|
|
146
323
|
})
|
|
147
324
|
}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { Effect, effect } from "@signe/reactive";
|
|
2
|
+
import { Mesh as PixiMesh, Geometry, Shader, Texture, Assets, BLEND_MODES } from "pixi.js";
|
|
3
|
+
import { createComponent, Element, registerComponent } from "../engine/reactive";
|
|
4
|
+
import { ComponentInstance, DisplayObject } from "./DisplayObject";
|
|
5
|
+
import { DisplayObjectProps } from "./types/DisplayObject";
|
|
6
|
+
import { useProps } from "../hooks/useProps";
|
|
7
|
+
import { SignalOrPrimitive } from "./types";
|
|
8
|
+
import { ComponentFunction } from "../engine/signal";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Interface defining the properties for a Mesh component.
|
|
12
|
+
* Extends DisplayObjectProps to inherit common display object properties.
|
|
13
|
+
*/
|
|
14
|
+
interface MeshProps extends DisplayObjectProps {
|
|
15
|
+
/** The geometry defining the mesh structure (vertices, indices, UVs, etc.) */
|
|
16
|
+
geometry?: Geometry;
|
|
17
|
+
/** The shader to render the mesh with */
|
|
18
|
+
shader?: Shader;
|
|
19
|
+
/** The texture to apply to the mesh */
|
|
20
|
+
texture?: Texture | string;
|
|
21
|
+
/** The image URL to load as texture */
|
|
22
|
+
image?: string;
|
|
23
|
+
/** The tint color to apply to the mesh */
|
|
24
|
+
tint?: SignalOrPrimitive<number>;
|
|
25
|
+
/** Whether to round pixels for sharper rendering */
|
|
26
|
+
roundPixels?: SignalOrPrimitive<boolean>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Canvas Mesh component class that extends DisplayObject with PixiMesh functionality.
|
|
31
|
+
* This component allows rendering of custom 3D meshes with shaders and textures.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```typescript
|
|
35
|
+
* // Basic mesh with geometry and texture
|
|
36
|
+
* const mesh = Mesh({
|
|
37
|
+
* geometry: myGeometry,
|
|
38
|
+
* texture: "path/to/texture.png",
|
|
39
|
+
* tint: 0xff0000
|
|
40
|
+
* });
|
|
41
|
+
*
|
|
42
|
+
* // Mesh with custom shader
|
|
43
|
+
* const customMesh = Mesh({
|
|
44
|
+
* geometry: myGeometry,
|
|
45
|
+
* shader: myCustomShader,
|
|
46
|
+
* draw: (mesh) => {
|
|
47
|
+
* // Custom mesh manipulation
|
|
48
|
+
* mesh.rotation += 0.01;
|
|
49
|
+
* }
|
|
50
|
+
* });
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
class CanvasMesh extends DisplayObject(PixiMesh) {
|
|
54
|
+
/**
|
|
55
|
+
* Constructor for the CanvasMesh component.
|
|
56
|
+
* Initializes the PixiMesh with default geometry and shader to prevent errors.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```typescript
|
|
60
|
+
* // This constructor is called internally by the engine
|
|
61
|
+
* const mesh = new CanvasMesh();
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
constructor() {
|
|
65
|
+
// Call parent constructor with minimal options to prevent destructuring error
|
|
66
|
+
// @ts-ignore - PixiMesh constructor expects options object but TypeScript doesn't recognize it
|
|
67
|
+
super({
|
|
68
|
+
geometry: new Geometry()
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Initializes the mesh component with the provided properties.
|
|
74
|
+
* This method is called before onUpdate to set up initial state.
|
|
75
|
+
*
|
|
76
|
+
* @param props - The initial properties
|
|
77
|
+
* @example
|
|
78
|
+
* ```typescript
|
|
79
|
+
* // This method is called internally when the component is created
|
|
80
|
+
* mesh.onInit({
|
|
81
|
+
* geometry: myGeometry,
|
|
82
|
+
* texture: "texture.png"
|
|
83
|
+
* });
|
|
84
|
+
* ```
|
|
85
|
+
*/
|
|
86
|
+
onInit(props: MeshProps) {
|
|
87
|
+
super.onInit(props);
|
|
88
|
+
|
|
89
|
+
// Set initial geometry if provided
|
|
90
|
+
if (props.geometry) {
|
|
91
|
+
try {
|
|
92
|
+
this.geometry = props.geometry;
|
|
93
|
+
} catch (error) {
|
|
94
|
+
console.warn('Failed to set geometry:', error);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Set initial shader if provided
|
|
99
|
+
if (props.shader) {
|
|
100
|
+
this.shader = props.shader;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Updates the mesh component when properties change.
|
|
106
|
+
* Handles texture loading, shader updates, and other property changes.
|
|
107
|
+
*
|
|
108
|
+
* @param props - The updated properties
|
|
109
|
+
* @example
|
|
110
|
+
* ```typescript
|
|
111
|
+
* // This method is called internally when props change
|
|
112
|
+
* mesh.onUpdate({
|
|
113
|
+
* tint: 0x00ff00,
|
|
114
|
+
* texture: "new-texture.png"
|
|
115
|
+
* });
|
|
116
|
+
* ```
|
|
117
|
+
*/
|
|
118
|
+
async onUpdate(props: MeshProps) {
|
|
119
|
+
super.onUpdate(props);
|
|
120
|
+
|
|
121
|
+
// Handle geometry updates
|
|
122
|
+
if (props.geometry) {
|
|
123
|
+
try {
|
|
124
|
+
this.geometry = props.geometry;
|
|
125
|
+
} catch (error) {
|
|
126
|
+
console.warn('Failed to update geometry:', error);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Handle shader/material updates
|
|
131
|
+
if (props.shader) {
|
|
132
|
+
this.shader = props.shader;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Handle texture updates
|
|
136
|
+
if (props.texture) {
|
|
137
|
+
if (typeof props.texture === 'string') {
|
|
138
|
+
this.texture = await Assets.load(props.texture);
|
|
139
|
+
} else {
|
|
140
|
+
this.texture = props.texture;
|
|
141
|
+
}
|
|
142
|
+
} else if (props.image) {
|
|
143
|
+
this.texture = await Assets.load(props.image);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Handle tint updates
|
|
147
|
+
if (props.tint !== undefined) {
|
|
148
|
+
this.tint = props.tint;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Handle blend mode updates
|
|
152
|
+
if (props.blendMode !== undefined) {
|
|
153
|
+
this.blendMode = props.blendMode;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Handle round pixels updates
|
|
157
|
+
if (props.roundPixels !== undefined) {
|
|
158
|
+
this.roundPixels = props.roundPixels;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Called when the component is about to be destroyed.
|
|
164
|
+
* Cleans up the draw effect subscription and calls the parent destroy method.
|
|
165
|
+
*
|
|
166
|
+
* @param parent - The parent element
|
|
167
|
+
* @param afterDestroy - Callback function to execute after destruction
|
|
168
|
+
* @example
|
|
169
|
+
* ```typescript
|
|
170
|
+
* // This method is typically called by the engine internally
|
|
171
|
+
* await mesh.onDestroy(parentElement, () => console.log('Mesh destroyed'));
|
|
172
|
+
* ```
|
|
173
|
+
*/
|
|
174
|
+
async onDestroy(parent: Element<ComponentInstance>, afterDestroy: () => void): Promise<void> {
|
|
175
|
+
const _afterDestroyCallback = async () => {
|
|
176
|
+
afterDestroy();
|
|
177
|
+
};
|
|
178
|
+
await super.onDestroy(parent, _afterDestroyCallback);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Register the component with the engine
|
|
183
|
+
registerComponent("Mesh", CanvasMesh);
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Creates a Mesh component with the specified properties.
|
|
187
|
+
* This is the main function used to create mesh instances in your application.
|
|
188
|
+
*
|
|
189
|
+
* @param props - The properties for the mesh component
|
|
190
|
+
* @returns A mesh component element
|
|
191
|
+
* @example
|
|
192
|
+
* ```typescript
|
|
193
|
+
* import { Mesh } from 'canvasengine';
|
|
194
|
+
*
|
|
195
|
+
* // Create a basic textured mesh
|
|
196
|
+
* const myMesh = Mesh({
|
|
197
|
+
* geometry: triangleGeometry,
|
|
198
|
+
* texture: "assets/texture.png",
|
|
199
|
+
* x: 100,
|
|
200
|
+
* y: 100,
|
|
201
|
+
* tint: 0xff0000
|
|
202
|
+
* });
|
|
203
|
+
*
|
|
204
|
+
* // Create a mesh with custom shader
|
|
205
|
+
* const shaderMesh = Mesh({
|
|
206
|
+
* geometry: planeGeometry,
|
|
207
|
+
* shader: customShader,
|
|
208
|
+
* draw: (mesh) => {
|
|
209
|
+
* mesh.rotation += 0.01;
|
|
210
|
+
* }
|
|
211
|
+
* });
|
|
212
|
+
* ```
|
|
213
|
+
*/
|
|
214
|
+
export const Mesh: ComponentFunction<MeshProps> = (props) => {
|
|
215
|
+
return createComponent("Mesh", props);
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
// Export the component class for advanced usage
|
|
219
|
+
export { CanvasMesh };
|
|
220
|
+
|
|
221
|
+
// Export the props interface for TypeScript users
|
|
222
|
+
export type { MeshProps };
|
|
@@ -2,6 +2,7 @@ import { Assets, NineSliceSprite as PixiNineSliceSprite, Texture } from "pixi.js
|
|
|
2
2
|
import { createComponent, registerComponent } from "../engine/reactive";
|
|
3
3
|
import { DisplayObject } from "./DisplayObject";
|
|
4
4
|
import { DisplayObjectProps } from "./types/DisplayObject";
|
|
5
|
+
import { Layout } from "@pixi/layout";
|
|
5
6
|
|
|
6
7
|
interface NineSliceSpriteProps extends DisplayObjectProps {
|
|
7
8
|
image?: string;
|
|
@@ -37,7 +38,9 @@ class CanvasNineSliceSprite extends DisplayObject(PixiNineSliceSprite) {
|
|
|
37
38
|
}
|
|
38
39
|
}
|
|
39
40
|
|
|
40
|
-
interface CanvasNineSliceSprite extends PixiNineSliceSprite {
|
|
41
|
+
interface CanvasNineSliceSprite extends PixiNineSliceSprite {
|
|
42
|
+
layout: Layout | null;
|
|
43
|
+
}
|
|
41
44
|
|
|
42
45
|
registerComponent("NineSliceSprite", CanvasNineSliceSprite);
|
|
43
46
|
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import * as particles from "@barvynkoa/particle-emitter";
|
|
2
|
-
import { createComponent, registerComponent } from "../engine/reactive";
|
|
2
|
+
import { createComponent, Element, registerComponent } from "../engine/reactive";
|
|
3
3
|
import { CanvasContainer } from "./Container";
|
|
4
4
|
import { Signal } from "@signe/reactive";
|
|
5
|
+
import { ComponentInstance } from "./DisplayObject";
|
|
5
6
|
|
|
6
7
|
class CanvasParticlesEmitter extends CanvasContainer {
|
|
7
8
|
private emitter: particles.Emitter | null;
|
|
8
9
|
private elapsed: number = Date.now();
|
|
9
10
|
|
|
10
|
-
onMount(params) {
|
|
11
|
-
super.onMount(params);
|
|
11
|
+
async onMount(params) {
|
|
12
|
+
await super.onMount(params);
|
|
12
13
|
const { props } = params;
|
|
13
14
|
const tick: Signal = props.context.tick;
|
|
14
15
|
this.emitter = new particles.Emitter(this as any, props.config);
|
|
@@ -24,11 +25,14 @@ class CanvasParticlesEmitter extends CanvasContainer {
|
|
|
24
25
|
|
|
25
26
|
onUpdate(props) {}
|
|
26
27
|
|
|
27
|
-
onDestroy(
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
async onDestroy(parent: Element<ComponentInstance>, afterDestroy: () => void) {
|
|
29
|
+
const _afterDestroy = async () => {
|
|
30
|
+
this.emitter?.destroy();
|
|
31
|
+
this.emitter = null;
|
|
32
|
+
this.subscriptionTick.unsubscribe();
|
|
33
|
+
afterDestroy();
|
|
34
|
+
}
|
|
35
|
+
await super.onDestroy(parent, _afterDestroy);
|
|
32
36
|
}
|
|
33
37
|
}
|
|
34
38
|
|