canvasengine 2.0.0-beta.2 → 2.0.0-beta.4

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.
@@ -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,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
- export function set(obj, path, value, onlyPlainObject = false) {
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
- export function get(obj, path) {
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
- export function log(text) {
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
- export function error(text) {
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
- export function setObservablePoint(observablePoint: ObservablePoint, point: { x: number, y: number } | number | [number, number]): void {
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
- export function calculateDistance(x1, y1, x2, y2) {
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
@@ -10,4 +10,6 @@ export * from './engine/animation'
10
10
  export { useProps, useDefineProps } from './hooks/useProps'
11
11
  export * from './utils/Ease'
12
12
  export * from './utils/RadialGradient'
13
- export { isObservable } from 'rxjs'
13
+ export * from './components/DisplayObject'
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;
@@ -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
- }
@@ -1,207 +0,0 @@
1
- interface TileOptions {
2
- tilesetIndex?: number
3
- tileId: number
4
- x: number
5
- y: number
6
- }
7
-
8
- interface TilesGroupOptions {
9
- ignore?: boolean
10
- probability?: number
11
- baseHeight?: number
12
- baseWidth?: number
13
- rectMargin?: number
14
- baseOffsetX?: number
15
- baseOffsetY?: number
16
- }
17
-
18
- export class TileInfo {
19
- tilesetIndex?: number
20
- tileId: number
21
- flags: Map<string, any> = new Map()
22
- id: number = Math.random()
23
-
24
- constructor(obj: TileOptions) {
25
- this.tilesetIndex = obj.tilesetIndex ?? 0
26
- this.tileId = obj.tileId
27
- }
28
-
29
- addFlag(key: string, value: any) {
30
- this.flags.set(key, value)
31
- }
32
- }
33
-
34
- export class TilesGroup {
35
- tiles: (TileInfo | null)[][] = []
36
- width: number
37
- height: number
38
- ignore: boolean = false
39
- probability: number = 1
40
- baseHeight: number = 1
41
- baseWidth?: number
42
- baseOffsetX: number = 0
43
- baseOffsetY: number = 0
44
- rectMargin: number = 0
45
-
46
- constructor(tiles: TileOptions[], public tilesetIndex: number = 0, options: TilesGroupOptions = {}) {
47
- const pointsX = tiles.map(tile => tile.x)
48
- const pointsY = tiles.map(tile => tile.y)
49
- const offsetX = Math.min(...pointsX)
50
- const offsetY = Math.min(...pointsY)
51
- this.width = Math.max(...pointsX) - offsetX + 1
52
- this.height = Math.max(...pointsY) - offsetY + 1
53
- this.ignore = !!options.ignore
54
- this.probability = options.probability ?? 1
55
- this.baseHeight = options.baseHeight ?? 1
56
- this.baseWidth = options.baseWidth
57
- this.rectMargin = options.rectMargin ?? 0
58
- this.baseOffsetX = options.baseOffsetX ?? 0
59
- this.baseOffsetY = options.baseOffsetY ?? 0
60
- this.fillTiles()
61
- for (let tile of tiles) {
62
- this.addTile(tile.x - offsetX, tile.y - offsetY, tile)
63
- }
64
- }
65
-
66
- getRect(x: number, y: number): { minX: number, minY: number, maxX: number, maxY: number } {
67
- const margin = this.rectMargin
68
- return {
69
- minX: x - margin + this.baseOffsetX,
70
- minY: y - this.tilesBaseHeight - margin - this.baseOffsetY,
71
- maxX: x + this.tilesBaseWidth + margin,
72
- maxY: y + margin
73
- }
74
- }
75
-
76
- get tilesBase() {
77
- return this.tiles[this.tiles.length - 1]
78
- }
79
-
80
- get tilesBaseWidth(): number {
81
- return this.baseWidth ?? this.tilesBase.length
82
- }
83
-
84
- get tilesBaseHeight(): number {
85
- return this.baseHeight
86
- }
87
-
88
- forEach(cb: (tileInfo: TileInfo | null, x: number, y: number) => void) {
89
- for (let i = 0; i < this.tiles.length; i++) {
90
- for (let j = 0; j < this.tiles[i].length; j++) {
91
- cb(this.tiles[i][j], j, i)
92
- }
93
- }
94
- }
95
-
96
- find(cb: (tileInfo: TileInfo | null, x: number, y: number) => boolean): TileInfo | null {
97
- let found: TileInfo | null = null
98
- this.forEach((tileInfo, x, y) => {
99
- const bool = cb(tileInfo, x, y)
100
- if (bool) found = tileInfo
101
- })
102
- return found
103
- }
104
-
105
- getOffsetY(): number {
106
- const tilesBase = this.tilesBase
107
- let offset = 0
108
- this.forEach((tile, x, y) => {
109
- if (tile?.tileId == (tilesBase?.[0]?.tileId)) {
110
- offset = y
111
- }
112
- })
113
- return offset
114
- }
115
-
116
- fillTiles() {
117
- for (let i = 0; i < this.height; i++) {
118
- this.tiles[i] = []
119
- for (let j = 0; j < this.width; j++) {
120
- this.tiles[i][j] = null
121
- }
122
- }
123
- }
124
-
125
- shiftToTopLeft(): void {
126
- const matrix = this.tiles
127
-
128
- // Find the first non-undefined element and its position
129
- const foundFirst = () => {
130
- let firstElementRow = -1;
131
- let firstElementColumn = -1;
132
-
133
- for (let col = 0; col < matrix.length; col++) {
134
- if (!matrix[col]) matrix[col] = []
135
- for (let row = 0; row < matrix[col].length; row++) {
136
- if (matrix[col][row] !== undefined) {
137
- firstElementRow = row;
138
- firstElementColumn = col;
139
- return {
140
- firstElementRow,
141
- firstElementColumn
142
- };
143
- }
144
- }
145
- }
146
-
147
- return {
148
- firstElementRow,
149
- firstElementColumn
150
- }
151
- }
152
-
153
- const { firstElementRow, firstElementColumn } = foundFirst()
154
-
155
- // If no non-undefined element is found, return the original matrix
156
- if (firstElementRow === -1) {
157
- return;
158
- }
159
-
160
- // Shift the matrix elements
161
- const newMatrix: (TileInfo | null)[][] = [];
162
- for (let col = firstElementColumn; col < matrix.length; col++) {
163
- const newRow: (TileInfo | null)[] = [];
164
- for (let row = firstElementRow; row < matrix[col].length; row++) {
165
- newRow.push(matrix[col][row]);
166
- }
167
- newMatrix.push(newRow);
168
- }
169
-
170
- this.tiles = newMatrix;
171
-
172
- this.width = this.tiles[0].length
173
- this.height = this.tiles.length
174
- }
175
-
176
- addTile(x: number, y: number, tileOptions: TileOptions) {
177
- if (!this.tiles[y]) this.tiles[y] = []
178
- this.tiles[y][x] = new TileInfo(tileOptions)
179
- }
180
-
181
- addTileFlag(x: number, y: number, key: string, value: any) {
182
- this.getTile(x, y)?.addFlag(key, value)
183
- }
184
-
185
- getTile(x: number, y: number): TileInfo | null {
186
- return this.tiles[y]?.[x]
187
- }
188
-
189
- getTilesByFlag(key: string, value: any): { tileInfo: TileInfo, x: number, y: number }[] {
190
- const array: any = []
191
- this.forEach((tileInfo, x, y) => {
192
- const flag = tileInfo?.flags.get(key)
193
- if (flag && flag == value) {
194
- array.push({
195
- tileInfo,
196
- x,
197
- y
198
- })
199
- }
200
- })
201
- return array
202
- }
203
-
204
- isTileBase(tileInfo: TileInfo): boolean {
205
- return !!this.tilesBase.find(tile => tile?.id == tileInfo.id)
206
- }
207
- }