canvasengine 2.0.0-beta.4 → 2.0.0-beta.6
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 +27 -3
- package/dist/index.js +241 -99
- 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/components/types/DisplayObject.ts +1 -0
- package/src/engine/reactive.ts +143 -100
- 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
|
+
src: 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.src,
|
|
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'
|
|
@@ -8,6 +8,7 @@ export type Size = number | `${number}%`
|
|
|
8
8
|
export type EdgeSize = SignalOrPrimitive<Size | [Size, Size] | [Size, Size, Size, Size]>
|
|
9
9
|
|
|
10
10
|
export interface DisplayObjectProps {
|
|
11
|
+
attach?: any;
|
|
11
12
|
ref?: string;
|
|
12
13
|
x?: SignalOrPrimitive<number>;
|
|
13
14
|
y?: SignalOrPrimitive<number>;
|
package/src/engine/reactive.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Signal, WritableArraySignal, isSignal } from "@signe/reactive";
|
|
1
|
+
import { Signal, WritableArraySignal, WritableObjectSignal, isSignal } from "@signe/reactive";
|
|
2
2
|
import {
|
|
3
3
|
Observable,
|
|
4
4
|
Subject,
|
|
@@ -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
|
-
}
|
|
220
|
-
}
|
|
221
|
-
if (!element.props.children) {
|
|
222
|
-
return;
|
|
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)
|
|
223
219
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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
|
|
|
@@ -360,37 +369,71 @@ export function loop<T = any>(
|
|
|
360
369
|
});
|
|
361
370
|
}
|
|
362
371
|
|
|
372
|
+
/**
|
|
373
|
+
* Conditionally creates and destroys elements based on a condition signal.
|
|
374
|
+
*
|
|
375
|
+
* @param {Signal<boolean> | boolean} condition - A signal or boolean that determines whether to create an element.
|
|
376
|
+
* @param {Function} createElementFn - A function that returns an element or a promise that resolves to an element.
|
|
377
|
+
* @returns {Observable} An observable that emits the created or destroyed element.
|
|
378
|
+
*/
|
|
363
379
|
export function cond(
|
|
364
|
-
condition: Signal,
|
|
380
|
+
condition: Signal<boolean> | boolean,
|
|
365
381
|
createElementFn: () => Element | Promise<Element>
|
|
366
382
|
): FlowObservable {
|
|
367
383
|
let element: Element | null = null;
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
384
|
+
|
|
385
|
+
if (isSignal(condition)) {
|
|
386
|
+
const signalCondition = condition as WritableObjectSignal<boolean>;
|
|
387
|
+
return new Observable<{elements: Element[], type?: "init" | "remove"}>(subscriber => {
|
|
388
|
+
return signalCondition.observable.subscribe(bool => {
|
|
389
|
+
if (bool) {
|
|
390
|
+
let _el = createElementFn();
|
|
391
|
+
if (isPromise(_el)) {
|
|
392
|
+
from(_el as Promise<Element>).subscribe(el => {
|
|
393
|
+
element = el;
|
|
394
|
+
subscriber.next({
|
|
377
395
|
type: "init",
|
|
378
396
|
elements: [el],
|
|
379
|
-
};
|
|
380
|
-
})
|
|
381
|
-
|
|
397
|
+
});
|
|
398
|
+
});
|
|
399
|
+
} else {
|
|
400
|
+
element = _el as Element;
|
|
401
|
+
subscriber.next({
|
|
402
|
+
type: "init",
|
|
403
|
+
elements: [element],
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
} else if (element) {
|
|
407
|
+
destroyElement(element);
|
|
408
|
+
subscriber.next({
|
|
409
|
+
elements: [],
|
|
410
|
+
});
|
|
411
|
+
} else {
|
|
412
|
+
subscriber.next({
|
|
413
|
+
elements: [],
|
|
414
|
+
});
|
|
382
415
|
}
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
416
|
+
});
|
|
417
|
+
});
|
|
418
|
+
} else {
|
|
419
|
+
// Handle boolean case
|
|
420
|
+
if (condition) {
|
|
421
|
+
let _el = createElementFn();
|
|
422
|
+
if (isPromise(_el)) {
|
|
423
|
+
return from(_el as Promise<Element>).pipe(
|
|
424
|
+
map((el) => ({
|
|
425
|
+
type: "init",
|
|
426
|
+
elements: [el],
|
|
427
|
+
}))
|
|
428
|
+
);
|
|
390
429
|
}
|
|
391
430
|
return of({
|
|
392
|
-
|
|
431
|
+
type: "init",
|
|
432
|
+
elements: [_el as Element],
|
|
393
433
|
});
|
|
394
|
-
}
|
|
395
|
-
|
|
434
|
+
}
|
|
435
|
+
return of({
|
|
436
|
+
elements: [],
|
|
437
|
+
});
|
|
438
|
+
}
|
|
396
439
|
}
|
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
|
+
}
|