@argila/core 0.0.0-alpha-20250607064138
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/LICENSE +21 -0
- package/dist/Argila.d.ts +24 -0
- package/dist/Argila.d.ts.map +1 -0
- package/dist/Argila.js +62 -0
- package/dist/Argila.test.d.ts +2 -0
- package/dist/Argila.test.d.ts.map +1 -0
- package/dist/Argila.test.js +150 -0
- package/dist/Canvas.d.ts +117 -0
- package/dist/Canvas.d.ts.map +1 -0
- package/dist/Canvas.js +311 -0
- package/dist/Canvas.test.d.ts +2 -0
- package/dist/Canvas.test.d.ts.map +1 -0
- package/dist/Canvas.test.js +311 -0
- package/dist/Common.d.ts +11 -0
- package/dist/Common.d.ts.map +1 -0
- package/dist/Common.js +64 -0
- package/dist/Common.test.js +118 -0
- package/dist/Event.d.ts +10 -0
- package/dist/Event.d.ts.map +1 -0
- package/dist/Event.js +23 -0
- package/dist/Event.test.js +72 -0
- package/dist/LinkedList.d.ts +44 -0
- package/dist/LinkedList.d.ts.map +1 -0
- package/dist/LinkedList.js +95 -0
- package/dist/LinkedList.test.d.ts +2 -0
- package/dist/LinkedList.test.d.ts.map +1 -0
- package/dist/LinkedList.test.js +197 -0
- package/dist/Vector3.d.ts +104 -0
- package/dist/Vector3.d.ts.map +1 -0
- package/dist/Vector3.js +185 -0
- package/dist/Vector3.test.d.ts +2 -0
- package/dist/Vector3.test.d.ts.map +1 -0
- package/dist/Vector3.test.js +127 -0
- package/dist/common.test.d.ts +2 -0
- package/dist/common.test.d.ts.map +1 -0
- package/dist/event.test.d.ts +2 -0
- package/dist/event.test.d.ts.map +1 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +41 -0
- package/dist/systems/Draw.d.ts +8 -0
- package/dist/systems/Draw.d.ts.map +1 -0
- package/dist/systems/Draw.js +15 -0
- package/dist/systems/Entity.d.ts +18 -0
- package/dist/systems/Entity.d.ts.map +1 -0
- package/dist/systems/Entity.js +44 -0
- package/dist/systems/Input.d.ts +127 -0
- package/dist/systems/Input.d.ts.map +1 -0
- package/dist/systems/Input.js +191 -0
- package/dist/systems/Loop.d.ts +29 -0
- package/dist/systems/Loop.d.ts.map +1 -0
- package/dist/systems/Loop.js +93 -0
- package/dist/systems/Render.d.ts +21 -0
- package/dist/systems/Render.d.ts.map +1 -0
- package/dist/systems/Render.js +112 -0
- package/dist/systems/Shape.d.ts +48 -0
- package/dist/systems/Shape.d.ts.map +1 -0
- package/dist/systems/Shape.js +146 -0
- package/dist/systems/System.d.ts +22 -0
- package/dist/systems/System.d.ts.map +1 -0
- package/dist/systems/System.js +20 -0
- package/dist/systems/World.d.ts +24 -0
- package/dist/systems/World.d.ts.map +1 -0
- package/dist/systems/World.js +84 -0
- package/dist/systems/debugs/Debug.d.ts +9 -0
- package/dist/systems/debugs/Debug.d.ts.map +1 -0
- package/dist/systems/debugs/Debug.js +12 -0
- package/dist/systems/debugs/InputDebug.d.ts +7 -0
- package/dist/systems/debugs/InputDebug.d.ts.map +1 -0
- package/dist/systems/debugs/InputDebug.js +11 -0
- package/dist/systems/debugs/LoopDebug.d.ts +7 -0
- package/dist/systems/debugs/LoopDebug.d.ts.map +1 -0
- package/dist/systems/debugs/LoopDebug.js +11 -0
- package/package.json +79 -0
package/dist/Canvas.js
ADDED
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
import { v4 as uuidv4 } from "uuid";
|
|
3
|
+
import Input from "./systems/Input";
|
|
4
|
+
export default class Canvas {
|
|
5
|
+
static id = uuidv4();
|
|
6
|
+
static list = /* @__PURE__ */ new Map();
|
|
7
|
+
static resizeObserver = null;
|
|
8
|
+
/**
|
|
9
|
+
* Creates a new canvas configuration
|
|
10
|
+
* @param options Canvas configuration options
|
|
11
|
+
* @returns The created canvas configuration
|
|
12
|
+
* @throws Error if a canvas with the same ID already exists
|
|
13
|
+
*/
|
|
14
|
+
static create(options) {
|
|
15
|
+
const element = options.element || document.createElement("canvas");
|
|
16
|
+
const ctx = element.getContext("2d", { alpha: true });
|
|
17
|
+
if (!ctx) {
|
|
18
|
+
throw new Error("Failed to get 2D context from canvas element");
|
|
19
|
+
}
|
|
20
|
+
const canvas = {
|
|
21
|
+
element,
|
|
22
|
+
ctx,
|
|
23
|
+
dpr: typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1,
|
|
24
|
+
windowResizeCallback: () => {
|
|
25
|
+
Canvas.autoResize(options.id);
|
|
26
|
+
},
|
|
27
|
+
lastWidth: element.width,
|
|
28
|
+
clear: true,
|
|
29
|
+
width: 800,
|
|
30
|
+
height: 800,
|
|
31
|
+
autoHeight: true,
|
|
32
|
+
autoWidth: true,
|
|
33
|
+
lastHeight: element.height,
|
|
34
|
+
clearColor: "rgba(0, 0, 0, 0)",
|
|
35
|
+
isVisible: true,
|
|
36
|
+
...options
|
|
37
|
+
};
|
|
38
|
+
if (Canvas.list.has(options.id)) {
|
|
39
|
+
throw Error(`Already exists a canvas with the id ${options.id}`);
|
|
40
|
+
}
|
|
41
|
+
Canvas.list.set(options.id, canvas);
|
|
42
|
+
Canvas.autoResize(options.id);
|
|
43
|
+
canvas.element.setAttribute("id", options.id);
|
|
44
|
+
canvas.lastWidth = canvas.width;
|
|
45
|
+
canvas.lastHeight = canvas.height;
|
|
46
|
+
if (canvas.autoWidth === true || canvas.autoHeight === true) {
|
|
47
|
+
if (typeof window !== "undefined") {
|
|
48
|
+
window.addEventListener("resize", canvas.windowResizeCallback);
|
|
49
|
+
if (typeof ResizeObserver !== "undefined" && !Canvas.resizeObserver) {
|
|
50
|
+
Canvas.resizeObserver = new ResizeObserver((entries) => {
|
|
51
|
+
for (const entry of entries) {
|
|
52
|
+
const canvasElement = entry.target;
|
|
53
|
+
const canvasId = canvasElement.getAttribute("id");
|
|
54
|
+
if (canvasId) {
|
|
55
|
+
Canvas.autoResize(canvasId);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
if (Canvas.resizeObserver) {
|
|
61
|
+
Canvas.resizeObserver.observe(canvas.element);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
canvas.element.addEventListener("webglcontextlost", (event) => {
|
|
66
|
+
event.preventDefault();
|
|
67
|
+
if (canvas.contextLostCallback) {
|
|
68
|
+
canvas.contextLostCallback(event);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
canvas.element.addEventListener("webglcontextrestored", (event) => {
|
|
72
|
+
if (canvas.contextRestoredCallback) {
|
|
73
|
+
canvas.contextRestoredCallback(event);
|
|
74
|
+
}
|
|
75
|
+
Canvas.fixDPI(options.id);
|
|
76
|
+
});
|
|
77
|
+
Input.setupCanvasEventListeners(options.id);
|
|
78
|
+
return canvas;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Retrieves a canvas configuration by its ID
|
|
82
|
+
* @param id The canvas ID
|
|
83
|
+
* @returns The canvas configuration or undefined if not found
|
|
84
|
+
*/
|
|
85
|
+
static get(id) {
|
|
86
|
+
return Canvas.list.get(id);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Updates canvas properties
|
|
90
|
+
* @param id The canvas ID
|
|
91
|
+
* @param options Options to update
|
|
92
|
+
*/
|
|
93
|
+
static set(id, options) {
|
|
94
|
+
const canvas = Canvas.get(id);
|
|
95
|
+
if (canvas) {
|
|
96
|
+
Canvas.list.set(id, { ...canvas, ...options });
|
|
97
|
+
if (options.width !== void 0 || options.height !== void 0) {
|
|
98
|
+
Canvas.fixDPI(id);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Sets the clear color for a canvas
|
|
104
|
+
* @param id The canvas ID
|
|
105
|
+
* @param clearColor The color to set
|
|
106
|
+
*/
|
|
107
|
+
static setClearColor(id, clearColor) {
|
|
108
|
+
const canvas = Canvas.get(id);
|
|
109
|
+
if (canvas) {
|
|
110
|
+
canvas.clearColor = clearColor;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Executes a callback for each canvas
|
|
115
|
+
* @param callback Function to execute for each canvas
|
|
116
|
+
*/
|
|
117
|
+
static forEach(callback) {
|
|
118
|
+
Canvas.list.forEach((canvas) => {
|
|
119
|
+
callback(canvas);
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Automatically resizes a canvas based on its parent element or window
|
|
124
|
+
* @param id The canvas ID
|
|
125
|
+
*/
|
|
126
|
+
static autoResize(id) {
|
|
127
|
+
const canvas = Canvas.get(id);
|
|
128
|
+
if (!canvas) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const rootNode = canvas.element.getRootNode();
|
|
132
|
+
const shadowHost = rootNode instanceof ShadowRoot ? rootNode.host : null;
|
|
133
|
+
if (!canvas.element.parentElement && !shadowHost) {
|
|
134
|
+
if (canvas.autoWidth) {
|
|
135
|
+
canvas.width = typeof window !== "undefined" ? window.innerWidth : canvas.width;
|
|
136
|
+
}
|
|
137
|
+
if (canvas.autoHeight) {
|
|
138
|
+
canvas.height = typeof window !== "undefined" ? window.innerHeight : canvas.height;
|
|
139
|
+
}
|
|
140
|
+
Canvas.fixDPI(id);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
const parent = canvas.element.parentElement || shadowHost;
|
|
144
|
+
if (parent) {
|
|
145
|
+
const width = parent.offsetWidth;
|
|
146
|
+
const height = parent.offsetHeight;
|
|
147
|
+
let needsUpdate = false;
|
|
148
|
+
if (canvas.autoWidth && canvas.width !== width) {
|
|
149
|
+
canvas.width = width;
|
|
150
|
+
needsUpdate = true;
|
|
151
|
+
}
|
|
152
|
+
if (canvas.autoHeight && canvas.height !== height) {
|
|
153
|
+
canvas.height = height;
|
|
154
|
+
needsUpdate = true;
|
|
155
|
+
}
|
|
156
|
+
if (needsUpdate) {
|
|
157
|
+
Canvas.fixDPI(id);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Appends a canvas to a container
|
|
163
|
+
* @param id The canvas ID
|
|
164
|
+
* @param containerId The container ID or element
|
|
165
|
+
*/
|
|
166
|
+
static append(id, containerId) {
|
|
167
|
+
const canvas = Canvas.get(id);
|
|
168
|
+
if (!canvas) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (typeof containerId === "string") {
|
|
172
|
+
const container = document.getElementById(containerId);
|
|
173
|
+
if (container) {
|
|
174
|
+
container.appendChild(canvas.element);
|
|
175
|
+
} else {
|
|
176
|
+
console.warn(`Container with ID ${containerId} not found`);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
} else {
|
|
180
|
+
containerId.appendChild(canvas.element);
|
|
181
|
+
}
|
|
182
|
+
if (canvas.autoWidth === true || canvas.autoHeight === true) {
|
|
183
|
+
Canvas.autoResize(id);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Fixes the device pixel ratio for a canvas
|
|
188
|
+
* @param id The canvas ID
|
|
189
|
+
*/
|
|
190
|
+
static fixDPI(id) {
|
|
191
|
+
const canvas = Canvas.get(id);
|
|
192
|
+
if (!canvas) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
canvas.dpr = typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1;
|
|
196
|
+
canvas.element.style.width = `${canvas.width}px`;
|
|
197
|
+
canvas.element.style.height = `${canvas.height}px`;
|
|
198
|
+
canvas.element.setAttribute(
|
|
199
|
+
"width",
|
|
200
|
+
(canvas.width * canvas.dpr).toString()
|
|
201
|
+
);
|
|
202
|
+
canvas.element.setAttribute(
|
|
203
|
+
"height",
|
|
204
|
+
(canvas.height * canvas.dpr).toString()
|
|
205
|
+
);
|
|
206
|
+
canvas.ctx.scale(canvas.dpr, canvas.dpr);
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Handles canvas resize events
|
|
210
|
+
* @param id The canvas ID
|
|
211
|
+
*/
|
|
212
|
+
static onResize(id) {
|
|
213
|
+
const canvas = Canvas.get(id);
|
|
214
|
+
if (!canvas) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
if (canvas.lastWidth !== canvas.width || canvas.lastHeight !== canvas.height) {
|
|
218
|
+
canvas.lastWidth = canvas.width;
|
|
219
|
+
canvas.lastHeight = canvas.height;
|
|
220
|
+
if (canvas.onResize) {
|
|
221
|
+
canvas.onResize();
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Destroys a canvas
|
|
227
|
+
* @param id The canvas ID
|
|
228
|
+
*/
|
|
229
|
+
static destroy(id) {
|
|
230
|
+
const canvas = Canvas.get(id);
|
|
231
|
+
if (!canvas) {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
if (canvas.autoWidth === true || canvas.autoHeight === true) {
|
|
235
|
+
if (typeof window !== "undefined") {
|
|
236
|
+
window.removeEventListener("resize", canvas.windowResizeCallback);
|
|
237
|
+
if (Canvas.resizeObserver) {
|
|
238
|
+
Canvas.resizeObserver.unobserve(canvas.element);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
if (canvas.element.parentElement) {
|
|
243
|
+
canvas.element.parentElement.removeChild(canvas.element);
|
|
244
|
+
}
|
|
245
|
+
Canvas.list.delete(id);
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Clears all canvases
|
|
249
|
+
*/
|
|
250
|
+
static clear() {
|
|
251
|
+
Canvas.list.forEach((canvas) => {
|
|
252
|
+
if (canvas.clear) {
|
|
253
|
+
canvas.ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
254
|
+
}
|
|
255
|
+
if (canvas.clearColor) {
|
|
256
|
+
canvas.ctx.fillStyle = canvas.clearColor;
|
|
257
|
+
canvas.ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Destroys all canvases
|
|
263
|
+
*/
|
|
264
|
+
static destroyAll() {
|
|
265
|
+
Canvas.list.forEach((canvas) => {
|
|
266
|
+
Canvas.destroy(canvas.id);
|
|
267
|
+
});
|
|
268
|
+
Canvas.list.clear();
|
|
269
|
+
if (Canvas.resizeObserver) {
|
|
270
|
+
Canvas.resizeObserver.disconnect();
|
|
271
|
+
Canvas.resizeObserver = null;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Checks if a canvas is visible in the viewport
|
|
276
|
+
* @param id The canvas ID
|
|
277
|
+
* @returns True if the canvas is visible
|
|
278
|
+
*/
|
|
279
|
+
static isVisible(id) {
|
|
280
|
+
const canvas = Canvas.get(id);
|
|
281
|
+
if (!canvas) {
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
const rect = canvas.element.getBoundingClientRect();
|
|
285
|
+
return rect.top < window.innerHeight && rect.bottom > 0 && rect.left < window.innerWidth && rect.right > 0;
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Updates visibility status for all canvases
|
|
289
|
+
*/
|
|
290
|
+
static updateVisibility() {
|
|
291
|
+
Canvas.list.forEach((canvas, id) => {
|
|
292
|
+
canvas.isVisible = Canvas.isVisible(id);
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Resizes a canvas to specific dimensions
|
|
297
|
+
* @param id The canvas ID
|
|
298
|
+
* @param width New width
|
|
299
|
+
* @param height New height
|
|
300
|
+
*/
|
|
301
|
+
static resize(id, width, height) {
|
|
302
|
+
const canvas = Canvas.get(id);
|
|
303
|
+
if (!canvas) {
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
canvas.width = width;
|
|
307
|
+
canvas.height = height;
|
|
308
|
+
Canvas.fixDPI(id);
|
|
309
|
+
Canvas.onResize(id);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Canvas.test.d.ts","sourceRoot":"","sources":["../src/Canvas.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
import {
|
|
3
|
+
describe,
|
|
4
|
+
it,
|
|
5
|
+
expect,
|
|
6
|
+
beforeEach,
|
|
7
|
+
afterEach,
|
|
8
|
+
beforeAll,
|
|
9
|
+
afterAll,
|
|
10
|
+
vi
|
|
11
|
+
} from "vitest";
|
|
12
|
+
import Canvas from "./Canvas";
|
|
13
|
+
describe("Canvas", () => {
|
|
14
|
+
let canvasConfig;
|
|
15
|
+
const originalOffsetHeight = Object.getOwnPropertyDescriptor(
|
|
16
|
+
HTMLElement.prototype,
|
|
17
|
+
"offsetHeight"
|
|
18
|
+
);
|
|
19
|
+
const originalOffsetWidth = Object.getOwnPropertyDescriptor(
|
|
20
|
+
HTMLElement.prototype,
|
|
21
|
+
"offsetWidth"
|
|
22
|
+
);
|
|
23
|
+
beforeAll(() => {
|
|
24
|
+
Object.defineProperty(HTMLElement.prototype, "offsetHeight", {
|
|
25
|
+
configurable: true,
|
|
26
|
+
value: 800
|
|
27
|
+
});
|
|
28
|
+
Object.defineProperty(HTMLElement.prototype, "offsetWidth", {
|
|
29
|
+
configurable: true,
|
|
30
|
+
value: 600
|
|
31
|
+
});
|
|
32
|
+
Element.prototype.getBoundingClientRect = vi.fn(() => ({
|
|
33
|
+
top: 10,
|
|
34
|
+
right: 110,
|
|
35
|
+
bottom: 110,
|
|
36
|
+
left: 10,
|
|
37
|
+
width: 100,
|
|
38
|
+
height: 100,
|
|
39
|
+
x: 10,
|
|
40
|
+
y: 10,
|
|
41
|
+
toJSON: vi.fn()
|
|
42
|
+
}));
|
|
43
|
+
global.ResizeObserver = vi.fn().mockImplementation(() => ({
|
|
44
|
+
observe: vi.fn(),
|
|
45
|
+
unobserve: vi.fn(),
|
|
46
|
+
disconnect: vi.fn()
|
|
47
|
+
}));
|
|
48
|
+
});
|
|
49
|
+
beforeEach(() => {
|
|
50
|
+
vi.stubGlobal("innerWidth", 1024);
|
|
51
|
+
vi.stubGlobal("innerHeight", 768);
|
|
52
|
+
});
|
|
53
|
+
afterEach(() => {
|
|
54
|
+
vi.unstubAllGlobals();
|
|
55
|
+
});
|
|
56
|
+
afterAll(() => {
|
|
57
|
+
if (originalOffsetHeight) {
|
|
58
|
+
Object.defineProperty(
|
|
59
|
+
HTMLElement.prototype,
|
|
60
|
+
"offsetHeight",
|
|
61
|
+
originalOffsetHeight
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
if (originalOffsetWidth) {
|
|
65
|
+
Object.defineProperty(
|
|
66
|
+
HTMLElement.prototype,
|
|
67
|
+
"offsetWidth",
|
|
68
|
+
originalOffsetWidth
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
beforeEach(() => {
|
|
73
|
+
canvasConfig = {
|
|
74
|
+
id: "testCanvas",
|
|
75
|
+
width: 500,
|
|
76
|
+
height: 500,
|
|
77
|
+
autoWidth: false,
|
|
78
|
+
autoHeight: false,
|
|
79
|
+
clear: true,
|
|
80
|
+
clearColor: "#ffffff"
|
|
81
|
+
};
|
|
82
|
+
});
|
|
83
|
+
afterEach(() => {
|
|
84
|
+
Canvas.destroyAll();
|
|
85
|
+
});
|
|
86
|
+
it("should create a new canvas", () => {
|
|
87
|
+
const canvas = Canvas.create(canvasConfig);
|
|
88
|
+
expect(canvas).toBeDefined();
|
|
89
|
+
expect(Canvas.list.has(canvasConfig.id)).toBe(true);
|
|
90
|
+
});
|
|
91
|
+
it("should get an existing canvas by id", () => {
|
|
92
|
+
Canvas.create(canvasConfig);
|
|
93
|
+
const canvas = Canvas.get(canvasConfig.id);
|
|
94
|
+
expect(canvas).toBeDefined();
|
|
95
|
+
expect(canvas?.id).toBe(canvasConfig.id);
|
|
96
|
+
});
|
|
97
|
+
it("should set properties of an existing canvas", () => {
|
|
98
|
+
Canvas.create(canvasConfig);
|
|
99
|
+
Canvas.set(canvasConfig.id, { width: 600 });
|
|
100
|
+
const canvas = Canvas.get(canvasConfig.id);
|
|
101
|
+
expect(canvas?.width).toBe(600);
|
|
102
|
+
});
|
|
103
|
+
it("should set the clear color of an existing canvas", () => {
|
|
104
|
+
Canvas.create(canvasConfig);
|
|
105
|
+
Canvas.setClearColor(canvasConfig.id, "#000000");
|
|
106
|
+
const canvas = Canvas.get(canvasConfig.id);
|
|
107
|
+
expect(canvas?.clearColor).toBe("#000000");
|
|
108
|
+
});
|
|
109
|
+
it("should call the callback for each canvas", () => {
|
|
110
|
+
Canvas.create(canvasConfig);
|
|
111
|
+
const callback = vi.fn();
|
|
112
|
+
Canvas.forEach(callback);
|
|
113
|
+
expect(callback).toHaveBeenCalled();
|
|
114
|
+
});
|
|
115
|
+
it("should auto resize the canvas", () => {
|
|
116
|
+
canvasConfig.autoWidth = true;
|
|
117
|
+
canvasConfig.autoHeight = true;
|
|
118
|
+
Canvas.create(canvasConfig);
|
|
119
|
+
const canvas = Canvas.get(canvasConfig.id);
|
|
120
|
+
const parentElement = document.createElement("div");
|
|
121
|
+
document.body.appendChild(parentElement);
|
|
122
|
+
if (canvas?.element) {
|
|
123
|
+
parentElement.appendChild(canvas.element);
|
|
124
|
+
}
|
|
125
|
+
Canvas.autoResize(canvasConfig.id);
|
|
126
|
+
expect(canvas?.width).toBe(600);
|
|
127
|
+
expect(canvas?.height).toBe(800);
|
|
128
|
+
});
|
|
129
|
+
it("should append the canvas to a container", () => {
|
|
130
|
+
const container = document.createElement("div");
|
|
131
|
+
container.id = "container";
|
|
132
|
+
document.body.appendChild(container);
|
|
133
|
+
Canvas.create(canvasConfig);
|
|
134
|
+
Canvas.append(canvasConfig.id, "container");
|
|
135
|
+
const canvas = Canvas.get(canvasConfig.id);
|
|
136
|
+
expect(container.contains(canvas.element)).toBe(true);
|
|
137
|
+
});
|
|
138
|
+
it("should log warning when appending to non-existent container", () => {
|
|
139
|
+
const consoleSpy = vi.spyOn(console, "warn");
|
|
140
|
+
Canvas.create(canvasConfig);
|
|
141
|
+
Canvas.append(canvasConfig.id, "nonExistentContainer");
|
|
142
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
143
|
+
"Container with ID nonExistentContainer not found"
|
|
144
|
+
);
|
|
145
|
+
});
|
|
146
|
+
it("should fix the DPI of the canvas", () => {
|
|
147
|
+
Canvas.create(canvasConfig);
|
|
148
|
+
const canvas = Canvas.get(canvasConfig.id);
|
|
149
|
+
Canvas.fixDPI(canvasConfig.id);
|
|
150
|
+
if (canvas) {
|
|
151
|
+
expect(canvas.element.width).toBe(canvas.width * canvas.dpr);
|
|
152
|
+
}
|
|
153
|
+
if (canvas?.dpr !== void 0) {
|
|
154
|
+
expect(canvas.element.height).toBe(canvas.height * canvas.dpr);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
it("should call onResize if dimensions change", () => {
|
|
158
|
+
const onResize = vi.fn();
|
|
159
|
+
canvasConfig.onResize = onResize;
|
|
160
|
+
Canvas.create(canvasConfig);
|
|
161
|
+
const canvas = Canvas.get(canvasConfig.id);
|
|
162
|
+
canvas.width = 600;
|
|
163
|
+
canvas.height = 600;
|
|
164
|
+
Canvas.onResize(canvasConfig.id);
|
|
165
|
+
expect(onResize).toHaveBeenCalled();
|
|
166
|
+
});
|
|
167
|
+
it("should destroy a canvas", () => {
|
|
168
|
+
Canvas.create(canvasConfig);
|
|
169
|
+
Canvas.destroy(canvasConfig.id);
|
|
170
|
+
expect(Canvas.list.has(canvasConfig.id)).toBe(false);
|
|
171
|
+
});
|
|
172
|
+
it("should clear all canvases", () => {
|
|
173
|
+
Canvas.create(canvasConfig);
|
|
174
|
+
const canvas = Canvas.get(canvasConfig.id);
|
|
175
|
+
Canvas.clear();
|
|
176
|
+
expect(canvas?.ctx.fillStyle).toBe(canvas?.clearColor);
|
|
177
|
+
});
|
|
178
|
+
it("should destroy all canvases", () => {
|
|
179
|
+
Canvas.create(canvasConfig);
|
|
180
|
+
Canvas.destroyAll();
|
|
181
|
+
expect(Canvas.list.size).toBe(0);
|
|
182
|
+
});
|
|
183
|
+
it("should not create a canvas with duplicate id", () => {
|
|
184
|
+
Canvas.create(canvasConfig);
|
|
185
|
+
expect(() => Canvas.create(canvasConfig)).toThrow(
|
|
186
|
+
`Already exists a canvas with the id ${canvasConfig.id}`
|
|
187
|
+
);
|
|
188
|
+
});
|
|
189
|
+
it("should not resize if autoWidth and autoHeight are false", () => {
|
|
190
|
+
canvasConfig.autoWidth = false;
|
|
191
|
+
canvasConfig.autoHeight = false;
|
|
192
|
+
Canvas.create(canvasConfig);
|
|
193
|
+
const canvas = Canvas.get(canvasConfig.id);
|
|
194
|
+
const initialWidth = canvas?.width;
|
|
195
|
+
const initialHeight = canvas?.height;
|
|
196
|
+
Canvas.autoResize(canvasConfig.id);
|
|
197
|
+
expect(canvas?.width).toBe(initialWidth);
|
|
198
|
+
expect(canvas?.height).toBe(initialHeight);
|
|
199
|
+
});
|
|
200
|
+
it("should not call onResize if dimensions do not change", () => {
|
|
201
|
+
const onResize = vi.fn();
|
|
202
|
+
canvasConfig.onResize = onResize;
|
|
203
|
+
Canvas.create(canvasConfig);
|
|
204
|
+
Canvas.onResize(canvasConfig.id);
|
|
205
|
+
expect(onResize).not.toHaveBeenCalled();
|
|
206
|
+
});
|
|
207
|
+
it("should remove canvas from DOM on destroy", () => {
|
|
208
|
+
Canvas.create(canvasConfig);
|
|
209
|
+
const canvas = Canvas.get(canvasConfig.id);
|
|
210
|
+
document.body.appendChild(canvas.element);
|
|
211
|
+
Canvas.destroy(canvasConfig.id);
|
|
212
|
+
expect(document.body.contains(canvas.element)).toBe(false);
|
|
213
|
+
});
|
|
214
|
+
it("should clear canvas with specified clear color", () => {
|
|
215
|
+
canvasConfig.clearColor = "#ff0000";
|
|
216
|
+
Canvas.create(canvasConfig);
|
|
217
|
+
const canvas = Canvas.get(canvasConfig.id);
|
|
218
|
+
Canvas.clear();
|
|
219
|
+
expect(canvas?.ctx.fillStyle).toBe(canvas?.clearColor);
|
|
220
|
+
});
|
|
221
|
+
it("should not clear canvas if clear is false", () => {
|
|
222
|
+
canvasConfig.clear = false;
|
|
223
|
+
Canvas.create(canvasConfig);
|
|
224
|
+
const canvas = Canvas.get(canvasConfig.id);
|
|
225
|
+
const clearRectSpy = vi.spyOn(canvas.ctx, "clearRect");
|
|
226
|
+
Canvas.clear();
|
|
227
|
+
expect(clearRectSpy).not.toHaveBeenCalled();
|
|
228
|
+
});
|
|
229
|
+
it("should handle window resize event", () => {
|
|
230
|
+
canvasConfig.autoWidth = true;
|
|
231
|
+
canvasConfig.autoHeight = true;
|
|
232
|
+
Canvas.create(canvasConfig);
|
|
233
|
+
const canvas = Canvas.get(canvasConfig.id);
|
|
234
|
+
window.dispatchEvent(new Event("resize"));
|
|
235
|
+
expect(canvas?.width).toBe(1024);
|
|
236
|
+
expect(canvas?.height).toBe(768);
|
|
237
|
+
});
|
|
238
|
+
it("should not throw error if trying to destroy non-existent canvas", () => {
|
|
239
|
+
expect(() => Canvas.destroy("nonExistentCanvas")).not.toThrow();
|
|
240
|
+
});
|
|
241
|
+
it("should not throw error if trying to get non-existent canvas", () => {
|
|
242
|
+
expect(Canvas.get("nonExistentCanvas")).toBeUndefined();
|
|
243
|
+
});
|
|
244
|
+
it("should not throw error if trying to set properties on non-existent canvas", () => {
|
|
245
|
+
expect(() => Canvas.set("nonExistentCanvas", { width: 600 })).not.toThrow();
|
|
246
|
+
});
|
|
247
|
+
it("should not throw error if trying to set clear color on non-existent canvas", () => {
|
|
248
|
+
expect(
|
|
249
|
+
() => Canvas.setClearColor("nonExistentCanvas", "#000000")
|
|
250
|
+
).not.toThrow();
|
|
251
|
+
});
|
|
252
|
+
it("should not throw error if trying to append non-existent canvas", () => {
|
|
253
|
+
expect(() => Canvas.append("nonExistentCanvas", "container")).not.toThrow();
|
|
254
|
+
});
|
|
255
|
+
it("should not throw error if trying to fix DPI on non-existent canvas", () => {
|
|
256
|
+
expect(() => Canvas.fixDPI("nonExistentCanvas")).not.toThrow();
|
|
257
|
+
});
|
|
258
|
+
it("should not throw error if trying to call onResize on non-existent canvas", () => {
|
|
259
|
+
expect(() => Canvas.onResize("nonExistentCanvas")).not.toThrow();
|
|
260
|
+
});
|
|
261
|
+
it("should check if canvas is visible in viewport", () => {
|
|
262
|
+
Canvas.create(canvasConfig);
|
|
263
|
+
expect(Canvas.isVisible(canvasConfig.id)).toBe(true);
|
|
264
|
+
});
|
|
265
|
+
it("should update visibility status for all canvases", () => {
|
|
266
|
+
Canvas.create(canvasConfig);
|
|
267
|
+
Canvas.updateVisibility();
|
|
268
|
+
const canvas = Canvas.get(canvasConfig.id);
|
|
269
|
+
expect(canvas?.isVisible).toBe(true);
|
|
270
|
+
});
|
|
271
|
+
it("should resize canvas to specific dimensions", () => {
|
|
272
|
+
Canvas.create(canvasConfig);
|
|
273
|
+
Canvas.resize(canvasConfig.id, 800, 600);
|
|
274
|
+
const canvas = Canvas.get(canvasConfig.id);
|
|
275
|
+
expect(canvas?.width).toBe(800);
|
|
276
|
+
expect(canvas?.height).toBe(600);
|
|
277
|
+
});
|
|
278
|
+
it("should handle context loss and restoration", () => {
|
|
279
|
+
const contextLostCallback = vi.fn();
|
|
280
|
+
const contextRestoredCallback = vi.fn();
|
|
281
|
+
canvasConfig.contextLostCallback = contextLostCallback;
|
|
282
|
+
canvasConfig.contextRestoredCallback = contextRestoredCallback;
|
|
283
|
+
Canvas.create(canvasConfig);
|
|
284
|
+
const canvas = Canvas.get(canvasConfig.id);
|
|
285
|
+
canvas?.element.dispatchEvent(new Event("webglcontextlost"));
|
|
286
|
+
expect(contextLostCallback).toHaveBeenCalled();
|
|
287
|
+
canvas?.element.dispatchEvent(new Event("webglcontextrestored"));
|
|
288
|
+
expect(contextRestoredCallback).toHaveBeenCalled();
|
|
289
|
+
});
|
|
290
|
+
it("should throw an error if context cannot be created", () => {
|
|
291
|
+
const originalGetContext = HTMLCanvasElement.prototype.getContext;
|
|
292
|
+
HTMLCanvasElement.prototype.getContext = vi.fn().mockReturnValue(null);
|
|
293
|
+
expect(() => Canvas.create(canvasConfig)).toThrow(
|
|
294
|
+
"Failed to get 2D context from canvas element"
|
|
295
|
+
);
|
|
296
|
+
HTMLCanvasElement.prototype.getContext = originalGetContext;
|
|
297
|
+
});
|
|
298
|
+
it("should setup ResizeObserver if available", () => {
|
|
299
|
+
canvasConfig.autoWidth = true;
|
|
300
|
+
Canvas.create(canvasConfig);
|
|
301
|
+
expect(global.ResizeObserver).toHaveBeenCalled();
|
|
302
|
+
});
|
|
303
|
+
it("should update canvas when set() is called with width or height", () => {
|
|
304
|
+
Canvas.create(canvasConfig);
|
|
305
|
+
const fixDPISpy = vi.spyOn(Canvas, "fixDPI");
|
|
306
|
+
Canvas.set(canvasConfig.id, { width: 700 });
|
|
307
|
+
const canvas = Canvas.get(canvasConfig.id);
|
|
308
|
+
expect(fixDPISpy).toHaveBeenCalledWith(canvasConfig.id);
|
|
309
|
+
expect(canvas?.width).toBe(700);
|
|
310
|
+
});
|
|
311
|
+
});
|
package/dist/Common.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare function clamp(n: number, min: number, max: number): number;
|
|
2
|
+
export declare function distance(ax: number, ay: number, bx: number, by: number): number;
|
|
3
|
+
export declare function pair(x: number, y: number): number;
|
|
4
|
+
export declare function unpairX(z: number): number;
|
|
5
|
+
export declare function unpairY(z: number): number;
|
|
6
|
+
export declare function degreesToRadians(degrees: number): number;
|
|
7
|
+
export declare function radiansToDegrees(radians: number): number;
|
|
8
|
+
export declare function angle(ax: number, ay: number, bx: number, by: number): number;
|
|
9
|
+
export declare function random(min?: number, max?: number, skew?: number): number;
|
|
10
|
+
export declare function randomInt(min: number, max: number): number;
|
|
11
|
+
//# sourceMappingURL=Common.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Common.d.ts","sourceRoot":"","sources":["../src/Common.ts"],"names":[],"mappings":"AAAA,wBAAgB,KAAK,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAEjE;AAED,wBAAgB,QAAQ,CACtB,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,GACT,MAAM,CAER;AAGD,wBAAgB,IAAI,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAIjD;AAED,wBAAgB,OAAO,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAKzC;AAED,wBAAgB,OAAO,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAKzC;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAExD;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAExD;AAED,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAI5E;AAGD,wBAAgB,MAAM,CACpB,GAAG,GAAE,MAAU,EACf,GAAG,GAAE,MAAU,EACf,IAAI,GAAE,MAAU,GACf,MAAM,CA2BR;AAED,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAE1D"}
|
package/dist/Common.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
export function clamp(n, min, max) {
|
|
3
|
+
return Math.min(Math.max(n, min), max);
|
|
4
|
+
}
|
|
5
|
+
export function distance(ax, ay, bx, by) {
|
|
6
|
+
return Math.hypot(bx - ax, by - ay);
|
|
7
|
+
}
|
|
8
|
+
export function pair(x, y) {
|
|
9
|
+
const xx = x >= 0 ? x * 2 : x * -2 - 1;
|
|
10
|
+
const yy = y >= 0 ? y * 2 : y * -2 - 1;
|
|
11
|
+
return xx >= yy ? xx * xx + xx + yy : yy * yy + xx;
|
|
12
|
+
}
|
|
13
|
+
export function unpairX(z) {
|
|
14
|
+
const sqrtz = Math.sqrt(z) >> 0;
|
|
15
|
+
const sqz = sqrtz * sqrtz;
|
|
16
|
+
const result1 = z - sqz >= sqrtz ? sqrtz : z - sqz;
|
|
17
|
+
return result1 % 2 === 0 ? result1 / 2 : (result1 + 1) / -2;
|
|
18
|
+
}
|
|
19
|
+
export function unpairY(z) {
|
|
20
|
+
const sqrtz = Math.sqrt(z) >> 0;
|
|
21
|
+
const sqz = sqrtz * sqrtz;
|
|
22
|
+
const result1 = z - sqz >= sqrtz ? z - sqz - sqrtz : sqrtz;
|
|
23
|
+
return result1 % 2 === 0 ? result1 / 2 : (result1 + 1) / -2;
|
|
24
|
+
}
|
|
25
|
+
export function degreesToRadians(degrees) {
|
|
26
|
+
return degrees * Math.PI / 180;
|
|
27
|
+
}
|
|
28
|
+
export function radiansToDegrees(radians) {
|
|
29
|
+
return radians * 180 / Math.PI;
|
|
30
|
+
}
|
|
31
|
+
export function angle(ax, ay, bx, by) {
|
|
32
|
+
const cy = by - ay;
|
|
33
|
+
const cx = bx - ax;
|
|
34
|
+
return Math.atan2(cy, cx);
|
|
35
|
+
}
|
|
36
|
+
export function random(min = 0, max = 1, skew = 1) {
|
|
37
|
+
if (min >= max) {
|
|
38
|
+
throw new Error("min must be less than max");
|
|
39
|
+
}
|
|
40
|
+
if (skew <= 0) {
|
|
41
|
+
throw new Error("skew must be greater than 0");
|
|
42
|
+
}
|
|
43
|
+
let u = 0;
|
|
44
|
+
let v = 0;
|
|
45
|
+
while (u === 0) {
|
|
46
|
+
u = crypto.getRandomValues(new Uint32Array(1))[0] / 4294967295;
|
|
47
|
+
}
|
|
48
|
+
while (v === 0) {
|
|
49
|
+
v = crypto.getRandomValues(new Uint32Array(1))[0] / 4294967295;
|
|
50
|
+
}
|
|
51
|
+
let num = Math.sqrt(-2 * Math.log(u)) * Math.cos(2 * Math.PI * v);
|
|
52
|
+
num = num / 10 + 0.5;
|
|
53
|
+
if (num > 1 || num < 0) {
|
|
54
|
+
return random(min, max, skew);
|
|
55
|
+
} else {
|
|
56
|
+
num = Math.pow(num, skew);
|
|
57
|
+
num *= max - min;
|
|
58
|
+
num += min;
|
|
59
|
+
}
|
|
60
|
+
return num;
|
|
61
|
+
}
|
|
62
|
+
export function randomInt(min, max) {
|
|
63
|
+
return Math.floor(random(min, max));
|
|
64
|
+
}
|