canvasengine 2.0.0-beta.4 → 2.0.0-beta.5
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 +18 -2
- package/dist/index.js +193 -75
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/Graphic.ts +1 -1
- package/src/components/Sprite.ts +24 -3
- package/src/components/Video.ts +110 -0
- package/src/components/index.ts +1 -0
- package/src/engine/reactive.ts +85 -76
- package/testing/index.ts +11 -0
package/package.json
CHANGED
package/src/components/Sprite.ts
CHANGED
|
@@ -227,6 +227,24 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
|
|
|
227
227
|
async onUpdate(props) {
|
|
228
228
|
super.onUpdate(props);
|
|
229
229
|
|
|
230
|
+
const setTexture = async (image: string) => {
|
|
231
|
+
const onProgress = this.fullProps.loader?.onProgress;
|
|
232
|
+
const texture = await Assets.load(image, (progress) => {
|
|
233
|
+
if (onProgress) onProgress(progress);
|
|
234
|
+
if (progress == 1) {
|
|
235
|
+
const onComplete = this.fullProps.loader?.onComplete;
|
|
236
|
+
if (onComplete) {
|
|
237
|
+
// hack to memoize the texture
|
|
238
|
+
setTimeout(() => {
|
|
239
|
+
onComplete(texture);
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
return texture
|
|
246
|
+
}
|
|
247
|
+
|
|
230
248
|
const sheet = props.sheet;
|
|
231
249
|
if (sheet?.params) this.sheetParams = sheet?.params;
|
|
232
250
|
|
|
@@ -239,14 +257,13 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
|
|
|
239
257
|
|
|
240
258
|
if (props.scaleMode) this.baseTexture.scaleMode = props.scaleMode;
|
|
241
259
|
else if (props.image && this.fullProps.rectangle === undefined) {
|
|
242
|
-
this.texture = await
|
|
260
|
+
this.texture = await setTexture(this.fullProps.image);
|
|
243
261
|
} else if (props.texture) {
|
|
244
262
|
this.texture = props.texture;
|
|
245
263
|
}
|
|
246
|
-
|
|
247
264
|
if (props.rectangle !== undefined) {
|
|
248
265
|
const { x, y, width, height } = props.rectangle?.value ?? props.rectangle;
|
|
249
|
-
const texture = await
|
|
266
|
+
const texture = await setTexture(this.fullProps.image);
|
|
250
267
|
this.texture = new Texture({
|
|
251
268
|
source: texture.source,
|
|
252
269
|
frame: new Rectangle(x, y, width, height),
|
|
@@ -483,6 +500,10 @@ export interface SpritePropsWithSheet
|
|
|
483
500
|
params?: any;
|
|
484
501
|
onFinish?: () => void;
|
|
485
502
|
};
|
|
503
|
+
loader?: {
|
|
504
|
+
onProgress?: (progress: number) => void;
|
|
505
|
+
onComplete?: (texture: Texture) => void;
|
|
506
|
+
};
|
|
486
507
|
}
|
|
487
508
|
|
|
488
509
|
export type SpritePropTypes = SpritePropsWithImage | SpritePropsWithSheet;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { Texture } from "pixi.js";
|
|
2
|
+
import { h, mount } from "../engine/signal";
|
|
3
|
+
import { useDefineProps } from "../hooks/useProps";
|
|
4
|
+
import { Sprite } from "./Sprite";
|
|
5
|
+
import { effect, Signal, signal } from "@signe/reactive";
|
|
6
|
+
|
|
7
|
+
interface VideoProps {
|
|
8
|
+
source: string;
|
|
9
|
+
paused?: boolean;
|
|
10
|
+
loop?: boolean;
|
|
11
|
+
muted?: boolean;
|
|
12
|
+
loader?: {
|
|
13
|
+
onComplete?: (texture: Texture) => void;
|
|
14
|
+
onProgress?: (progress: number) => void;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function Video(props: VideoProps) {
|
|
19
|
+
const eventsMap = {
|
|
20
|
+
audioprocess: null,
|
|
21
|
+
canplay: null,
|
|
22
|
+
canplaythrough: null,
|
|
23
|
+
complete: null,
|
|
24
|
+
durationchange: null,
|
|
25
|
+
emptied: null,
|
|
26
|
+
ended: null,
|
|
27
|
+
loadeddata: null,
|
|
28
|
+
loadedmetadata: null,
|
|
29
|
+
pause: null,
|
|
30
|
+
play: null,
|
|
31
|
+
playing: null,
|
|
32
|
+
progress: null,
|
|
33
|
+
ratechange: null,
|
|
34
|
+
seeked: null,
|
|
35
|
+
seeking: null,
|
|
36
|
+
stalled: null,
|
|
37
|
+
suspend: null,
|
|
38
|
+
timeupdate: null,
|
|
39
|
+
volumechange: null,
|
|
40
|
+
waiting: null
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const video: Signal<HTMLVideoElement | null> = signal(null)
|
|
44
|
+
const defineProps = useDefineProps(props)
|
|
45
|
+
const { play, loop, muted } = defineProps({
|
|
46
|
+
play: {
|
|
47
|
+
type: Boolean,
|
|
48
|
+
default: true
|
|
49
|
+
},
|
|
50
|
+
loop: {
|
|
51
|
+
type: Boolean,
|
|
52
|
+
default: false
|
|
53
|
+
},
|
|
54
|
+
muted: {
|
|
55
|
+
type: Boolean,
|
|
56
|
+
default: false
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
effect(() => {
|
|
61
|
+
const _video = video()
|
|
62
|
+
const state = play()
|
|
63
|
+
if (_video && state !== undefined) {
|
|
64
|
+
if (state) {
|
|
65
|
+
_video.play()
|
|
66
|
+
} else {
|
|
67
|
+
_video.pause()
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (_video && loop()) {
|
|
71
|
+
_video.loop = loop()
|
|
72
|
+
}
|
|
73
|
+
if (_video && muted()) {
|
|
74
|
+
_video.muted = muted()
|
|
75
|
+
}
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
mount(() => {
|
|
79
|
+
return () => {
|
|
80
|
+
for (let event in eventsMap) {
|
|
81
|
+
if (eventsMap[event]) {
|
|
82
|
+
video().removeEventListener(event, eventsMap[event])
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
return h(Sprite, {
|
|
89
|
+
...props,
|
|
90
|
+
image: props.source,
|
|
91
|
+
loader: {
|
|
92
|
+
onComplete: (texture) => {
|
|
93
|
+
const source = texture.source.resource
|
|
94
|
+
video.set(source)
|
|
95
|
+
if (props?.loader?.onComplete) {
|
|
96
|
+
props.loader.onComplete(texture)
|
|
97
|
+
}
|
|
98
|
+
for (let event in eventsMap) {
|
|
99
|
+
if (props[event]) {
|
|
100
|
+
const cb = (ev) => {
|
|
101
|
+
props[event](ev)
|
|
102
|
+
}
|
|
103
|
+
eventsMap[event] = cb
|
|
104
|
+
source.addEventListener(event, cb)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
})
|
|
110
|
+
}
|
package/src/components/index.ts
CHANGED
|
@@ -4,6 +4,7 @@ export { Graphics, Rect, Circle, Ellipse, Triangle, Svg as svg } from './Graphic
|
|
|
4
4
|
export { Scene } from './Scene'
|
|
5
5
|
export { ParticlesEmitter } from './ParticleEmitter'
|
|
6
6
|
export { Sprite } from './Sprite'
|
|
7
|
+
export { Video } from './Video'
|
|
7
8
|
export { Text } from './Text'
|
|
8
9
|
export { TilingSprite } from './TilingSprite'
|
|
9
10
|
export { Viewport } from './Viewport'
|
package/src/engine/reactive.ts
CHANGED
|
@@ -182,9 +182,24 @@ export function createComponent(tag: string, props?: Props): Element {
|
|
|
182
182
|
}
|
|
183
183
|
|
|
184
184
|
instance.onInit?.(element.props);
|
|
185
|
-
instance.onUpdate?.(element.props);
|
|
186
185
|
|
|
187
|
-
const
|
|
186
|
+
const elementsListen = new Subject<any>()
|
|
187
|
+
|
|
188
|
+
if (props?.isRoot) {
|
|
189
|
+
element.allElements = elementsListen
|
|
190
|
+
element.props.context.rootElement = element;
|
|
191
|
+
element.componentInstance.onMount?.(element);
|
|
192
|
+
propagateContext(element);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (props) {
|
|
196
|
+
for (let key in props) {
|
|
197
|
+
const directive = applyDirective(element, key);
|
|
198
|
+
if (directive) element.directives[key] = directive;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function onMount(parent: Element, element: Element, index?: number) {
|
|
188
203
|
element.props.context = parent.props.context;
|
|
189
204
|
element.parent = parent;
|
|
190
205
|
element.componentInstance.onMount?.(element, index);
|
|
@@ -196,85 +211,79 @@ export function createComponent(tag: string, props?: Props): Element {
|
|
|
196
211
|
});
|
|
197
212
|
};
|
|
198
213
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
if (element.props.attach) {
|
|
205
|
-
const isReactiveAttach = isSignal(element.propObservables?.attach)
|
|
206
|
-
if (!isReactiveAttach) {
|
|
207
|
-
element.props.children.push(element.props.attach)
|
|
208
|
-
}
|
|
209
|
-
else {
|
|
210
|
-
let lastElement = null
|
|
211
|
-
element.propObservables.attach.observable.subscribe(({ value, type }) => {
|
|
212
|
-
if (type != "init") {
|
|
213
|
-
destroyElement(lastElement)
|
|
214
|
-
}
|
|
215
|
-
lastElement = value
|
|
216
|
-
onMount(element, value);
|
|
217
|
-
propagateContext(value);
|
|
218
|
-
})
|
|
219
|
-
}
|
|
214
|
+
async function propagateContext(element) {
|
|
215
|
+
if (element.props.attach) {
|
|
216
|
+
const isReactiveAttach = isSignal(element.propObservables?.attach)
|
|
217
|
+
if (!isReactiveAttach) {
|
|
218
|
+
element.props.children.push(element.props.attach)
|
|
220
219
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
}
|
|
229
|
-
if (child instanceof Observable) {
|
|
230
|
-
child.subscribe(
|
|
231
|
-
({
|
|
232
|
-
elements: comp,
|
|
233
|
-
prev,
|
|
234
|
-
}: {
|
|
235
|
-
elements: Element[];
|
|
236
|
-
prev?: Element;
|
|
237
|
-
}) => {
|
|
238
|
-
// if prev, insert element after this
|
|
239
|
-
const components = comp.filter((c) => c !== null);
|
|
240
|
-
if (prev) {
|
|
241
|
-
components.forEach((c) => {
|
|
242
|
-
const index = element.props.children.indexOf(prev.props.key);
|
|
243
|
-
onMount(element, c, index + 1);
|
|
244
|
-
propagateContext(c);
|
|
245
|
-
});
|
|
246
|
-
return;
|
|
247
|
-
}
|
|
248
|
-
components.forEach((component) => {
|
|
249
|
-
if (!Array.isArray(component)) {
|
|
250
|
-
onMount(element, component);
|
|
251
|
-
propagateContext(component);
|
|
252
|
-
} else {
|
|
253
|
-
component.forEach((comp) => {
|
|
254
|
-
onMount(element, comp);
|
|
255
|
-
propagateContext(comp);
|
|
256
|
-
});
|
|
220
|
+
else {
|
|
221
|
+
await new Promise((resolve) => {
|
|
222
|
+
let lastElement = null
|
|
223
|
+
element.propSubscriptions.push(element.propObservables.attach.observable.subscribe(async (args) => {
|
|
224
|
+
const value = args?.value ?? args
|
|
225
|
+
if (!value) {
|
|
226
|
+
throw new Error(`attach in ${element.tag} is undefined or null, add a component`)
|
|
257
227
|
}
|
|
228
|
+
if (lastElement) {
|
|
229
|
+
destroyElement(lastElement)
|
|
230
|
+
}
|
|
231
|
+
lastElement = value
|
|
232
|
+
await createElement(element, value)
|
|
233
|
+
resolve(undefined)
|
|
234
|
+
}))
|
|
235
|
+
})
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
if (!element.props.children) {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
for (let child of element.props.children) {
|
|
242
|
+
if (!child) continue;
|
|
243
|
+
await createElement(element, child)
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
async function createElement(parent: Element, child: Element) {
|
|
248
|
+
if (isPromise(child)) {
|
|
249
|
+
child = await child;
|
|
250
|
+
}
|
|
251
|
+
if (child instanceof Observable) {
|
|
252
|
+
child.subscribe(
|
|
253
|
+
({
|
|
254
|
+
elements: comp,
|
|
255
|
+
prev,
|
|
256
|
+
}: {
|
|
257
|
+
elements: Element[];
|
|
258
|
+
prev?: Element;
|
|
259
|
+
}) => {
|
|
260
|
+
// if prev, insert element after this
|
|
261
|
+
const components = comp.filter((c) => c !== null);
|
|
262
|
+
if (prev) {
|
|
263
|
+
components.forEach((c) => {
|
|
264
|
+
const index = parent.props.children.indexOf(prev.props.key);
|
|
265
|
+
onMount(parent, c, index + 1);
|
|
266
|
+
propagateContext(c);
|
|
267
|
+
});
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
components.forEach((component) => {
|
|
271
|
+
if (!Array.isArray(component)) {
|
|
272
|
+
onMount(parent, component);
|
|
273
|
+
propagateContext(component);
|
|
274
|
+
} else {
|
|
275
|
+
component.forEach((comp) => {
|
|
276
|
+
onMount(parent, comp);
|
|
277
|
+
propagateContext(comp);
|
|
258
278
|
});
|
|
259
|
-
elementsListen.next(undefined)
|
|
260
279
|
}
|
|
261
|
-
);
|
|
262
|
-
|
|
263
|
-
onMount(element, child);
|
|
264
|
-
await propagateContext(child);
|
|
280
|
+
});
|
|
281
|
+
elementsListen.next(undefined)
|
|
265
282
|
}
|
|
266
|
-
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
element.componentInstance.onMount?.(element);
|
|
271
|
-
propagateContext(element);
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
if (props) {
|
|
275
|
-
for (let key in props) {
|
|
276
|
-
const directive = applyDirective(element, key);
|
|
277
|
-
if (directive) element.directives[key] = directive;
|
|
283
|
+
);
|
|
284
|
+
} else {
|
|
285
|
+
onMount(parent, child);
|
|
286
|
+
await propagateContext(child);
|
|
278
287
|
}
|
|
279
288
|
}
|
|
280
289
|
|
package/testing/index.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { bootstrapCanvas, Canvas, ComponentInstance, Element, h } from "canvasengine";
|
|
2
|
+
|
|
3
|
+
export class TestBed {
|
|
4
|
+
static async createComponent(component: any, props: any = {}, children: any = []): Promise<Element<ComponentInstance>> {
|
|
5
|
+
const comp = () => h(Canvas, {
|
|
6
|
+
tickStart: false
|
|
7
|
+
}, h(component, props, children))
|
|
8
|
+
const canvas = await bootstrapCanvas(document.getElementById('root'), comp)
|
|
9
|
+
return canvas.props.children?.[0]
|
|
10
|
+
}
|
|
11
|
+
}
|