canvasengine 2.0.0-beta.2 → 2.0.0-beta.21

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 (41) hide show
  1. package/dist/index.d.ts +1289 -0
  2. package/dist/index.js +4248 -0
  3. package/dist/index.js.map +1 -0
  4. package/index.d.ts +4 -0
  5. package/package.json +5 -12
  6. package/src/components/Canvas.ts +53 -45
  7. package/src/components/Container.ts +2 -2
  8. package/src/components/DOMContainer.ts +123 -0
  9. package/src/components/DOMElement.ts +421 -0
  10. package/src/components/DisplayObject.ts +263 -189
  11. package/src/components/Graphic.ts +213 -36
  12. package/src/components/Mesh.ts +222 -0
  13. package/src/components/NineSliceSprite.ts +4 -1
  14. package/src/components/ParticleEmitter.ts +12 -8
  15. package/src/components/Sprite.ts +77 -14
  16. package/src/components/Text.ts +34 -14
  17. package/src/components/Video.ts +110 -0
  18. package/src/components/Viewport.ts +59 -43
  19. package/src/components/index.ts +6 -4
  20. package/src/components/types/DisplayObject.ts +30 -0
  21. package/src/directives/Drag.ts +357 -52
  22. package/src/directives/KeyboardControls.ts +3 -1
  23. package/src/directives/Sound.ts +94 -31
  24. package/src/directives/ViewportFollow.ts +35 -7
  25. package/src/engine/animation.ts +41 -5
  26. package/src/engine/bootstrap.ts +22 -3
  27. package/src/engine/directive.ts +2 -2
  28. package/src/engine/reactive.ts +337 -168
  29. package/src/engine/trigger.ts +65 -9
  30. package/src/engine/utils.ts +97 -9
  31. package/src/hooks/useProps.ts +1 -1
  32. package/src/index.ts +5 -1
  33. package/src/utils/RadialGradient.ts +29 -0
  34. package/src/utils/functions.ts +7 -0
  35. package/testing/index.ts +12 -0
  36. package/src/components/DrawMap/index.ts +0 -65
  37. package/src/components/Tilemap/Tile.ts +0 -79
  38. package/src/components/Tilemap/TileGroup.ts +0 -207
  39. package/src/components/Tilemap/TileLayer.ts +0 -163
  40. package/src/components/Tilemap/TileSet.ts +0 -41
  41. package/src/components/Tilemap/index.ts +0 -80
@@ -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: number;
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
- export function trigger<T = any>(config?: T): Trigger<T> {
18
- const _signal = signal(0);
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
- _signal.set(Math.random());
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
- export function on(triggerSignal: any, callback: (config: any) => void) {
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) callback(result.config);
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
  }
@@ -1,17 +1,36 @@
1
1
  import { ObservablePoint } from "pixi.js"
2
+ import { Observable } from "rxjs"
2
3
 
4
+ /**
5
+ * Checks if code is running in a browser environment
6
+ * @returns {boolean} True if running in browser, false otherwise
7
+ */
3
8
  export function isBrowser(): boolean {
4
9
  return typeof window !== 'undefined'
5
10
  }
6
11
 
12
+ /**
13
+ * Returns current high-resolution timestamp
14
+ * @returns {number} Current time in milliseconds
15
+ */
7
16
  export function preciseNow(): number {
8
17
  return typeof performance !== 'undefined' ? performance.now() : Date.now()
9
18
  }
10
19
 
20
+ /**
21
+ * Converts frames per second to milliseconds
22
+ * @param {number} fps - Frames per second
23
+ * @returns {number} Milliseconds per frame
24
+ */
11
25
  export function fps2ms(fps: number): number {
12
26
  return 1000 / fps
13
27
  }
14
28
 
29
+ /**
30
+ * Checks if a value is a Promise
31
+ * @param {any} value - Value to check
32
+ * @returns {boolean} True if value is a Promise, false otherwise
33
+ */
15
34
  export function isPromise(value: any): boolean {
16
35
  return value && value instanceof Promise
17
36
  }
