canvasengine 2.0.0-beta.3 → 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 +186 -6
- package/dist/index.js +281 -72
- package/dist/index.js.map +1 -1
- package/index.d.ts +4 -0
- 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 -59
- package/src/engine/trigger.ts +65 -9
- package/src/engine/utils.ts +84 -8
- package/src/index.ts +2 -1
- package/src/utils/RadialGradient.ts +29 -0
- package/testing/index.ts +11 -0
package/index.d.ts
ADDED
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,68 +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.children) {
|
|
205
|
-
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)
|
|
206
219
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
({
|
|
215
|
-
elements: comp,
|
|
216
|
-
prev,
|
|
217
|
-
}: {
|
|
218
|
-
elements: Element[];
|
|
219
|
-
prev?: Element;
|
|
220
|
-
}) => {
|
|
221
|
-
// if prev, insert element after this
|
|
222
|
-
const components = comp.filter((c) => c !== null);
|
|
223
|
-
if (prev) {
|
|
224
|
-
components.forEach((c) => {
|
|
225
|
-
const index = element.props.children.indexOf(prev.props.key);
|
|
226
|
-
onMount(element, c, index + 1);
|
|
227
|
-
propagateContext(c);
|
|
228
|
-
});
|
|
229
|
-
return;
|
|
230
|
-
}
|
|
231
|
-
components.forEach((component) => {
|
|
232
|
-
if (!Array.isArray(component)) {
|
|
233
|
-
onMount(element, component);
|
|
234
|
-
propagateContext(component);
|
|
235
|
-
} else {
|
|
236
|
-
component.forEach((comp) => {
|
|
237
|
-
onMount(element, comp);
|
|
238
|
-
propagateContext(comp);
|
|
239
|
-
});
|
|
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`)
|
|
240
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);
|
|
241
278
|
});
|
|
242
|
-
elementsListen.next(undefined)
|
|
243
279
|
}
|
|
244
|
-
);
|
|
245
|
-
|
|
246
|
-
onMount(element, child);
|
|
247
|
-
await propagateContext(child);
|
|
280
|
+
});
|
|
281
|
+
elementsListen.next(undefined)
|
|
248
282
|
}
|
|
249
|
-
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
element.componentInstance.onMount?.(element);
|
|
254
|
-
propagateContext(element);
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
if (props) {
|
|
258
|
-
for (let key in props) {
|
|
259
|
-
const directive = applyDirective(element, key);
|
|
260
|
-
if (directive) element.directives[key] = directive;
|
|
283
|
+
);
|
|
284
|
+
} else {
|
|
285
|
+
onMount(parent, child);
|
|
286
|
+
await propagateContext(child);
|
|
261
287
|
}
|
|
262
288
|
}
|
|
263
289
|
|
package/src/engine/trigger.ts
CHANGED
|
@@ -2,39 +2,95 @@ import { effect, signal } from "@signe/reactive";
|
|
|
2
2
|
|
|
3
3
|
interface Listen<T = any> {
|
|
4
4
|
config: T | undefined;
|
|
5
|
-
seed:
|
|
5
|
+
seed: {
|
|
6
|
+
config: T | undefined;
|
|
7
|
+
value: number;
|
|
8
|
+
resolve: (value: any) => void;
|
|
9
|
+
};
|
|
6
10
|
}
|
|
7
11
|
|
|
8
12
|
interface Trigger<T = any> {
|
|
9
|
-
start: () => void
|
|
13
|
+
start: () => Promise<void>;
|
|
10
14
|
listen: () => Listen<T> | undefined;
|
|
11
15
|
}
|
|
12
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Checks if the given argument is a Trigger object
|
|
19
|
+
* @param arg - The value to check
|
|
20
|
+
* @returns True if the argument is a Trigger object
|
|
21
|
+
*/
|
|
13
22
|
export function isTrigger(arg: any): arg is Trigger<any> {
|
|
14
23
|
return arg?.start && arg?.listen;
|
|
15
24
|
}
|
|
16
25
|
|
|
17
|
-
|
|
18
|
-
|
|
26
|
+
/**
|
|
27
|
+
* Creates a new trigger that can be used to pass data between components
|
|
28
|
+
* @param globalConfig - Optional configuration data to be passed when the trigger is activated
|
|
29
|
+
* @returns A Trigger object with start and listen methods
|
|
30
|
+
* @example
|
|
31
|
+
* ```ts
|
|
32
|
+
* const myTrigger = trigger()
|
|
33
|
+
*
|
|
34
|
+
* on(myTrigger, (data) => {
|
|
35
|
+
* console.log('Triggered with data:', data)
|
|
36
|
+
* })
|
|
37
|
+
*
|
|
38
|
+
* myTrigger.start({ message: 'Hello' })
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export function trigger<T = any>(globalConfig?: T): Trigger<T> {
|
|
42
|
+
const _signal = signal({
|
|
43
|
+
config: globalConfig,
|
|
44
|
+
value: 0,
|
|
45
|
+
resolve: (value: any) => void 0,
|
|
46
|
+
});
|
|
19
47
|
return {
|
|
20
|
-
start: () => {
|
|
21
|
-
|
|
48
|
+
start: (config?: T) => {
|
|
49
|
+
return new Promise((resolve: (value: any) => void) => {
|
|
50
|
+
_signal.set({
|
|
51
|
+
config: {
|
|
52
|
+
...globalConfig,
|
|
53
|
+
...config,
|
|
54
|
+
},
|
|
55
|
+
resolve,
|
|
56
|
+
value: Math.random(),
|
|
57
|
+
});
|
|
58
|
+
});
|
|
22
59
|
},
|
|
23
60
|
listen: (): Listen<T> | undefined => {
|
|
24
61
|
return {
|
|
25
|
-
config,
|
|
62
|
+
config: globalConfig,
|
|
26
63
|
seed: _signal(),
|
|
27
64
|
};
|
|
28
65
|
},
|
|
29
66
|
};
|
|
30
67
|
}
|
|
31
68
|
|
|
32
|
-
|
|
69
|
+
/**
|
|
70
|
+
* Subscribes to a trigger and executes a callback when the trigger is activated
|
|
71
|
+
* @param triggerSignal - The trigger to subscribe to
|
|
72
|
+
* @param callback - Function to execute when the trigger is activated
|
|
73
|
+
* @throws Error if triggerSignal is not a valid trigger
|
|
74
|
+
* @example
|
|
75
|
+
* ```ts
|
|
76
|
+
* const click = trigger()
|
|
77
|
+
*
|
|
78
|
+
* on(click, () => {
|
|
79
|
+
* console.log('Click triggered')
|
|
80
|
+
* })
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
export function on(triggerSignal: any, callback: (config: any) => void | Promise<void>) {
|
|
33
84
|
if (!isTrigger(triggerSignal)) {
|
|
34
85
|
throw new Error("In 'on(arg)' must have a trigger signal type");
|
|
35
86
|
}
|
|
36
87
|
effect(() => {
|
|
37
88
|
const result = triggerSignal.listen();
|
|
38
|
-
if (result?.seed)
|
|
89
|
+
if (result?.seed.value) {
|
|
90
|
+
const ret = callback(result?.seed.config);
|
|
91
|
+
if (ret && typeof ret.then === 'function') {
|
|
92
|
+
ret.then(result?.seed.resolve);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
39
95
|
});
|
|
40
96
|
}
|
package/src/engine/utils.ts
CHANGED
|
@@ -1,17 +1,35 @@
|
|
|
1
1
|
import { ObservablePoint } from "pixi.js"
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Checks if code is running in a browser environment
|
|
5
|
+
* @returns {boolean} True if running in browser, false otherwise
|
|
6
|
+
*/
|
|
3
7
|
export function isBrowser(): boolean {
|
|
4
8
|
return typeof window !== 'undefined'
|
|
5
9
|
}
|
|
6
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Returns current high-resolution timestamp
|
|
13
|
+
* @returns {number} Current time in milliseconds
|
|
14
|
+
*/
|
|
7
15
|
export function preciseNow(): number {
|
|
8
16
|
return typeof performance !== 'undefined' ? performance.now() : Date.now()
|
|
9
17
|
}
|
|
10
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Converts frames per second to milliseconds
|
|
21
|
+
* @param {number} fps - Frames per second
|
|
22
|
+
* @returns {number} Milliseconds per frame
|
|
23
|
+
*/
|
|
11
24
|
export function fps2ms(fps: number): number {
|
|
12
25
|
return 1000 / fps
|
|
13
26
|
}
|
|
14
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Checks if a value is a Promise
|
|
30
|
+
* @param {any} value - Value to check
|
|
31
|
+
* @returns {boolean} True if value is a Promise, false otherwise
|
|
32
|
+
*/
|
|
15
33
|
export function isPromise(value: any): boolean {
|
|
16
34
|
return value && value instanceof Promise
|
|
17
35
|
}
|
|
@@ -58,15 +76,38 @@ function deepEquals(a: any, b: any): boolean {
|
|
|
58
76
|
return false;
|
|
59
77
|
}
|
|
60
78
|
|
|
79
|
+
/**
|
|
80
|
+
* Checks if a value is a function
|
|
81
|
+
* @param {unknown} val - Value to check
|
|
82
|
+
* @returns {boolean} True if value is a function, false otherwise
|
|
83
|
+
*/
|
|
61
84
|
export function isFunction(val: unknown): boolean {
|
|
62
85
|
return {}.toString.call(val) === '[object Function]'
|
|
63
86
|
}
|
|
64
87
|
|
|
88
|
+
/**
|
|
89
|
+
* Checks if a value is a plain object
|
|
90
|
+
* @param {unknown} val - Value to check
|
|
91
|
+
* @returns {boolean} True if value is an object (not null and not array), false otherwise
|
|
92
|
+
*/
|
|
65
93
|
export function isObject(val: unknown): boolean {
|
|
66
94
|
return typeof val == 'object' && val != null && !Array.isArray(val)
|
|
67
95
|
}
|
|
68
96
|
|
|
69
|
-
|
|
97
|
+
/**
|
|
98
|
+
* Sets a value in an object using a dot notation path
|
|
99
|
+
* @param {Record<string, any>} obj - Target object
|
|
100
|
+
* @param {string | string[]} path - Path to set value at (e.g., 'a.b.c' or ['a', 'b', 'c'])
|
|
101
|
+
* @param {any} value - Value to set
|
|
102
|
+
* @param {boolean} onlyPlainObject - If true, only creates plain objects in path
|
|
103
|
+
* @returns {Record<string, any>} Modified object
|
|
104
|
+
*/
|
|
105
|
+
export function set(
|
|
106
|
+
obj: Record<string, any>,
|
|
107
|
+
path: string | string[],
|
|
108
|
+
value: any,
|
|
109
|
+
onlyPlainObject = false
|
|
110
|
+
): Record<string, any> {
|
|
70
111
|
if (Object(obj) !== obj) return obj;
|
|
71
112
|
|
|
72
113
|
if (typeof path === 'string') {
|
|
@@ -79,8 +120,8 @@ export function set(obj, path, value, onlyPlainObject = false) {
|
|
|
79
120
|
let current = obj;
|
|
80
121
|
for (let i = 0; i < len - 1; i++) {
|
|
81
122
|
let segment = path[i];
|
|
82
|
-
let nextSegment = path[i + 1];
|
|
83
|
-
let isNextNumeric = !isNaN(nextSegment) && isFinite(nextSegment);
|
|
123
|
+
let nextSegment: number | string = path[i + 1];
|
|
124
|
+
let isNextNumeric = !isNaN(Number(nextSegment)) && isFinite(Number(nextSegment));
|
|
84
125
|
|
|
85
126
|
if (!current[segment] || typeof current[segment] !== 'object') {
|
|
86
127
|
current[segment] = (isNextNumeric && !onlyPlainObject) ? [] : {};
|
|
@@ -94,7 +135,13 @@ export function set(obj, path, value, onlyPlainObject = false) {
|
|
|
94
135
|
return obj;
|
|
95
136
|
}
|
|
96
137
|
|
|
97
|
-
|
|
138
|
+
/**
|
|
139
|
+
* Gets a value from an object using a dot notation path
|
|
140
|
+
* @param {Record<string, any>} obj - Source object
|
|
141
|
+
* @param {string} path - Path to get value from (e.g., 'a.b.c')
|
|
142
|
+
* @returns {any} Value at path or undefined if not found
|
|
143
|
+
*/
|
|
144
|
+
export function get(obj: Record<string, any>, path: string): any {
|
|
98
145
|
const keys = path.split('.');
|
|
99
146
|
let current = obj;
|
|
100
147
|
|
|
@@ -108,15 +155,31 @@ export function get(obj, path) {
|
|
|
108
155
|
return current;
|
|
109
156
|
}
|
|
110
157
|
|
|
111
|
-
|
|
158
|
+
/**
|
|
159
|
+
* Logs a message to console
|
|
160
|
+
* @param {any} text - Message to log
|
|
161
|
+
*/
|
|
162
|
+
export function log(text: any): void {
|
|
112
163
|
console.log(text)
|
|
113
164
|
}
|
|
114
165
|
|
|
115
|
-
|
|
166
|
+
/**
|
|
167
|
+
* Logs an error message to console
|
|
168
|
+
* @param {any} text - Error message to log
|
|
169
|
+
*/
|
|
170
|
+
export function error(text: any): void {
|
|
116
171
|
console.error(text)
|
|
117
172
|
}
|
|
118
173
|
|
|
119
|
-
|
|
174
|
+
/**
|
|
175
|
+
* Sets the position of an ObservablePoint using various input formats
|
|
176
|
+
* @param {ObservablePoint} observablePoint - The point to modify
|
|
177
|
+
* @param {Object | number | [number, number]} point - New position value
|
|
178
|
+
*/
|
|
179
|
+
export function setObservablePoint(
|
|
180
|
+
observablePoint: ObservablePoint,
|
|
181
|
+
point: { x: number, y: number } | number | [number, number]
|
|
182
|
+
): void {
|
|
120
183
|
if (typeof point === 'number') {
|
|
121
184
|
observablePoint.set(point);
|
|
122
185
|
}
|
|
@@ -128,7 +191,20 @@ export function setObservablePoint(observablePoint: ObservablePoint, point: { x:
|
|
|
128
191
|
}
|
|
129
192
|
}
|
|
130
193
|
|
|
131
|
-
|
|
194
|
+
/**
|
|
195
|
+
* Calculates the Euclidean distance between two points
|
|
196
|
+
* @param {number} x1 - X coordinate of first point
|
|
197
|
+
* @param {number} y1 - Y coordinate of first point
|
|
198
|
+
* @param {number} x2 - X coordinate of second point
|
|
199
|
+
* @param {number} y2 - Y coordinate of second point
|
|
200
|
+
* @returns {number} Distance between the points
|
|
201
|
+
*/
|
|
202
|
+
export function calculateDistance(
|
|
203
|
+
x1: number,
|
|
204
|
+
y1: number,
|
|
205
|
+
x2: number,
|
|
206
|
+
y2: number
|
|
207
|
+
): number {
|
|
132
208
|
const dx = x1 - x2;
|
|
133
209
|
const dy = y1 - y2;
|
|
134
210
|
return Math.sqrt(dx * dx + dy * dy);
|
package/src/index.ts
CHANGED
|
@@ -11,4 +11,5 @@ export { useProps, useDefineProps } from './hooks/useProps'
|
|
|
11
11
|
export * from './utils/Ease'
|
|
12
12
|
export * from './utils/RadialGradient'
|
|
13
13
|
export * from './components/DisplayObject'
|
|
14
|
-
export { isObservable } from 'rxjs'
|
|
14
|
+
export { isObservable } from 'rxjs'
|
|
15
|
+
export * as Utils from './engine/utils'
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import { Texture, ImageSource, DOMAdapter, Matrix } from "pixi.js";
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Creates a radial gradient texture that can be used in PixiJS.
|
|
5
|
+
* @example
|
|
6
|
+
* const gradient = new RadialGradient(size, size, 0, size, size, 0);
|
|
7
|
+
* gradient.addColorStop(0, "rgba(255, 255, 0, 1)");
|
|
8
|
+
* gradient.addColorStop(0.5, "rgba(255, 255, 0, 0.3)");
|
|
9
|
+
* gradient.addColorStop(0.8, "rgba(255, 255, 0, 0)");
|
|
10
|
+
*/
|
|
3
11
|
export class RadialGradient {
|
|
4
12
|
private canvas: HTMLCanvasElement;
|
|
5
13
|
private ctx: CanvasRenderingContext2D | null;
|
|
@@ -9,6 +17,16 @@ export class RadialGradient {
|
|
|
9
17
|
|
|
10
18
|
public size = 600;
|
|
11
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Creates a new RadialGradient instance
|
|
22
|
+
* @param x0 - The x-coordinate of the starting circle
|
|
23
|
+
* @param y0 - The y-coordinate of the starting circle
|
|
24
|
+
* @param x1 - The x-coordinate of the ending circle
|
|
25
|
+
* @param y1 - The y-coordinate of the ending circle
|
|
26
|
+
* @param x2 - The x-coordinate for gradient transformation
|
|
27
|
+
* @param y2 - The y-coordinate for gradient transformation
|
|
28
|
+
* @param focalPoint - The focal point of the gradient (0-1), defaults to 0
|
|
29
|
+
*/
|
|
12
30
|
constructor(
|
|
13
31
|
private x0: number,
|
|
14
32
|
private y0: number,
|
|
@@ -38,12 +56,23 @@ export class RadialGradient {
|
|
|
38
56
|
}
|
|
39
57
|
}
|
|
40
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Adds a color stop to the gradient
|
|
61
|
+
* @param offset - The position of the color stop (0-1)
|
|
62
|
+
* @param color - The color value (any valid CSS color string)
|
|
63
|
+
*/
|
|
41
64
|
addColorStop(offset: number, color: string) {
|
|
42
65
|
if (this.gradient) {
|
|
43
66
|
this.gradient.addColorStop(offset, color);
|
|
44
67
|
}
|
|
45
68
|
}
|
|
46
69
|
|
|
70
|
+
/**
|
|
71
|
+
* Renders the gradient and returns the texture with its transformation matrix
|
|
72
|
+
* @param options - Render options
|
|
73
|
+
* @param options.translate - Optional translation coordinates
|
|
74
|
+
* @returns Object containing the texture and transformation matrix
|
|
75
|
+
*/
|
|
47
76
|
render({ translate }: { translate?: { x: number; y: number } } = {}) {
|
|
48
77
|
const { x0, y0, x1, y1, x2, y2, focalPoint } = this;
|
|
49
78
|
const defaultSize = this.size;
|