@@ -58,15 +77,49 @@ function deepEquals(a: any, b: any): boolean {
58
77
  return false;
59
78
  }
60
79
 
80
+ /**
81
+ * Checks if a value is a function
82
+ * @param {unknown} val - Value to check
83
+ * @returns {boolean} True if value is a function, false otherwise
84
+ */
61
85
  export function isFunction(val: unknown): boolean {
62
86
  return {}.toString.call(val) === '[object Function]'
63
87
  }
64
88
 
89
+ /**
90
+ * Checks if a value is a plain object (not an instance of a class)
91
+ * @param {unknown} val - Value to check
92
+ * @returns {boolean} True if value is a plain object (not null, not array, not instance), false otherwise
93
+ * @example
94
+ * ```ts
95
+ * isObject({}) // true
96
+ * isObject(new Date()) // false
97
+ * isObject([]) // false
98
+ * isObject(null) // false
99
+ * ```
100
+ */
65
101
  export function isObject(val: unknown): boolean {
66
- return typeof val == 'object' && val != null && !Array.isArray(val)
102
+ return typeof val == 'object' && val != null && !Array.isArray(val) && val.constructor === Object
67
103
  }
68
104
 
69
- export function set(obj, path, value, onlyPlainObject = false) {
105
+ export function isObservable(val: unknown): boolean {
106
+ return val instanceof Observable
107
+ }
108
+
109
+ /**
110
+ * Sets a value in an object using a dot notation path
111
+ * @param {Record<string, any>} obj - Target object
112
+ * @param {string | string[]} path - Path to set value at (e.g., 'a.b.c' or ['a', 'b', 'c'])
113
+ * @param {any} value - Value to set
114
+ * @param {boolean} onlyPlainObject - If true, only creates plain objects in path
115
+ * @returns {Record<string, any>} Modified object
116
+ */
117
+ export function set(
118
+ obj: Record<string, any>,
119
+ path: string | string[],
120
+ value: any,
121
+ onlyPlainObject = false
122
+ ): Record<string, any> {
70
123
  if (Object(obj) !== obj) return obj;
71
124
 
72
125
  if (typeof path === 'string') {
@@ -79,8 +132,8 @@ export function set(obj, path, value, onlyPlainObject = false) {
79
132
  let current = obj;
80
133
  for (let i = 0; i < len - 1; i++) {
81
134
  let segment = path[i];
82
- let nextSegment = path[i + 1];
83
- let isNextNumeric = !isNaN(nextSegment) && isFinite(nextSegment);
135
+ let nextSegment: number | string = path[i + 1];
136
+ let isNextNumeric = !isNaN(Number(nextSegment)) && isFinite(Number(nextSegment));
84
137
 
85
138
  if (!current[segment] || typeof current[segment] !== 'object') {
86
139
  current[segment] = (isNextNumeric && !onlyPlainObject) ? [] : {};
@@ -94,7 +147,13 @@ export function set(obj, path, value, onlyPlainObject = false) {
94
147
  return obj;
95
148
  }
96
149
 
97
- export function get(obj, path) {
150
+ /**
151
+ * Gets a value from an object using a dot notation path
152
+ * @param {Record<string, any>} obj - Source object
153
+ * @param {string} path - Path to get value from (e.g., 'a.b.c')
154
+ * @returns {any} Value at path or undefined if not found
155
+ */
156
+ export function get(obj: Record<string, any>, path: string): any {
98
157
  const keys = path.split('.');
99
158
  let current = obj;
100
159
 
@@ -108,15 +167,31 @@ export function get(obj, path) {
108
167
  return current;
109
168
  }
110
169
 
111
- export function log(text) {
170
+ /**
171
+ * Logs a message to console
172
+ * @param {any} text - Message to log
173
+ */
174
+ export function log(text: any): void {
112
175
  console.log(text)
113
176
  }
114
177
 
115
- export function error(text) {
178
+ /**
179
+ * Logs an error message to console
180
+ * @param {any} text - Error message to log
181
+ */
182
+ export function error(text: any): void {
116
183
  console.error(text)
117
184
  }
118
185
 
119
- export function setObservablePoint(observablePoint: ObservablePoint, point: { x: number, y: number } | number | [number, number]): void {
186
+ /**
187
+ * Sets the position of an ObservablePoint using various input formats
188
+ * @param {ObservablePoint} observablePoint - The point to modify
189
+ * @param {Object | number | [number, number]} point - New position value
190
+ */
191
+ export function setObservablePoint(
192
+ observablePoint: ObservablePoint,
193
+ point: { x: number, y: number } | number | [number, number]
194
+ ): void {
120
195
  if (typeof point === 'number') {
121
196
  observablePoint.set(point);
122
197
  }
@@ -128,7 +203,20 @@ export function setObservablePoint(observablePoint: ObservablePoint, point: { x:
128
203
  }
129
204
  }
130
205
 
131
- export function calculateDistance(x1, y1, x2, y2) {
206
+ /**
207
+ * Calculates the Euclidean distance between two points
208
+ * @param {number} x1 - X coordinate of first point
209
+ * @param {number} y1 - Y coordinate of first point
210
+ * @param {number} x2 - X coordinate of second point
211
+ * @param {number} y2 - Y coordinate of second point
212
+ * @returns {number} Distance between the points
213
+ */
214
+ export function calculateDistance(
215
+ x1: number,
216
+ y1: number,
217
+ x2: number,
218
+ y2: number
219
+ ): number {
132
220
  const dx = x1 - x2;
133
221
  const dy = y1 - y2;
134
222
  return Math.sqrt(dx * dx + dy * dy);
@@ -25,7 +25,7 @@ export const useProps = (props, defaults = {}): any => {
25
25
  }
26
26
  for (let key in defaults) {
27
27
  if (!(key in obj)) {
28
- obj[key] = signal(defaults[key])
28
+ obj[key] = isPrimitive(defaults[key]) ? signal(defaults[key]) : defaults[key]
29
29
  }
30
30
  }
31
31
  return obj
package/src/index.ts CHANGED
@@ -1,3 +1,4 @@
1
+
1
2
  import './directives'
2
3
  export * from '@signe/reactive'
3
4
  export { Howler } from 'howler'
@@ -10,4 +11,7 @@ export * from './engine/animation'
10
11
  export { useProps, useDefineProps } from './hooks/useProps'
11
12
  export * from './utils/Ease'
12
13
  export * from './utils/RadialGradient'
13
- export { isObservable } from 'rxjs'
14
+ export * from './components/DisplayObject'
15
+ export { isObservable } from 'rxjs'
16
+ export * as Utils from './engine/utils'
17
+ export * as Howl from 'howler'
@@ -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;
@@ -0,0 +1,7 @@
1
+ export function isPercent(value?: string | number) {
2
+ if (!value) return false
3
+ if (typeof value === "string") {
4
+ return value.endsWith("%")
5
+ }
6
+ return false
7
+ }
@@ -0,0 +1,12 @@
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 { canvasElement, app } = await bootstrapCanvas(document.getElementById('root'), comp)
9
+ app.render()
10
+ return canvasElement.props.children?.[0]
11
+ }
12
+ }
@@ -1,65 +0,0 @@
1
- import { effect, signal } from "@signe/reactive";
2
- import { loop } from "../../engine/reactive";
3
- import { h } from "../../engine/signal";
4
- import { useProps } from "../../hooks/useProps";
5
- import { Container } from "../Container";
6
- import { Sprite } from "../Sprite";
7
-
8
- interface TileData {
9
- id: number;
10
- rect: [number, number, number, number];
11
- drawIn: [number, number];
12
- layerIndex: number;
13
- }
14
-
15
- export function ImageMap(props) {
16
- const { imageSource, tileData } = useProps(props);
17
- const tiles = signal<TileData[]>([]);
18
-
19
- effect(async () => {
20
- const data = await fetch(tileData()).then((response) => response.json());
21
- const objects = data;
22
- if (props.objects) {
23
- objects.push(...props.objects(data));
24
- }
25
- tiles.set(objects);
26
- });
27
-
28
- const createLayeredTiles = () => {
29
- const layers = [createTileLayer(0), createTileLayer(1, true), createTileLayer(2)];
30
-
31
- return h(Container, props, ...layers);
32
- };
33
-
34
- const createTileLayer = (layerIndex: number, sortableChildren = false) => {
35
- return h(
36
- Container,
37
- {
38
- sortableChildren,
39
- },
40
- loop(tiles, (object) => {
41
-
42
- if (object.tag && layerIndex == 1) {
43
- return object
44
- }
45
-
46
- object.layerIndex ||= 1;
47
- if (object.layerIndex !== layerIndex) return null;
48
-
49
- const [x, y, width, height] = object.rect;
50
- const [drawX, drawY] = object.drawIn;
51
-
52
- return h(Sprite, {
53
- image: imageSource(),
54
- x: drawX,
55
- y: drawY,
56
- rectangle: { x, y, width, height },
57
- zIndex: drawY + height - 70,
58
- // zIndex: 0
59
- });
60
- })
61
- );
62
- };
63
-
64
- return createLayeredTiles();
65
- }
@@ -1,79 +0,0 @@
1
- import { CompositeTilemap } from "@pixi/tilemap";
2
- import { Tile as TiledTileClass } from '@rpgjs/tiled';
3
- import { AnimatedSprite, Texture, groupD8 } from "pixi.js";
4
- import { TileSet } from "./TileSet";
5
-
6
- export class Tile extends AnimatedSprite {
7
- static getTextures(tile: TiledTileClass, tileSet: TileSet) {
8
- const textures: Texture[] = [];
9
-
10
- if (tile.animations && tile.animations.length) {
11
- tile.animations.forEach(frame => {
12
- textures.push(tileSet.textures[frame.tileid])
13
- });
14
- } else {
15
- textures.push(tileSet.textures[tile.gid - tileSet.firstgid])
16
- }
17
-
18
- return textures;
19
- }
20
-
21
- animations: { tileid: number, duration: number }[] = []
22
- _x: number = 0
23
- _y: number = 0
24
- pointsBufIndex: number
25
- properties: any = {}
26
-
27
- constructor(
28
- private tile: TiledTileClass,
29
- private tileSet: TileSet
30
- ) {
31
- super(Tile.getTextures(tile, tileSet));
32
- this.animations = tile.animations || []
33
- this.properties = tile.properties
34
- this.textures = Tile.getTextures(tile, tileSet)
35
- this.texture = this.textures[0] as Texture
36
- this.flip()
37
- }
38
-
39
- get gid() {
40
- return this.tile.gid
41
- }
42
-
43
- setAnimation(frame: CompositeTilemap) {
44
- const size = this.animations.length
45
- if (size > 1) {
46
- const offset = (this.animations[1].tileid - this.animations[0].tileid) * this.width
47
- frame.tileAnimX(offset, size)
48
- }
49
- }
50
-
51
- flip() {
52
- let symmetry
53
- let i = 0
54
- const add = (symmetrySecond) => {
55
- i++
56
- if (symmetry) symmetry = groupD8.add(symmetry, symmetrySecond)
57
- else symmetry = symmetrySecond
58
- }
59
-
60
- if (this.tile.horizontalFlip) {
61
- add(groupD8.MIRROR_HORIZONTAL)
62
- }
63
-
64
- if (this.tile.verticalFlip) {
65
- add(groupD8.MIRROR_VERTICAL)
66
- }
67
-
68
- if (this.tile.diagonalFlip) {
69
- if (i % 2 == 0) {
70
- add(groupD8.MAIN_DIAGONAL)
71
- }
72
- else {
73
- add(groupD8.REVERSE_DIAGONAL)
74
- }
75
- }
76
-
77
- //if (symmetry) this.texture.rotate = symmetry
78
- }
79
- }