@woven-canvas/core 0.1.0
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/README.md +34 -0
- package/build/_tsup-dts-rollup.d.cts +1509 -0
- package/build/_tsup-dts-rollup.d.ts +1509 -0
- package/build/index.cjs +4109 -0
- package/build/index.cjs.map +1 -0
- package/build/index.d.cts +133 -0
- package/build/index.d.ts +133 -0
- package/build/index.js +4036 -0
- package/build/index.js.map +1 -0
- package/build/tsconfig.tsbuildinfo +1 -0
- package/package.json +58 -0
package/build/index.cjs
ADDED
|
@@ -0,0 +1,4109 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
Aabb: () => Aabb,
|
|
24
|
+
Asset: () => Asset,
|
|
25
|
+
Block: () => Block,
|
|
26
|
+
BlockDef: () => BlockDef2,
|
|
27
|
+
Camera: () => Camera,
|
|
28
|
+
CanvasComponentDef: () => import_canvas_store34.CanvasComponentDef,
|
|
29
|
+
CanvasSingletonDef: () => import_canvas_store34.CanvasSingletonDef,
|
|
30
|
+
Color: () => Color,
|
|
31
|
+
CommandMarker: () => CommandMarker,
|
|
32
|
+
ComponentDef: () => import_core49.ComponentDef,
|
|
33
|
+
Connector: () => Connector,
|
|
34
|
+
Controls: () => Controls,
|
|
35
|
+
ControlsOptions: () => ControlsOptions,
|
|
36
|
+
CorePlugin: () => CorePlugin,
|
|
37
|
+
Cursor: () => Cursor,
|
|
38
|
+
CursorDef: () => CursorDef,
|
|
39
|
+
Edited: () => Edited,
|
|
40
|
+
Editor: () => Editor,
|
|
41
|
+
EditorStateDef: () => EditorStateDef,
|
|
42
|
+
EventType: () => import_core49.EventType,
|
|
43
|
+
FontFamily: () => FontFamily,
|
|
44
|
+
FontLoader: () => FontLoader,
|
|
45
|
+
Frame: () => Frame,
|
|
46
|
+
Grid: () => Grid,
|
|
47
|
+
GridOptions: () => GridOptions,
|
|
48
|
+
Held: () => Held,
|
|
49
|
+
HitGeometry: () => HitGeometry,
|
|
50
|
+
Hovered: () => Hovered,
|
|
51
|
+
Image: () => Image,
|
|
52
|
+
Intersect: () => Intersect,
|
|
53
|
+
Key: () => Key,
|
|
54
|
+
Keybind: () => Keybind,
|
|
55
|
+
Keyboard: () => Keyboard,
|
|
56
|
+
MAX_HIT_ARCS: () => MAX_HIT_ARCS,
|
|
57
|
+
MAX_HIT_CAPSULES: () => MAX_HIT_CAPSULES,
|
|
58
|
+
MainThreadSystem: () => import_core49.MainThreadSystem,
|
|
59
|
+
Mouse: () => Mouse,
|
|
60
|
+
Opacity: () => Opacity,
|
|
61
|
+
Pointer: () => Pointer,
|
|
62
|
+
PointerButton: () => PointerButton,
|
|
63
|
+
PointerType: () => PointerType,
|
|
64
|
+
RankBounds: () => RankBounds,
|
|
65
|
+
Redo: () => Redo,
|
|
66
|
+
SINGLETON_ENTITY_ID: () => import_core49.SINGLETON_ENTITY_ID,
|
|
67
|
+
STRATUM_ORDER: () => STRATUM_ORDER,
|
|
68
|
+
ScaleWithZoom: () => ScaleWithZoom,
|
|
69
|
+
ScaleWithZoomState: () => ScaleWithZoomState,
|
|
70
|
+
Screen: () => Screen,
|
|
71
|
+
Shape: () => Shape,
|
|
72
|
+
Stratum: () => Stratum,
|
|
73
|
+
StrokeKind: () => StrokeKind,
|
|
74
|
+
Synced: () => import_canvas_store34.Synced,
|
|
75
|
+
Text: () => Text,
|
|
76
|
+
TextAlignment: () => TextAlignment,
|
|
77
|
+
Undo: () => Undo,
|
|
78
|
+
UploadState: () => UploadState,
|
|
79
|
+
User: () => User,
|
|
80
|
+
UserData: () => UserData,
|
|
81
|
+
VerticalAlign: () => VerticalAlign,
|
|
82
|
+
VerticalAlignment: () => VerticalAlignment,
|
|
83
|
+
addComponent: () => import_core49.addComponent,
|
|
84
|
+
canBlockEdit: () => canBlockEdit,
|
|
85
|
+
clearPointerTrackingState: () => clearPointerTrackingState,
|
|
86
|
+
createEntity: () => import_core49.createEntity,
|
|
87
|
+
defineCanvasComponent: () => import_canvas_store34.defineCanvasComponent,
|
|
88
|
+
defineCanvasSingleton: () => import_canvas_store34.defineCanvasSingleton,
|
|
89
|
+
defineCommand: () => defineCommand,
|
|
90
|
+
defineEditorState: () => defineEditorState,
|
|
91
|
+
defineEditorSystem: () => defineEditorSystem,
|
|
92
|
+
defineQuery: () => import_core49.defineQuery,
|
|
93
|
+
defineSystem: () => import_core49.defineSystem,
|
|
94
|
+
field: () => import_core49.field,
|
|
95
|
+
getBackrefs: () => import_core49.getBackrefs,
|
|
96
|
+
getBlockDef: () => getBlockDef,
|
|
97
|
+
getFrameInput: () => getFrameInput,
|
|
98
|
+
getKeyboardInput: () => getKeyboardInput,
|
|
99
|
+
getMouseInput: () => getMouseInput,
|
|
100
|
+
getPluginResources: () => getPluginResources,
|
|
101
|
+
getPointerInput: () => getPointerInput,
|
|
102
|
+
getResources: () => import_core49.getResources,
|
|
103
|
+
getTopmostBlockAtPoint: () => getTopmostBlockAtPoint,
|
|
104
|
+
hasComponent: () => import_core49.hasComponent,
|
|
105
|
+
intersectAabb: () => intersectAabb,
|
|
106
|
+
intersectCapsule: () => intersectCapsule,
|
|
107
|
+
intersectPoint: () => intersectPoint,
|
|
108
|
+
isAlive: () => import_core49.isAlive,
|
|
109
|
+
isHeldByRemote: () => isHeldByRemote,
|
|
110
|
+
on: () => on,
|
|
111
|
+
parsePlugin: () => parsePlugin,
|
|
112
|
+
removeComponent: () => import_core49.removeComponent,
|
|
113
|
+
removeEntity: () => import_core49.removeEntity,
|
|
114
|
+
runMachine: () => runMachine
|
|
115
|
+
});
|
|
116
|
+
module.exports = __toCommonJS(index_exports);
|
|
117
|
+
var import_canvas_store34 = require("@woven-ecs/canvas-store");
|
|
118
|
+
var import_core49 = require("@woven-ecs/core");
|
|
119
|
+
|
|
120
|
+
// src/CorePlugin.ts
|
|
121
|
+
var import_canvas_store31 = require("@woven-ecs/canvas-store");
|
|
122
|
+
var import_core46 = require("@woven-ecs/core");
|
|
123
|
+
|
|
124
|
+
// src/components/index.ts
|
|
125
|
+
var components_exports = {};
|
|
126
|
+
__export(components_exports, {
|
|
127
|
+
Aabb: () => Aabb,
|
|
128
|
+
Asset: () => Asset,
|
|
129
|
+
Block: () => Block,
|
|
130
|
+
Color: () => Color,
|
|
131
|
+
Connector: () => Connector,
|
|
132
|
+
Edited: () => Edited,
|
|
133
|
+
Held: () => Held,
|
|
134
|
+
HitGeometry: () => HitGeometry,
|
|
135
|
+
Hovered: () => Hovered,
|
|
136
|
+
Image: () => Image,
|
|
137
|
+
MAX_HIT_ARCS: () => MAX_HIT_ARCS,
|
|
138
|
+
MAX_HIT_CAPSULES: () => MAX_HIT_CAPSULES,
|
|
139
|
+
Opacity: () => Opacity,
|
|
140
|
+
Pointer: () => Pointer,
|
|
141
|
+
PointerButton: () => PointerButton,
|
|
142
|
+
PointerType: () => PointerType,
|
|
143
|
+
ScaleWithZoom: () => ScaleWithZoom,
|
|
144
|
+
Shape: () => Shape,
|
|
145
|
+
StrokeKind: () => StrokeKind,
|
|
146
|
+
Text: () => Text,
|
|
147
|
+
UploadState: () => UploadState,
|
|
148
|
+
User: () => User,
|
|
149
|
+
VerticalAlign: () => VerticalAlign,
|
|
150
|
+
addPointerSample: () => addPointerSample
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// src/components/Aabb.ts
|
|
154
|
+
var import_math2 = require("@woven-canvas/math");
|
|
155
|
+
var import_canvas_store2 = require("@woven-ecs/canvas-store");
|
|
156
|
+
var import_core2 = require("@woven-ecs/core");
|
|
157
|
+
|
|
158
|
+
// src/components/Block.ts
|
|
159
|
+
var import_math = require("@woven-canvas/math");
|
|
160
|
+
var import_canvas_store = require("@woven-ecs/canvas-store");
|
|
161
|
+
var import_core = require("@woven-ecs/core");
|
|
162
|
+
var _aabbCorners = [
|
|
163
|
+
[0, 0],
|
|
164
|
+
[0, 0],
|
|
165
|
+
[0, 0],
|
|
166
|
+
[0, 0]
|
|
167
|
+
];
|
|
168
|
+
var _blockCorners = [
|
|
169
|
+
[0, 0],
|
|
170
|
+
[0, 0],
|
|
171
|
+
[0, 0],
|
|
172
|
+
[0, 0]
|
|
173
|
+
];
|
|
174
|
+
var _blockAxes = [
|
|
175
|
+
[0, 0],
|
|
176
|
+
[0, 0]
|
|
177
|
+
];
|
|
178
|
+
var BlockSchema = {
|
|
179
|
+
/** Element tag name (e.g., "div", "img") */
|
|
180
|
+
tag: import_core.field.string().max(36).default("div"),
|
|
181
|
+
/** Position as [left, top] */
|
|
182
|
+
position: import_core.field.tuple(import_core.field.float64(), 2).default([0, 0]),
|
|
183
|
+
/** Size as [width, height] */
|
|
184
|
+
size: import_core.field.tuple(import_core.field.float64(), 2).default([100, 100]),
|
|
185
|
+
/** Rotation around center in radians */
|
|
186
|
+
rotateZ: import_core.field.float64().default(0),
|
|
187
|
+
/** Flip state as [flipX, flipY] */
|
|
188
|
+
flip: import_core.field.tuple(import_core.field.boolean(), 2).default([false, false]),
|
|
189
|
+
/** Z-order rank (LexoRank string) */
|
|
190
|
+
rank: import_core.field.string().max(36).default("")
|
|
191
|
+
};
|
|
192
|
+
var BlockDef = class extends import_canvas_store.CanvasComponentDef {
|
|
193
|
+
constructor() {
|
|
194
|
+
super({ name: "block", sync: "document" }, BlockSchema);
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Get the center point of a block.
|
|
198
|
+
* @param out - Optional output vector to write to (avoids allocation)
|
|
199
|
+
*/
|
|
200
|
+
getCenter(ctx, entityId, out) {
|
|
201
|
+
const { position, size } = this.read(ctx, entityId);
|
|
202
|
+
const result = out ?? [0, 0];
|
|
203
|
+
import_math.Rect.getCenter(position, size, result);
|
|
204
|
+
return result;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Set the center point of a block (adjusts position accordingly).
|
|
208
|
+
*/
|
|
209
|
+
setCenter(ctx, entityId, center) {
|
|
210
|
+
const block = this.write(ctx, entityId);
|
|
211
|
+
import_math.Rect.setCenter(block.position, block.size, center);
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Get the four corner points of a block (accounting for rotation).
|
|
215
|
+
* Returns corners in order: top-left, top-right, bottom-right, bottom-left.
|
|
216
|
+
* @param out - Optional output array to write to (avoids allocation)
|
|
217
|
+
*/
|
|
218
|
+
getCorners(ctx, entityId, out) {
|
|
219
|
+
const { position, size, rotateZ } = this.read(ctx, entityId);
|
|
220
|
+
const result = out ?? [
|
|
221
|
+
[0, 0],
|
|
222
|
+
[0, 0],
|
|
223
|
+
[0, 0],
|
|
224
|
+
[0, 0]
|
|
225
|
+
];
|
|
226
|
+
import_math.Rect.getCorners(position, size, rotateZ, result);
|
|
227
|
+
return result;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Check if a point intersects a block (accounting for rotation).
|
|
231
|
+
*/
|
|
232
|
+
containsPoint(ctx, entityId, point) {
|
|
233
|
+
const { position, size, rotateZ } = this.read(ctx, entityId);
|
|
234
|
+
return import_math.Rect.containsPoint(position, size, rotateZ, point);
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Move a block by a delta offset.
|
|
238
|
+
*/
|
|
239
|
+
translate(ctx, entityId, delta) {
|
|
240
|
+
const block = this.write(ctx, entityId);
|
|
241
|
+
import_math.Rect.translate(block.position, delta);
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Set the position of a block.
|
|
245
|
+
*/
|
|
246
|
+
setPosition(ctx, entityId, position) {
|
|
247
|
+
const block = this.write(ctx, entityId);
|
|
248
|
+
import_math.Vec2.copy(block.position, position);
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Set the size of a block.
|
|
252
|
+
*/
|
|
253
|
+
setSize(ctx, entityId, size) {
|
|
254
|
+
const block = this.write(ctx, entityId);
|
|
255
|
+
import_math.Vec2.copy(block.size, size);
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Rotate a block by a delta angle (in radians).
|
|
259
|
+
*/
|
|
260
|
+
rotateBy(ctx, entityId, deltaAngle) {
|
|
261
|
+
const block = this.write(ctx, entityId);
|
|
262
|
+
block.rotateZ += deltaAngle;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Rotate a block around a pivot point.
|
|
266
|
+
*/
|
|
267
|
+
rotateAround(ctx, entityId, pivot, angle) {
|
|
268
|
+
const block = this.write(ctx, entityId);
|
|
269
|
+
block.rotateZ = import_math.Rect.rotateAround(block.position, block.size, block.rotateZ, pivot, angle);
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Scale a block uniformly around its center.
|
|
273
|
+
*/
|
|
274
|
+
scaleBy(ctx, entityId, scaleFactor) {
|
|
275
|
+
const block = this.write(ctx, entityId);
|
|
276
|
+
import_math.Rect.scaleBy(block.position, block.size, scaleFactor);
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Scale a block around a pivot point.
|
|
280
|
+
*/
|
|
281
|
+
scaleAround(ctx, entityId, pivot, scaleFactor) {
|
|
282
|
+
const block = this.write(ctx, entityId);
|
|
283
|
+
import_math.Rect.scaleAround(block.position, block.size, pivot, scaleFactor);
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Check if an AABB intersects with this block using Separating Axis Theorem.
|
|
287
|
+
* This handles all intersection cases including narrow AABBs that pass through
|
|
288
|
+
* the middle of a rotated block without touching any corners.
|
|
289
|
+
* Optimized to avoid allocations for hot path usage.
|
|
290
|
+
*/
|
|
291
|
+
intersectsAabb(ctx, entityId, aabb) {
|
|
292
|
+
const { position, size, rotateZ } = this.read(ctx, entityId);
|
|
293
|
+
return import_math.Rect.intersectsAabb(position, size, rotateZ, aabb, _blockCorners, _aabbCorners, _blockAxes);
|
|
294
|
+
}
|
|
295
|
+
worldToUv(ctx, entityId, worldPos) {
|
|
296
|
+
const { position, size, rotateZ, flip } = this.read(ctx, entityId);
|
|
297
|
+
const uv = import_math.Rect.worldToUv(position, size, rotateZ, worldPos);
|
|
298
|
+
if (flip[0]) uv[0] = 1 - uv[0];
|
|
299
|
+
if (flip[1]) uv[1] = 1 - uv[1];
|
|
300
|
+
return uv;
|
|
301
|
+
}
|
|
302
|
+
uvToWorld(ctx, entityId, uv) {
|
|
303
|
+
const { position, size, rotateZ, flip } = this.read(ctx, entityId);
|
|
304
|
+
const flippedUv = [flip[0] ? 1 - uv[0] : uv[0], flip[1] ? 1 - uv[1] : uv[1]];
|
|
305
|
+
return import_math.Rect.uvToWorld(position, size, rotateZ, flippedUv);
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Get the UV-to-world transformation matrix for a block.
|
|
309
|
+
* More efficient than multiple uvToWorld() calls since sin/cos is computed once.
|
|
310
|
+
* Use with Mat2.transformPoint() to transform UV coordinates to world.
|
|
311
|
+
* Note: This matrix does NOT account for flip - use uvToWorld() for flipped blocks.
|
|
312
|
+
*
|
|
313
|
+
* @param ctx - ECS context
|
|
314
|
+
* @param entityId - Entity ID
|
|
315
|
+
* @param out - Output matrix to write to [a, b, c, d, tx, ty]
|
|
316
|
+
*/
|
|
317
|
+
getUvToWorldMatrix(ctx, entityId, out) {
|
|
318
|
+
const { position, size, rotateZ, flip } = this.read(ctx, entityId);
|
|
319
|
+
import_math.Rect.getUvToWorldMatrix(position, size, rotateZ, out);
|
|
320
|
+
if (flip[0]) {
|
|
321
|
+
out[0] = -out[0];
|
|
322
|
+
out[2] = -out[2];
|
|
323
|
+
out[4] += size[0] * Math.cos(rotateZ);
|
|
324
|
+
out[5] += size[0] * Math.sin(rotateZ);
|
|
325
|
+
}
|
|
326
|
+
if (flip[1]) {
|
|
327
|
+
out[1] = -out[1];
|
|
328
|
+
out[3] = -out[3];
|
|
329
|
+
out[4] -= size[1] * Math.sin(rotateZ);
|
|
330
|
+
out[5] += size[1] * Math.cos(rotateZ);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
var Block = new BlockDef();
|
|
335
|
+
|
|
336
|
+
// src/components/Aabb.ts
|
|
337
|
+
var LEFT = 0;
|
|
338
|
+
var TOP = 1;
|
|
339
|
+
var RIGHT = 2;
|
|
340
|
+
var BOTTOM = 3;
|
|
341
|
+
var AabbSchema = {
|
|
342
|
+
/** Bounds as [left, top, right, bottom] */
|
|
343
|
+
value: import_core2.field.tuple(import_core2.field.float32(), 4).default([0, 0, 0, 0])
|
|
344
|
+
};
|
|
345
|
+
var AabbDef = class extends import_canvas_store2.CanvasComponentDef {
|
|
346
|
+
constructor() {
|
|
347
|
+
super({ name: "aabb" }, AabbSchema);
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Check if a point is contained within an entity's AABB.
|
|
351
|
+
*/
|
|
352
|
+
containsPoint(ctx, entityId, point, inclusive = true) {
|
|
353
|
+
const { value } = this.read(ctx, entityId);
|
|
354
|
+
return import_math2.Aabb.containsPoint(value, point, inclusive);
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Expand AABB to include a point.
|
|
358
|
+
*/
|
|
359
|
+
expandByPoint(ctx, entityId, point) {
|
|
360
|
+
const { value } = this.write(ctx, entityId);
|
|
361
|
+
import_math2.Aabb.expand(value, point);
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Expand AABB to include another entity's block.
|
|
365
|
+
*/
|
|
366
|
+
expandByBlock(ctx, entityId, blockEntityId) {
|
|
367
|
+
const corners = Block.getCorners(ctx, blockEntityId);
|
|
368
|
+
const { value } = this.write(ctx, entityId);
|
|
369
|
+
for (const corner of corners) {
|
|
370
|
+
import_math2.Aabb.expand(value, corner);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Expand AABB to include another AABB.
|
|
375
|
+
*/
|
|
376
|
+
expandByAabb(ctx, entityId, other) {
|
|
377
|
+
const { value } = this.write(ctx, entityId);
|
|
378
|
+
import_math2.Aabb.union(value, other);
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Copy bounds from another entity's AABB.
|
|
382
|
+
*/
|
|
383
|
+
copyFrom(ctx, entityId, otherEntityId) {
|
|
384
|
+
const { value: src } = this.read(ctx, otherEntityId);
|
|
385
|
+
const { value: dst } = this.write(ctx, entityId);
|
|
386
|
+
import_math2.Aabb.copy(dst, src);
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Set AABB from an array of points.
|
|
390
|
+
*/
|
|
391
|
+
setByPoints(ctx, entityId, points) {
|
|
392
|
+
if (points.length === 0) return;
|
|
393
|
+
const { value } = this.write(ctx, entityId);
|
|
394
|
+
import_math2.Aabb.setFromPoints(value, points);
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Get the center point of an entity's AABB.
|
|
398
|
+
*/
|
|
399
|
+
getCenter(ctx, entityId) {
|
|
400
|
+
const { value } = this.read(ctx, entityId);
|
|
401
|
+
return import_math2.Aabb.center(value);
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Get the width of an entity's AABB.
|
|
405
|
+
*/
|
|
406
|
+
getWidth(ctx, entityId) {
|
|
407
|
+
const { value } = this.read(ctx, entityId);
|
|
408
|
+
return import_math2.Aabb.width(value);
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Get the height of an entity's AABB.
|
|
412
|
+
*/
|
|
413
|
+
getHeight(ctx, entityId) {
|
|
414
|
+
const { value } = this.read(ctx, entityId);
|
|
415
|
+
return import_math2.Aabb.height(value);
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Get the distance from AABB to a point.
|
|
419
|
+
*/
|
|
420
|
+
distanceToPoint(ctx, entityId, point) {
|
|
421
|
+
const { value } = this.read(ctx, entityId);
|
|
422
|
+
return import_math2.Aabb.distanceToPoint(value, point);
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Check if two entity AABBs intersect.
|
|
426
|
+
*/
|
|
427
|
+
intersectsEntity(ctx, entityIdA, entityIdB) {
|
|
428
|
+
const { value: a } = this.read(ctx, entityIdA);
|
|
429
|
+
const { value: b } = this.read(ctx, entityIdB);
|
|
430
|
+
return import_math2.Aabb.intersects(a, b);
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Check if entity A's AABB completely surrounds entity B's AABB.
|
|
434
|
+
*/
|
|
435
|
+
surroundsEntity(ctx, entityIdA, entityIdB) {
|
|
436
|
+
const { value: a } = this.read(ctx, entityIdA);
|
|
437
|
+
const { value: b } = this.read(ctx, entityIdB);
|
|
438
|
+
return import_math2.Aabb.contains(a, b);
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Get the four corner points of an entity's AABB.
|
|
442
|
+
* Returns corners in order: top-left, top-right, bottom-right, bottom-left.
|
|
443
|
+
* @param out - Optional output array to write to (avoids allocation)
|
|
444
|
+
*/
|
|
445
|
+
getCorners(ctx, entityId, out) {
|
|
446
|
+
const { value } = this.read(ctx, entityId);
|
|
447
|
+
const result = out ?? [
|
|
448
|
+
[0, 0],
|
|
449
|
+
[0, 0],
|
|
450
|
+
[0, 0],
|
|
451
|
+
[0, 0]
|
|
452
|
+
];
|
|
453
|
+
result[0][0] = value[LEFT];
|
|
454
|
+
result[0][1] = value[TOP];
|
|
455
|
+
result[1][0] = value[RIGHT];
|
|
456
|
+
result[1][1] = value[TOP];
|
|
457
|
+
result[2][0] = value[RIGHT];
|
|
458
|
+
result[2][1] = value[BOTTOM];
|
|
459
|
+
result[3][0] = value[LEFT];
|
|
460
|
+
result[3][1] = value[BOTTOM];
|
|
461
|
+
return result;
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Apply padding to all sides of the AABB.
|
|
465
|
+
*/
|
|
466
|
+
applyPadding(ctx, entityId, padding) {
|
|
467
|
+
const { value } = this.write(ctx, entityId);
|
|
468
|
+
import_math2.Aabb.pad(value, padding);
|
|
469
|
+
}
|
|
470
|
+
};
|
|
471
|
+
var Aabb = new AabbDef();
|
|
472
|
+
|
|
473
|
+
// src/components/Asset.ts
|
|
474
|
+
var import_canvas_store3 = require("@woven-ecs/canvas-store");
|
|
475
|
+
var import_core3 = require("@woven-ecs/core");
|
|
476
|
+
var UploadState = {
|
|
477
|
+
/** Asset queued for upload, not yet started */
|
|
478
|
+
Pending: "pending",
|
|
479
|
+
/** Upload in progress */
|
|
480
|
+
Uploading: "uploading",
|
|
481
|
+
/** Upload completed successfully */
|
|
482
|
+
Complete: "complete",
|
|
483
|
+
/** Upload failed */
|
|
484
|
+
Failed: "failed"
|
|
485
|
+
};
|
|
486
|
+
var Asset = (0, import_canvas_store3.defineCanvasComponent)(
|
|
487
|
+
{ name: "asset", sync: "document", excludeFromHistory: ["uploadState"] },
|
|
488
|
+
{
|
|
489
|
+
/** Permanent identifier from AssetProvider (empty until upload complete) */
|
|
490
|
+
identifier: import_core3.field.string().max(512).default(""),
|
|
491
|
+
/** Current upload state */
|
|
492
|
+
uploadState: import_core3.field.enum(UploadState).default(UploadState.Pending)
|
|
493
|
+
}
|
|
494
|
+
);
|
|
495
|
+
|
|
496
|
+
// src/components/Color.ts
|
|
497
|
+
var import_canvas_store4 = require("@woven-ecs/canvas-store");
|
|
498
|
+
var import_core4 = require("@woven-ecs/core");
|
|
499
|
+
var ColorSchema = {
|
|
500
|
+
red: import_core4.field.uint8().default(0),
|
|
501
|
+
green: import_core4.field.uint8().default(0),
|
|
502
|
+
blue: import_core4.field.uint8().default(0),
|
|
503
|
+
alpha: import_core4.field.uint8().default(255)
|
|
504
|
+
};
|
|
505
|
+
var ColorDef = class extends import_canvas_store4.CanvasComponentDef {
|
|
506
|
+
constructor() {
|
|
507
|
+
super({ name: "color", sync: "document" }, ColorSchema);
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* Convert a color to a hex string.
|
|
511
|
+
*/
|
|
512
|
+
toHex(ctx, entityId) {
|
|
513
|
+
const { red, green, blue, alpha } = this.read(ctx, entityId);
|
|
514
|
+
const rHex = red.toString(16).padStart(2, "0");
|
|
515
|
+
const gHex = green.toString(16).padStart(2, "0");
|
|
516
|
+
const bHex = blue.toString(16).padStart(2, "0");
|
|
517
|
+
const aHex = alpha.toString(16).padStart(2, "0");
|
|
518
|
+
return `#${rHex}${gHex}${bHex}${aHex}`;
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* Set color from a hex string.
|
|
522
|
+
*/
|
|
523
|
+
fromHex(ctx, entityId, hex) {
|
|
524
|
+
const color = this.write(ctx, entityId);
|
|
525
|
+
color.red = Number.parseInt(hex.slice(1, 3), 16);
|
|
526
|
+
color.green = Number.parseInt(hex.slice(3, 5), 16);
|
|
527
|
+
color.blue = Number.parseInt(hex.slice(5, 7), 16);
|
|
528
|
+
color.alpha = hex.length > 7 ? Number.parseInt(hex.slice(7, 9), 16) : 255;
|
|
529
|
+
}
|
|
530
|
+
};
|
|
531
|
+
var Color = new ColorDef();
|
|
532
|
+
|
|
533
|
+
// src/components/Connector.ts
|
|
534
|
+
var import_canvas_store5 = require("@woven-ecs/canvas-store");
|
|
535
|
+
var import_core5 = require("@woven-ecs/core");
|
|
536
|
+
var Connector = (0, import_canvas_store5.defineCanvasComponent)(
|
|
537
|
+
{ name: "connector", sync: "document" },
|
|
538
|
+
{
|
|
539
|
+
startBlock: import_core5.field.ref(),
|
|
540
|
+
startBlockUv: import_core5.field.tuple(import_core5.field.float64(), 2).default([0, 0]),
|
|
541
|
+
startUv: import_core5.field.tuple(import_core5.field.float64(), 2).default([0, 0]),
|
|
542
|
+
endBlock: import_core5.field.ref(),
|
|
543
|
+
endBlockUv: import_core5.field.tuple(import_core5.field.float64(), 2).default([0, 0]),
|
|
544
|
+
endUv: import_core5.field.tuple(import_core5.field.float64(), 2).default([1, 1])
|
|
545
|
+
}
|
|
546
|
+
);
|
|
547
|
+
|
|
548
|
+
// src/components/Edited.ts
|
|
549
|
+
var import_canvas_store6 = require("@woven-ecs/canvas-store");
|
|
550
|
+
var Edited = (0, import_canvas_store6.defineCanvasComponent)({ name: "edited" }, {});
|
|
551
|
+
|
|
552
|
+
// src/components/Held.ts
|
|
553
|
+
var import_canvas_store7 = require("@woven-ecs/canvas-store");
|
|
554
|
+
var import_core6 = require("@woven-ecs/core");
|
|
555
|
+
var Held = (0, import_canvas_store7.defineCanvasComponent)(
|
|
556
|
+
{ name: "held", sync: "ephemeral" },
|
|
557
|
+
{
|
|
558
|
+
sessionId: import_core6.field.string().max(36).default("")
|
|
559
|
+
}
|
|
560
|
+
);
|
|
561
|
+
|
|
562
|
+
// src/components/HitGeometry.ts
|
|
563
|
+
var import_math3 = require("@woven-canvas/math");
|
|
564
|
+
var import_canvas_store8 = require("@woven-ecs/canvas-store");
|
|
565
|
+
var import_core7 = require("@woven-ecs/core");
|
|
566
|
+
var _uvToWorldMatrix = [1, 0, 0, 1, 0, 0];
|
|
567
|
+
var MAX_HIT_CAPSULES = 64;
|
|
568
|
+
var FLOATS_PER_CAPSULE = 5;
|
|
569
|
+
var MAX_HIT_ARCS = 2;
|
|
570
|
+
var FLOATS_PER_ARC = 7;
|
|
571
|
+
var HitGeometrySchema = {
|
|
572
|
+
hitCapsules: import_core7.field.buffer(import_core7.field.float32()).size(MAX_HIT_CAPSULES * FLOATS_PER_CAPSULE),
|
|
573
|
+
capsuleCount: import_core7.field.uint16().default(0),
|
|
574
|
+
hitArcs: import_core7.field.buffer(import_core7.field.float32()).size(MAX_HIT_ARCS * FLOATS_PER_ARC),
|
|
575
|
+
arcCount: import_core7.field.uint16().default(0)
|
|
576
|
+
};
|
|
577
|
+
var HitGeometryDef = class extends import_canvas_store8.CanvasComponentDef {
|
|
578
|
+
constructor() {
|
|
579
|
+
super({ name: "hitHeometry" }, HitGeometrySchema);
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* Get a capsule at a specific index from the hitCapsules buffer.
|
|
583
|
+
* @param ctx - ECS context
|
|
584
|
+
* @param entityId - Entity ID
|
|
585
|
+
* @param index - Index of the capsule (0-based)
|
|
586
|
+
* @returns Capsule tuple [ax, ay, bx, by, radius]
|
|
587
|
+
*/
|
|
588
|
+
getCapsuleAt(ctx, entityId, index) {
|
|
589
|
+
const hitGeometry = this.read(ctx, entityId);
|
|
590
|
+
const offset = index * FLOATS_PER_CAPSULE;
|
|
591
|
+
return [
|
|
592
|
+
hitGeometry.hitCapsules[offset],
|
|
593
|
+
hitGeometry.hitCapsules[offset + 1],
|
|
594
|
+
hitGeometry.hitCapsules[offset + 2],
|
|
595
|
+
hitGeometry.hitCapsules[offset + 3],
|
|
596
|
+
hitGeometry.hitCapsules[offset + 4]
|
|
597
|
+
];
|
|
598
|
+
}
|
|
599
|
+
/**
|
|
600
|
+
* Set a capsule at a specific index in the hitCapsules buffer.
|
|
601
|
+
* @param ctx - ECS context
|
|
602
|
+
* @param entityId - Entity ID
|
|
603
|
+
* @param index - Index of the capsule (0-based)
|
|
604
|
+
* @param capsule - Capsule tuple [ax, ay, bx, by, radius]
|
|
605
|
+
*/
|
|
606
|
+
setCapsuleAt(ctx, entityId, index, capsule) {
|
|
607
|
+
const hitGeometry = this.write(ctx, entityId);
|
|
608
|
+
const offset = index * FLOATS_PER_CAPSULE;
|
|
609
|
+
for (let i = 0; i < FLOATS_PER_CAPSULE; i++) {
|
|
610
|
+
hitGeometry.hitCapsules[offset + i] = capsule[i];
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* Get an arc at a specific index from the hitArcs buffer.
|
|
615
|
+
* @param ctx - ECS context
|
|
616
|
+
* @param entityId - Entity ID
|
|
617
|
+
* @param index - Index of the arc (0-based)
|
|
618
|
+
* @returns Arc tuple [ax, ay, bx, by, cx, cy, thickness]
|
|
619
|
+
*/
|
|
620
|
+
getArcAt(ctx, entityId, index) {
|
|
621
|
+
const hitGeometry = this.read(ctx, entityId);
|
|
622
|
+
const offset = index * FLOATS_PER_ARC;
|
|
623
|
+
return [
|
|
624
|
+
hitGeometry.hitArcs[offset],
|
|
625
|
+
hitGeometry.hitArcs[offset + 1],
|
|
626
|
+
hitGeometry.hitArcs[offset + 2],
|
|
627
|
+
hitGeometry.hitArcs[offset + 3],
|
|
628
|
+
hitGeometry.hitArcs[offset + 4],
|
|
629
|
+
hitGeometry.hitArcs[offset + 5],
|
|
630
|
+
hitGeometry.hitArcs[offset + 6]
|
|
631
|
+
];
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* Set an arc at a specific index in the hitArcs buffer.
|
|
635
|
+
* @param ctx - ECS context
|
|
636
|
+
* @param entityId - Entity ID
|
|
637
|
+
* @param index - Index of the arc (0-based)
|
|
638
|
+
* @param arc - Arc tuple [ax, ay, bx, by, cx, cy, thickness]
|
|
639
|
+
*/
|
|
640
|
+
setArcAt(ctx, entityId, index, arc) {
|
|
641
|
+
const hitGeometry = this.write(ctx, entityId);
|
|
642
|
+
const offset = index * FLOATS_PER_ARC;
|
|
643
|
+
for (let i = 0; i < FLOATS_PER_ARC; i++) {
|
|
644
|
+
hitGeometry.hitArcs[offset + i] = arc[i];
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
/**
|
|
648
|
+
* Check if a point is inside the entity's hit geometry.
|
|
649
|
+
* Hit geometry is stored in UV coordinates (0-1) relative to the block.
|
|
650
|
+
* This method transforms UV geometry to world space for intersection testing.
|
|
651
|
+
*
|
|
652
|
+
* @param ctx - ECS context
|
|
653
|
+
* @param entityId - Entity ID
|
|
654
|
+
* @param point - Point to test in world coordinates [x, y]
|
|
655
|
+
* @returns True if the point is inside any of the entity's hit capsules or arc
|
|
656
|
+
*/
|
|
657
|
+
containsPointWorld(ctx, entityId, point) {
|
|
658
|
+
const hitGeometry = this.read(ctx, entityId);
|
|
659
|
+
Block.getUvToWorldMatrix(ctx, entityId, _uvToWorldMatrix);
|
|
660
|
+
for (let i = 0; i < hitGeometry.arcCount; i++) {
|
|
661
|
+
const uvArc = this.getArcAt(ctx, entityId, i);
|
|
662
|
+
const worldA = [uvArc[0], uvArc[1]];
|
|
663
|
+
import_math3.Mat2.transformPoint(_uvToWorldMatrix, worldA);
|
|
664
|
+
const worldB = [uvArc[2], uvArc[3]];
|
|
665
|
+
import_math3.Mat2.transformPoint(_uvToWorldMatrix, worldB);
|
|
666
|
+
const worldC = [uvArc[4], uvArc[5]];
|
|
667
|
+
import_math3.Mat2.transformPoint(_uvToWorldMatrix, worldC);
|
|
668
|
+
const thickness = uvArc[6];
|
|
669
|
+
const worldArc = import_math3.Arc.create(worldA[0], worldA[1], worldB[0], worldB[1], worldC[0], worldC[1], thickness);
|
|
670
|
+
if (import_math3.Arc.containsPoint(worldArc, point)) {
|
|
671
|
+
return true;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
for (let i = 0; i < hitGeometry.capsuleCount; i++) {
|
|
675
|
+
const uvCapsule = this.getCapsuleAt(ctx, entityId, i);
|
|
676
|
+
const worldA = [uvCapsule[0], uvCapsule[1]];
|
|
677
|
+
import_math3.Mat2.transformPoint(_uvToWorldMatrix, worldA);
|
|
678
|
+
const worldB = [uvCapsule[2], uvCapsule[3]];
|
|
679
|
+
import_math3.Mat2.transformPoint(_uvToWorldMatrix, worldB);
|
|
680
|
+
const radius = uvCapsule[4];
|
|
681
|
+
const worldCapsule = import_math3.Capsule.create(worldA[0], worldA[1], worldB[0], worldB[1], radius);
|
|
682
|
+
if (import_math3.Capsule.containsPoint(worldCapsule, point)) {
|
|
683
|
+
return true;
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
return false;
|
|
687
|
+
}
|
|
688
|
+
/**
|
|
689
|
+
* Check if an AABB intersects with the entity's hit geometry.
|
|
690
|
+
* Hit geometry is stored in UV coordinates (0-1) relative to the block.
|
|
691
|
+
* This method transforms UV geometry to world space for intersection testing.
|
|
692
|
+
*
|
|
693
|
+
* @param ctx - ECS context
|
|
694
|
+
* @param entityId - Entity ID
|
|
695
|
+
* @param aabb - AABB to test in world coordinates [left, top, right, bottom]
|
|
696
|
+
* @returns True if the AABB intersects any of the entity's hit capsules or arc
|
|
697
|
+
*/
|
|
698
|
+
intersectsAabbWorld(ctx, entityId, aabb) {
|
|
699
|
+
const hitGeometry = this.read(ctx, entityId);
|
|
700
|
+
Block.getUvToWorldMatrix(ctx, entityId, _uvToWorldMatrix);
|
|
701
|
+
for (let i = 0; i < hitGeometry.arcCount; i++) {
|
|
702
|
+
const uvArc = this.getArcAt(ctx, entityId, i);
|
|
703
|
+
const worldA = [uvArc[0], uvArc[1]];
|
|
704
|
+
import_math3.Mat2.transformPoint(_uvToWorldMatrix, worldA);
|
|
705
|
+
const worldB = [uvArc[2], uvArc[3]];
|
|
706
|
+
import_math3.Mat2.transformPoint(_uvToWorldMatrix, worldB);
|
|
707
|
+
const worldC = [uvArc[4], uvArc[5]];
|
|
708
|
+
import_math3.Mat2.transformPoint(_uvToWorldMatrix, worldC);
|
|
709
|
+
const thickness = uvArc[6];
|
|
710
|
+
const worldArc = import_math3.Arc.create(worldA[0], worldA[1], worldB[0], worldB[1], worldC[0], worldC[1], thickness);
|
|
711
|
+
if (import_math3.Arc.intersectsAabb(worldArc, aabb)) {
|
|
712
|
+
return true;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
for (let i = 0; i < hitGeometry.capsuleCount; i++) {
|
|
716
|
+
const uvCapsule = this.getCapsuleAt(ctx, entityId, i);
|
|
717
|
+
const worldA = [uvCapsule[0], uvCapsule[1]];
|
|
718
|
+
import_math3.Mat2.transformPoint(_uvToWorldMatrix, worldA);
|
|
719
|
+
const worldB = [uvCapsule[2], uvCapsule[3]];
|
|
720
|
+
import_math3.Mat2.transformPoint(_uvToWorldMatrix, worldB);
|
|
721
|
+
const radius = uvCapsule[4];
|
|
722
|
+
const worldCapsule = import_math3.Capsule.create(worldA[0], worldA[1], worldB[0], worldB[1], radius);
|
|
723
|
+
if (import_math3.Capsule.intersectsAabb(worldCapsule, aabb)) {
|
|
724
|
+
return true;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
return false;
|
|
728
|
+
}
|
|
729
|
+
/**
|
|
730
|
+
* Get all extrema points of the hit geometry in world coordinates for AABB computation.
|
|
731
|
+
* Hit geometry is stored in UV coordinates; this transforms them to world space.
|
|
732
|
+
* Returns corner points expanded by radius/thickness for each capsule and arc.
|
|
733
|
+
*
|
|
734
|
+
* @param ctx - ECS context
|
|
735
|
+
* @param entityId - Entity ID
|
|
736
|
+
* @returns Array of extrema points in world coordinates
|
|
737
|
+
*/
|
|
738
|
+
getExtremaWorld(ctx, entityId) {
|
|
739
|
+
const hitGeometry = this.read(ctx, entityId);
|
|
740
|
+
const pts = [];
|
|
741
|
+
Block.getUvToWorldMatrix(ctx, entityId, _uvToWorldMatrix);
|
|
742
|
+
for (let i = 0; i < hitGeometry.capsuleCount; i++) {
|
|
743
|
+
const uvCapsule = this.getCapsuleAt(ctx, entityId, i);
|
|
744
|
+
const worldA = [uvCapsule[0], uvCapsule[1]];
|
|
745
|
+
import_math3.Mat2.transformPoint(_uvToWorldMatrix, worldA);
|
|
746
|
+
const worldB = [uvCapsule[2], uvCapsule[3]];
|
|
747
|
+
import_math3.Mat2.transformPoint(_uvToWorldMatrix, worldB);
|
|
748
|
+
const radius = uvCapsule[4];
|
|
749
|
+
const worldCapsule = import_math3.Capsule.create(worldA[0], worldA[1], worldB[0], worldB[1], radius);
|
|
750
|
+
pts.push(...import_math3.Capsule.getExtrema(worldCapsule));
|
|
751
|
+
}
|
|
752
|
+
for (let i = 0; i < hitGeometry.arcCount; i++) {
|
|
753
|
+
const uvArc = this.getArcAt(ctx, entityId, i);
|
|
754
|
+
const worldA = [uvArc[0], uvArc[1]];
|
|
755
|
+
import_math3.Mat2.transformPoint(_uvToWorldMatrix, worldA);
|
|
756
|
+
const worldB = [uvArc[2], uvArc[3]];
|
|
757
|
+
import_math3.Mat2.transformPoint(_uvToWorldMatrix, worldB);
|
|
758
|
+
const worldC = [uvArc[4], uvArc[5]];
|
|
759
|
+
import_math3.Mat2.transformPoint(_uvToWorldMatrix, worldC);
|
|
760
|
+
const thickness = uvArc[6];
|
|
761
|
+
const worldArc = import_math3.Arc.create(worldA[0], worldA[1], worldB[0], worldB[1], worldC[0], worldC[1], thickness);
|
|
762
|
+
pts.push(...import_math3.Arc.getExtrema(worldArc));
|
|
763
|
+
}
|
|
764
|
+
return pts;
|
|
765
|
+
}
|
|
766
|
+
// ============================================
|
|
767
|
+
// User-facing helper methods for adding hit geometry
|
|
768
|
+
// ============================================
|
|
769
|
+
/**
|
|
770
|
+
* Add a capsule from a capsule tuple [ax, ay, bx, by, radius].
|
|
771
|
+
* Positions are in UV coordinates (0-1), radius is in world units.
|
|
772
|
+
*
|
|
773
|
+
* @param ctx - ECS context
|
|
774
|
+
* @param entityId - Entity ID
|
|
775
|
+
* @param capsule - Capsule tuple [ax, ay, bx, by, radius]
|
|
776
|
+
*/
|
|
777
|
+
addCapsule(ctx, entityId, capsule) {
|
|
778
|
+
const hitGeometry = this.write(ctx, entityId);
|
|
779
|
+
const index = hitGeometry.capsuleCount;
|
|
780
|
+
if (index >= MAX_HIT_CAPSULES) {
|
|
781
|
+
console.warn(`HitGeometry: Max capsules (${MAX_HIT_CAPSULES}) reached`);
|
|
782
|
+
return;
|
|
783
|
+
}
|
|
784
|
+
this.setCapsuleAt(ctx, entityId, index, capsule);
|
|
785
|
+
hitGeometry.capsuleCount = index + 1;
|
|
786
|
+
}
|
|
787
|
+
/**
|
|
788
|
+
* Add a capsule using UV coordinates.
|
|
789
|
+
* UV (0,0) is top-left, (1,1) is bottom-right of the block.
|
|
790
|
+
*
|
|
791
|
+
* @param ctx - ECS context
|
|
792
|
+
* @param entityId - Entity ID
|
|
793
|
+
* @param uvA - First endpoint in UV coordinates [0-1, 0-1]
|
|
794
|
+
* @param uvB - Second endpoint in UV coordinates [0-1, 0-1]
|
|
795
|
+
* @param worldRadius - Radius in world units (pixels)
|
|
796
|
+
*/
|
|
797
|
+
addCapsuleUv(ctx, entityId, uvA, uvB, worldRadius) {
|
|
798
|
+
const capsule = [uvA[0], uvA[1], uvB[0], uvB[1], worldRadius];
|
|
799
|
+
this.addCapsule(ctx, entityId, capsule);
|
|
800
|
+
}
|
|
801
|
+
/**
|
|
802
|
+
* Add a capsule using world coordinates.
|
|
803
|
+
* Automatically converts to UV coordinates relative to the block.
|
|
804
|
+
*
|
|
805
|
+
* @param ctx - ECS context
|
|
806
|
+
* @param entityId - Entity ID
|
|
807
|
+
* @param worldA - First endpoint in world coordinates
|
|
808
|
+
* @param worldB - Second endpoint in world coordinates
|
|
809
|
+
* @param worldRadius - Radius in world units (pixels)
|
|
810
|
+
*/
|
|
811
|
+
addCapsuleWorld(ctx, entityId, worldA, worldB, worldRadius) {
|
|
812
|
+
const uvA = Block.worldToUv(ctx, entityId, worldA);
|
|
813
|
+
const uvB = Block.worldToUv(ctx, entityId, worldB);
|
|
814
|
+
this.addCapsuleUv(ctx, entityId, uvA, uvB, worldRadius);
|
|
815
|
+
}
|
|
816
|
+
/**
|
|
817
|
+
* Add an arc from an arc tuple [aX, aY, bX, bY, cX, cY, thickness].
|
|
818
|
+
* Positions are in UV coordinates (0-1), thickness is in world units.
|
|
819
|
+
*
|
|
820
|
+
* @param ctx - ECS context
|
|
821
|
+
* @param entityId - Entity ID
|
|
822
|
+
* @param arc - Arc tuple [aX, aY, bX, bY, cX, cY, thickness]
|
|
823
|
+
*/
|
|
824
|
+
addArc(ctx, entityId, arc) {
|
|
825
|
+
const hitGeometry = this.write(ctx, entityId);
|
|
826
|
+
const index = hitGeometry.arcCount;
|
|
827
|
+
if (index >= MAX_HIT_ARCS) {
|
|
828
|
+
console.warn(`HitGeometry: Max arcs (${MAX_HIT_ARCS}) reached`);
|
|
829
|
+
return;
|
|
830
|
+
}
|
|
831
|
+
this.setArcAt(ctx, entityId, index, arc);
|
|
832
|
+
hitGeometry.arcCount = index + 1;
|
|
833
|
+
}
|
|
834
|
+
/**
|
|
835
|
+
* Add an arc using UV coordinates.
|
|
836
|
+
* UV (0,0) is top-left, (1,1) is bottom-right of the block.
|
|
837
|
+
*
|
|
838
|
+
* @param ctx - ECS context
|
|
839
|
+
* @param entityId - Entity ID
|
|
840
|
+
* @param uvA - Start point in UV coordinates
|
|
841
|
+
* @param uvB - Control point in UV coordinates
|
|
842
|
+
* @param uvC - End point in UV coordinates
|
|
843
|
+
* @param worldThickness - Thickness in world units (pixels)
|
|
844
|
+
*/
|
|
845
|
+
addArcUv(ctx, entityId, uvA, uvB, uvC, worldThickness) {
|
|
846
|
+
const arc = [uvA[0], uvA[1], uvB[0], uvB[1], uvC[0], uvC[1], worldThickness];
|
|
847
|
+
this.addArc(ctx, entityId, arc);
|
|
848
|
+
}
|
|
849
|
+
/**
|
|
850
|
+
* Add an arc using world coordinates.
|
|
851
|
+
* Automatically converts to UV coordinates relative to the block.
|
|
852
|
+
*
|
|
853
|
+
* @param ctx - ECS context
|
|
854
|
+
* @param entityId - Entity ID
|
|
855
|
+
* @param worldA - Start point in world coordinates
|
|
856
|
+
* @param worldB - Control point in world coordinates
|
|
857
|
+
* @param worldC - End point in world coordinates
|
|
858
|
+
* @param worldThickness - Thickness in world units (pixels)
|
|
859
|
+
*/
|
|
860
|
+
addArcWorld(ctx, entityId, worldA, worldB, worldC, worldThickness) {
|
|
861
|
+
const uvA = Block.worldToUv(ctx, entityId, worldA);
|
|
862
|
+
const uvB = Block.worldToUv(ctx, entityId, worldB);
|
|
863
|
+
const uvC = Block.worldToUv(ctx, entityId, worldC);
|
|
864
|
+
this.addArcUv(ctx, entityId, uvA, uvB, uvC, worldThickness);
|
|
865
|
+
}
|
|
866
|
+
/**
|
|
867
|
+
* Clear all hit geometry from an entity.
|
|
868
|
+
*
|
|
869
|
+
* @param ctx - ECS context
|
|
870
|
+
* @param entityId - Entity ID
|
|
871
|
+
*/
|
|
872
|
+
clear(ctx, entityId) {
|
|
873
|
+
const hitGeometry = this.write(ctx, entityId);
|
|
874
|
+
hitGeometry.capsuleCount = 0;
|
|
875
|
+
hitGeometry.arcCount = 0;
|
|
876
|
+
}
|
|
877
|
+
};
|
|
878
|
+
var HitGeometry = new HitGeometryDef();
|
|
879
|
+
|
|
880
|
+
// src/components/Hovered.ts
|
|
881
|
+
var import_canvas_store9 = require("@woven-ecs/canvas-store");
|
|
882
|
+
var Hovered = (0, import_canvas_store9.defineCanvasComponent)({ name: "hovered" }, {});
|
|
883
|
+
|
|
884
|
+
// src/components/Image.ts
|
|
885
|
+
var import_canvas_store10 = require("@woven-ecs/canvas-store");
|
|
886
|
+
var import_core8 = require("@woven-ecs/core");
|
|
887
|
+
var Image = (0, import_canvas_store10.defineCanvasComponent)(
|
|
888
|
+
{ name: "image", sync: "document" },
|
|
889
|
+
{
|
|
890
|
+
/** Image width in pixels */
|
|
891
|
+
width: import_core8.field.uint16().default(0),
|
|
892
|
+
/** Image height in pixels */
|
|
893
|
+
height: import_core8.field.uint16().default(0),
|
|
894
|
+
/** Alt text for accessibility */
|
|
895
|
+
alt: import_core8.field.string().max(256).default("")
|
|
896
|
+
}
|
|
897
|
+
);
|
|
898
|
+
|
|
899
|
+
// src/components/Opacity.ts
|
|
900
|
+
var import_canvas_store11 = require("@woven-ecs/canvas-store");
|
|
901
|
+
var import_core9 = require("@woven-ecs/core");
|
|
902
|
+
var Opacity = (0, import_canvas_store11.defineCanvasComponent)(
|
|
903
|
+
{ name: "opacity" },
|
|
904
|
+
{
|
|
905
|
+
value: import_core9.field.uint8().default(255)
|
|
906
|
+
}
|
|
907
|
+
);
|
|
908
|
+
|
|
909
|
+
// src/components/Pointer.ts
|
|
910
|
+
var import_canvas_store12 = require("@woven-ecs/canvas-store");
|
|
911
|
+
var import_core10 = require("@woven-ecs/core");
|
|
912
|
+
var PointerButton = {
|
|
913
|
+
None: "none",
|
|
914
|
+
Left: "left",
|
|
915
|
+
Middle: "middle",
|
|
916
|
+
Right: "right",
|
|
917
|
+
Back: "back",
|
|
918
|
+
Forward: "forward"
|
|
919
|
+
};
|
|
920
|
+
var PointerType = {
|
|
921
|
+
Mouse: "mouse",
|
|
922
|
+
Pen: "pen",
|
|
923
|
+
Touch: "touch"
|
|
924
|
+
};
|
|
925
|
+
var SAMPLE_COUNT = 6;
|
|
926
|
+
var PointerSchema = {
|
|
927
|
+
/** Unique pointer ID (from PointerEvent.pointerId) */
|
|
928
|
+
pointerId: import_core10.field.uint16().default(0),
|
|
929
|
+
/** Current position relative to the editor element [x, y] */
|
|
930
|
+
position: import_core10.field.tuple(import_core10.field.float32(), 2).default([0, 0]),
|
|
931
|
+
/** Position where the pointer went down [x, y] */
|
|
932
|
+
downPosition: import_core10.field.tuple(import_core10.field.float32(), 2).default([0, 0]),
|
|
933
|
+
/** Frame number when the pointer went down (for click detection) */
|
|
934
|
+
downFrame: import_core10.field.uint32().default(0),
|
|
935
|
+
/** Which button is pressed */
|
|
936
|
+
button: import_core10.field.enum(PointerButton).default(PointerButton.None),
|
|
937
|
+
/** Type of pointer device */
|
|
938
|
+
pointerType: import_core10.field.enum(PointerType).default(PointerType.Mouse),
|
|
939
|
+
/** Pressure from 0 to 1 (for pen/touch) */
|
|
940
|
+
pressure: import_core10.field.float32().default(0),
|
|
941
|
+
/** Whether the pointer event target was not the editor element */
|
|
942
|
+
obscured: import_core10.field.boolean().default(false),
|
|
943
|
+
// Velocity tracking (ring buffer for position samples)
|
|
944
|
+
/** Ring buffer of previous positions [x0, y0, x1, y1, ...] @internal */
|
|
945
|
+
_prevPositions: import_core10.field.array(import_core10.field.float32(), SAMPLE_COUNT * 2),
|
|
946
|
+
/** Ring buffer of timestamps for each position sample @internal */
|
|
947
|
+
_prevTimes: import_core10.field.array(import_core10.field.float32(), SAMPLE_COUNT),
|
|
948
|
+
/** Total number of samples added (used for ring buffer indexing) @internal */
|
|
949
|
+
_sampleCount: import_core10.field.int32().default(0),
|
|
950
|
+
/** Computed velocity [vx, vy] in pixels per second @internal */
|
|
951
|
+
_velocity: import_core10.field.tuple(import_core10.field.float32(), 2).default([0, 0])
|
|
952
|
+
};
|
|
953
|
+
var PointerDef = class extends import_canvas_store12.CanvasComponentDef {
|
|
954
|
+
constructor() {
|
|
955
|
+
super({ name: "pointer" }, PointerSchema);
|
|
956
|
+
}
|
|
957
|
+
/** Get the computed velocity of a pointer */
|
|
958
|
+
getVelocity(ctx, entityId) {
|
|
959
|
+
const p = this.read(ctx, entityId);
|
|
960
|
+
return [p._velocity[0], p._velocity[1]];
|
|
961
|
+
}
|
|
962
|
+
/**
|
|
963
|
+
* Get the pointer button from a PointerEvent button number.
|
|
964
|
+
* @param button - PointerEvent.button value
|
|
965
|
+
* @returns PointerButton enum value
|
|
966
|
+
*/
|
|
967
|
+
getButton(button) {
|
|
968
|
+
switch (button) {
|
|
969
|
+
case 0:
|
|
970
|
+
return PointerButton.Left;
|
|
971
|
+
case 1:
|
|
972
|
+
return PointerButton.Middle;
|
|
973
|
+
case 2:
|
|
974
|
+
return PointerButton.Right;
|
|
975
|
+
case 3:
|
|
976
|
+
return PointerButton.Back;
|
|
977
|
+
case 4:
|
|
978
|
+
return PointerButton.Forward;
|
|
979
|
+
default:
|
|
980
|
+
return PointerButton.None;
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
/**
|
|
984
|
+
* Get the pointer type from a PointerEvent pointerType string.
|
|
985
|
+
* @param pointerType - PointerEvent.pointerType value
|
|
986
|
+
* @returns PointerType enum value
|
|
987
|
+
*/
|
|
988
|
+
getType(pointerType) {
|
|
989
|
+
switch (pointerType) {
|
|
990
|
+
case "pen":
|
|
991
|
+
return PointerType.Pen;
|
|
992
|
+
case "touch":
|
|
993
|
+
return PointerType.Touch;
|
|
994
|
+
default:
|
|
995
|
+
return PointerType.Mouse;
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
};
|
|
999
|
+
var Pointer = new PointerDef();
|
|
1000
|
+
function addPointerSample(pointer, position, time) {
|
|
1001
|
+
const currentIndex = pointer._sampleCount % SAMPLE_COUNT;
|
|
1002
|
+
const mostRecentTime = pointer._prevTimes[currentIndex] || 0;
|
|
1003
|
+
if (Math.abs(mostRecentTime - time) < 1e-3) return;
|
|
1004
|
+
pointer.position = position;
|
|
1005
|
+
pointer._sampleCount++;
|
|
1006
|
+
const writeIndex = pointer._sampleCount % SAMPLE_COUNT;
|
|
1007
|
+
pointer._prevPositions[writeIndex * 2] = position[0];
|
|
1008
|
+
pointer._prevPositions[writeIndex * 2 + 1] = position[1];
|
|
1009
|
+
pointer._prevTimes[writeIndex] = time;
|
|
1010
|
+
const pointCount = Math.min(pointer._sampleCount, SAMPLE_COUNT);
|
|
1011
|
+
if (pointCount <= 1) {
|
|
1012
|
+
pointer._velocity = [0, 0];
|
|
1013
|
+
return;
|
|
1014
|
+
}
|
|
1015
|
+
const mod = (n) => (n % SAMPLE_COUNT + SAMPLE_COUNT) % SAMPLE_COUNT;
|
|
1016
|
+
const TAU = 0.04;
|
|
1017
|
+
const EPS = 1e-6;
|
|
1018
|
+
let W = 0;
|
|
1019
|
+
let WU = 0;
|
|
1020
|
+
let WUU = 0;
|
|
1021
|
+
let WX = 0;
|
|
1022
|
+
let WY = 0;
|
|
1023
|
+
let WU_X = 0;
|
|
1024
|
+
let WU_Y = 0;
|
|
1025
|
+
for (let j = 0; j < pointCount; j++) {
|
|
1026
|
+
const idx = mod(pointer._sampleCount - pointCount + 1 + j);
|
|
1027
|
+
const t = pointer._prevTimes[idx] || 0;
|
|
1028
|
+
const u = t - time;
|
|
1029
|
+
const recency = -u;
|
|
1030
|
+
if (recency > 5 * TAU) continue;
|
|
1031
|
+
const w = Math.exp(-recency / TAU);
|
|
1032
|
+
const x = pointer._prevPositions[idx * 2] || 0;
|
|
1033
|
+
const y = pointer._prevPositions[idx * 2 + 1] || 0;
|
|
1034
|
+
W += w;
|
|
1035
|
+
WU += w * u;
|
|
1036
|
+
WUU += w * u * u;
|
|
1037
|
+
WX += w * x;
|
|
1038
|
+
WY += w * y;
|
|
1039
|
+
WU_X += w * u * x;
|
|
1040
|
+
WU_Y += w * u * y;
|
|
1041
|
+
}
|
|
1042
|
+
const denom = W * WUU - WU * WU;
|
|
1043
|
+
if (Math.abs(denom) <= EPS) {
|
|
1044
|
+
const iCurr = pointer._sampleCount % SAMPLE_COUNT;
|
|
1045
|
+
const iPrev = mod(pointer._sampleCount - 1);
|
|
1046
|
+
const dt = (pointer._prevTimes[iCurr] || 0) - (pointer._prevTimes[iPrev] || 0);
|
|
1047
|
+
if (dt > EPS) {
|
|
1048
|
+
const dx = (pointer._prevPositions[iCurr * 2] || 0) - (pointer._prevPositions[iPrev * 2] || 0);
|
|
1049
|
+
const dy = (pointer._prevPositions[iCurr * 2 + 1] || 0) - (pointer._prevPositions[iPrev * 2 + 1] || 0);
|
|
1050
|
+
pointer._velocity = [dx / dt, dy / dt];
|
|
1051
|
+
} else {
|
|
1052
|
+
pointer._velocity = [0, 0];
|
|
1053
|
+
}
|
|
1054
|
+
return;
|
|
1055
|
+
}
|
|
1056
|
+
const vx = (W * WU_X - WU * WX) / denom;
|
|
1057
|
+
const vy = (W * WU_Y - WU * WY) / denom;
|
|
1058
|
+
pointer._velocity = [vx, vy];
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
// src/components/ScaleWithZoom.ts
|
|
1062
|
+
var import_canvas_store13 = require("@woven-ecs/canvas-store");
|
|
1063
|
+
var import_core11 = require("@woven-ecs/core");
|
|
1064
|
+
var ScaleWithZoom = (0, import_canvas_store13.defineCanvasComponent)(
|
|
1065
|
+
{ name: "scaleWithZoom" },
|
|
1066
|
+
{
|
|
1067
|
+
/** Pivot point for scaling as [x, y] (0-1, default 0.5,0.5 = center) */
|
|
1068
|
+
anchor: import_core11.field.tuple(import_core11.field.float64(), 2).default([0.5, 0.5]),
|
|
1069
|
+
/** Initial position as [left, top] at zoom=1 */
|
|
1070
|
+
startPosition: import_core11.field.tuple(import_core11.field.float64(), 2).default([0, 0]),
|
|
1071
|
+
/** Initial size as [width, height] at zoom=1 */
|
|
1072
|
+
startSize: import_core11.field.tuple(import_core11.field.float64(), 2).default([0, 0]),
|
|
1073
|
+
/** Scale multiplier per dimension: [x, y] (0 = no zoom effect, 1 = full zoom effect, 0.5 = half effect) */
|
|
1074
|
+
scaleMultiplier: import_core11.field.tuple(import_core11.field.float64(), 2).default([1, 1])
|
|
1075
|
+
}
|
|
1076
|
+
);
|
|
1077
|
+
|
|
1078
|
+
// src/components/Shape.ts
|
|
1079
|
+
var import_canvas_store14 = require("@woven-ecs/canvas-store");
|
|
1080
|
+
var import_core12 = require("@woven-ecs/core");
|
|
1081
|
+
var StrokeKind = {
|
|
1082
|
+
Solid: "solid",
|
|
1083
|
+
Dashed: "dashed",
|
|
1084
|
+
None: "none"
|
|
1085
|
+
};
|
|
1086
|
+
var Shape = (0, import_canvas_store14.defineCanvasComponent)(
|
|
1087
|
+
{ name: "shape", sync: "document" },
|
|
1088
|
+
{
|
|
1089
|
+
/** The kind of shape to render (e.g. 'rectangle', 'ellipse', or custom shape key) */
|
|
1090
|
+
kind: import_core12.field.string().default("rectangle"),
|
|
1091
|
+
/** Stroke style */
|
|
1092
|
+
strokeKind: import_core12.field.enum(StrokeKind).default(StrokeKind.Solid),
|
|
1093
|
+
/** Stroke width in pixels */
|
|
1094
|
+
strokeWidth: import_core12.field.uint16().default(2),
|
|
1095
|
+
/** Stroke color - red component (0-255) */
|
|
1096
|
+
strokeRed: import_core12.field.uint8().default(0),
|
|
1097
|
+
/** Stroke color - green component (0-255) */
|
|
1098
|
+
strokeGreen: import_core12.field.uint8().default(0),
|
|
1099
|
+
/** Stroke color - blue component (0-255) */
|
|
1100
|
+
strokeBlue: import_core12.field.uint8().default(0),
|
|
1101
|
+
/** Stroke color - alpha component (0-255) */
|
|
1102
|
+
strokeAlpha: import_core12.field.uint8().default(255),
|
|
1103
|
+
/** Fill color - red component (0-255) */
|
|
1104
|
+
fillRed: import_core12.field.uint8().default(255),
|
|
1105
|
+
/** Fill color - green component (0-255) */
|
|
1106
|
+
fillGreen: import_core12.field.uint8().default(255),
|
|
1107
|
+
/** Fill color - blue component (0-255) */
|
|
1108
|
+
fillBlue: import_core12.field.uint8().default(255),
|
|
1109
|
+
/** Fill color - alpha component (0-255) */
|
|
1110
|
+
fillAlpha: import_core12.field.uint8().default(0)
|
|
1111
|
+
}
|
|
1112
|
+
);
|
|
1113
|
+
|
|
1114
|
+
// src/components/Text.ts
|
|
1115
|
+
var import_canvas_store15 = require("@woven-ecs/canvas-store");
|
|
1116
|
+
var import_core13 = require("@woven-ecs/core");
|
|
1117
|
+
|
|
1118
|
+
// src/types.ts
|
|
1119
|
+
var import_zod = require("zod");
|
|
1120
|
+
function generateUserColor() {
|
|
1121
|
+
const colors = [
|
|
1122
|
+
"#f43f5e",
|
|
1123
|
+
// rose
|
|
1124
|
+
"#ec4899",
|
|
1125
|
+
// pink
|
|
1126
|
+
"#a855f7",
|
|
1127
|
+
// purple
|
|
1128
|
+
"#6366f1",
|
|
1129
|
+
// indigo
|
|
1130
|
+
"#3b82f6",
|
|
1131
|
+
// blue
|
|
1132
|
+
"#0ea5e9",
|
|
1133
|
+
// sky
|
|
1134
|
+
"#14b8a6",
|
|
1135
|
+
// teal
|
|
1136
|
+
"#22c55e",
|
|
1137
|
+
// green
|
|
1138
|
+
"#eab308",
|
|
1139
|
+
// yellow
|
|
1140
|
+
"#f97316"
|
|
1141
|
+
// orange
|
|
1142
|
+
];
|
|
1143
|
+
return colors[Math.floor(Math.random() * colors.length)];
|
|
1144
|
+
}
|
|
1145
|
+
var UserData = import_zod.z.object({
|
|
1146
|
+
userId: import_zod.z.string().max(36).default(() => crypto.randomUUID()),
|
|
1147
|
+
sessionId: import_zod.z.string().max(36).default(() => crypto.randomUUID()),
|
|
1148
|
+
color: import_zod.z.string().max(7).default(generateUserColor),
|
|
1149
|
+
name: import_zod.z.string().max(100).default("Anonymous"),
|
|
1150
|
+
avatar: import_zod.z.string().max(500).default("")
|
|
1151
|
+
});
|
|
1152
|
+
function getPluginResources(ctx, pluginName) {
|
|
1153
|
+
const resources = ctx.resources;
|
|
1154
|
+
return resources.pluginResources[pluginName];
|
|
1155
|
+
}
|
|
1156
|
+
var GridOptions = import_zod.z.object({
|
|
1157
|
+
/**
|
|
1158
|
+
* Whether grid snapping is enabled.
|
|
1159
|
+
* @default true
|
|
1160
|
+
*/
|
|
1161
|
+
enabled: import_zod.z.boolean().default(true),
|
|
1162
|
+
/**
|
|
1163
|
+
* Whether resized/rotated objects must stay aligned to the grid.
|
|
1164
|
+
* If true, objects snap to grid during resize/rotate.
|
|
1165
|
+
* If false, objects scale proportionally to the transform box, which may
|
|
1166
|
+
* cause them to be unaligned with the grid.
|
|
1167
|
+
* @default false
|
|
1168
|
+
*/
|
|
1169
|
+
strict: import_zod.z.boolean().default(false),
|
|
1170
|
+
/**
|
|
1171
|
+
* Width of each grid column in world units.
|
|
1172
|
+
* @default 20
|
|
1173
|
+
*/
|
|
1174
|
+
colWidth: import_zod.z.number().nonnegative().default(20),
|
|
1175
|
+
/**
|
|
1176
|
+
* Height of each grid row in world units.
|
|
1177
|
+
* @default 20
|
|
1178
|
+
*/
|
|
1179
|
+
rowHeight: import_zod.z.number().nonnegative().default(20),
|
|
1180
|
+
/**
|
|
1181
|
+
* Angular snap increment in radians when grid is enabled.
|
|
1182
|
+
* @default Math.PI / 36 (5 degrees)
|
|
1183
|
+
*/
|
|
1184
|
+
snapAngleRad: import_zod.z.number().nonnegative().default(Math.PI / 36),
|
|
1185
|
+
/**
|
|
1186
|
+
* Angular snap increment in radians when shift key is held.
|
|
1187
|
+
* @default Math.PI / 12 (15 degrees)
|
|
1188
|
+
*/
|
|
1189
|
+
shiftSnapAngleRad: import_zod.z.number().nonnegative().default(Math.PI / 12)
|
|
1190
|
+
});
|
|
1191
|
+
var ControlsOptions = import_zod.z.object({
|
|
1192
|
+
/**
|
|
1193
|
+
* Tool activated by left mouse button.
|
|
1194
|
+
* @default 'select'
|
|
1195
|
+
*/
|
|
1196
|
+
leftMouseTool: import_zod.z.string().max(32).default("select"),
|
|
1197
|
+
/**
|
|
1198
|
+
* Tool activated by middle mouse button.
|
|
1199
|
+
* @default 'hand'
|
|
1200
|
+
*/
|
|
1201
|
+
middleMouseTool: import_zod.z.string().max(32).default("hand"),
|
|
1202
|
+
/**
|
|
1203
|
+
* Tool activated by right mouse button.
|
|
1204
|
+
* @default 'menu'
|
|
1205
|
+
*/
|
|
1206
|
+
rightMouseTool: import_zod.z.string().max(32).default("menu"),
|
|
1207
|
+
/**
|
|
1208
|
+
* Tool activated by mouse wheel.
|
|
1209
|
+
* @default 'scroll'
|
|
1210
|
+
*/
|
|
1211
|
+
wheelTool: import_zod.z.string().max(32).default("scroll"),
|
|
1212
|
+
/**
|
|
1213
|
+
* Tool activated by mouse wheel with modifier key held.
|
|
1214
|
+
* @default 'zoom'
|
|
1215
|
+
*/
|
|
1216
|
+
modWheelTool: import_zod.z.string().max(32).default("zoom")
|
|
1217
|
+
});
|
|
1218
|
+
var EditorOptionsSchema = import_zod.z.object({
|
|
1219
|
+
/**
|
|
1220
|
+
* Plugins to load.
|
|
1221
|
+
* Plugins are sorted by dependencies automatically.
|
|
1222
|
+
*/
|
|
1223
|
+
plugins: import_zod.z.array(import_zod.z.custom()).default([]),
|
|
1224
|
+
/**
|
|
1225
|
+
* Maximum number of entities.
|
|
1226
|
+
* @default 5_000
|
|
1227
|
+
*/
|
|
1228
|
+
maxEntities: import_zod.z.number().default(5e3),
|
|
1229
|
+
/**
|
|
1230
|
+
* User data for presence tracking.
|
|
1231
|
+
* All fields are optional - defaults will be applied.
|
|
1232
|
+
*/
|
|
1233
|
+
user: import_zod.z.custom().default({}),
|
|
1234
|
+
/**
|
|
1235
|
+
* Grid configuration for snap-to-grid behavior.
|
|
1236
|
+
* All fields are optional - defaults will be applied.
|
|
1237
|
+
*/
|
|
1238
|
+
grid: import_zod.z.custom().default({}),
|
|
1239
|
+
/**
|
|
1240
|
+
* Custom block definitions.
|
|
1241
|
+
* Accepts partial block definitions - defaults will be applied automatically.
|
|
1242
|
+
*/
|
|
1243
|
+
blockDefs: import_zod.z.array(import_zod.z.custom()).default([]),
|
|
1244
|
+
/**
|
|
1245
|
+
* Keybind definitions for keyboard shortcuts.
|
|
1246
|
+
* These map key combinations to plugin commands.
|
|
1247
|
+
*/
|
|
1248
|
+
keybinds: import_zod.z.array(import_zod.z.custom()).default([]),
|
|
1249
|
+
/**
|
|
1250
|
+
* Custom cursor definitions.
|
|
1251
|
+
* Override default cursors or define new ones for transform operations.
|
|
1252
|
+
*/
|
|
1253
|
+
cursors: import_zod.z.record(import_zod.z.string(), import_zod.z.custom()).default({}),
|
|
1254
|
+
/**
|
|
1255
|
+
* Custom components to register without creating a plugin.
|
|
1256
|
+
*/
|
|
1257
|
+
components: import_zod.z.array(import_zod.z.custom()).default([]),
|
|
1258
|
+
/**
|
|
1259
|
+
* Custom singletons to register without creating a plugin.
|
|
1260
|
+
*/
|
|
1261
|
+
singletons: import_zod.z.array(import_zod.z.custom()).default([]),
|
|
1262
|
+
/**
|
|
1263
|
+
* Custom systems to register without creating a plugin.
|
|
1264
|
+
* Each system specifies its phase and priority.
|
|
1265
|
+
*/
|
|
1266
|
+
systems: import_zod.z.array(import_zod.z.custom()).default([]),
|
|
1267
|
+
/**
|
|
1268
|
+
* Custom font families to load and make available in the font selector.
|
|
1269
|
+
* Fonts will be loaded automatically during editor initialization.
|
|
1270
|
+
*/
|
|
1271
|
+
fonts: import_zod.z.array(import_zod.z.custom()).default([]),
|
|
1272
|
+
/**
|
|
1273
|
+
* If true, keybinds from plugins will be ignored.
|
|
1274
|
+
* Use this when you want full control over keybinds.
|
|
1275
|
+
*/
|
|
1276
|
+
omitPluginKeybinds: import_zod.z.boolean().default(false),
|
|
1277
|
+
/**
|
|
1278
|
+
* If true, cursors from plugins will be ignored.
|
|
1279
|
+
* Use this when you want full control over cursors.
|
|
1280
|
+
*/
|
|
1281
|
+
omitPluginCursors: import_zod.z.boolean().default(false),
|
|
1282
|
+
/**
|
|
1283
|
+
* If true, fonts from plugins will be ignored.
|
|
1284
|
+
* Use this when you want full control over fonts.
|
|
1285
|
+
*/
|
|
1286
|
+
omitPluginFonts: import_zod.z.boolean().default(false),
|
|
1287
|
+
/**
|
|
1288
|
+
* Initial controls configuration.
|
|
1289
|
+
* Override default tool mappings for mouse buttons, wheel, etc.
|
|
1290
|
+
*
|
|
1291
|
+
* @example
|
|
1292
|
+
* ```typescript
|
|
1293
|
+
* controls: {
|
|
1294
|
+
* leftMouseTool: 'pen', // Start with pen tool active
|
|
1295
|
+
* middleMouseTool: 'hand', // Middle mouse pans
|
|
1296
|
+
* }
|
|
1297
|
+
* ```
|
|
1298
|
+
*/
|
|
1299
|
+
controls: import_zod.z.custom().optional()
|
|
1300
|
+
});
|
|
1301
|
+
var Keybind = import_zod.z.object({
|
|
1302
|
+
/** The command to execute when this keybind is triggered */
|
|
1303
|
+
command: import_zod.z.string(),
|
|
1304
|
+
/** The key index from the Key constants (e.g., Key.A, Key.Delete) */
|
|
1305
|
+
key: import_zod.z.number(),
|
|
1306
|
+
/** Whether the modifier key (Cmd on Mac, Ctrl on Windows) must be held */
|
|
1307
|
+
mod: import_zod.z.boolean().optional(),
|
|
1308
|
+
/** Whether the Shift key must be held */
|
|
1309
|
+
shift: import_zod.z.boolean().optional()
|
|
1310
|
+
});
|
|
1311
|
+
var CursorDef = import_zod.z.object({
|
|
1312
|
+
/** Function that generates the SVG string for a given rotation angle (in radians) */
|
|
1313
|
+
makeSvg: import_zod.z.function({ input: import_zod.z.tuple([import_zod.z.number()]), output: import_zod.z.string() }),
|
|
1314
|
+
/** Hotspot coordinates [x, y] for the cursor */
|
|
1315
|
+
hotspot: import_zod.z.tuple([import_zod.z.number(), import_zod.z.number()]),
|
|
1316
|
+
/** Base rotation offset applied before the dynamic rotation */
|
|
1317
|
+
rotationOffset: import_zod.z.number()
|
|
1318
|
+
});
|
|
1319
|
+
var VerticalAlignment = {
|
|
1320
|
+
Top: "top",
|
|
1321
|
+
Center: "center",
|
|
1322
|
+
Bottom: "bottom"
|
|
1323
|
+
};
|
|
1324
|
+
var TextAlignment = {
|
|
1325
|
+
Left: "left",
|
|
1326
|
+
Center: "center",
|
|
1327
|
+
Right: "right",
|
|
1328
|
+
Justify: "justify"
|
|
1329
|
+
};
|
|
1330
|
+
var BlockDefEditOptions = import_zod.z.object({
|
|
1331
|
+
canEdit: import_zod.z.boolean().default(false),
|
|
1332
|
+
removeWhenTextEmpty: import_zod.z.boolean().default(false)
|
|
1333
|
+
});
|
|
1334
|
+
var BlockDefConnectors = import_zod.z.object({
|
|
1335
|
+
/** Whether connectors are enabled for this block type */
|
|
1336
|
+
enabled: import_zod.z.boolean().default(true),
|
|
1337
|
+
/** UV coordinates of terminal positions (0-1 range, where [0,0] is top-left) */
|
|
1338
|
+
terminals: import_zod.z.array(import_zod.z.tuple([import_zod.z.number(), import_zod.z.number()])).default([
|
|
1339
|
+
[0.5, 0],
|
|
1340
|
+
// top
|
|
1341
|
+
[0.5, 1],
|
|
1342
|
+
// bottom
|
|
1343
|
+
[0.5, 0.5],
|
|
1344
|
+
// center
|
|
1345
|
+
[0, 0.5],
|
|
1346
|
+
// left
|
|
1347
|
+
[1, 0.5]
|
|
1348
|
+
// right
|
|
1349
|
+
])
|
|
1350
|
+
});
|
|
1351
|
+
var Stratum = import_zod.z.enum(["background", "content", "overlay"]);
|
|
1352
|
+
var BlockDef2 = import_zod.z.object({
|
|
1353
|
+
tag: import_zod.z.string(),
|
|
1354
|
+
stratum: Stratum.default("content"),
|
|
1355
|
+
editOptions: BlockDefEditOptions.default(BlockDefEditOptions.parse({})),
|
|
1356
|
+
components: import_zod.z.array(import_zod.z.custom()).default([]),
|
|
1357
|
+
resizeMode: import_zod.z.enum(["scale", "text", "free", "groupOnly"]).default("scale"),
|
|
1358
|
+
canRotate: import_zod.z.boolean().default(true),
|
|
1359
|
+
canScale: import_zod.z.boolean().default(true),
|
|
1360
|
+
connectors: BlockDefConnectors.default(BlockDefConnectors.parse({}))
|
|
1361
|
+
});
|
|
1362
|
+
|
|
1363
|
+
// src/components/Text.ts
|
|
1364
|
+
var Text = (0, import_canvas_store15.defineCanvasComponent)(
|
|
1365
|
+
{ name: "text", sync: "document" },
|
|
1366
|
+
{
|
|
1367
|
+
/** HTML content (supports rich text formatting) */
|
|
1368
|
+
content: import_core13.field.string().max(1e4).default(""),
|
|
1369
|
+
/** Font size in pixels */
|
|
1370
|
+
fontSizePx: import_core13.field.float64().default(24),
|
|
1371
|
+
/** Font family name */
|
|
1372
|
+
fontFamily: import_core13.field.string().max(64).default("Figtree"),
|
|
1373
|
+
/** Line height multiplier */
|
|
1374
|
+
lineHeight: import_core13.field.float64().default(1.2),
|
|
1375
|
+
/** Letter spacing in em units */
|
|
1376
|
+
letterSpacingEm: import_core13.field.float64().default(0),
|
|
1377
|
+
/** Whether width is constrained (text wraps) */
|
|
1378
|
+
constrainWidth: import_core13.field.boolean().default(true),
|
|
1379
|
+
/** Default text alignment for new paragraphs */
|
|
1380
|
+
defaultAlignment: import_core13.field.enum(TextAlignment).default(TextAlignment.Left)
|
|
1381
|
+
}
|
|
1382
|
+
);
|
|
1383
|
+
|
|
1384
|
+
// src/components/User.ts
|
|
1385
|
+
var import_canvas_store16 = require("@woven-ecs/canvas-store");
|
|
1386
|
+
var import_core14 = require("@woven-ecs/core");
|
|
1387
|
+
var User = (0, import_canvas_store16.defineCanvasComponent)(
|
|
1388
|
+
{ name: "user", sync: "ephemeral" },
|
|
1389
|
+
{
|
|
1390
|
+
userId: import_core14.field.string().max(36),
|
|
1391
|
+
sessionId: import_core14.field.string().max(36),
|
|
1392
|
+
color: import_core14.field.string().max(7),
|
|
1393
|
+
name: import_core14.field.string().max(100),
|
|
1394
|
+
avatar: import_core14.field.string().max(500),
|
|
1395
|
+
position: import_core14.field.tuple(import_core14.field.float32(), 2).default([0, 0])
|
|
1396
|
+
}
|
|
1397
|
+
);
|
|
1398
|
+
|
|
1399
|
+
// src/components/VerticalAlign.ts
|
|
1400
|
+
var import_canvas_store17 = require("@woven-ecs/canvas-store");
|
|
1401
|
+
var import_core15 = require("@woven-ecs/core");
|
|
1402
|
+
var VerticalAlign = (0, import_canvas_store17.defineCanvasComponent)(
|
|
1403
|
+
{ name: "verticalAlign", sync: "document" },
|
|
1404
|
+
{
|
|
1405
|
+
value: import_core15.field.enum(VerticalAlignment).default(VerticalAlignment.Top)
|
|
1406
|
+
}
|
|
1407
|
+
);
|
|
1408
|
+
|
|
1409
|
+
// src/constants.ts
|
|
1410
|
+
var PLUGIN_NAME = "core";
|
|
1411
|
+
var STRATUM_ORDER = {
|
|
1412
|
+
background: 0,
|
|
1413
|
+
content: 1,
|
|
1414
|
+
overlay: 2
|
|
1415
|
+
};
|
|
1416
|
+
|
|
1417
|
+
// src/singletons/index.ts
|
|
1418
|
+
var singletons_exports = {};
|
|
1419
|
+
__export(singletons_exports, {
|
|
1420
|
+
Camera: () => Camera,
|
|
1421
|
+
Controls: () => Controls,
|
|
1422
|
+
Cursor: () => Cursor,
|
|
1423
|
+
Frame: () => Frame,
|
|
1424
|
+
Grid: () => Grid,
|
|
1425
|
+
Intersect: () => Intersect,
|
|
1426
|
+
Key: () => Key,
|
|
1427
|
+
Keyboard: () => Keyboard,
|
|
1428
|
+
Mouse: () => Mouse,
|
|
1429
|
+
RankBounds: () => RankBounds,
|
|
1430
|
+
ScaleWithZoomState: () => ScaleWithZoomState,
|
|
1431
|
+
Screen: () => Screen,
|
|
1432
|
+
clearBits: () => clearBits,
|
|
1433
|
+
codeToIndex: () => codeToIndex,
|
|
1434
|
+
setBit: () => setBit
|
|
1435
|
+
});
|
|
1436
|
+
|
|
1437
|
+
// src/singletons/Camera.ts
|
|
1438
|
+
var import_canvas_store19 = require("@woven-ecs/canvas-store");
|
|
1439
|
+
var import_core17 = require("@woven-ecs/core");
|
|
1440
|
+
|
|
1441
|
+
// src/singletons/Screen.ts
|
|
1442
|
+
var import_canvas_store18 = require("@woven-ecs/canvas-store");
|
|
1443
|
+
var import_core16 = require("@woven-ecs/core");
|
|
1444
|
+
var ScreenSchema = {
|
|
1445
|
+
/** Width of the editor element in pixels */
|
|
1446
|
+
width: import_core16.field.float64().default(0),
|
|
1447
|
+
/** Height of the editor element in pixels */
|
|
1448
|
+
height: import_core16.field.float64().default(0),
|
|
1449
|
+
/** Left offset of the editor element relative to the viewport */
|
|
1450
|
+
left: import_core16.field.float64().default(0),
|
|
1451
|
+
/** Top offset of the editor element relative to the viewport */
|
|
1452
|
+
top: import_core16.field.float64().default(0)
|
|
1453
|
+
};
|
|
1454
|
+
var ScreenDef = class extends import_canvas_store18.CanvasSingletonDef {
|
|
1455
|
+
constructor() {
|
|
1456
|
+
super({ name: "screen" }, ScreenSchema);
|
|
1457
|
+
}
|
|
1458
|
+
/** Get screen dimensions as [width, height] */
|
|
1459
|
+
getSize(ctx) {
|
|
1460
|
+
const s = this.read(ctx);
|
|
1461
|
+
return [s.width, s.height];
|
|
1462
|
+
}
|
|
1463
|
+
/** Get screen position as [left, top] */
|
|
1464
|
+
getPosition(ctx) {
|
|
1465
|
+
const s = this.read(ctx);
|
|
1466
|
+
return [s.left, s.top];
|
|
1467
|
+
}
|
|
1468
|
+
/** Get the center point of the screen */
|
|
1469
|
+
getCenter(ctx) {
|
|
1470
|
+
const s = this.read(ctx);
|
|
1471
|
+
return [s.left + s.width / 2, s.top + s.height / 2];
|
|
1472
|
+
}
|
|
1473
|
+
};
|
|
1474
|
+
var Screen = new ScreenDef();
|
|
1475
|
+
|
|
1476
|
+
// src/singletons/Camera.ts
|
|
1477
|
+
var CameraSchema = {
|
|
1478
|
+
/** Top position of the camera in world coordinates */
|
|
1479
|
+
top: import_core17.field.float64().default(0),
|
|
1480
|
+
/** Left position of the camera in world coordinates */
|
|
1481
|
+
left: import_core17.field.float64().default(0),
|
|
1482
|
+
/** Zoom level (1 = 100%, 2 = 200%, 0.5 = 50%) */
|
|
1483
|
+
zoom: import_core17.field.float64().default(1),
|
|
1484
|
+
/** Whether the camera viewport intersects any blocks */
|
|
1485
|
+
canSeeBlocks: import_core17.field.boolean().default(true),
|
|
1486
|
+
/** Reference to a block that the camera can currently see (for optimization) */
|
|
1487
|
+
lastSeenBlock: import_core17.field.ref()
|
|
1488
|
+
};
|
|
1489
|
+
var CameraDef = class extends import_canvas_store19.CanvasSingletonDef {
|
|
1490
|
+
constructor() {
|
|
1491
|
+
super({ name: "camera", sync: "local" }, CameraSchema);
|
|
1492
|
+
}
|
|
1493
|
+
/**
|
|
1494
|
+
* Convert screen coordinates to world coordinates.
|
|
1495
|
+
* @param ctx - ECS context
|
|
1496
|
+
* @param screenPos - Position in screen pixels [x, y]
|
|
1497
|
+
* @returns Position in world coordinates [x, y]
|
|
1498
|
+
*/
|
|
1499
|
+
toWorld(ctx, screenPos) {
|
|
1500
|
+
const camera = this.read(ctx);
|
|
1501
|
+
const worldX = camera.left + screenPos[0] / camera.zoom;
|
|
1502
|
+
const worldY = camera.top + screenPos[1] / camera.zoom;
|
|
1503
|
+
return [worldX, worldY];
|
|
1504
|
+
}
|
|
1505
|
+
/**
|
|
1506
|
+
* Convert world coordinates to screen coordinates.
|
|
1507
|
+
* @param ctx - ECS context
|
|
1508
|
+
* @param worldPos - Position in world coordinates [x, y]
|
|
1509
|
+
* @returns Position in screen pixels [x, y]
|
|
1510
|
+
*/
|
|
1511
|
+
toScreen(ctx, worldPos) {
|
|
1512
|
+
const camera = this.read(ctx);
|
|
1513
|
+
const screenX = (worldPos[0] - camera.left) * camera.zoom;
|
|
1514
|
+
const screenY = (worldPos[1] - camera.top) * camera.zoom;
|
|
1515
|
+
return [screenX, screenY];
|
|
1516
|
+
}
|
|
1517
|
+
/**
|
|
1518
|
+
* Get the world coordinates of the viewport center.
|
|
1519
|
+
* @param ctx - ECS context
|
|
1520
|
+
* @returns Center position in world coordinates [x, y]
|
|
1521
|
+
*/
|
|
1522
|
+
getWorldCenter(ctx) {
|
|
1523
|
+
const camera = this.read(ctx);
|
|
1524
|
+
const screen = Screen.read(ctx);
|
|
1525
|
+
return [camera.left + screen.width / camera.zoom / 2, camera.top + screen.height / camera.zoom / 2];
|
|
1526
|
+
}
|
|
1527
|
+
/**
|
|
1528
|
+
* Get the world-space bounds of the visible viewport.
|
|
1529
|
+
* @param ctx - ECS context
|
|
1530
|
+
* @returns Bounds as { left, top, right, bottom } in world coordinates
|
|
1531
|
+
*/
|
|
1532
|
+
getWorldBounds(ctx) {
|
|
1533
|
+
const camera = this.read(ctx);
|
|
1534
|
+
const screen = Screen.read(ctx);
|
|
1535
|
+
return {
|
|
1536
|
+
left: camera.left,
|
|
1537
|
+
top: camera.top,
|
|
1538
|
+
right: camera.left + screen.width / camera.zoom,
|
|
1539
|
+
bottom: camera.top + screen.height / camera.zoom
|
|
1540
|
+
};
|
|
1541
|
+
}
|
|
1542
|
+
/**
|
|
1543
|
+
* Get the camera viewport as an AABB tuple [left, top, right, bottom].
|
|
1544
|
+
* @param ctx - ECS context
|
|
1545
|
+
* @param out - Optional output array to write to (avoids allocation)
|
|
1546
|
+
* @returns AABB tuple in world coordinates
|
|
1547
|
+
*/
|
|
1548
|
+
getAabb(ctx, out) {
|
|
1549
|
+
const camera = this.read(ctx);
|
|
1550
|
+
const screen = Screen.read(ctx);
|
|
1551
|
+
const result = out ?? [0, 0, 0, 0];
|
|
1552
|
+
result[0] = camera.left;
|
|
1553
|
+
result[1] = camera.top;
|
|
1554
|
+
result[2] = camera.left + screen.width / camera.zoom;
|
|
1555
|
+
result[3] = camera.top + screen.height / camera.zoom;
|
|
1556
|
+
return result;
|
|
1557
|
+
}
|
|
1558
|
+
};
|
|
1559
|
+
var Camera = new CameraDef();
|
|
1560
|
+
|
|
1561
|
+
// src/singletons/Controls.ts
|
|
1562
|
+
var import_canvas_store20 = require("@woven-ecs/canvas-store");
|
|
1563
|
+
var import_core18 = require("@woven-ecs/core");
|
|
1564
|
+
var ControlsSchema = {
|
|
1565
|
+
/** Tool activated by left mouse button */
|
|
1566
|
+
leftMouseTool: import_core18.field.string().max(32).default("select"),
|
|
1567
|
+
/** Tool activated by middle mouse button */
|
|
1568
|
+
middleMouseTool: import_core18.field.string().max(32).default("hand"),
|
|
1569
|
+
/** Tool activated by right mouse button */
|
|
1570
|
+
rightMouseTool: import_core18.field.string().max(32).default("menu"),
|
|
1571
|
+
/** Tool activated by mouse wheel */
|
|
1572
|
+
wheelTool: import_core18.field.string().max(32).default("scroll"),
|
|
1573
|
+
/** Tool activated by mouse wheel with modifier key held */
|
|
1574
|
+
modWheelTool: import_core18.field.string().max(32).default("zoom"),
|
|
1575
|
+
/** JSON snapshot of block to place on next click (empty string = no placement active) */
|
|
1576
|
+
heldSnapshot: import_core18.field.string().max(4096).default("")
|
|
1577
|
+
};
|
|
1578
|
+
var ControlsDef = class extends import_canvas_store20.CanvasSingletonDef {
|
|
1579
|
+
constructor() {
|
|
1580
|
+
super({ name: "controls" }, ControlsSchema);
|
|
1581
|
+
}
|
|
1582
|
+
/**
|
|
1583
|
+
* Get the pointer buttons that are mapped to the given tools.
|
|
1584
|
+
* @param ctx - ECS context
|
|
1585
|
+
* @param tools - Tool names to check
|
|
1586
|
+
* @returns Array of PointerButton values that activate any of the given tools
|
|
1587
|
+
*/
|
|
1588
|
+
getButtons(ctx, ...tools) {
|
|
1589
|
+
const controls = this.read(ctx);
|
|
1590
|
+
const buttons = [];
|
|
1591
|
+
if (tools.includes(controls.leftMouseTool)) {
|
|
1592
|
+
buttons.push(PointerButton.Left);
|
|
1593
|
+
}
|
|
1594
|
+
if (tools.includes(controls.middleMouseTool)) {
|
|
1595
|
+
buttons.push(PointerButton.Middle);
|
|
1596
|
+
}
|
|
1597
|
+
if (tools.includes(controls.rightMouseTool)) {
|
|
1598
|
+
buttons.push(PointerButton.Right);
|
|
1599
|
+
}
|
|
1600
|
+
return buttons;
|
|
1601
|
+
}
|
|
1602
|
+
/**
|
|
1603
|
+
* Check if a wheel tool is active based on modifier key state.
|
|
1604
|
+
* @param ctx - ECS context
|
|
1605
|
+
* @param tool - Tool name to check
|
|
1606
|
+
* @param modDown - Whether the modifier key is held
|
|
1607
|
+
* @returns True if the given tool is active for wheel input
|
|
1608
|
+
*/
|
|
1609
|
+
wheelActive(ctx, tool, modDown) {
|
|
1610
|
+
const controls = this.read(ctx);
|
|
1611
|
+
return controls.wheelTool === tool && !modDown || controls.modWheelTool === tool && modDown;
|
|
1612
|
+
}
|
|
1613
|
+
};
|
|
1614
|
+
var Controls = new ControlsDef();
|
|
1615
|
+
|
|
1616
|
+
// src/singletons/Cursor.ts
|
|
1617
|
+
var import_canvas_store21 = require("@woven-ecs/canvas-store");
|
|
1618
|
+
var import_core19 = require("@woven-ecs/core");
|
|
1619
|
+
var CursorSchema = {
|
|
1620
|
+
/** Base cursor kind (from current tool) */
|
|
1621
|
+
cursorKind: import_core19.field.string().max(64).default("select"),
|
|
1622
|
+
/** Base cursor rotation in radians */
|
|
1623
|
+
rotation: import_core19.field.float64().default(0),
|
|
1624
|
+
/** Context-specific cursor kind (overrides cursorKind when set, e.g., during drag/hover) */
|
|
1625
|
+
contextCursorKind: import_core19.field.string().max(64).default(""),
|
|
1626
|
+
/** Context cursor rotation in radians */
|
|
1627
|
+
contextRotation: import_core19.field.float64().default(0)
|
|
1628
|
+
};
|
|
1629
|
+
var CursorDef2 = class extends import_canvas_store21.CanvasSingletonDef {
|
|
1630
|
+
constructor() {
|
|
1631
|
+
super({ name: "cursor" }, CursorSchema);
|
|
1632
|
+
}
|
|
1633
|
+
/**
|
|
1634
|
+
* Get the effective cursor kind and rotation (context if set, otherwise base).
|
|
1635
|
+
*/
|
|
1636
|
+
getEffective(ctx) {
|
|
1637
|
+
const cursor = this.read(ctx);
|
|
1638
|
+
if (cursor.contextCursorKind) {
|
|
1639
|
+
return {
|
|
1640
|
+
cursorKind: cursor.contextCursorKind,
|
|
1641
|
+
rotation: cursor.contextRotation
|
|
1642
|
+
};
|
|
1643
|
+
}
|
|
1644
|
+
return { cursorKind: cursor.cursorKind, rotation: cursor.rotation };
|
|
1645
|
+
}
|
|
1646
|
+
/**
|
|
1647
|
+
* Set the base cursor kind and rotation.
|
|
1648
|
+
*/
|
|
1649
|
+
setCursor(ctx, cursorKind, rotation = 0) {
|
|
1650
|
+
const cursor = this.write(ctx);
|
|
1651
|
+
cursor.cursorKind = cursorKind;
|
|
1652
|
+
cursor.rotation = rotation;
|
|
1653
|
+
}
|
|
1654
|
+
/**
|
|
1655
|
+
* Set the context cursor kind and rotation (temporary override).
|
|
1656
|
+
*/
|
|
1657
|
+
setContextCursor(ctx, cursorKind, rotation = 0) {
|
|
1658
|
+
const cursor = this.write(ctx);
|
|
1659
|
+
cursor.contextCursorKind = cursorKind;
|
|
1660
|
+
cursor.contextRotation = rotation;
|
|
1661
|
+
}
|
|
1662
|
+
/**
|
|
1663
|
+
* Clear the context cursor.
|
|
1664
|
+
*/
|
|
1665
|
+
clearContextCursor(ctx) {
|
|
1666
|
+
const cursor = this.write(ctx);
|
|
1667
|
+
cursor.contextCursorKind = "";
|
|
1668
|
+
cursor.contextRotation = 0;
|
|
1669
|
+
}
|
|
1670
|
+
};
|
|
1671
|
+
var Cursor = new CursorDef2();
|
|
1672
|
+
|
|
1673
|
+
// src/singletons/Frame.ts
|
|
1674
|
+
var import_canvas_store22 = require("@woven-ecs/canvas-store");
|
|
1675
|
+
var import_core20 = require("@woven-ecs/core");
|
|
1676
|
+
var Frame = (0, import_canvas_store22.defineCanvasSingleton)(
|
|
1677
|
+
{ name: "frame" },
|
|
1678
|
+
{
|
|
1679
|
+
/** Current frame number (increments each tick) */
|
|
1680
|
+
number: import_core20.field.uint32().default(0),
|
|
1681
|
+
/** Time since last frame in seconds */
|
|
1682
|
+
delta: import_core20.field.float64().default(0),
|
|
1683
|
+
/** Timestamp of current frame in milliseconds (from performance.now()) */
|
|
1684
|
+
time: import_core20.field.float64().default(0),
|
|
1685
|
+
/** Timestamp of previous frame in milliseconds (0 if first frame) */
|
|
1686
|
+
lastTime: import_core20.field.float64().default(0)
|
|
1687
|
+
}
|
|
1688
|
+
);
|
|
1689
|
+
|
|
1690
|
+
// src/singletons/Grid.ts
|
|
1691
|
+
var import_canvas_store23 = require("@woven-ecs/canvas-store");
|
|
1692
|
+
var import_core21 = require("@woven-ecs/core");
|
|
1693
|
+
var GridSchema = {
|
|
1694
|
+
/** Whether grid snapping is enabled */
|
|
1695
|
+
enabled: import_core21.field.boolean().default(false),
|
|
1696
|
+
/** Whether resized/rotated objects must stay aligned to the grid */
|
|
1697
|
+
strict: import_core21.field.boolean().default(false),
|
|
1698
|
+
/** Width of each grid column in world units */
|
|
1699
|
+
colWidth: import_core21.field.float64().default(20),
|
|
1700
|
+
/** Height of each grid row in world units */
|
|
1701
|
+
rowHeight: import_core21.field.float64().default(20),
|
|
1702
|
+
/** Angular snap increment in radians when grid is enabled */
|
|
1703
|
+
snapAngleRad: import_core21.field.float64().default(Math.PI / 36),
|
|
1704
|
+
/** Angular snap increment in radians when shift key is held */
|
|
1705
|
+
shiftSnapAngleRad: import_core21.field.float64().default(Math.PI / 12)
|
|
1706
|
+
};
|
|
1707
|
+
var GridDef = class extends import_canvas_store23.CanvasSingletonDef {
|
|
1708
|
+
constructor() {
|
|
1709
|
+
super({ name: "grid" }, GridSchema);
|
|
1710
|
+
}
|
|
1711
|
+
/**
|
|
1712
|
+
* Snap a position to the grid.
|
|
1713
|
+
* @param ctx - ECS context
|
|
1714
|
+
* @param position - Position to snap [x, y] (mutated in place)
|
|
1715
|
+
*/
|
|
1716
|
+
snapPosition(ctx, position) {
|
|
1717
|
+
const grid = this.read(ctx);
|
|
1718
|
+
if (!grid.enabled) return;
|
|
1719
|
+
if (grid.colWidth !== 0) {
|
|
1720
|
+
position[0] = Math.round(position[0] / grid.colWidth) * grid.colWidth;
|
|
1721
|
+
}
|
|
1722
|
+
if (grid.rowHeight !== 0) {
|
|
1723
|
+
position[1] = Math.round(position[1] / grid.rowHeight) * grid.rowHeight;
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
/**
|
|
1727
|
+
* Snap a size to the grid (minimum one grid cell).
|
|
1728
|
+
* @param ctx - ECS context
|
|
1729
|
+
* @param size - Size to snap [width, height] (mutated in place)
|
|
1730
|
+
*/
|
|
1731
|
+
snapSize(ctx, size) {
|
|
1732
|
+
const grid = this.read(ctx);
|
|
1733
|
+
if (!grid.enabled) return;
|
|
1734
|
+
if (grid.colWidth !== 0) {
|
|
1735
|
+
size[0] = Math.max(grid.colWidth, Math.round(size[0] / grid.colWidth) * grid.colWidth);
|
|
1736
|
+
}
|
|
1737
|
+
if (grid.rowHeight !== 0) {
|
|
1738
|
+
size[1] = Math.max(grid.rowHeight, Math.round(size[1] / grid.rowHeight) * grid.rowHeight);
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
/**
|
|
1742
|
+
* Snap a value to the grid column width.
|
|
1743
|
+
* @param ctx - ECS context
|
|
1744
|
+
* @param value - Value to snap
|
|
1745
|
+
* @returns Snapped value, or original if grid disabled
|
|
1746
|
+
*/
|
|
1747
|
+
snapX(ctx, value) {
|
|
1748
|
+
const grid = this.read(ctx);
|
|
1749
|
+
if (!grid.enabled || grid.colWidth === 0) return value;
|
|
1750
|
+
return Math.round(value / grid.colWidth) * grid.colWidth;
|
|
1751
|
+
}
|
|
1752
|
+
/**
|
|
1753
|
+
* Snap a value to the grid row height.
|
|
1754
|
+
* @param ctx - ECS context
|
|
1755
|
+
* @param value - Value to snap
|
|
1756
|
+
* @returns Snapped value, or original if grid disabled
|
|
1757
|
+
*/
|
|
1758
|
+
snapY(ctx, value) {
|
|
1759
|
+
const grid = this.read(ctx);
|
|
1760
|
+
if (!grid.enabled || grid.rowHeight === 0) return value;
|
|
1761
|
+
return Math.round(value / grid.rowHeight) * grid.rowHeight;
|
|
1762
|
+
}
|
|
1763
|
+
};
|
|
1764
|
+
var Grid = new GridDef();
|
|
1765
|
+
|
|
1766
|
+
// src/singletons/Intersect.ts
|
|
1767
|
+
var import_canvas_store24 = require("@woven-ecs/canvas-store");
|
|
1768
|
+
var import_core22 = require("@woven-ecs/core");
|
|
1769
|
+
var IntersectSchema = {
|
|
1770
|
+
// Store up to 5 intersected entity IDs
|
|
1771
|
+
entity1: import_core22.field.ref(),
|
|
1772
|
+
entity2: import_core22.field.ref(),
|
|
1773
|
+
entity3: import_core22.field.ref(),
|
|
1774
|
+
entity4: import_core22.field.ref(),
|
|
1775
|
+
entity5: import_core22.field.ref()
|
|
1776
|
+
};
|
|
1777
|
+
var IntersectDef = class extends import_canvas_store24.CanvasSingletonDef {
|
|
1778
|
+
constructor() {
|
|
1779
|
+
super({ name: "intersect" }, IntersectSchema);
|
|
1780
|
+
}
|
|
1781
|
+
/**
|
|
1782
|
+
* Get the topmost intersected entity.
|
|
1783
|
+
*/
|
|
1784
|
+
getTop(ctx) {
|
|
1785
|
+
return this.read(ctx).entity1;
|
|
1786
|
+
}
|
|
1787
|
+
/**
|
|
1788
|
+
* Get all intersected entities as an array.
|
|
1789
|
+
*/
|
|
1790
|
+
getAll(ctx) {
|
|
1791
|
+
const intersect = this.read(ctx);
|
|
1792
|
+
const result = [];
|
|
1793
|
+
if (intersect.entity1 !== null) result.push(intersect.entity1);
|
|
1794
|
+
if (intersect.entity2 !== null) result.push(intersect.entity2);
|
|
1795
|
+
if (intersect.entity3 !== null) result.push(intersect.entity3);
|
|
1796
|
+
if (intersect.entity4 !== null) result.push(intersect.entity4);
|
|
1797
|
+
if (intersect.entity5 !== null) result.push(intersect.entity5);
|
|
1798
|
+
return result;
|
|
1799
|
+
}
|
|
1800
|
+
/**
|
|
1801
|
+
* Set intersected entities from an array.
|
|
1802
|
+
*/
|
|
1803
|
+
setAll(ctx, entities) {
|
|
1804
|
+
const intersect = this.write(ctx);
|
|
1805
|
+
intersect.entity1 = entities[0] ?? null;
|
|
1806
|
+
intersect.entity2 = entities[1] ?? null;
|
|
1807
|
+
intersect.entity3 = entities[2] ?? null;
|
|
1808
|
+
intersect.entity4 = entities[3] ?? null;
|
|
1809
|
+
intersect.entity5 = entities[4] ?? null;
|
|
1810
|
+
}
|
|
1811
|
+
/**
|
|
1812
|
+
* Clear all intersections.
|
|
1813
|
+
*/
|
|
1814
|
+
clear(ctx) {
|
|
1815
|
+
const intersect = this.write(ctx);
|
|
1816
|
+
intersect.entity1 = null;
|
|
1817
|
+
intersect.entity2 = null;
|
|
1818
|
+
intersect.entity3 = null;
|
|
1819
|
+
intersect.entity4 = null;
|
|
1820
|
+
intersect.entity5 = null;
|
|
1821
|
+
}
|
|
1822
|
+
};
|
|
1823
|
+
var Intersect = new IntersectDef();
|
|
1824
|
+
|
|
1825
|
+
// src/singletons/Keyboard.ts
|
|
1826
|
+
var import_canvas_store25 = require("@woven-ecs/canvas-store");
|
|
1827
|
+
var import_core23 = require("@woven-ecs/core");
|
|
1828
|
+
var KEY_BUFFER_SIZE = 32;
|
|
1829
|
+
var KeyboardSchema = {
|
|
1830
|
+
/**
|
|
1831
|
+
* Buffer where each bit represents whether a key is currently pressed.
|
|
1832
|
+
* Uses field.buffer for zero-allocation subarray views.
|
|
1833
|
+
*/
|
|
1834
|
+
keysDown: import_core23.field.buffer(import_core23.field.uint8()).size(KEY_BUFFER_SIZE),
|
|
1835
|
+
/**
|
|
1836
|
+
* Buffer for key-down triggers (true for exactly 1 frame when key is pressed).
|
|
1837
|
+
* Uses field.buffer for zero-allocation subarray views.
|
|
1838
|
+
*/
|
|
1839
|
+
keysDownTrigger: import_core23.field.buffer(import_core23.field.uint8()).size(KEY_BUFFER_SIZE),
|
|
1840
|
+
/**
|
|
1841
|
+
* Buffer for key-up triggers (true for exactly 1 frame when key is released).
|
|
1842
|
+
* Uses field.buffer for zero-allocation subarray views.
|
|
1843
|
+
*/
|
|
1844
|
+
keysUpTrigger: import_core23.field.buffer(import_core23.field.uint8()).size(KEY_BUFFER_SIZE),
|
|
1845
|
+
/** Common modifier - Shift key is down */
|
|
1846
|
+
shiftDown: import_core23.field.boolean().default(false),
|
|
1847
|
+
/** Common modifier - Alt/Option key is down */
|
|
1848
|
+
altDown: import_core23.field.boolean().default(false),
|
|
1849
|
+
/** Common modifier - Ctrl (Windows/Linux) or Cmd (Mac) is down */
|
|
1850
|
+
modDown: import_core23.field.boolean().default(false)
|
|
1851
|
+
};
|
|
1852
|
+
function getBit(buffer, bitIndex) {
|
|
1853
|
+
if (bitIndex < 0 || bitIndex >= buffer.length * 8) return false;
|
|
1854
|
+
const byteIndex = Math.floor(bitIndex / 8);
|
|
1855
|
+
const bitOffset = bitIndex % 8;
|
|
1856
|
+
return (buffer[byteIndex] & 1 << bitOffset) !== 0;
|
|
1857
|
+
}
|
|
1858
|
+
var KeyboardDef = class extends import_canvas_store25.CanvasSingletonDef {
|
|
1859
|
+
constructor() {
|
|
1860
|
+
super({ name: "keyboard" }, KeyboardSchema);
|
|
1861
|
+
}
|
|
1862
|
+
/**
|
|
1863
|
+
* Check if a key is currently pressed.
|
|
1864
|
+
* @param ctx - Editor context
|
|
1865
|
+
* @param key - The key index to check (use Key.A, Key.Space, etc.)
|
|
1866
|
+
*/
|
|
1867
|
+
isKeyDown(ctx, key) {
|
|
1868
|
+
return getBit(this.read(ctx).keysDown, key);
|
|
1869
|
+
}
|
|
1870
|
+
/**
|
|
1871
|
+
* Check if a key was just pressed this frame.
|
|
1872
|
+
* @param ctx - Editor context
|
|
1873
|
+
* @param key - The key index to check (use Key.A, Key.Space, etc.)
|
|
1874
|
+
*/
|
|
1875
|
+
isKeyDownTrigger(ctx, key) {
|
|
1876
|
+
return getBit(this.read(ctx).keysDownTrigger, key);
|
|
1877
|
+
}
|
|
1878
|
+
/**
|
|
1879
|
+
* Check if a key was just released this frame.
|
|
1880
|
+
* @param ctx - Editor context
|
|
1881
|
+
* @param key - The key index to check (use Key.A, Key.Space, etc.)
|
|
1882
|
+
*/
|
|
1883
|
+
isKeyUpTrigger(ctx, key) {
|
|
1884
|
+
return getBit(this.read(ctx).keysUpTrigger, key);
|
|
1885
|
+
}
|
|
1886
|
+
};
|
|
1887
|
+
var Keyboard = new KeyboardDef();
|
|
1888
|
+
function setBit(buffer, bitIndex, value) {
|
|
1889
|
+
if (bitIndex < 0 || bitIndex >= buffer.length * 8) return;
|
|
1890
|
+
const byteIndex = Math.floor(bitIndex / 8);
|
|
1891
|
+
const bitOffset = bitIndex % 8;
|
|
1892
|
+
if (value) {
|
|
1893
|
+
buffer[byteIndex] |= 1 << bitOffset;
|
|
1894
|
+
} else {
|
|
1895
|
+
buffer[byteIndex] &= ~(1 << bitOffset);
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
function clearBits(buffer) {
|
|
1899
|
+
for (let i = 0; i < buffer.length; i++) {
|
|
1900
|
+
buffer[i] = 0;
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
var codeToIndex = {
|
|
1904
|
+
// Letters (0-25)
|
|
1905
|
+
KeyA: 0,
|
|
1906
|
+
KeyB: 1,
|
|
1907
|
+
KeyC: 2,
|
|
1908
|
+
KeyD: 3,
|
|
1909
|
+
KeyE: 4,
|
|
1910
|
+
KeyF: 5,
|
|
1911
|
+
KeyG: 6,
|
|
1912
|
+
KeyH: 7,
|
|
1913
|
+
KeyI: 8,
|
|
1914
|
+
KeyJ: 9,
|
|
1915
|
+
KeyK: 10,
|
|
1916
|
+
KeyL: 11,
|
|
1917
|
+
KeyM: 12,
|
|
1918
|
+
KeyN: 13,
|
|
1919
|
+
KeyO: 14,
|
|
1920
|
+
KeyP: 15,
|
|
1921
|
+
KeyQ: 16,
|
|
1922
|
+
KeyR: 17,
|
|
1923
|
+
KeyS: 18,
|
|
1924
|
+
KeyT: 19,
|
|
1925
|
+
KeyU: 20,
|
|
1926
|
+
KeyV: 21,
|
|
1927
|
+
KeyW: 22,
|
|
1928
|
+
KeyX: 23,
|
|
1929
|
+
KeyY: 24,
|
|
1930
|
+
KeyZ: 25,
|
|
1931
|
+
// Numbers (26-35)
|
|
1932
|
+
Digit0: 26,
|
|
1933
|
+
Digit1: 27,
|
|
1934
|
+
Digit2: 28,
|
|
1935
|
+
Digit3: 29,
|
|
1936
|
+
Digit4: 30,
|
|
1937
|
+
Digit5: 31,
|
|
1938
|
+
Digit6: 32,
|
|
1939
|
+
Digit7: 33,
|
|
1940
|
+
Digit8: 34,
|
|
1941
|
+
Digit9: 35,
|
|
1942
|
+
// Function keys (36-47)
|
|
1943
|
+
F1: 36,
|
|
1944
|
+
F2: 37,
|
|
1945
|
+
F3: 38,
|
|
1946
|
+
F4: 39,
|
|
1947
|
+
F5: 40,
|
|
1948
|
+
F6: 41,
|
|
1949
|
+
F7: 42,
|
|
1950
|
+
F8: 43,
|
|
1951
|
+
F9: 44,
|
|
1952
|
+
F10: 45,
|
|
1953
|
+
F11: 46,
|
|
1954
|
+
F12: 47,
|
|
1955
|
+
// Modifiers (48-51)
|
|
1956
|
+
ShiftLeft: 48,
|
|
1957
|
+
ShiftRight: 49,
|
|
1958
|
+
ControlLeft: 50,
|
|
1959
|
+
ControlRight: 51,
|
|
1960
|
+
AltLeft: 52,
|
|
1961
|
+
AltRight: 53,
|
|
1962
|
+
MetaLeft: 54,
|
|
1963
|
+
MetaRight: 55,
|
|
1964
|
+
// Navigation (56-71)
|
|
1965
|
+
Escape: 56,
|
|
1966
|
+
Space: 57,
|
|
1967
|
+
Enter: 58,
|
|
1968
|
+
Tab: 59,
|
|
1969
|
+
Backspace: 60,
|
|
1970
|
+
Delete: 61,
|
|
1971
|
+
ArrowLeft: 62,
|
|
1972
|
+
ArrowUp: 63,
|
|
1973
|
+
ArrowRight: 64,
|
|
1974
|
+
ArrowDown: 65,
|
|
1975
|
+
Home: 66,
|
|
1976
|
+
End: 67,
|
|
1977
|
+
PageUp: 68,
|
|
1978
|
+
PageDown: 69,
|
|
1979
|
+
Insert: 70,
|
|
1980
|
+
// Punctuation (72-83)
|
|
1981
|
+
Semicolon: 72,
|
|
1982
|
+
Equal: 73,
|
|
1983
|
+
Comma: 74,
|
|
1984
|
+
Minus: 75,
|
|
1985
|
+
Period: 76,
|
|
1986
|
+
Slash: 77,
|
|
1987
|
+
Backquote: 78,
|
|
1988
|
+
BracketLeft: 79,
|
|
1989
|
+
Backslash: 80,
|
|
1990
|
+
BracketRight: 81,
|
|
1991
|
+
Quote: 82,
|
|
1992
|
+
// Numpad (84-99)
|
|
1993
|
+
Numpad0: 84,
|
|
1994
|
+
Numpad1: 85,
|
|
1995
|
+
Numpad2: 86,
|
|
1996
|
+
Numpad3: 87,
|
|
1997
|
+
Numpad4: 88,
|
|
1998
|
+
Numpad5: 89,
|
|
1999
|
+
Numpad6: 90,
|
|
2000
|
+
Numpad7: 91,
|
|
2001
|
+
Numpad8: 92,
|
|
2002
|
+
Numpad9: 93,
|
|
2003
|
+
NumpadAdd: 94,
|
|
2004
|
+
NumpadSubtract: 95,
|
|
2005
|
+
NumpadMultiply: 96,
|
|
2006
|
+
NumpadDivide: 97,
|
|
2007
|
+
NumpadDecimal: 98,
|
|
2008
|
+
NumpadEnter: 99
|
|
2009
|
+
};
|
|
2010
|
+
var Key = {
|
|
2011
|
+
// Letters
|
|
2012
|
+
A: codeToIndex.KeyA,
|
|
2013
|
+
B: codeToIndex.KeyB,
|
|
2014
|
+
C: codeToIndex.KeyC,
|
|
2015
|
+
D: codeToIndex.KeyD,
|
|
2016
|
+
E: codeToIndex.KeyE,
|
|
2017
|
+
F: codeToIndex.KeyF,
|
|
2018
|
+
G: codeToIndex.KeyG,
|
|
2019
|
+
H: codeToIndex.KeyH,
|
|
2020
|
+
I: codeToIndex.KeyI,
|
|
2021
|
+
J: codeToIndex.KeyJ,
|
|
2022
|
+
K: codeToIndex.KeyK,
|
|
2023
|
+
L: codeToIndex.KeyL,
|
|
2024
|
+
M: codeToIndex.KeyM,
|
|
2025
|
+
N: codeToIndex.KeyN,
|
|
2026
|
+
O: codeToIndex.KeyO,
|
|
2027
|
+
P: codeToIndex.KeyP,
|
|
2028
|
+
Q: codeToIndex.KeyQ,
|
|
2029
|
+
R: codeToIndex.KeyR,
|
|
2030
|
+
S: codeToIndex.KeyS,
|
|
2031
|
+
T: codeToIndex.KeyT,
|
|
2032
|
+
U: codeToIndex.KeyU,
|
|
2033
|
+
V: codeToIndex.KeyV,
|
|
2034
|
+
W: codeToIndex.KeyW,
|
|
2035
|
+
X: codeToIndex.KeyX,
|
|
2036
|
+
Y: codeToIndex.KeyY,
|
|
2037
|
+
Z: codeToIndex.KeyZ,
|
|
2038
|
+
// Numbers
|
|
2039
|
+
Digit0: codeToIndex.Digit0,
|
|
2040
|
+
Digit1: codeToIndex.Digit1,
|
|
2041
|
+
Digit2: codeToIndex.Digit2,
|
|
2042
|
+
Digit3: codeToIndex.Digit3,
|
|
2043
|
+
Digit4: codeToIndex.Digit4,
|
|
2044
|
+
Digit5: codeToIndex.Digit5,
|
|
2045
|
+
Digit6: codeToIndex.Digit6,
|
|
2046
|
+
Digit7: codeToIndex.Digit7,
|
|
2047
|
+
Digit8: codeToIndex.Digit8,
|
|
2048
|
+
Digit9: codeToIndex.Digit9,
|
|
2049
|
+
// Function keys
|
|
2050
|
+
F1: codeToIndex.F1,
|
|
2051
|
+
F2: codeToIndex.F2,
|
|
2052
|
+
F3: codeToIndex.F3,
|
|
2053
|
+
F4: codeToIndex.F4,
|
|
2054
|
+
F5: codeToIndex.F5,
|
|
2055
|
+
F6: codeToIndex.F6,
|
|
2056
|
+
F7: codeToIndex.F7,
|
|
2057
|
+
F8: codeToIndex.F8,
|
|
2058
|
+
F9: codeToIndex.F9,
|
|
2059
|
+
F10: codeToIndex.F10,
|
|
2060
|
+
F11: codeToIndex.F11,
|
|
2061
|
+
F12: codeToIndex.F12,
|
|
2062
|
+
// Modifiers
|
|
2063
|
+
ShiftLeft: codeToIndex.ShiftLeft,
|
|
2064
|
+
ShiftRight: codeToIndex.ShiftRight,
|
|
2065
|
+
ControlLeft: codeToIndex.ControlLeft,
|
|
2066
|
+
ControlRight: codeToIndex.ControlRight,
|
|
2067
|
+
AltLeft: codeToIndex.AltLeft,
|
|
2068
|
+
AltRight: codeToIndex.AltRight,
|
|
2069
|
+
MetaLeft: codeToIndex.MetaLeft,
|
|
2070
|
+
MetaRight: codeToIndex.MetaRight,
|
|
2071
|
+
// Navigation
|
|
2072
|
+
Escape: codeToIndex.Escape,
|
|
2073
|
+
Space: codeToIndex.Space,
|
|
2074
|
+
Enter: codeToIndex.Enter,
|
|
2075
|
+
Tab: codeToIndex.Tab,
|
|
2076
|
+
Backspace: codeToIndex.Backspace,
|
|
2077
|
+
Delete: codeToIndex.Delete,
|
|
2078
|
+
ArrowLeft: codeToIndex.ArrowLeft,
|
|
2079
|
+
ArrowUp: codeToIndex.ArrowUp,
|
|
2080
|
+
ArrowRight: codeToIndex.ArrowRight,
|
|
2081
|
+
ArrowDown: codeToIndex.ArrowDown,
|
|
2082
|
+
Home: codeToIndex.Home,
|
|
2083
|
+
End: codeToIndex.End,
|
|
2084
|
+
PageUp: codeToIndex.PageUp,
|
|
2085
|
+
PageDown: codeToIndex.PageDown,
|
|
2086
|
+
Insert: codeToIndex.Insert,
|
|
2087
|
+
// Punctuation
|
|
2088
|
+
Semicolon: codeToIndex.Semicolon,
|
|
2089
|
+
Equal: codeToIndex.Equal,
|
|
2090
|
+
Comma: codeToIndex.Comma,
|
|
2091
|
+
Minus: codeToIndex.Minus,
|
|
2092
|
+
Period: codeToIndex.Period,
|
|
2093
|
+
Slash: codeToIndex.Slash,
|
|
2094
|
+
Backquote: codeToIndex.Backquote,
|
|
2095
|
+
BracketLeft: codeToIndex.BracketLeft,
|
|
2096
|
+
Backslash: codeToIndex.Backslash,
|
|
2097
|
+
BracketRight: codeToIndex.BracketRight,
|
|
2098
|
+
Quote: codeToIndex.Quote,
|
|
2099
|
+
// Numpad
|
|
2100
|
+
Numpad0: codeToIndex.Numpad0,
|
|
2101
|
+
Numpad1: codeToIndex.Numpad1,
|
|
2102
|
+
Numpad2: codeToIndex.Numpad2,
|
|
2103
|
+
Numpad3: codeToIndex.Numpad3,
|
|
2104
|
+
Numpad4: codeToIndex.Numpad4,
|
|
2105
|
+
Numpad5: codeToIndex.Numpad5,
|
|
2106
|
+
Numpad6: codeToIndex.Numpad6,
|
|
2107
|
+
Numpad7: codeToIndex.Numpad7,
|
|
2108
|
+
Numpad8: codeToIndex.Numpad8,
|
|
2109
|
+
Numpad9: codeToIndex.Numpad9,
|
|
2110
|
+
NumpadAdd: codeToIndex.NumpadAdd,
|
|
2111
|
+
NumpadSubtract: codeToIndex.NumpadSubtract,
|
|
2112
|
+
NumpadMultiply: codeToIndex.NumpadMultiply,
|
|
2113
|
+
NumpadDivide: codeToIndex.NumpadDivide,
|
|
2114
|
+
NumpadDecimal: codeToIndex.NumpadDecimal,
|
|
2115
|
+
NumpadEnter: codeToIndex.NumpadEnter
|
|
2116
|
+
};
|
|
2117
|
+
|
|
2118
|
+
// src/singletons/Mouse.ts
|
|
2119
|
+
var import_canvas_store26 = require("@woven-ecs/canvas-store");
|
|
2120
|
+
var import_core24 = require("@woven-ecs/core");
|
|
2121
|
+
var MouseSchema = {
|
|
2122
|
+
/** Current mouse position relative to the editor element [x, y] */
|
|
2123
|
+
position: import_core24.field.tuple(import_core24.field.float32(), 2).default([0, 0]),
|
|
2124
|
+
/** Horizontal wheel delta (positive = scroll right) */
|
|
2125
|
+
wheelDeltaX: import_core24.field.float32().default(0),
|
|
2126
|
+
/** Vertical wheel delta (positive = scroll down), normalized across browsers */
|
|
2127
|
+
wheelDeltaY: import_core24.field.float32().default(0),
|
|
2128
|
+
/** True for 1 frame when mouse moves */
|
|
2129
|
+
moveTrigger: import_core24.field.boolean().default(false),
|
|
2130
|
+
/** True for 1 frame when wheel is scrolled */
|
|
2131
|
+
wheelTrigger: import_core24.field.boolean().default(false),
|
|
2132
|
+
/** True for 1 frame when mouse enters the editor element */
|
|
2133
|
+
enterTrigger: import_core24.field.boolean().default(false),
|
|
2134
|
+
/** True for 1 frame when mouse leaves the editor element */
|
|
2135
|
+
leaveTrigger: import_core24.field.boolean().default(false)
|
|
2136
|
+
};
|
|
2137
|
+
var MouseDef = class extends import_canvas_store26.CanvasSingletonDef {
|
|
2138
|
+
constructor() {
|
|
2139
|
+
super({ name: "mouse" }, MouseSchema);
|
|
2140
|
+
}
|
|
2141
|
+
/** Check if mouse moved this frame */
|
|
2142
|
+
didMove(ctx) {
|
|
2143
|
+
return this.read(ctx).moveTrigger;
|
|
2144
|
+
}
|
|
2145
|
+
/** Check if wheel was scrolled this frame */
|
|
2146
|
+
didScroll(ctx) {
|
|
2147
|
+
return this.read(ctx).wheelTrigger;
|
|
2148
|
+
}
|
|
2149
|
+
/** Check if mouse entered the editor element this frame */
|
|
2150
|
+
didEnter(ctx) {
|
|
2151
|
+
return this.read(ctx).enterTrigger;
|
|
2152
|
+
}
|
|
2153
|
+
/** Check if mouse left the editor element this frame */
|
|
2154
|
+
didLeave(ctx) {
|
|
2155
|
+
return this.read(ctx).leaveTrigger;
|
|
2156
|
+
}
|
|
2157
|
+
/** Get current mouse position as [x, y] */
|
|
2158
|
+
getPosition(ctx) {
|
|
2159
|
+
const m = this.read(ctx);
|
|
2160
|
+
return [m.position[0], m.position[1]];
|
|
2161
|
+
}
|
|
2162
|
+
/** Get wheel delta as [dx, dy] */
|
|
2163
|
+
getWheelDelta(ctx) {
|
|
2164
|
+
const m = this.read(ctx);
|
|
2165
|
+
return [m.wheelDeltaX, m.wheelDeltaY];
|
|
2166
|
+
}
|
|
2167
|
+
};
|
|
2168
|
+
var Mouse = new MouseDef();
|
|
2169
|
+
|
|
2170
|
+
// src/singletons/RankBounds.ts
|
|
2171
|
+
var import_canvas_store27 = require("@woven-ecs/canvas-store");
|
|
2172
|
+
var import_core25 = require("@woven-ecs/core");
|
|
2173
|
+
var import_fractional_indexing_jittered = require("fractional-indexing-jittered");
|
|
2174
|
+
var RankBoundsSchema = {
|
|
2175
|
+
minRank: import_core25.field.string().max(36).default(""),
|
|
2176
|
+
maxRank: import_core25.field.string().max(36).default("")
|
|
2177
|
+
};
|
|
2178
|
+
var RankBoundsDef = class extends import_canvas_store27.CanvasSingletonDef {
|
|
2179
|
+
constructor() {
|
|
2180
|
+
super({ name: "rankBounds" }, RankBoundsSchema);
|
|
2181
|
+
}
|
|
2182
|
+
/**
|
|
2183
|
+
* Generate the next rank (higher z-order than all existing).
|
|
2184
|
+
* @returns A new rank string that sorts after maxRank
|
|
2185
|
+
*/
|
|
2186
|
+
genNext(ctx) {
|
|
2187
|
+
const bounds = this.write(ctx);
|
|
2188
|
+
const next = (0, import_fractional_indexing_jittered.generateJitteredKeyBetween)(bounds.maxRank || null, null);
|
|
2189
|
+
bounds.maxRank = next;
|
|
2190
|
+
if (!bounds.minRank) {
|
|
2191
|
+
bounds.minRank = next;
|
|
2192
|
+
}
|
|
2193
|
+
return next;
|
|
2194
|
+
}
|
|
2195
|
+
/**
|
|
2196
|
+
* Generate the previous rank (lower z-order than all existing).
|
|
2197
|
+
* @returns A new rank string that sorts before minRank
|
|
2198
|
+
*/
|
|
2199
|
+
genPrev(ctx) {
|
|
2200
|
+
const bounds = this.write(ctx);
|
|
2201
|
+
const prev = (0, import_fractional_indexing_jittered.generateJitteredKeyBetween)(null, bounds.minRank || null);
|
|
2202
|
+
bounds.minRank = prev;
|
|
2203
|
+
if (!bounds.maxRank) {
|
|
2204
|
+
bounds.maxRank = prev;
|
|
2205
|
+
}
|
|
2206
|
+
return prev;
|
|
2207
|
+
}
|
|
2208
|
+
/**
|
|
2209
|
+
* Generate a rank between two existing ranks.
|
|
2210
|
+
* @param before - Rank to come after (or null for start)
|
|
2211
|
+
* @param after - Rank to come before (or null for end)
|
|
2212
|
+
* @returns A new rank string that sorts between the two
|
|
2213
|
+
*/
|
|
2214
|
+
genBetween(before, after) {
|
|
2215
|
+
return (0, import_fractional_indexing_jittered.generateJitteredKeyBetween)(before, after);
|
|
2216
|
+
}
|
|
2217
|
+
/**
|
|
2218
|
+
* Add a rank to tracking (expands bounds if needed).
|
|
2219
|
+
*/
|
|
2220
|
+
add(ctx, rank) {
|
|
2221
|
+
if (!rank) return;
|
|
2222
|
+
const bounds = this.write(ctx);
|
|
2223
|
+
if (!bounds.minRank || rank < bounds.minRank) {
|
|
2224
|
+
bounds.minRank = rank;
|
|
2225
|
+
}
|
|
2226
|
+
if (!bounds.maxRank || rank > bounds.maxRank) {
|
|
2227
|
+
bounds.maxRank = rank;
|
|
2228
|
+
}
|
|
2229
|
+
}
|
|
2230
|
+
};
|
|
2231
|
+
var RankBounds = new RankBoundsDef();
|
|
2232
|
+
|
|
2233
|
+
// src/singletons/ScaleWithZoomState.ts
|
|
2234
|
+
var import_canvas_store28 = require("@woven-ecs/canvas-store");
|
|
2235
|
+
var import_core26 = require("@woven-ecs/core");
|
|
2236
|
+
var ScaleWithZoomState = (0, import_canvas_store28.defineCanvasSingleton)(
|
|
2237
|
+
{ name: "scaleWithZoomState" },
|
|
2238
|
+
{
|
|
2239
|
+
/** Last processed zoom level */
|
|
2240
|
+
lastZoom: import_core26.field.float64().default(1)
|
|
2241
|
+
}
|
|
2242
|
+
);
|
|
2243
|
+
|
|
2244
|
+
// src/systems/capture/keybindSystem.ts
|
|
2245
|
+
var import_core29 = require("@woven-ecs/core");
|
|
2246
|
+
|
|
2247
|
+
// src/command.ts
|
|
2248
|
+
var import_core27 = require("@woven-ecs/core");
|
|
2249
|
+
var CommandMarker = (0, import_core27.defineComponent)({
|
|
2250
|
+
name: import_core27.field.string().max(128)
|
|
2251
|
+
});
|
|
2252
|
+
var editorPayloads = /* @__PURE__ */ new WeakMap();
|
|
2253
|
+
function getPayloadMap(ctx) {
|
|
2254
|
+
const { editor } = (0, import_core27.getResources)(ctx);
|
|
2255
|
+
let map = editorPayloads.get(editor);
|
|
2256
|
+
if (!map) {
|
|
2257
|
+
map = /* @__PURE__ */ new Map();
|
|
2258
|
+
editorPayloads.set(editor, map);
|
|
2259
|
+
}
|
|
2260
|
+
return map;
|
|
2261
|
+
}
|
|
2262
|
+
var commands = (0, import_core27.defineQuery)((q) => q.with(CommandMarker));
|
|
2263
|
+
function defineCommand(name) {
|
|
2264
|
+
return {
|
|
2265
|
+
name,
|
|
2266
|
+
spawn(ctx, payload) {
|
|
2267
|
+
const eid = (0, import_core27.createEntity)(ctx);
|
|
2268
|
+
(0, import_core27.addComponent)(ctx, eid, CommandMarker, { name });
|
|
2269
|
+
getPayloadMap(ctx).set(eid, payload);
|
|
2270
|
+
return eid;
|
|
2271
|
+
},
|
|
2272
|
+
*iter(ctx) {
|
|
2273
|
+
const payloads = getPayloadMap(ctx);
|
|
2274
|
+
for (const eid of commands.current(ctx)) {
|
|
2275
|
+
const marker = CommandMarker.read(ctx, eid);
|
|
2276
|
+
if (marker.name === name) {
|
|
2277
|
+
const payload = payloads.get(eid);
|
|
2278
|
+
yield { eid, payload };
|
|
2279
|
+
}
|
|
2280
|
+
}
|
|
2281
|
+
},
|
|
2282
|
+
didSpawnLastFrame(ctx) {
|
|
2283
|
+
for (const eid of commands.removed(ctx)) {
|
|
2284
|
+
const marker = CommandMarker.read(ctx, eid);
|
|
2285
|
+
if (marker.name === name) {
|
|
2286
|
+
return true;
|
|
2287
|
+
}
|
|
2288
|
+
}
|
|
2289
|
+
return false;
|
|
2290
|
+
}
|
|
2291
|
+
};
|
|
2292
|
+
}
|
|
2293
|
+
function cleanupCommands(ctx) {
|
|
2294
|
+
const payloads = getPayloadMap(ctx);
|
|
2295
|
+
for (const eid of commands.current(ctx)) {
|
|
2296
|
+
payloads.delete(eid);
|
|
2297
|
+
(0, import_core27.removeEntity)(ctx, eid);
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2300
|
+
function on(ctx, def, handler) {
|
|
2301
|
+
for (const { payload } of def.iter(ctx)) {
|
|
2302
|
+
handler(ctx, payload);
|
|
2303
|
+
}
|
|
2304
|
+
}
|
|
2305
|
+
var Undo = defineCommand("undo");
|
|
2306
|
+
var Redo = defineCommand("redo");
|
|
2307
|
+
|
|
2308
|
+
// src/EditorSystem.ts
|
|
2309
|
+
var import_core28 = require("@woven-ecs/core");
|
|
2310
|
+
var DEFAULT_PRIORITY = 0;
|
|
2311
|
+
function defineEditorSystem(options, execute) {
|
|
2312
|
+
return {
|
|
2313
|
+
_system: new import_core28.MainThreadSystem(execute),
|
|
2314
|
+
phase: options.phase,
|
|
2315
|
+
priority: options.priority ?? DEFAULT_PRIORITY
|
|
2316
|
+
};
|
|
2317
|
+
}
|
|
2318
|
+
|
|
2319
|
+
// src/systems/capture/keybindSystem.ts
|
|
2320
|
+
var keybindSystem = defineEditorSystem({ phase: "capture" }, (ctx) => {
|
|
2321
|
+
const keyboard = Keyboard.read(ctx);
|
|
2322
|
+
const { editor } = (0, import_core29.getResources)(ctx);
|
|
2323
|
+
for (const keybind of editor.keybinds) {
|
|
2324
|
+
let triggered = Keyboard.isKeyDownTrigger(ctx, keybind.key);
|
|
2325
|
+
triggered &&= !!keybind.mod === keyboard.modDown;
|
|
2326
|
+
triggered &&= !!keybind.shift === keyboard.shiftDown;
|
|
2327
|
+
if (triggered) {
|
|
2328
|
+
const eid = (0, import_core29.createEntity)(ctx);
|
|
2329
|
+
(0, import_core29.addComponent)(ctx, eid, CommandMarker, { name: keybind.command });
|
|
2330
|
+
break;
|
|
2331
|
+
}
|
|
2332
|
+
}
|
|
2333
|
+
});
|
|
2334
|
+
|
|
2335
|
+
// src/systems/input/frameSystem.ts
|
|
2336
|
+
var frameSystem = defineEditorSystem({ phase: "input", priority: 100 }, (ctx) => {
|
|
2337
|
+
const now = performance.now();
|
|
2338
|
+
const buffer = Frame._getInstance(ctx).buffer;
|
|
2339
|
+
const last = buffer.lastTime[0];
|
|
2340
|
+
buffer.number[0]++;
|
|
2341
|
+
buffer.lastTime[0] = now;
|
|
2342
|
+
buffer.time[0] = now;
|
|
2343
|
+
if (last === 0) {
|
|
2344
|
+
buffer.delta[0] = 0.016;
|
|
2345
|
+
} else {
|
|
2346
|
+
buffer.delta[0] = Math.min((now - last) / 1e3, 0.1);
|
|
2347
|
+
}
|
|
2348
|
+
});
|
|
2349
|
+
|
|
2350
|
+
// src/systems/input/keyboardSystem.ts
|
|
2351
|
+
var import_core30 = require("@woven-ecs/core");
|
|
2352
|
+
var instanceState = /* @__PURE__ */ new WeakMap();
|
|
2353
|
+
function attachKeyboardListeners(domElement) {
|
|
2354
|
+
if (instanceState.has(domElement)) return;
|
|
2355
|
+
if (!domElement.hasAttribute("tabindex")) {
|
|
2356
|
+
domElement.setAttribute("tabindex", "0");
|
|
2357
|
+
}
|
|
2358
|
+
const state = {
|
|
2359
|
+
eventsBuffer: [],
|
|
2360
|
+
onKeyDown: (e) => {
|
|
2361
|
+
if (e.key === "Tab" || e.key === "Alt" || e.key === " ") {
|
|
2362
|
+
e.preventDefault();
|
|
2363
|
+
}
|
|
2364
|
+
state.eventsBuffer.push(e);
|
|
2365
|
+
},
|
|
2366
|
+
onKeyUp: (e) => {
|
|
2367
|
+
state.eventsBuffer.push(e);
|
|
2368
|
+
},
|
|
2369
|
+
onBlur: () => {
|
|
2370
|
+
state.eventsBuffer.push({ type: "blur" });
|
|
2371
|
+
},
|
|
2372
|
+
// Reusable buffers - allocated once per instance
|
|
2373
|
+
keysDown: new Uint8Array(32),
|
|
2374
|
+
keysDownTrigger: new Uint8Array(32),
|
|
2375
|
+
keysUpTrigger: new Uint8Array(32)
|
|
2376
|
+
};
|
|
2377
|
+
instanceState.set(domElement, state);
|
|
2378
|
+
domElement.addEventListener("keydown", state.onKeyDown);
|
|
2379
|
+
domElement.addEventListener("keyup", state.onKeyUp);
|
|
2380
|
+
domElement.addEventListener("blur", state.onBlur);
|
|
2381
|
+
}
|
|
2382
|
+
function detachKeyboardListeners(domElement) {
|
|
2383
|
+
const state = instanceState.get(domElement);
|
|
2384
|
+
if (!state) return;
|
|
2385
|
+
domElement.removeEventListener("keydown", state.onKeyDown);
|
|
2386
|
+
domElement.removeEventListener("keyup", state.onKeyUp);
|
|
2387
|
+
domElement.removeEventListener("blur", state.onBlur);
|
|
2388
|
+
instanceState.delete(domElement);
|
|
2389
|
+
}
|
|
2390
|
+
var keyboardSystem = defineEditorSystem({ phase: "input" }, (ctx) => {
|
|
2391
|
+
const resources = (0, import_core30.getResources)(ctx);
|
|
2392
|
+
const state = instanceState.get(resources.domElement);
|
|
2393
|
+
if (!state) return;
|
|
2394
|
+
const hasEvents = state.eventsBuffer.length > 0;
|
|
2395
|
+
const triggersNeedClearing = !isZeroed(state.keysDownTrigger) || !isZeroed(state.keysUpTrigger);
|
|
2396
|
+
if (!hasEvents && !triggersNeedClearing) return;
|
|
2397
|
+
const keyboard = Keyboard.write(ctx);
|
|
2398
|
+
const keysDown = state.keysDown;
|
|
2399
|
+
const keysDownTrigger = state.keysDownTrigger;
|
|
2400
|
+
const keysUpTrigger = state.keysUpTrigger;
|
|
2401
|
+
keysDownTrigger.fill(0);
|
|
2402
|
+
keysUpTrigger.fill(0);
|
|
2403
|
+
keysDown.set(keyboard.keysDown);
|
|
2404
|
+
for (const event of state.eventsBuffer) {
|
|
2405
|
+
if (event.type === "blur") {
|
|
2406
|
+
keysDown.fill(0);
|
|
2407
|
+
keyboard.shiftDown = false;
|
|
2408
|
+
keyboard.altDown = false;
|
|
2409
|
+
keyboard.modDown = false;
|
|
2410
|
+
continue;
|
|
2411
|
+
}
|
|
2412
|
+
const keyIndex = codeToIndex[event.code];
|
|
2413
|
+
if (keyIndex === void 0) continue;
|
|
2414
|
+
if (event.type === "keydown") {
|
|
2415
|
+
const wasDown = getBit2(keysDown, keyIndex);
|
|
2416
|
+
if (!wasDown) {
|
|
2417
|
+
setBit(keysDownTrigger, keyIndex, true);
|
|
2418
|
+
}
|
|
2419
|
+
setBit(keysDown, keyIndex, true);
|
|
2420
|
+
} else if (event.type === "keyup") {
|
|
2421
|
+
setBit(keysDown, keyIndex, false);
|
|
2422
|
+
setBit(keysUpTrigger, keyIndex, true);
|
|
2423
|
+
}
|
|
2424
|
+
keyboard.shiftDown = event.shiftKey;
|
|
2425
|
+
keyboard.altDown = event.altKey;
|
|
2426
|
+
keyboard.modDown = event.ctrlKey || event.metaKey;
|
|
2427
|
+
}
|
|
2428
|
+
keyboard.keysDown = keysDown;
|
|
2429
|
+
keyboard.keysDownTrigger = keysDownTrigger;
|
|
2430
|
+
keyboard.keysUpTrigger = keysUpTrigger;
|
|
2431
|
+
state.eventsBuffer.length = 0;
|
|
2432
|
+
});
|
|
2433
|
+
function getBit2(buffer, bitIndex) {
|
|
2434
|
+
if (bitIndex < 0 || bitIndex >= buffer.length * 8) return false;
|
|
2435
|
+
const byteIndex = Math.floor(bitIndex / 8);
|
|
2436
|
+
const bitOffset = bitIndex % 8;
|
|
2437
|
+
return (buffer[byteIndex] & 1 << bitOffset) !== 0;
|
|
2438
|
+
}
|
|
2439
|
+
function isZeroed(buffer) {
|
|
2440
|
+
for (let i = 0; i < buffer.length; i++) {
|
|
2441
|
+
if (buffer[i] !== 0) return false;
|
|
2442
|
+
}
|
|
2443
|
+
return true;
|
|
2444
|
+
}
|
|
2445
|
+
|
|
2446
|
+
// src/systems/input/mouseSystem.ts
|
|
2447
|
+
var import_core31 = require("@woven-ecs/core");
|
|
2448
|
+
var instanceState2 = /* @__PURE__ */ new WeakMap();
|
|
2449
|
+
function attachMouseListeners(domElement) {
|
|
2450
|
+
if (instanceState2.has(domElement)) return;
|
|
2451
|
+
const state = {
|
|
2452
|
+
eventsBuffer: [],
|
|
2453
|
+
onMouseMove: (e) => {
|
|
2454
|
+
state.eventsBuffer.push({
|
|
2455
|
+
type: "mousemove",
|
|
2456
|
+
clientX: e.clientX,
|
|
2457
|
+
clientY: e.clientY
|
|
2458
|
+
});
|
|
2459
|
+
},
|
|
2460
|
+
onWheel: (e) => {
|
|
2461
|
+
e.preventDefault();
|
|
2462
|
+
state.eventsBuffer.push({
|
|
2463
|
+
type: "wheel",
|
|
2464
|
+
clientX: e.clientX,
|
|
2465
|
+
clientY: e.clientY,
|
|
2466
|
+
deltaX: e.deltaX,
|
|
2467
|
+
deltaY: e.deltaY,
|
|
2468
|
+
deltaMode: e.deltaMode
|
|
2469
|
+
});
|
|
2470
|
+
},
|
|
2471
|
+
onMouseEnter: () => {
|
|
2472
|
+
state.eventsBuffer.push({ type: "mouseenter" });
|
|
2473
|
+
},
|
|
2474
|
+
onMouseLeave: () => {
|
|
2475
|
+
state.eventsBuffer.push({ type: "mouseleave" });
|
|
2476
|
+
}
|
|
2477
|
+
};
|
|
2478
|
+
instanceState2.set(domElement, state);
|
|
2479
|
+
window.addEventListener("mousemove", state.onMouseMove);
|
|
2480
|
+
domElement.addEventListener("wheel", state.onWheel, { passive: false });
|
|
2481
|
+
domElement.addEventListener("mouseenter", state.onMouseEnter);
|
|
2482
|
+
domElement.addEventListener("mouseleave", state.onMouseLeave);
|
|
2483
|
+
}
|
|
2484
|
+
function detachMouseListeners(domElement) {
|
|
2485
|
+
const state = instanceState2.get(domElement);
|
|
2486
|
+
if (!state) return;
|
|
2487
|
+
window.removeEventListener("mousemove", state.onMouseMove);
|
|
2488
|
+
domElement.removeEventListener("wheel", state.onWheel);
|
|
2489
|
+
domElement.removeEventListener("mouseenter", state.onMouseEnter);
|
|
2490
|
+
domElement.removeEventListener("mouseleave", state.onMouseLeave);
|
|
2491
|
+
instanceState2.delete(domElement);
|
|
2492
|
+
}
|
|
2493
|
+
var mouseSystem = defineEditorSystem({ phase: "input" }, (ctx) => {
|
|
2494
|
+
const resources = (0, import_core31.getResources)(ctx);
|
|
2495
|
+
const state = instanceState2.get(resources.domElement);
|
|
2496
|
+
if (!state) return;
|
|
2497
|
+
const currentMouse = Mouse.read(ctx);
|
|
2498
|
+
const hadTriggers = currentMouse.moveTrigger || currentMouse.wheelTrigger || currentMouse.enterTrigger || currentMouse.leaveTrigger;
|
|
2499
|
+
const hasEvents = state.eventsBuffer.length > 0;
|
|
2500
|
+
if (!hadTriggers && !hasEvents) return;
|
|
2501
|
+
const mouse = Mouse.write(ctx);
|
|
2502
|
+
const screen = Screen.read(ctx);
|
|
2503
|
+
mouse.moveTrigger = false;
|
|
2504
|
+
mouse.wheelTrigger = false;
|
|
2505
|
+
mouse.enterTrigger = false;
|
|
2506
|
+
mouse.leaveTrigger = false;
|
|
2507
|
+
mouse.wheelDeltaX = 0;
|
|
2508
|
+
mouse.wheelDeltaY = 0;
|
|
2509
|
+
for (const event of state.eventsBuffer) {
|
|
2510
|
+
switch (event.type) {
|
|
2511
|
+
case "mousemove":
|
|
2512
|
+
mouse.position = [event.clientX - screen.left, event.clientY - screen.top];
|
|
2513
|
+
mouse.moveTrigger = true;
|
|
2514
|
+
break;
|
|
2515
|
+
case "wheel":
|
|
2516
|
+
mouse.wheelDeltaX = event.deltaX;
|
|
2517
|
+
mouse.wheelDeltaY = normalizeWheelDelta(event.deltaY, event.deltaMode);
|
|
2518
|
+
mouse.wheelTrigger = true;
|
|
2519
|
+
break;
|
|
2520
|
+
case "mouseenter":
|
|
2521
|
+
mouse.enterTrigger = true;
|
|
2522
|
+
break;
|
|
2523
|
+
case "mouseleave":
|
|
2524
|
+
mouse.leaveTrigger = true;
|
|
2525
|
+
break;
|
|
2526
|
+
}
|
|
2527
|
+
}
|
|
2528
|
+
state.eventsBuffer.length = 0;
|
|
2529
|
+
});
|
|
2530
|
+
function normalizeWheelDelta(deltaY, deltaMode) {
|
|
2531
|
+
const LINE_HEIGHT = 16;
|
|
2532
|
+
const PAGE_HEIGHT = typeof window !== "undefined" ? window.innerHeight : 800;
|
|
2533
|
+
let normalized = deltaY;
|
|
2534
|
+
if (deltaMode === 1) {
|
|
2535
|
+
normalized *= LINE_HEIGHT;
|
|
2536
|
+
} else if (deltaMode === 2) {
|
|
2537
|
+
normalized *= PAGE_HEIGHT;
|
|
2538
|
+
}
|
|
2539
|
+
if (typeof navigator !== "undefined" && navigator.userAgent.includes("Firefox")) {
|
|
2540
|
+
normalized *= 4;
|
|
2541
|
+
}
|
|
2542
|
+
if (typeof navigator !== "undefined" && (navigator.userAgent.includes("Mac") || navigator.userAgent.includes("Macintosh"))) {
|
|
2543
|
+
normalized *= 1.5;
|
|
2544
|
+
}
|
|
2545
|
+
return Math.min(Math.max(normalized, -100), 100);
|
|
2546
|
+
}
|
|
2547
|
+
|
|
2548
|
+
// src/systems/input/pointerSystem.ts
|
|
2549
|
+
var import_core32 = require("@woven-ecs/core");
|
|
2550
|
+
var instanceState3 = /* @__PURE__ */ new WeakMap();
|
|
2551
|
+
function attachPointerListeners(domElement) {
|
|
2552
|
+
if (instanceState3.has(domElement)) return;
|
|
2553
|
+
const state = {
|
|
2554
|
+
eventsBuffer: [],
|
|
2555
|
+
frameCount: 0,
|
|
2556
|
+
pointerEntityMap: /* @__PURE__ */ new Map(),
|
|
2557
|
+
onPointerDown: (e) => {
|
|
2558
|
+
state.eventsBuffer.push({
|
|
2559
|
+
type: "pointerdown",
|
|
2560
|
+
pointerId: e.pointerId,
|
|
2561
|
+
clientX: e.clientX,
|
|
2562
|
+
clientY: e.clientY,
|
|
2563
|
+
button: e.button,
|
|
2564
|
+
pointerType: e.pointerType,
|
|
2565
|
+
pressure: e.pressure,
|
|
2566
|
+
target: e.target
|
|
2567
|
+
});
|
|
2568
|
+
},
|
|
2569
|
+
onContextMenu: (e) => {
|
|
2570
|
+
e.preventDefault();
|
|
2571
|
+
const hasPointerDown = state.eventsBuffer.some((evt) => evt.type === "pointerdown");
|
|
2572
|
+
if (hasPointerDown) return;
|
|
2573
|
+
state.eventsBuffer.push({
|
|
2574
|
+
type: "pointerdown",
|
|
2575
|
+
pointerId: 0,
|
|
2576
|
+
clientX: e.clientX,
|
|
2577
|
+
clientY: e.clientY,
|
|
2578
|
+
button: 2,
|
|
2579
|
+
// Right button
|
|
2580
|
+
pointerType: "mouse",
|
|
2581
|
+
pressure: 0.5,
|
|
2582
|
+
target: e.target
|
|
2583
|
+
});
|
|
2584
|
+
},
|
|
2585
|
+
onPointerMove: (e) => {
|
|
2586
|
+
state.eventsBuffer.push({
|
|
2587
|
+
type: "pointermove",
|
|
2588
|
+
pointerId: e.pointerId,
|
|
2589
|
+
clientX: e.clientX,
|
|
2590
|
+
clientY: e.clientY,
|
|
2591
|
+
button: e.button,
|
|
2592
|
+
pointerType: e.pointerType,
|
|
2593
|
+
pressure: e.pressure,
|
|
2594
|
+
target: e.target
|
|
2595
|
+
});
|
|
2596
|
+
},
|
|
2597
|
+
onPointerUp: (e) => {
|
|
2598
|
+
state.eventsBuffer.push({
|
|
2599
|
+
type: "pointerup",
|
|
2600
|
+
pointerId: e.pointerId,
|
|
2601
|
+
clientX: e.clientX,
|
|
2602
|
+
clientY: e.clientY,
|
|
2603
|
+
button: e.button,
|
|
2604
|
+
pointerType: e.pointerType,
|
|
2605
|
+
pressure: e.pressure,
|
|
2606
|
+
target: e.target
|
|
2607
|
+
});
|
|
2608
|
+
},
|
|
2609
|
+
onPointerCancel: (e) => {
|
|
2610
|
+
state.eventsBuffer.push({
|
|
2611
|
+
type: "pointercancel",
|
|
2612
|
+
pointerId: e.pointerId,
|
|
2613
|
+
clientX: e.clientX,
|
|
2614
|
+
clientY: e.clientY,
|
|
2615
|
+
button: e.button,
|
|
2616
|
+
pointerType: e.pointerType,
|
|
2617
|
+
pressure: e.pressure,
|
|
2618
|
+
target: e.target
|
|
2619
|
+
});
|
|
2620
|
+
}
|
|
2621
|
+
};
|
|
2622
|
+
instanceState3.set(domElement, state);
|
|
2623
|
+
domElement.addEventListener("pointerdown", state.onPointerDown);
|
|
2624
|
+
domElement.addEventListener("contextmenu", state.onContextMenu);
|
|
2625
|
+
window.addEventListener("pointermove", state.onPointerMove);
|
|
2626
|
+
window.addEventListener("pointerup", state.onPointerUp);
|
|
2627
|
+
window.addEventListener("pointercancel", state.onPointerCancel);
|
|
2628
|
+
}
|
|
2629
|
+
function detachPointerListeners(domElement) {
|
|
2630
|
+
const state = instanceState3.get(domElement);
|
|
2631
|
+
if (!state) return;
|
|
2632
|
+
domElement.removeEventListener("pointerdown", state.onPointerDown);
|
|
2633
|
+
domElement.removeEventListener("contextmenu", state.onContextMenu);
|
|
2634
|
+
window.removeEventListener("pointermove", state.onPointerMove);
|
|
2635
|
+
window.removeEventListener("pointerup", state.onPointerUp);
|
|
2636
|
+
window.removeEventListener("pointercancel", state.onPointerCancel);
|
|
2637
|
+
instanceState3.delete(domElement);
|
|
2638
|
+
}
|
|
2639
|
+
var pointerSystem = defineEditorSystem({ phase: "input" }, (ctx) => {
|
|
2640
|
+
const resources = (0, import_core32.getResources)(ctx);
|
|
2641
|
+
const { domElement } = resources;
|
|
2642
|
+
const state = instanceState3.get(domElement);
|
|
2643
|
+
if (!state) return;
|
|
2644
|
+
state.frameCount++;
|
|
2645
|
+
const screen = Screen.read(ctx);
|
|
2646
|
+
const time = state.frameCount / 60;
|
|
2647
|
+
for (const event of state.eventsBuffer) {
|
|
2648
|
+
switch (event.type) {
|
|
2649
|
+
case "pointerdown": {
|
|
2650
|
+
const position = [event.clientX - screen.left, event.clientY - screen.top];
|
|
2651
|
+
const entityId = (0, import_core32.createEntity)(ctx);
|
|
2652
|
+
(0, import_core32.addComponent)(ctx, entityId, Pointer, {
|
|
2653
|
+
pointerId: event.pointerId,
|
|
2654
|
+
position,
|
|
2655
|
+
downPosition: position,
|
|
2656
|
+
downFrame: state.frameCount,
|
|
2657
|
+
button: Pointer.getButton(event.button),
|
|
2658
|
+
pointerType: Pointer.getType(event.pointerType),
|
|
2659
|
+
pressure: event.pressure,
|
|
2660
|
+
obscured: event.target !== domElement
|
|
2661
|
+
});
|
|
2662
|
+
const pointer = Pointer.write(ctx, entityId);
|
|
2663
|
+
addPointerSample(pointer, position, time);
|
|
2664
|
+
state.pointerEntityMap.set(event.pointerId, entityId);
|
|
2665
|
+
break;
|
|
2666
|
+
}
|
|
2667
|
+
case "pointermove": {
|
|
2668
|
+
const entityId = state.pointerEntityMap.get(event.pointerId);
|
|
2669
|
+
if (entityId === void 0) break;
|
|
2670
|
+
const position = [event.clientX - screen.left, event.clientY - screen.top];
|
|
2671
|
+
const pointer = Pointer.write(ctx, entityId);
|
|
2672
|
+
addPointerSample(pointer, position, time);
|
|
2673
|
+
pointer.pressure = event.pressure;
|
|
2674
|
+
pointer.obscured = event.target !== domElement;
|
|
2675
|
+
break;
|
|
2676
|
+
}
|
|
2677
|
+
case "pointerup":
|
|
2678
|
+
case "pointercancel": {
|
|
2679
|
+
const entityId = state.pointerEntityMap.get(event.pointerId);
|
|
2680
|
+
if (entityId === void 0) break;
|
|
2681
|
+
const position = [event.clientX - screen.left, event.clientY - screen.top];
|
|
2682
|
+
const pointer = Pointer.write(ctx, entityId);
|
|
2683
|
+
addPointerSample(pointer, position, time);
|
|
2684
|
+
(0, import_core32.removeEntity)(ctx, entityId);
|
|
2685
|
+
state.pointerEntityMap.delete(event.pointerId);
|
|
2686
|
+
break;
|
|
2687
|
+
}
|
|
2688
|
+
}
|
|
2689
|
+
}
|
|
2690
|
+
state.eventsBuffer.length = 0;
|
|
2691
|
+
});
|
|
2692
|
+
|
|
2693
|
+
// src/systems/input/screenSystem.ts
|
|
2694
|
+
var import_core33 = require("@woven-ecs/core");
|
|
2695
|
+
var instanceState4 = /* @__PURE__ */ new WeakMap();
|
|
2696
|
+
function attachScreenObserver(domElement) {
|
|
2697
|
+
if (instanceState4.has(domElement)) return;
|
|
2698
|
+
const state = {
|
|
2699
|
+
needsUpdate: false,
|
|
2700
|
+
resizeObserver: new ResizeObserver(() => {
|
|
2701
|
+
state.needsUpdate = true;
|
|
2702
|
+
}),
|
|
2703
|
+
onScroll: () => {
|
|
2704
|
+
state.needsUpdate = true;
|
|
2705
|
+
}
|
|
2706
|
+
};
|
|
2707
|
+
instanceState4.set(domElement, state);
|
|
2708
|
+
state.resizeObserver.observe(domElement);
|
|
2709
|
+
window.addEventListener("scroll", state.onScroll, true);
|
|
2710
|
+
}
|
|
2711
|
+
function detachScreenObserver(domElement) {
|
|
2712
|
+
const state = instanceState4.get(domElement);
|
|
2713
|
+
if (!state) return;
|
|
2714
|
+
state.resizeObserver.disconnect();
|
|
2715
|
+
window.removeEventListener("scroll", state.onScroll, true);
|
|
2716
|
+
instanceState4.delete(domElement);
|
|
2717
|
+
}
|
|
2718
|
+
var screenSystem = defineEditorSystem({ phase: "input" }, (ctx) => {
|
|
2719
|
+
const resources = (0, import_core33.getResources)(ctx);
|
|
2720
|
+
const { domElement } = resources;
|
|
2721
|
+
const state = instanceState4.get(domElement);
|
|
2722
|
+
if (!state) return;
|
|
2723
|
+
const frame = Frame.read(ctx);
|
|
2724
|
+
if (frame.number === 1) {
|
|
2725
|
+
state.needsUpdate = true;
|
|
2726
|
+
}
|
|
2727
|
+
if (!state.needsUpdate) return;
|
|
2728
|
+
const screen = Screen.write(ctx);
|
|
2729
|
+
const rect = domElement.getBoundingClientRect();
|
|
2730
|
+
screen.left = rect.left;
|
|
2731
|
+
screen.top = rect.top;
|
|
2732
|
+
screen.width = rect.width;
|
|
2733
|
+
screen.height = rect.height;
|
|
2734
|
+
state.needsUpdate = false;
|
|
2735
|
+
});
|
|
2736
|
+
|
|
2737
|
+
// src/systems/postRender/cursorSystem.ts
|
|
2738
|
+
var import_core34 = require("@woven-ecs/core");
|
|
2739
|
+
var cursorQuery = (0, import_core34.defineQuery)((q) => q.tracking(Cursor));
|
|
2740
|
+
function getCursorSvg(cursors, kind, rotateZ) {
|
|
2741
|
+
const def = cursors[kind];
|
|
2742
|
+
if (!def) return "auto";
|
|
2743
|
+
const svg = def.makeSvg(rotateZ + def.rotationOffset);
|
|
2744
|
+
return `url("data:image/svg+xml,${encodeURIComponent(svg.trim())}") ${def.hotspot[0]} ${def.hotspot[1]}, auto`;
|
|
2745
|
+
}
|
|
2746
|
+
var cursorSystem = defineEditorSystem({ phase: "render", priority: -100 }, (ctx) => {
|
|
2747
|
+
const changedCursors = cursorQuery.changed(ctx);
|
|
2748
|
+
const frame = Frame.read(ctx);
|
|
2749
|
+
if (changedCursors.length === 0 && frame.number !== 1) {
|
|
2750
|
+
return;
|
|
2751
|
+
}
|
|
2752
|
+
const { cursorKind, rotation } = Cursor.getEffective(ctx);
|
|
2753
|
+
if (!cursorKind) {
|
|
2754
|
+
document.body.style.cursor = "default";
|
|
2755
|
+
return;
|
|
2756
|
+
}
|
|
2757
|
+
const { editor } = (0, import_core34.getResources)(ctx);
|
|
2758
|
+
const cursorValue = getCursorSvg(editor.cursors, cursorKind, rotation);
|
|
2759
|
+
document.body.style.cursor = cursorValue;
|
|
2760
|
+
});
|
|
2761
|
+
|
|
2762
|
+
// src/systems/postRender/presenceSystem.ts
|
|
2763
|
+
var import_core41 = require("@woven-ecs/core");
|
|
2764
|
+
|
|
2765
|
+
// src/helpers/blockDefs.ts
|
|
2766
|
+
var import_core35 = require("@woven-ecs/core");
|
|
2767
|
+
function getBlockDefs(ctx) {
|
|
2768
|
+
const { editor } = (0, import_core35.getResources)(ctx);
|
|
2769
|
+
return editor.blockDefs;
|
|
2770
|
+
}
|
|
2771
|
+
function getBlockDef(ctx, tag) {
|
|
2772
|
+
const blockDefs = getBlockDefs(ctx);
|
|
2773
|
+
return blockDefs[tag] ?? BlockDef2.parse({ tag });
|
|
2774
|
+
}
|
|
2775
|
+
function canBlockEdit(ctx, tag) {
|
|
2776
|
+
return getBlockDef(ctx, tag).editOptions.canEdit;
|
|
2777
|
+
}
|
|
2778
|
+
|
|
2779
|
+
// src/helpers/computeAabb.ts
|
|
2780
|
+
var import_math4 = require("@woven-canvas/math");
|
|
2781
|
+
var import_core36 = require("@woven-ecs/core");
|
|
2782
|
+
function computeAabb(ctx, entityId, out) {
|
|
2783
|
+
if ((0, import_core36.hasComponent)(ctx, entityId, HitGeometry)) {
|
|
2784
|
+
const pts = HitGeometry.getExtremaWorld(ctx, entityId);
|
|
2785
|
+
import_math4.Aabb.setFromPoints(out, pts);
|
|
2786
|
+
} else {
|
|
2787
|
+
const corners = Block.getCorners(ctx, entityId);
|
|
2788
|
+
import_math4.Aabb.setFromPoints(out, corners);
|
|
2789
|
+
}
|
|
2790
|
+
}
|
|
2791
|
+
|
|
2792
|
+
// src/helpers/held.ts
|
|
2793
|
+
var import_core37 = require("@woven-ecs/core");
|
|
2794
|
+
function isHeldByRemote(ctx, entityId) {
|
|
2795
|
+
if (!(0, import_core37.hasComponent)(ctx, entityId, Held)) return false;
|
|
2796
|
+
const { sessionId } = (0, import_core37.getResources)(ctx);
|
|
2797
|
+
const held = Held.read(ctx, entityId);
|
|
2798
|
+
return held.sessionId !== "" && held.sessionId !== sessionId;
|
|
2799
|
+
}
|
|
2800
|
+
|
|
2801
|
+
// src/helpers/intersect.ts
|
|
2802
|
+
var import_math5 = require("@woven-canvas/math");
|
|
2803
|
+
var import_core38 = require("@woven-ecs/core");
|
|
2804
|
+
var blocksWithAabb = (0, import_core38.defineQuery)((q) => q.with(Block, Aabb));
|
|
2805
|
+
function intersectPoint(ctx, point, entityIds) {
|
|
2806
|
+
const intersects = [];
|
|
2807
|
+
const entities = entityIds ?? blocksWithAabb.current(ctx);
|
|
2808
|
+
for (const entityId of entities) {
|
|
2809
|
+
if (!Aabb.containsPoint(ctx, entityId, point)) {
|
|
2810
|
+
continue;
|
|
2811
|
+
}
|
|
2812
|
+
if ((0, import_core38.hasComponent)(ctx, entityId, HitGeometry)) {
|
|
2813
|
+
if (!HitGeometry.containsPointWorld(ctx, entityId, point)) {
|
|
2814
|
+
continue;
|
|
2815
|
+
}
|
|
2816
|
+
} else {
|
|
2817
|
+
if (!Block.containsPoint(ctx, entityId, point)) {
|
|
2818
|
+
continue;
|
|
2819
|
+
}
|
|
2820
|
+
}
|
|
2821
|
+
intersects.push(entityId);
|
|
2822
|
+
}
|
|
2823
|
+
return sortByRankDescending(ctx, intersects);
|
|
2824
|
+
}
|
|
2825
|
+
function intersectAabb(ctx, bounds, entityIds) {
|
|
2826
|
+
const intersecting = [];
|
|
2827
|
+
const entities = entityIds ?? blocksWithAabb.current(ctx);
|
|
2828
|
+
for (const entityId of entities) {
|
|
2829
|
+
const { value: entityAabb } = Aabb.read(ctx, entityId);
|
|
2830
|
+
if (!import_math5.Aabb.intersects(bounds, entityAabb)) {
|
|
2831
|
+
continue;
|
|
2832
|
+
}
|
|
2833
|
+
if (import_math5.Aabb.contains(bounds, entityAabb)) {
|
|
2834
|
+
intersecting.push(entityId);
|
|
2835
|
+
continue;
|
|
2836
|
+
}
|
|
2837
|
+
if ((0, import_core38.hasComponent)(ctx, entityId, HitGeometry)) {
|
|
2838
|
+
if (HitGeometry.intersectsAabbWorld(ctx, entityId, bounds)) {
|
|
2839
|
+
intersecting.push(entityId);
|
|
2840
|
+
}
|
|
2841
|
+
} else {
|
|
2842
|
+
if (Block.intersectsAabb(ctx, entityId, bounds)) {
|
|
2843
|
+
intersecting.push(entityId);
|
|
2844
|
+
}
|
|
2845
|
+
}
|
|
2846
|
+
}
|
|
2847
|
+
return intersecting;
|
|
2848
|
+
}
|
|
2849
|
+
function sortByRankDescending(ctx, entityIds) {
|
|
2850
|
+
return entityIds.sort((a, b) => {
|
|
2851
|
+
const blockA = Block.read(ctx, a);
|
|
2852
|
+
const blockB = Block.read(ctx, b);
|
|
2853
|
+
const stratumA = getBlockDef(ctx, blockA.tag).stratum;
|
|
2854
|
+
const stratumB = getBlockDef(ctx, blockB.tag).stratum;
|
|
2855
|
+
const stratumOrderA = STRATUM_ORDER[stratumA];
|
|
2856
|
+
const stratumOrderB = STRATUM_ORDER[stratumB];
|
|
2857
|
+
if (stratumOrderB !== stratumOrderA) {
|
|
2858
|
+
return stratumOrderB - stratumOrderA;
|
|
2859
|
+
}
|
|
2860
|
+
const rankA = blockA.rank;
|
|
2861
|
+
const rankB = blockB.rank;
|
|
2862
|
+
if (!rankA && !rankB) return 0;
|
|
2863
|
+
if (!rankA) return 1;
|
|
2864
|
+
if (!rankB) return -1;
|
|
2865
|
+
if (rankB > rankA) return 1;
|
|
2866
|
+
if (rankB < rankA) return -1;
|
|
2867
|
+
return 0;
|
|
2868
|
+
});
|
|
2869
|
+
}
|
|
2870
|
+
function getTopmostBlockAtPoint(ctx, point) {
|
|
2871
|
+
const intersects = intersectPoint(ctx, point);
|
|
2872
|
+
return intersects[0];
|
|
2873
|
+
}
|
|
2874
|
+
|
|
2875
|
+
// src/helpers/intersectCapsule.ts
|
|
2876
|
+
var import_math6 = require("@woven-canvas/math");
|
|
2877
|
+
var import_core39 = require("@woven-ecs/core");
|
|
2878
|
+
var _uvToWorldMatrix2 = [1, 0, 0, 1, 0, 0];
|
|
2879
|
+
function intersectCapsule(ctx, capsule, entityIds) {
|
|
2880
|
+
const intersects = [];
|
|
2881
|
+
const capsuleBounds = import_math6.Capsule.bounds(capsule);
|
|
2882
|
+
for (const entityId of entityIds) {
|
|
2883
|
+
const aabb = Aabb.read(ctx, entityId);
|
|
2884
|
+
if (!import_math6.Aabb.intersects(capsuleBounds, aabb.value)) {
|
|
2885
|
+
continue;
|
|
2886
|
+
}
|
|
2887
|
+
if ((0, import_core39.hasComponent)(ctx, entityId, HitGeometry)) {
|
|
2888
|
+
if (intersectsCapsuleHitGeometry(ctx, entityId, capsule)) {
|
|
2889
|
+
intersects.push(entityId);
|
|
2890
|
+
}
|
|
2891
|
+
} else {
|
|
2892
|
+
if (import_math6.Capsule.intersectsAabb(capsule, aabb.value)) {
|
|
2893
|
+
intersects.push(entityId);
|
|
2894
|
+
}
|
|
2895
|
+
}
|
|
2896
|
+
}
|
|
2897
|
+
return intersects;
|
|
2898
|
+
}
|
|
2899
|
+
function intersectsCapsuleHitGeometry(ctx, entityId, capsule) {
|
|
2900
|
+
const hitGeometry = HitGeometry.read(ctx, entityId);
|
|
2901
|
+
Block.getUvToWorldMatrix(ctx, entityId, _uvToWorldMatrix2);
|
|
2902
|
+
for (let i = 0; i < hitGeometry.arcCount; i++) {
|
|
2903
|
+
const uvArc = HitGeometry.getArcAt(ctx, entityId, i);
|
|
2904
|
+
const worldA = [uvArc[0], uvArc[1]];
|
|
2905
|
+
import_math6.Mat2.transformPoint(_uvToWorldMatrix2, worldA);
|
|
2906
|
+
const worldB = [uvArc[2], uvArc[3]];
|
|
2907
|
+
import_math6.Mat2.transformPoint(_uvToWorldMatrix2, worldB);
|
|
2908
|
+
const worldC = [uvArc[4], uvArc[5]];
|
|
2909
|
+
import_math6.Mat2.transformPoint(_uvToWorldMatrix2, worldC);
|
|
2910
|
+
const thickness = uvArc[6];
|
|
2911
|
+
const worldArc = import_math6.Arc.create(worldA[0], worldA[1], worldB[0], worldB[1], worldC[0], worldC[1], thickness);
|
|
2912
|
+
const capsuleA = import_math6.Capsule.pointA(capsule);
|
|
2913
|
+
const capsuleB = import_math6.Capsule.pointB(capsule);
|
|
2914
|
+
if (import_math6.Arc.intersectsCapsule(worldArc, capsuleA, capsuleB, import_math6.Capsule.radius(capsule))) {
|
|
2915
|
+
return true;
|
|
2916
|
+
}
|
|
2917
|
+
}
|
|
2918
|
+
for (let i = 0; i < hitGeometry.capsuleCount; i++) {
|
|
2919
|
+
const uvCapsule = HitGeometry.getCapsuleAt(ctx, entityId, i);
|
|
2920
|
+
const worldA = [uvCapsule[0], uvCapsule[1]];
|
|
2921
|
+
import_math6.Mat2.transformPoint(_uvToWorldMatrix2, worldA);
|
|
2922
|
+
const worldB = [uvCapsule[2], uvCapsule[3]];
|
|
2923
|
+
import_math6.Mat2.transformPoint(_uvToWorldMatrix2, worldB);
|
|
2924
|
+
const radius = uvCapsule[4];
|
|
2925
|
+
const worldCapsule = import_math6.Capsule.create(worldA[0], worldA[1], worldB[0], worldB[1], radius);
|
|
2926
|
+
if (import_math6.Capsule.intersectsCapsule(capsule, worldCapsule)) {
|
|
2927
|
+
return true;
|
|
2928
|
+
}
|
|
2929
|
+
}
|
|
2930
|
+
return false;
|
|
2931
|
+
}
|
|
2932
|
+
|
|
2933
|
+
// src/helpers/user.ts
|
|
2934
|
+
var import_core40 = require("@woven-ecs/core");
|
|
2935
|
+
var userQuery = (0, import_core40.defineQuery)((q) => q.with(User));
|
|
2936
|
+
function getMyUserEntityId(ctx) {
|
|
2937
|
+
const mySessionId = (0, import_core40.getResources)(ctx).sessionId;
|
|
2938
|
+
for (const eid of userQuery.current(ctx)) {
|
|
2939
|
+
const user = User.read(ctx, eid);
|
|
2940
|
+
if (user.sessionId === mySessionId) {
|
|
2941
|
+
return eid;
|
|
2942
|
+
}
|
|
2943
|
+
}
|
|
2944
|
+
return null;
|
|
2945
|
+
}
|
|
2946
|
+
|
|
2947
|
+
// src/systems/postRender/presenceSystem.ts
|
|
2948
|
+
var pointerQuery = (0, import_core41.defineQuery)((q) => q.tracking(Pointer));
|
|
2949
|
+
var presenceSystem = defineEditorSystem({ phase: "render", priority: -100 }, (ctx) => {
|
|
2950
|
+
if (Mouse.didMove(ctx)) {
|
|
2951
|
+
const mouse = Mouse.read(ctx);
|
|
2952
|
+
updateUserPosition(ctx, mouse.position);
|
|
2953
|
+
return;
|
|
2954
|
+
}
|
|
2955
|
+
const changedPointers = pointerQuery.changed(ctx);
|
|
2956
|
+
if (changedPointers.length === 0) return;
|
|
2957
|
+
const pointers = pointerQuery.current(ctx);
|
|
2958
|
+
let mainPointerEid = null;
|
|
2959
|
+
let lowestId = Number.MAX_SAFE_INTEGER;
|
|
2960
|
+
for (const eid of pointers) {
|
|
2961
|
+
const pointer2 = Pointer.read(ctx, eid);
|
|
2962
|
+
if (pointer2.pointerId < lowestId) {
|
|
2963
|
+
lowestId = pointer2.pointerId;
|
|
2964
|
+
mainPointerEid = eid;
|
|
2965
|
+
}
|
|
2966
|
+
}
|
|
2967
|
+
const pointer = Pointer.read(ctx, mainPointerEid);
|
|
2968
|
+
updateUserPosition(ctx, pointer.position);
|
|
2969
|
+
});
|
|
2970
|
+
function updateUserPosition(ctx, position) {
|
|
2971
|
+
const myUserEid = getMyUserEntityId(ctx);
|
|
2972
|
+
if (myUserEid === null) return;
|
|
2973
|
+
const user = User.write(ctx, myUserEid);
|
|
2974
|
+
user.position = Camera.toWorld(ctx, position);
|
|
2975
|
+
}
|
|
2976
|
+
|
|
2977
|
+
// src/systems/preCapture/intersectSystem.ts
|
|
2978
|
+
var import_core42 = require("@woven-ecs/core");
|
|
2979
|
+
var blocksChanged = (0, import_core42.defineQuery)((q) => q.tracking(Block));
|
|
2980
|
+
var hitGeometryChanged = (0, import_core42.defineQuery)((q) => q.tracking(HitGeometry));
|
|
2981
|
+
var heldQuery = (0, import_core42.defineQuery)((q) => q.with(Held).tracking(Held));
|
|
2982
|
+
var hoveredQuery = (0, import_core42.defineQuery)((q) => q.with(Hovered));
|
|
2983
|
+
var pointerQuery2 = (0, import_core42.defineQuery)((q) => q.with(Pointer));
|
|
2984
|
+
function clearHovered(ctx) {
|
|
2985
|
+
for (const entityId of hoveredQuery.current(ctx)) {
|
|
2986
|
+
if ((0, import_core42.hasComponent)(ctx, entityId, Hovered)) {
|
|
2987
|
+
(0, import_core42.removeComponent)(ctx, entityId, Hovered);
|
|
2988
|
+
}
|
|
2989
|
+
}
|
|
2990
|
+
}
|
|
2991
|
+
function findValidHoverTarget(ctx, intersected) {
|
|
2992
|
+
for (const entityId of intersected) {
|
|
2993
|
+
if (isHeldByRemote(ctx, entityId)) return void 0;
|
|
2994
|
+
return entityId;
|
|
2995
|
+
}
|
|
2996
|
+
return void 0;
|
|
2997
|
+
}
|
|
2998
|
+
function updateHovered(ctx, intersected) {
|
|
2999
|
+
const controls = Controls.read(ctx);
|
|
3000
|
+
const selectToolActive = controls.leftMouseTool === "select";
|
|
3001
|
+
clearHovered(ctx);
|
|
3002
|
+
if (!selectToolActive) return;
|
|
3003
|
+
const newHoveredId = findValidHoverTarget(ctx, intersected);
|
|
3004
|
+
if (newHoveredId !== void 0) {
|
|
3005
|
+
(0, import_core42.addComponent)(ctx, newHoveredId, Hovered, {});
|
|
3006
|
+
}
|
|
3007
|
+
}
|
|
3008
|
+
function arraysEqual(a, b) {
|
|
3009
|
+
if (a.length !== b.length) return false;
|
|
3010
|
+
for (let i = 0; i < a.length; i++) {
|
|
3011
|
+
if (a[i] !== b[i]) return false;
|
|
3012
|
+
}
|
|
3013
|
+
return true;
|
|
3014
|
+
}
|
|
3015
|
+
var added = /* @__PURE__ */ new Set();
|
|
3016
|
+
var changed = /* @__PURE__ */ new Set();
|
|
3017
|
+
var intersectSystem = defineEditorSystem({ phase: "capture", priority: 100 }, (ctx) => {
|
|
3018
|
+
added.clear();
|
|
3019
|
+
for (const entityId of blocksChanged.added(ctx)) {
|
|
3020
|
+
added.add(entityId);
|
|
3021
|
+
}
|
|
3022
|
+
for (const entityId of hitGeometryChanged.added(ctx)) {
|
|
3023
|
+
added.add(entityId);
|
|
3024
|
+
}
|
|
3025
|
+
changed.clear();
|
|
3026
|
+
for (const entityId of blocksChanged.changed(ctx)) {
|
|
3027
|
+
changed.add(entityId);
|
|
3028
|
+
}
|
|
3029
|
+
for (const entityId of hitGeometryChanged.changed(ctx)) {
|
|
3030
|
+
changed.add(entityId);
|
|
3031
|
+
}
|
|
3032
|
+
for (const entityId of added) {
|
|
3033
|
+
if (!(0, import_core42.hasComponent)(ctx, entityId, Aabb)) {
|
|
3034
|
+
(0, import_core42.addComponent)(ctx, entityId, Aabb, { value: [0, 0, 0, 0] });
|
|
3035
|
+
}
|
|
3036
|
+
const aabb = Aabb.write(ctx, entityId);
|
|
3037
|
+
computeAabb(ctx, entityId, aabb.value);
|
|
3038
|
+
}
|
|
3039
|
+
for (const entityId of changed) {
|
|
3040
|
+
const aabb = Aabb.write(ctx, entityId);
|
|
3041
|
+
computeAabb(ctx, entityId, aabb.value);
|
|
3042
|
+
}
|
|
3043
|
+
const mouseDidMove = Mouse.didMove(ctx);
|
|
3044
|
+
const mouseDidLeave = Mouse.didLeave(ctx);
|
|
3045
|
+
const mouseDidScroll = Mouse.didScroll(ctx);
|
|
3046
|
+
const blocksHaveChanged = added.size > 0 || changed.size > 0;
|
|
3047
|
+
const heldAdded = heldQuery.added(ctx);
|
|
3048
|
+
const heldRemoved = heldQuery.removed(ctx);
|
|
3049
|
+
const heldChanged = heldAdded.length > 0 || heldRemoved.length > 0;
|
|
3050
|
+
if (!mouseDidMove && !mouseDidLeave && !mouseDidScroll && !blocksHaveChanged && !heldChanged) {
|
|
3051
|
+
return;
|
|
3052
|
+
}
|
|
3053
|
+
if (mouseDidLeave) {
|
|
3054
|
+
Intersect.clear(ctx);
|
|
3055
|
+
clearHovered(ctx);
|
|
3056
|
+
return;
|
|
3057
|
+
}
|
|
3058
|
+
const mousePos = Mouse.getPosition(ctx);
|
|
3059
|
+
const worldPos = Camera.toWorld(ctx, mousePos);
|
|
3060
|
+
const intersected = intersectPoint(ctx, worldPos);
|
|
3061
|
+
const prevIntersected = Intersect.getAll(ctx);
|
|
3062
|
+
const intersectsChanged = !arraysEqual(intersected, prevIntersected);
|
|
3063
|
+
if (intersectsChanged) {
|
|
3064
|
+
Intersect.setAll(ctx, intersected);
|
|
3065
|
+
}
|
|
3066
|
+
const pointers = pointerQuery2.current(ctx);
|
|
3067
|
+
if (pointers.length > 0) {
|
|
3068
|
+
return;
|
|
3069
|
+
}
|
|
3070
|
+
if (intersectsChanged || heldChanged) {
|
|
3071
|
+
updateHovered(ctx, intersected);
|
|
3072
|
+
}
|
|
3073
|
+
});
|
|
3074
|
+
|
|
3075
|
+
// src/systems/preInput/rankBoundsSystem.ts
|
|
3076
|
+
var import_canvas_store29 = require("@woven-ecs/canvas-store");
|
|
3077
|
+
var import_core43 = require("@woven-ecs/core");
|
|
3078
|
+
var blocksQuery = (0, import_core43.defineQuery)((q) => q.with(import_canvas_store29.Synced, Block));
|
|
3079
|
+
var rankBoundsSystem = defineEditorSystem({ phase: "input", priority: 100 }, (ctx) => {
|
|
3080
|
+
const added2 = blocksQuery.added(ctx);
|
|
3081
|
+
for (const entityId of added2) {
|
|
3082
|
+
const block = Block.read(ctx, entityId);
|
|
3083
|
+
if (block.rank === "") {
|
|
3084
|
+
const writableBlock = Block.write(ctx, entityId);
|
|
3085
|
+
writableBlock.rank = RankBounds.genNext(ctx);
|
|
3086
|
+
} else {
|
|
3087
|
+
RankBounds.add(ctx, block.rank);
|
|
3088
|
+
}
|
|
3089
|
+
}
|
|
3090
|
+
});
|
|
3091
|
+
|
|
3092
|
+
// src/systems/preRender/canSeeBlocksSystem.ts
|
|
3093
|
+
var import_math7 = require("@woven-canvas/math");
|
|
3094
|
+
var import_canvas_store30 = require("@woven-ecs/canvas-store");
|
|
3095
|
+
var import_core44 = require("@woven-ecs/core");
|
|
3096
|
+
var camerasQuery = (0, import_core44.defineQuery)((q) => q.tracking(Camera));
|
|
3097
|
+
var blocksQuery2 = (0, import_core44.defineQuery)((q) => q.with(import_canvas_store30.Synced, Block, Aabb));
|
|
3098
|
+
var syncedBlocksQuery = (0, import_core44.defineQuery)((q) => q.with(import_canvas_store30.Synced, Block, Aabb));
|
|
3099
|
+
var _cameraAabb = [0, 0, 0, 0];
|
|
3100
|
+
var canSeeBlocksSystem = defineEditorSystem({ phase: "render", priority: 90 }, (ctx) => {
|
|
3101
|
+
if (camerasQuery.changed(ctx).length === 0 && syncedBlocksQuery.addedOrRemoved(ctx).length === 0) {
|
|
3102
|
+
return;
|
|
3103
|
+
}
|
|
3104
|
+
const camera = Camera.read(ctx);
|
|
3105
|
+
const cameraAabb = Camera.getAabb(ctx, _cameraAabb);
|
|
3106
|
+
if (camera.canSeeBlocks && camera.lastSeenBlock !== null) {
|
|
3107
|
+
if ((0, import_core44.hasComponent)(ctx, camera.lastSeenBlock, Aabb)) {
|
|
3108
|
+
const aabb = Aabb.read(ctx, camera.lastSeenBlock);
|
|
3109
|
+
if (import_math7.Aabb.intersects(cameraAabb, aabb.value)) {
|
|
3110
|
+
return;
|
|
3111
|
+
}
|
|
3112
|
+
}
|
|
3113
|
+
}
|
|
3114
|
+
let seenBlock = null;
|
|
3115
|
+
for (const entityId of blocksQuery2.current(ctx)) {
|
|
3116
|
+
const aabb = Aabb.read(ctx, entityId);
|
|
3117
|
+
if (import_math7.Aabb.intersects(cameraAabb, aabb.value)) {
|
|
3118
|
+
seenBlock = entityId;
|
|
3119
|
+
break;
|
|
3120
|
+
}
|
|
3121
|
+
}
|
|
3122
|
+
const canSeeBlocks = seenBlock !== null;
|
|
3123
|
+
if (canSeeBlocks === camera.canSeeBlocks) {
|
|
3124
|
+
if (seenBlock !== camera.lastSeenBlock) {
|
|
3125
|
+
Camera.write(ctx).lastSeenBlock = seenBlock;
|
|
3126
|
+
}
|
|
3127
|
+
return;
|
|
3128
|
+
}
|
|
3129
|
+
const writableCamera = Camera.write(ctx);
|
|
3130
|
+
writableCamera.canSeeBlocks = canSeeBlocks;
|
|
3131
|
+
writableCamera.lastSeenBlock = seenBlock;
|
|
3132
|
+
});
|
|
3133
|
+
|
|
3134
|
+
// src/systems/preRender/scaleWithZoomSystem.ts
|
|
3135
|
+
var import_math8 = require("@woven-canvas/math");
|
|
3136
|
+
var import_core45 = require("@woven-ecs/core");
|
|
3137
|
+
var scaleWithZoomQuery = (0, import_core45.defineQuery)((q) => q.with(Block).tracking(ScaleWithZoom));
|
|
3138
|
+
var _scaledSize = [0, 0];
|
|
3139
|
+
var _anchorOffset = [0, 0];
|
|
3140
|
+
var scaleWithZoomSystem = defineEditorSystem({ phase: "render", priority: 100 }, (ctx) => {
|
|
3141
|
+
const camera = Camera.read(ctx);
|
|
3142
|
+
const state = ScaleWithZoomState.read(ctx);
|
|
3143
|
+
const zoomChanged = !import_math8.Scalar.approxEqual(camera.zoom, state.lastZoom);
|
|
3144
|
+
if (zoomChanged) {
|
|
3145
|
+
for (const entityId of scaleWithZoomQuery.current(ctx)) {
|
|
3146
|
+
scaleBlock(ctx, entityId, camera.zoom);
|
|
3147
|
+
}
|
|
3148
|
+
ScaleWithZoomState.write(ctx).lastZoom = camera.zoom;
|
|
3149
|
+
}
|
|
3150
|
+
for (const entityId of scaleWithZoomQuery.addedOrChanged(ctx)) {
|
|
3151
|
+
scaleBlock(ctx, entityId, camera.zoom);
|
|
3152
|
+
}
|
|
3153
|
+
});
|
|
3154
|
+
function scaleBlock(ctx, entityId, zoom) {
|
|
3155
|
+
const block = Block.write(ctx, entityId);
|
|
3156
|
+
const swz = ScaleWithZoom.read(ctx, entityId);
|
|
3157
|
+
const baseScale = 1 / zoom;
|
|
3158
|
+
const scaleX = 1 + (baseScale - 1) * swz.scaleMultiplier[0];
|
|
3159
|
+
const scaleY = 1 + (baseScale - 1) * swz.scaleMultiplier[1];
|
|
3160
|
+
_scaledSize[0] = swz.startSize[0] * scaleX;
|
|
3161
|
+
_scaledSize[1] = swz.startSize[1] * scaleY;
|
|
3162
|
+
import_math8.Vec2.copy(_anchorOffset, swz.startSize);
|
|
3163
|
+
import_math8.Vec2.sub(_anchorOffset, _scaledSize);
|
|
3164
|
+
import_math8.Vec2.multiply(_anchorOffset, swz.anchor);
|
|
3165
|
+
import_math8.Vec2.copy(block.position, swz.startPosition);
|
|
3166
|
+
import_math8.Vec2.add(block.position, _anchorOffset);
|
|
3167
|
+
import_math8.Vec2.copy(block.size, _scaledSize);
|
|
3168
|
+
}
|
|
3169
|
+
|
|
3170
|
+
// src/CorePlugin.ts
|
|
3171
|
+
var CorePlugin = {
|
|
3172
|
+
name: PLUGIN_NAME,
|
|
3173
|
+
singletons: Object.values(singletons_exports).filter((v) => v instanceof import_canvas_store31.CanvasSingletonDef),
|
|
3174
|
+
components: Object.values(components_exports).filter((v) => v instanceof import_canvas_store31.CanvasComponentDef),
|
|
3175
|
+
blockDefs: [
|
|
3176
|
+
{
|
|
3177
|
+
tag: "sticky-note",
|
|
3178
|
+
components: [Color, Text, VerticalAlign],
|
|
3179
|
+
editOptions: {
|
|
3180
|
+
canEdit: true
|
|
3181
|
+
}
|
|
3182
|
+
},
|
|
3183
|
+
{
|
|
3184
|
+
tag: "text",
|
|
3185
|
+
components: [Text],
|
|
3186
|
+
resizeMode: "text",
|
|
3187
|
+
editOptions: {
|
|
3188
|
+
canEdit: true,
|
|
3189
|
+
removeWhenTextEmpty: true
|
|
3190
|
+
}
|
|
3191
|
+
},
|
|
3192
|
+
{
|
|
3193
|
+
tag: "image",
|
|
3194
|
+
components: [Image, Asset],
|
|
3195
|
+
resizeMode: "scale"
|
|
3196
|
+
},
|
|
3197
|
+
{
|
|
3198
|
+
tag: "shape",
|
|
3199
|
+
components: [Shape, Text, VerticalAlign],
|
|
3200
|
+
resizeMode: "free",
|
|
3201
|
+
editOptions: {
|
|
3202
|
+
canEdit: true
|
|
3203
|
+
}
|
|
3204
|
+
}
|
|
3205
|
+
],
|
|
3206
|
+
systems: [
|
|
3207
|
+
// Input phase
|
|
3208
|
+
frameSystem,
|
|
3209
|
+
// priority: 100
|
|
3210
|
+
rankBoundsSystem,
|
|
3211
|
+
// priority: 100
|
|
3212
|
+
keyboardSystem,
|
|
3213
|
+
mouseSystem,
|
|
3214
|
+
screenSystem,
|
|
3215
|
+
pointerSystem,
|
|
3216
|
+
// Capture phase
|
|
3217
|
+
intersectSystem,
|
|
3218
|
+
// priority: 100
|
|
3219
|
+
keybindSystem,
|
|
3220
|
+
// Render phase
|
|
3221
|
+
scaleWithZoomSystem,
|
|
3222
|
+
// priority: 100
|
|
3223
|
+
canSeeBlocksSystem,
|
|
3224
|
+
// priority: 90
|
|
3225
|
+
cursorSystem,
|
|
3226
|
+
// priority: -100
|
|
3227
|
+
presenceSystem
|
|
3228
|
+
// priority: -100
|
|
3229
|
+
],
|
|
3230
|
+
setup(ctx) {
|
|
3231
|
+
const { domElement } = (0, import_core46.getResources)(ctx);
|
|
3232
|
+
attachKeyboardListeners(domElement);
|
|
3233
|
+
attachMouseListeners(domElement);
|
|
3234
|
+
attachScreenObserver(domElement);
|
|
3235
|
+
attachPointerListeners(domElement);
|
|
3236
|
+
},
|
|
3237
|
+
teardown(ctx) {
|
|
3238
|
+
const { domElement } = (0, import_core46.getResources)(ctx);
|
|
3239
|
+
detachKeyboardListeners(domElement);
|
|
3240
|
+
detachMouseListeners(domElement);
|
|
3241
|
+
detachScreenObserver(domElement);
|
|
3242
|
+
detachPointerListeners(domElement);
|
|
3243
|
+
}
|
|
3244
|
+
};
|
|
3245
|
+
|
|
3246
|
+
// src/Editor.ts
|
|
3247
|
+
var import_canvas_store32 = require("@woven-ecs/canvas-store");
|
|
3248
|
+
var import_core47 = require("@woven-ecs/core");
|
|
3249
|
+
|
|
3250
|
+
// src/FontLoader.ts
|
|
3251
|
+
var import_zod2 = require("zod");
|
|
3252
|
+
var FontFamily = import_zod2.z.object({
|
|
3253
|
+
/** The CSS font-family name */
|
|
3254
|
+
name: import_zod2.z.string(),
|
|
3255
|
+
/** Display name shown in the font selector UI */
|
|
3256
|
+
displayName: import_zod2.z.string(),
|
|
3257
|
+
/** URL to the font stylesheet (e.g., Google Fonts URL) */
|
|
3258
|
+
url: import_zod2.z.string(),
|
|
3259
|
+
/** Optional preview image URL for the font selector */
|
|
3260
|
+
previewImage: import_zod2.z.string().optional(),
|
|
3261
|
+
/** Whether this font appears in the font selector (default: true) */
|
|
3262
|
+
selectable: import_zod2.z.boolean().default(true),
|
|
3263
|
+
/**
|
|
3264
|
+
* Font weights to load (e.g., [400, 700] for regular and bold).
|
|
3265
|
+
* Only applies to Google Fonts URLs - will auto-construct the variant URL.
|
|
3266
|
+
* Default: [400, 700]
|
|
3267
|
+
*/
|
|
3268
|
+
weights: import_zod2.z.array(import_zod2.z.number()).default([400, 700]),
|
|
3269
|
+
/**
|
|
3270
|
+
* Whether to also load italic variants for each weight.
|
|
3271
|
+
* Only applies to Google Fonts URLs.
|
|
3272
|
+
* Default: true
|
|
3273
|
+
*/
|
|
3274
|
+
italics: import_zod2.z.boolean().default(true)
|
|
3275
|
+
});
|
|
3276
|
+
var FontLoader = class {
|
|
3277
|
+
constructor() {
|
|
3278
|
+
this.loadedFonts = /* @__PURE__ */ new Set();
|
|
3279
|
+
}
|
|
3280
|
+
/**
|
|
3281
|
+
* Load multiple font families.
|
|
3282
|
+
* Fonts that have already been loaded will be skipped.
|
|
3283
|
+
*
|
|
3284
|
+
* @param families - Array of font families to load
|
|
3285
|
+
*/
|
|
3286
|
+
async loadFonts(families) {
|
|
3287
|
+
const unloadedFamilies = families.filter((family) => !this.loadedFonts.has(family.name));
|
|
3288
|
+
if (unloadedFamilies.length === 0) {
|
|
3289
|
+
return;
|
|
3290
|
+
}
|
|
3291
|
+
const fontPromises = unloadedFamilies.map((family) => this.loadSingleFont(family));
|
|
3292
|
+
await Promise.all(fontPromises);
|
|
3293
|
+
}
|
|
3294
|
+
/**
|
|
3295
|
+
* Build a Google Fonts URL with weight and italic variants.
|
|
3296
|
+
* Format: https://fonts.googleapis.com/css2?family=FontName:ital,wght@0,400;0,700;1,400;1,700
|
|
3297
|
+
*
|
|
3298
|
+
* @param family - Font family configuration
|
|
3299
|
+
* @returns The constructed URL with variants
|
|
3300
|
+
*/
|
|
3301
|
+
buildGoogleFontsUrl(family) {
|
|
3302
|
+
const baseUrl = family.url;
|
|
3303
|
+
if (!baseUrl.includes("fonts.googleapis.com")) {
|
|
3304
|
+
return baseUrl;
|
|
3305
|
+
}
|
|
3306
|
+
const urlObj = new URL(baseUrl);
|
|
3307
|
+
const familyParam = urlObj.searchParams.get("family");
|
|
3308
|
+
if (!familyParam) {
|
|
3309
|
+
return baseUrl;
|
|
3310
|
+
}
|
|
3311
|
+
const baseFamilyName = familyParam.split(":")[0];
|
|
3312
|
+
const weights = family.weights;
|
|
3313
|
+
const variants = [];
|
|
3314
|
+
if (family.italics) {
|
|
3315
|
+
for (const weight of weights) {
|
|
3316
|
+
variants.push(`0,${weight}`);
|
|
3317
|
+
}
|
|
3318
|
+
for (const weight of weights) {
|
|
3319
|
+
variants.push(`1,${weight}`);
|
|
3320
|
+
}
|
|
3321
|
+
urlObj.searchParams.set("family", `${baseFamilyName}:ital,wght@${variants.join(";")}`);
|
|
3322
|
+
} else {
|
|
3323
|
+
urlObj.searchParams.set("family", `${baseFamilyName}:wght@${weights.join(";")}`);
|
|
3324
|
+
}
|
|
3325
|
+
return urlObj.toString();
|
|
3326
|
+
}
|
|
3327
|
+
/**
|
|
3328
|
+
* Load a single font family.
|
|
3329
|
+
* Adds a link element to the document head and waits for the font to be ready.
|
|
3330
|
+
*
|
|
3331
|
+
* @param family - Font family to load
|
|
3332
|
+
*/
|
|
3333
|
+
loadSingleFont(family) {
|
|
3334
|
+
this.loadedFonts.add(family.name);
|
|
3335
|
+
return new Promise((resolve, reject) => {
|
|
3336
|
+
const link = document.createElement("link");
|
|
3337
|
+
link.rel = "stylesheet";
|
|
3338
|
+
link.href = this.buildGoogleFontsUrl(family);
|
|
3339
|
+
link.onload = async () => {
|
|
3340
|
+
try {
|
|
3341
|
+
const loadPromises = this.getFontLoadPromises(family);
|
|
3342
|
+
await Promise.all(loadPromises);
|
|
3343
|
+
await document.fonts.ready;
|
|
3344
|
+
resolve();
|
|
3345
|
+
} catch (error) {
|
|
3346
|
+
reject(new Error(`Failed to load font face for: ${family.name}. ${error}`));
|
|
3347
|
+
}
|
|
3348
|
+
};
|
|
3349
|
+
link.onerror = () => {
|
|
3350
|
+
reject(new Error(`Failed to load font stylesheet: ${family.name}`));
|
|
3351
|
+
};
|
|
3352
|
+
document.head.appendChild(link);
|
|
3353
|
+
});
|
|
3354
|
+
}
|
|
3355
|
+
/**
|
|
3356
|
+
* Get promises for loading all font variants (weights and italics).
|
|
3357
|
+
*
|
|
3358
|
+
* @param family - Font family configuration
|
|
3359
|
+
* @returns Array of promises for loading each font variant
|
|
3360
|
+
*/
|
|
3361
|
+
getFontLoadPromises(family) {
|
|
3362
|
+
const promises = [];
|
|
3363
|
+
const fontName = family.name;
|
|
3364
|
+
for (const weight of family.weights) {
|
|
3365
|
+
promises.push(document.fonts.load(`${weight} 12px "${fontName}"`));
|
|
3366
|
+
if (family.italics) {
|
|
3367
|
+
promises.push(document.fonts.load(`italic ${weight} 12px "${fontName}"`));
|
|
3368
|
+
}
|
|
3369
|
+
}
|
|
3370
|
+
return promises;
|
|
3371
|
+
}
|
|
3372
|
+
/**
|
|
3373
|
+
* Check if a font has been loaded.
|
|
3374
|
+
*
|
|
3375
|
+
* @param fontName - The font-family name to check
|
|
3376
|
+
* @returns True if the font has been loaded
|
|
3377
|
+
*/
|
|
3378
|
+
isLoaded(fontName) {
|
|
3379
|
+
return this.loadedFonts.has(fontName);
|
|
3380
|
+
}
|
|
3381
|
+
/**
|
|
3382
|
+
* Get the set of loaded font names.
|
|
3383
|
+
*/
|
|
3384
|
+
getLoadedFonts() {
|
|
3385
|
+
return this.loadedFonts;
|
|
3386
|
+
}
|
|
3387
|
+
};
|
|
3388
|
+
|
|
3389
|
+
// src/plugin.ts
|
|
3390
|
+
function parsePlugin(input) {
|
|
3391
|
+
if (typeof input === "function") {
|
|
3392
|
+
return input({});
|
|
3393
|
+
}
|
|
3394
|
+
return input;
|
|
3395
|
+
}
|
|
3396
|
+
function sortPluginsByDependencies(plugins) {
|
|
3397
|
+
const sorted = [];
|
|
3398
|
+
const visited = /* @__PURE__ */ new Set();
|
|
3399
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
3400
|
+
const pluginMap = new Map(plugins.map((p) => [p.name, p]));
|
|
3401
|
+
function visit(plugin) {
|
|
3402
|
+
if (visited.has(plugin.name)) return;
|
|
3403
|
+
if (visiting.has(plugin.name)) {
|
|
3404
|
+
throw new Error(`Circular plugin dependency detected: ${plugin.name}`);
|
|
3405
|
+
}
|
|
3406
|
+
visiting.add(plugin.name);
|
|
3407
|
+
for (const depName of plugin.dependencies ?? []) {
|
|
3408
|
+
const dep = pluginMap.get(depName);
|
|
3409
|
+
if (!dep) {
|
|
3410
|
+
throw new Error(`Plugin "${plugin.name}" depends on "${depName}" which is not registered`);
|
|
3411
|
+
}
|
|
3412
|
+
visit(dep);
|
|
3413
|
+
}
|
|
3414
|
+
visiting.delete(plugin.name);
|
|
3415
|
+
visited.add(plugin.name);
|
|
3416
|
+
sorted.push(plugin);
|
|
3417
|
+
}
|
|
3418
|
+
for (const plugin of plugins) {
|
|
3419
|
+
visit(plugin);
|
|
3420
|
+
}
|
|
3421
|
+
return sorted;
|
|
3422
|
+
}
|
|
3423
|
+
|
|
3424
|
+
// src/Editor.ts
|
|
3425
|
+
var PHASE_ORDER = ["input", "capture", "update", "render"];
|
|
3426
|
+
function batchSystems(systems) {
|
|
3427
|
+
const byPhase = /* @__PURE__ */ new Map();
|
|
3428
|
+
for (const phase of PHASE_ORDER) {
|
|
3429
|
+
byPhase.set(phase, []);
|
|
3430
|
+
}
|
|
3431
|
+
for (const system of systems) {
|
|
3432
|
+
byPhase.get(system.phase).push(system);
|
|
3433
|
+
}
|
|
3434
|
+
const result = [];
|
|
3435
|
+
for (const phase of PHASE_ORDER) {
|
|
3436
|
+
const phaseSystems = byPhase.get(phase);
|
|
3437
|
+
const byPriority = /* @__PURE__ */ new Map();
|
|
3438
|
+
for (const system of phaseSystems) {
|
|
3439
|
+
const group = byPriority.get(system.priority) ?? [];
|
|
3440
|
+
group.push(system);
|
|
3441
|
+
byPriority.set(system.priority, group);
|
|
3442
|
+
}
|
|
3443
|
+
const priorities = [...byPriority.keys()].sort((a, b) => b - a);
|
|
3444
|
+
for (const p of priorities) {
|
|
3445
|
+
result.push(byPriority.get(p));
|
|
3446
|
+
}
|
|
3447
|
+
}
|
|
3448
|
+
return result;
|
|
3449
|
+
}
|
|
3450
|
+
var Editor = class {
|
|
3451
|
+
constructor(domElement, optionsInput) {
|
|
3452
|
+
this.cursors = {};
|
|
3453
|
+
this.blockDefs = {};
|
|
3454
|
+
this.fonts = [];
|
|
3455
|
+
this.components = [];
|
|
3456
|
+
this.singletons = [];
|
|
3457
|
+
const options = EditorOptionsSchema.parse(optionsInput ?? {});
|
|
3458
|
+
const { plugins: pluginInputs, maxEntities } = options;
|
|
3459
|
+
const user = UserData.parse(options.user ?? {});
|
|
3460
|
+
this.userData = user;
|
|
3461
|
+
this.gridOptions = GridOptions.parse(options.grid ?? {});
|
|
3462
|
+
this.controlsOptions = ControlsOptions.parse(options.controls ?? {});
|
|
3463
|
+
const plugins = pluginInputs.map(parsePlugin);
|
|
3464
|
+
const sortedPlugins = sortPluginsByDependencies([CorePlugin, ...plugins]);
|
|
3465
|
+
const allDefs = [CommandMarker, import_canvas_store32.Synced];
|
|
3466
|
+
for (const plugin of sortedPlugins) {
|
|
3467
|
+
if (plugin.components) {
|
|
3468
|
+
allDefs.push(...plugin.components);
|
|
3469
|
+
}
|
|
3470
|
+
if (plugin.singletons) {
|
|
3471
|
+
allDefs.push(...plugin.singletons);
|
|
3472
|
+
}
|
|
3473
|
+
}
|
|
3474
|
+
allDefs.push(...options.components);
|
|
3475
|
+
allDefs.push(...options.singletons);
|
|
3476
|
+
const keybinds = options.keybinds;
|
|
3477
|
+
if (!options.omitPluginKeybinds) {
|
|
3478
|
+
for (const plugin of sortedPlugins) {
|
|
3479
|
+
if (plugin.keybinds) {
|
|
3480
|
+
keybinds.push(...plugin.keybinds);
|
|
3481
|
+
}
|
|
3482
|
+
}
|
|
3483
|
+
}
|
|
3484
|
+
this.keybinds = keybinds;
|
|
3485
|
+
const blockDefs = {};
|
|
3486
|
+
for (const input of options.blockDefs) {
|
|
3487
|
+
const parsed = BlockDef2.parse(input);
|
|
3488
|
+
blockDefs[parsed.tag] = parsed;
|
|
3489
|
+
}
|
|
3490
|
+
for (const plugin of sortedPlugins) {
|
|
3491
|
+
if (plugin.blockDefs) {
|
|
3492
|
+
for (const input of plugin.blockDefs) {
|
|
3493
|
+
const parsed = BlockDef2.parse(input);
|
|
3494
|
+
blockDefs[parsed.tag] = parsed;
|
|
3495
|
+
}
|
|
3496
|
+
}
|
|
3497
|
+
}
|
|
3498
|
+
this.blockDefs = blockDefs;
|
|
3499
|
+
const cursors = {};
|
|
3500
|
+
Object.assign(cursors, options.cursors);
|
|
3501
|
+
if (!options.omitPluginCursors) {
|
|
3502
|
+
for (const plugin of sortedPlugins) {
|
|
3503
|
+
if (plugin.cursors) {
|
|
3504
|
+
Object.assign(cursors, plugin.cursors);
|
|
3505
|
+
}
|
|
3506
|
+
}
|
|
3507
|
+
}
|
|
3508
|
+
this.cursors = cursors;
|
|
3509
|
+
this.fontLoader = new FontLoader();
|
|
3510
|
+
const fontInputs = [...options.fonts];
|
|
3511
|
+
if (!options.omitPluginFonts) {
|
|
3512
|
+
for (const plugin of sortedPlugins) {
|
|
3513
|
+
if (plugin.fonts) {
|
|
3514
|
+
fontInputs.push(...plugin.fonts);
|
|
3515
|
+
}
|
|
3516
|
+
}
|
|
3517
|
+
}
|
|
3518
|
+
this.fonts = fontInputs.map((input) => FontFamily.parse(input));
|
|
3519
|
+
const pluginResources = {};
|
|
3520
|
+
for (const plugin of sortedPlugins) {
|
|
3521
|
+
if (plugin.resources !== void 0) {
|
|
3522
|
+
pluginResources[plugin.name] = plugin.resources;
|
|
3523
|
+
}
|
|
3524
|
+
}
|
|
3525
|
+
const componentsByName = /* @__PURE__ */ new Map();
|
|
3526
|
+
const singletonsByName = /* @__PURE__ */ new Map();
|
|
3527
|
+
const componentsById = /* @__PURE__ */ new Map();
|
|
3528
|
+
const singletonsById = /* @__PURE__ */ new Map();
|
|
3529
|
+
const allResources = {
|
|
3530
|
+
domElement,
|
|
3531
|
+
editor: this,
|
|
3532
|
+
userId: user.userId,
|
|
3533
|
+
sessionId: user.sessionId,
|
|
3534
|
+
pluginResources,
|
|
3535
|
+
componentsByName,
|
|
3536
|
+
singletonsByName,
|
|
3537
|
+
componentsById,
|
|
3538
|
+
singletonsById
|
|
3539
|
+
};
|
|
3540
|
+
this.world = new import_core47.World(allDefs, {
|
|
3541
|
+
maxEntities,
|
|
3542
|
+
resources: allResources
|
|
3543
|
+
});
|
|
3544
|
+
const allSystems = [];
|
|
3545
|
+
for (const plugin of sortedPlugins) {
|
|
3546
|
+
if (plugin.systems) {
|
|
3547
|
+
allSystems.push(...plugin.systems);
|
|
3548
|
+
}
|
|
3549
|
+
}
|
|
3550
|
+
allSystems.push(...options.systems);
|
|
3551
|
+
this.systemBatches = batchSystems(allSystems);
|
|
3552
|
+
this.plugins = new Map(sortedPlugins.map((p) => [p.name, p]));
|
|
3553
|
+
for (const plugin of sortedPlugins) {
|
|
3554
|
+
if (plugin.components) {
|
|
3555
|
+
for (const comp of plugin.components) {
|
|
3556
|
+
const componentId = comp._getComponentId(this.ctx);
|
|
3557
|
+
componentsById.set(componentId, comp);
|
|
3558
|
+
componentsByName.set(comp.name, comp);
|
|
3559
|
+
}
|
|
3560
|
+
}
|
|
3561
|
+
if (plugin.singletons) {
|
|
3562
|
+
for (const singleton of plugin.singletons) {
|
|
3563
|
+
const componentId = singleton._getComponentId(this.ctx);
|
|
3564
|
+
singletonsById.set(componentId, singleton);
|
|
3565
|
+
singletonsByName.set(singleton.name, singleton);
|
|
3566
|
+
}
|
|
3567
|
+
}
|
|
3568
|
+
}
|
|
3569
|
+
for (const comp of options.components) {
|
|
3570
|
+
const componentId = comp._getComponentId(this.ctx);
|
|
3571
|
+
componentsById.set(componentId, comp);
|
|
3572
|
+
componentsByName.set(comp.name, comp);
|
|
3573
|
+
}
|
|
3574
|
+
for (const singleton of options.singletons) {
|
|
3575
|
+
const componentId = singleton._getComponentId(this.ctx);
|
|
3576
|
+
singletonsById.set(componentId, singleton);
|
|
3577
|
+
singletonsByName.set(singleton.name, singleton);
|
|
3578
|
+
}
|
|
3579
|
+
this.components = [...componentsByName.values()];
|
|
3580
|
+
this.singletons = [...singletonsByName.values()];
|
|
3581
|
+
}
|
|
3582
|
+
/**
|
|
3583
|
+
* Get the ECS context.
|
|
3584
|
+
* Use this to access ECS functions and resources.
|
|
3585
|
+
*/
|
|
3586
|
+
get ctx() {
|
|
3587
|
+
return this.world._getContext();
|
|
3588
|
+
}
|
|
3589
|
+
/**
|
|
3590
|
+
* Initialize the editor.
|
|
3591
|
+
* Call this after construction to run async setup.
|
|
3592
|
+
*/
|
|
3593
|
+
async initialize() {
|
|
3594
|
+
if (this.fonts.length > 0) {
|
|
3595
|
+
await this.fontLoader.loadFonts(this.fonts);
|
|
3596
|
+
}
|
|
3597
|
+
Grid.copy(this.ctx, this.gridOptions);
|
|
3598
|
+
Controls.copy(this.ctx, this.controlsOptions);
|
|
3599
|
+
this.nextTick((ctx) => {
|
|
3600
|
+
const userEntity = (0, import_core47.createEntity)(ctx);
|
|
3601
|
+
(0, import_core47.addComponent)(ctx, userEntity, import_canvas_store32.Synced, {
|
|
3602
|
+
id: crypto.randomUUID()
|
|
3603
|
+
});
|
|
3604
|
+
(0, import_core47.addComponent)(ctx, userEntity, User, {
|
|
3605
|
+
userId: this.userData.userId,
|
|
3606
|
+
sessionId: this.userData.sessionId,
|
|
3607
|
+
color: this.userData.color,
|
|
3608
|
+
name: this.userData.name,
|
|
3609
|
+
avatar: this.userData.avatar
|
|
3610
|
+
});
|
|
3611
|
+
});
|
|
3612
|
+
for (const plugin of this.plugins.values()) {
|
|
3613
|
+
if (plugin.setup) {
|
|
3614
|
+
await plugin.setup(this.ctx);
|
|
3615
|
+
}
|
|
3616
|
+
}
|
|
3617
|
+
}
|
|
3618
|
+
/**
|
|
3619
|
+
* Run one frame of the editor loop.
|
|
3620
|
+
*
|
|
3621
|
+
* Executes systems in phase order:
|
|
3622
|
+
* 1. Input - convert raw events to ECS state
|
|
3623
|
+
* 2. Capture - detect targets, compute intersections
|
|
3624
|
+
* 3. Update - modify document state, process commands
|
|
3625
|
+
* 4. Render - sync ECS state to output
|
|
3626
|
+
*
|
|
3627
|
+
* Commands spawned via `command()` are available during this frame
|
|
3628
|
+
* and automatically cleaned up at the end.
|
|
3629
|
+
*/
|
|
3630
|
+
async tick() {
|
|
3631
|
+
this.world.sync();
|
|
3632
|
+
for (const batch of this.systemBatches) {
|
|
3633
|
+
const systems = batch.map((s) => s._system);
|
|
3634
|
+
await this.world.execute(...systems);
|
|
3635
|
+
}
|
|
3636
|
+
const currentEventIndex = this.ctx.eventBuffer.getWriteIndex();
|
|
3637
|
+
this.ctx.prevEventIndex = currentEventIndex;
|
|
3638
|
+
this.ctx.currEventIndex = currentEventIndex;
|
|
3639
|
+
cleanupCommands(this.ctx);
|
|
3640
|
+
}
|
|
3641
|
+
/**
|
|
3642
|
+
* Schedule work for the next tick.
|
|
3643
|
+
* Use this from event handlers to safely modify ECS state.
|
|
3644
|
+
*
|
|
3645
|
+
* @param callback - Function to execute at next tick
|
|
3646
|
+
*
|
|
3647
|
+
* @example
|
|
3648
|
+
* ```typescript
|
|
3649
|
+
* element.addEventListener('click', () => {
|
|
3650
|
+
* editor.nextTick((ctx) => {
|
|
3651
|
+
* const block = Block.write(ctx, entityId);
|
|
3652
|
+
* block.selected = true;
|
|
3653
|
+
* });
|
|
3654
|
+
* });
|
|
3655
|
+
* ```
|
|
3656
|
+
*/
|
|
3657
|
+
nextTick(callback) {
|
|
3658
|
+
return this.world.nextSync((ctx) => {
|
|
3659
|
+
callback(ctx);
|
|
3660
|
+
});
|
|
3661
|
+
}
|
|
3662
|
+
/**
|
|
3663
|
+
* Spawn a command to be processed this frame.
|
|
3664
|
+
* Commands are ephemeral entities that systems can react to via `CommandDef.iter()`.
|
|
3665
|
+
*
|
|
3666
|
+
* @param def - The command definition created with `defineCommand()`
|
|
3667
|
+
* @param payload - Command payload data
|
|
3668
|
+
*
|
|
3669
|
+
* @example
|
|
3670
|
+
* ```typescript
|
|
3671
|
+
* const SelectAll = defineCommand<{ filter?: string }>("select-all");
|
|
3672
|
+
*
|
|
3673
|
+
* // From UI event handler
|
|
3674
|
+
* editor.command(SelectAll, { filter: "blocks" });
|
|
3675
|
+
* ```
|
|
3676
|
+
*/
|
|
3677
|
+
command(def, ...args) {
|
|
3678
|
+
const payload = args[0];
|
|
3679
|
+
this.nextTick((ctx) => {
|
|
3680
|
+
def.spawn(ctx, payload);
|
|
3681
|
+
});
|
|
3682
|
+
}
|
|
3683
|
+
/**
|
|
3684
|
+
* Subscribe to query changes.
|
|
3685
|
+
*
|
|
3686
|
+
* @param query - Query definition
|
|
3687
|
+
* @param callback - Called when query results change
|
|
3688
|
+
* @returns Unsubscribe function
|
|
3689
|
+
*
|
|
3690
|
+
* @example
|
|
3691
|
+
* ```typescript
|
|
3692
|
+
* const unsubscribe = editor.subscribe(selectedBlocks, (ctx, { added, removed }) => {
|
|
3693
|
+
* console.log('Selection changed:', added, removed);
|
|
3694
|
+
* });
|
|
3695
|
+
*
|
|
3696
|
+
* // Later:
|
|
3697
|
+
* unsubscribe();
|
|
3698
|
+
* ```
|
|
3699
|
+
*/
|
|
3700
|
+
subscribe(query, callback) {
|
|
3701
|
+
return this.world.subscribe(query, (ctx, result) => {
|
|
3702
|
+
callback(ctx, result);
|
|
3703
|
+
});
|
|
3704
|
+
}
|
|
3705
|
+
/**
|
|
3706
|
+
* Get the ECS context.
|
|
3707
|
+
* Only for testing and advanced use cases.
|
|
3708
|
+
* @internal
|
|
3709
|
+
*/
|
|
3710
|
+
_getContext() {
|
|
3711
|
+
return this.ctx;
|
|
3712
|
+
}
|
|
3713
|
+
/**
|
|
3714
|
+
* Clean up the editor.
|
|
3715
|
+
* Call this when done to release resources.
|
|
3716
|
+
*/
|
|
3717
|
+
dispose() {
|
|
3718
|
+
const plugins = Array.from(this.plugins.values()).reverse();
|
|
3719
|
+
for (const plugin of plugins) {
|
|
3720
|
+
if (plugin.teardown) {
|
|
3721
|
+
plugin.teardown(this.ctx);
|
|
3722
|
+
}
|
|
3723
|
+
}
|
|
3724
|
+
this.world.dispose();
|
|
3725
|
+
this.plugins.clear();
|
|
3726
|
+
this.systemBatches = [];
|
|
3727
|
+
}
|
|
3728
|
+
};
|
|
3729
|
+
|
|
3730
|
+
// src/EditorStateDef.ts
|
|
3731
|
+
var import_canvas_store33 = require("@woven-ecs/canvas-store");
|
|
3732
|
+
|
|
3733
|
+
// src/machine.ts
|
|
3734
|
+
var import_xstate = require("xstate");
|
|
3735
|
+
function runMachine(machine, currentState, context, events) {
|
|
3736
|
+
if (events.length === 0) {
|
|
3737
|
+
return { value: currentState, context };
|
|
3738
|
+
}
|
|
3739
|
+
let state = machine.resolveState({
|
|
3740
|
+
value: String(currentState),
|
|
3741
|
+
context
|
|
3742
|
+
});
|
|
3743
|
+
for (const event of events) {
|
|
3744
|
+
const [nextState, actions] = (0, import_xstate.transition)(machine, state, event);
|
|
3745
|
+
state = nextState;
|
|
3746
|
+
for (const action of actions) {
|
|
3747
|
+
if (typeof action.exec === "function") {
|
|
3748
|
+
action.exec(action.info, action.params);
|
|
3749
|
+
}
|
|
3750
|
+
}
|
|
3751
|
+
}
|
|
3752
|
+
return {
|
|
3753
|
+
value: state.value,
|
|
3754
|
+
context: state.context
|
|
3755
|
+
};
|
|
3756
|
+
}
|
|
3757
|
+
|
|
3758
|
+
// src/EditorStateDef.ts
|
|
3759
|
+
var EditorStateDef = class extends import_canvas_store33.CanvasSingletonDef {
|
|
3760
|
+
constructor(name, schema) {
|
|
3761
|
+
super({ name, sync: "none" }, schema);
|
|
3762
|
+
}
|
|
3763
|
+
/**
|
|
3764
|
+
* Get the current state value.
|
|
3765
|
+
*
|
|
3766
|
+
* @param ctx - The ECS context
|
|
3767
|
+
* @returns The current state machine state value
|
|
3768
|
+
*/
|
|
3769
|
+
getState(ctx) {
|
|
3770
|
+
return this.read(ctx).state;
|
|
3771
|
+
}
|
|
3772
|
+
/**
|
|
3773
|
+
* Get the machine context (all fields except 'state').
|
|
3774
|
+
*
|
|
3775
|
+
* @param ctx - The ECS context
|
|
3776
|
+
* @returns Plain object with context field values
|
|
3777
|
+
*/
|
|
3778
|
+
getContext(ctx) {
|
|
3779
|
+
const snapshot = this.snapshot(ctx);
|
|
3780
|
+
const result = {};
|
|
3781
|
+
for (const key of Object.keys(snapshot)) {
|
|
3782
|
+
if (key !== "state") {
|
|
3783
|
+
result[key] = snapshot[key];
|
|
3784
|
+
}
|
|
3785
|
+
}
|
|
3786
|
+
return result;
|
|
3787
|
+
}
|
|
3788
|
+
/**
|
|
3789
|
+
* Run an XState machine through events and update the singleton state.
|
|
3790
|
+
*
|
|
3791
|
+
* This method encapsulates the common pattern of:
|
|
3792
|
+
* 1. Reading the current state and context
|
|
3793
|
+
* 2. Running the machine through events
|
|
3794
|
+
* 3. Writing the updated state and context back
|
|
3795
|
+
*
|
|
3796
|
+
* @param ctx - The ECS context
|
|
3797
|
+
* @param machine - The XState machine definition
|
|
3798
|
+
* @param events - Array of events to process (must have a 'type' property)
|
|
3799
|
+
* @returns The resulting state value and context
|
|
3800
|
+
*
|
|
3801
|
+
* @example
|
|
3802
|
+
* ```typescript
|
|
3803
|
+
* const capturePanSystem = defineSystem((ctx) => {
|
|
3804
|
+
* const events = getPointerInput(ctx, ["middle"]);
|
|
3805
|
+
* if (events.length === 0) return;
|
|
3806
|
+
*
|
|
3807
|
+
* PanState.run(ctx, createPanMachine(ctx), events);
|
|
3808
|
+
* });
|
|
3809
|
+
* ```
|
|
3810
|
+
*/
|
|
3811
|
+
run(ctx, machine, events) {
|
|
3812
|
+
const currentState = this.getState(ctx);
|
|
3813
|
+
const currentContext = this.getContext(ctx);
|
|
3814
|
+
const result = runMachine(
|
|
3815
|
+
machine,
|
|
3816
|
+
currentState,
|
|
3817
|
+
currentContext,
|
|
3818
|
+
events
|
|
3819
|
+
);
|
|
3820
|
+
const writable = this.write(ctx);
|
|
3821
|
+
writable.state = result.value;
|
|
3822
|
+
for (const [key, value] of Object.entries(result.context)) {
|
|
3823
|
+
;
|
|
3824
|
+
writable[key] = value;
|
|
3825
|
+
}
|
|
3826
|
+
return result;
|
|
3827
|
+
}
|
|
3828
|
+
};
|
|
3829
|
+
function defineEditorState(name, schema) {
|
|
3830
|
+
return new EditorStateDef(name, schema);
|
|
3831
|
+
}
|
|
3832
|
+
|
|
3833
|
+
// src/events/frameInputEvents.ts
|
|
3834
|
+
function getFrameInput(ctx) {
|
|
3835
|
+
const frame = Frame.read(ctx);
|
|
3836
|
+
return {
|
|
3837
|
+
type: "frame",
|
|
3838
|
+
ctx,
|
|
3839
|
+
delta: frame.delta,
|
|
3840
|
+
frameNumber: frame.number,
|
|
3841
|
+
time: frame.time
|
|
3842
|
+
};
|
|
3843
|
+
}
|
|
3844
|
+
|
|
3845
|
+
// src/events/keyboardInputEvents.ts
|
|
3846
|
+
function getKeyboardInput(ctx, keys) {
|
|
3847
|
+
const events = [];
|
|
3848
|
+
for (const key of keys) {
|
|
3849
|
+
if (Keyboard.isKeyDownTrigger(ctx, key)) {
|
|
3850
|
+
events.push({ type: "keyDown", key, ctx });
|
|
3851
|
+
}
|
|
3852
|
+
if (Keyboard.isKeyUpTrigger(ctx, key)) {
|
|
3853
|
+
events.push({ type: "keyUp", key, ctx });
|
|
3854
|
+
}
|
|
3855
|
+
}
|
|
3856
|
+
return events;
|
|
3857
|
+
}
|
|
3858
|
+
|
|
3859
|
+
// src/events/mouseInputEvents.ts
|
|
3860
|
+
function getMouseInput(ctx) {
|
|
3861
|
+
const events = [];
|
|
3862
|
+
const mouse = Mouse.read(ctx);
|
|
3863
|
+
const screenPos = [mouse.position[0], mouse.position[1]];
|
|
3864
|
+
const worldPos = Camera.toWorld(ctx, screenPos);
|
|
3865
|
+
if (mouse.wheelTrigger) {
|
|
3866
|
+
events.push({
|
|
3867
|
+
type: "wheel",
|
|
3868
|
+
ctx,
|
|
3869
|
+
screenPosition: screenPos,
|
|
3870
|
+
worldPosition: worldPos,
|
|
3871
|
+
wheelDeltaX: mouse.wheelDeltaX,
|
|
3872
|
+
wheelDeltaY: mouse.wheelDeltaY
|
|
3873
|
+
});
|
|
3874
|
+
}
|
|
3875
|
+
if (mouse.moveTrigger) {
|
|
3876
|
+
events.push({
|
|
3877
|
+
type: "mouseMove",
|
|
3878
|
+
ctx,
|
|
3879
|
+
screenPosition: screenPos,
|
|
3880
|
+
worldPosition: worldPos,
|
|
3881
|
+
wheelDeltaX: 0,
|
|
3882
|
+
wheelDeltaY: 0
|
|
3883
|
+
});
|
|
3884
|
+
}
|
|
3885
|
+
return events;
|
|
3886
|
+
}
|
|
3887
|
+
|
|
3888
|
+
// src/events/pointerInputEvents.ts
|
|
3889
|
+
var import_math9 = require("@woven-canvas/math");
|
|
3890
|
+
var import_core48 = require("@woven-ecs/core");
|
|
3891
|
+
var DEFAULT_CLICK_MOVE_THRESHOLD = 3;
|
|
3892
|
+
var DEFAULT_CLICK_FRAME_THRESHOLD = 30;
|
|
3893
|
+
var pointerQuery3 = (0, import_core48.defineQuery)((q) => q.tracking(Pointer));
|
|
3894
|
+
var trackingState = /* @__PURE__ */ new Map();
|
|
3895
|
+
function getTrackingState(ctx) {
|
|
3896
|
+
const key = ctx.readerId;
|
|
3897
|
+
let state = trackingState.get(key);
|
|
3898
|
+
if (!state) {
|
|
3899
|
+
state = {
|
|
3900
|
+
prevPositions: /* @__PURE__ */ new Map(),
|
|
3901
|
+
prevModifiers: {
|
|
3902
|
+
shiftDown: false,
|
|
3903
|
+
altDown: false,
|
|
3904
|
+
modDown: false
|
|
3905
|
+
},
|
|
3906
|
+
prevCamera: {
|
|
3907
|
+
left: 0,
|
|
3908
|
+
top: 0,
|
|
3909
|
+
zoom: 1
|
|
3910
|
+
}
|
|
3911
|
+
};
|
|
3912
|
+
trackingState.set(key, state);
|
|
3913
|
+
}
|
|
3914
|
+
return state;
|
|
3915
|
+
}
|
|
3916
|
+
function getPointerInput(ctx, buttons, options = {}) {
|
|
3917
|
+
if (buttons.length === 0) return [];
|
|
3918
|
+
const {
|
|
3919
|
+
includeFrameEvent = false,
|
|
3920
|
+
clickMoveThreshold = DEFAULT_CLICK_MOVE_THRESHOLD,
|
|
3921
|
+
clickFrameThreshold = DEFAULT_CLICK_FRAME_THRESHOLD
|
|
3922
|
+
} = options;
|
|
3923
|
+
const state = getTrackingState(ctx);
|
|
3924
|
+
const frameNumber = Frame.read(ctx).number;
|
|
3925
|
+
const events = [];
|
|
3926
|
+
const keyboard = Keyboard.read(ctx);
|
|
3927
|
+
const modifiers = {
|
|
3928
|
+
shiftDown: keyboard.shiftDown,
|
|
3929
|
+
altDown: keyboard.altDown,
|
|
3930
|
+
modDown: keyboard.modDown
|
|
3931
|
+
};
|
|
3932
|
+
const camera = Camera.read(ctx);
|
|
3933
|
+
const matchesButtons = (entityId) => {
|
|
3934
|
+
const pointer = Pointer.read(ctx, entityId);
|
|
3935
|
+
return buttons.includes(pointer.button);
|
|
3936
|
+
};
|
|
3937
|
+
const intersects = Intersect.getAll(ctx);
|
|
3938
|
+
const createEvent = (type, entityId) => {
|
|
3939
|
+
const pointer = Pointer.read(ctx, entityId);
|
|
3940
|
+
const screenPos = [pointer.position[0], pointer.position[1]];
|
|
3941
|
+
const worldPos = Camera.toWorld(ctx, screenPos);
|
|
3942
|
+
return {
|
|
3943
|
+
type,
|
|
3944
|
+
ctx,
|
|
3945
|
+
screenPosition: screenPos,
|
|
3946
|
+
worldPosition: worldPos,
|
|
3947
|
+
velocity: [pointer._velocity[0], pointer._velocity[1]],
|
|
3948
|
+
pressure: pointer.pressure,
|
|
3949
|
+
button: pointer.button,
|
|
3950
|
+
pointerType: pointer.pointerType,
|
|
3951
|
+
obscured: pointer.obscured,
|
|
3952
|
+
shiftDown: modifiers.shiftDown,
|
|
3953
|
+
altDown: modifiers.altDown,
|
|
3954
|
+
modDown: modifiers.modDown,
|
|
3955
|
+
cameraLeft: camera.left,
|
|
3956
|
+
cameraTop: camera.top,
|
|
3957
|
+
cameraZoom: camera.zoom,
|
|
3958
|
+
pointerId: entityId,
|
|
3959
|
+
intersects
|
|
3960
|
+
};
|
|
3961
|
+
};
|
|
3962
|
+
const addedPointers = pointerQuery3.added(ctx);
|
|
3963
|
+
const removedPointers = pointerQuery3.removed(ctx);
|
|
3964
|
+
const changedPointers = pointerQuery3.changed(ctx);
|
|
3965
|
+
const currentPointers = pointerQuery3.current(ctx);
|
|
3966
|
+
const matchingAdded = addedPointers.filter(matchesButtons);
|
|
3967
|
+
const matchingRemoved = removedPointers.filter(matchesButtons);
|
|
3968
|
+
const matchingChanged = changedPointers.filter(matchesButtons);
|
|
3969
|
+
const matchingCurrent = Array.from(currentPointers).filter(matchesButtons);
|
|
3970
|
+
const escapePressed = Keyboard.isKeyDownTrigger(ctx, Key.Escape);
|
|
3971
|
+
const multiTouch = matchingCurrent.length > 1 && matchingAdded.length > 0;
|
|
3972
|
+
if ((escapePressed || multiTouch) && matchingCurrent.length > 0) {
|
|
3973
|
+
events.push(createEvent("cancel", matchingCurrent[0]));
|
|
3974
|
+
return events;
|
|
3975
|
+
}
|
|
3976
|
+
if (matchingAdded.length > 0 && matchingCurrent.length === 1) {
|
|
3977
|
+
const entityId = matchingCurrent[0];
|
|
3978
|
+
const pointer = Pointer.read(ctx, entityId);
|
|
3979
|
+
if (!pointer.obscured) {
|
|
3980
|
+
events.push(createEvent("pointerDown", entityId));
|
|
3981
|
+
state.prevPositions.set(entityId, [pointer.position[0], pointer.position[1]]);
|
|
3982
|
+
}
|
|
3983
|
+
}
|
|
3984
|
+
if (matchingRemoved.length > 0 && matchingCurrent.length === 0) {
|
|
3985
|
+
const entityId = matchingRemoved[0];
|
|
3986
|
+
const pointer = Pointer.read(ctx, entityId);
|
|
3987
|
+
events.push(createEvent("pointerUp", entityId));
|
|
3988
|
+
const downPos = state.prevPositions.get(entityId);
|
|
3989
|
+
if (downPos) {
|
|
3990
|
+
const currentPos = [pointer.position[0], pointer.position[1]];
|
|
3991
|
+
const dist = import_math9.Vec2.distance(downPos, currentPos);
|
|
3992
|
+
const deltaFrame = frameNumber - pointer.downFrame;
|
|
3993
|
+
if (dist < clickMoveThreshold && deltaFrame < clickFrameThreshold) {
|
|
3994
|
+
events.push(createEvent("click", entityId));
|
|
3995
|
+
}
|
|
3996
|
+
state.prevPositions.delete(entityId);
|
|
3997
|
+
}
|
|
3998
|
+
}
|
|
3999
|
+
const modifiersChanged = modifiers.shiftDown !== state.prevModifiers.shiftDown || modifiers.altDown !== state.prevModifiers.altDown || modifiers.modDown !== state.prevModifiers.modDown;
|
|
4000
|
+
const cameraChanged = camera.left !== state.prevCamera.left || camera.top !== state.prevCamera.top || camera.zoom !== state.prevCamera.zoom;
|
|
4001
|
+
if ((matchingChanged.length > 0 || modifiersChanged || cameraChanged) && matchingCurrent.length === 1) {
|
|
4002
|
+
events.push(createEvent("pointerMove", matchingCurrent[0]));
|
|
4003
|
+
}
|
|
4004
|
+
if (includeFrameEvent && matchingCurrent.length === 1) {
|
|
4005
|
+
events.push(createEvent("frame", matchingCurrent[0]));
|
|
4006
|
+
}
|
|
4007
|
+
state.prevModifiers = { ...modifiers };
|
|
4008
|
+
state.prevCamera = { left: camera.left, top: camera.top, zoom: camera.zoom };
|
|
4009
|
+
return events;
|
|
4010
|
+
}
|
|
4011
|
+
function clearPointerTrackingState(ctx) {
|
|
4012
|
+
trackingState.delete(ctx.readerId);
|
|
4013
|
+
}
|
|
4014
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
4015
|
+
0 && (module.exports = {
|
|
4016
|
+
Aabb,
|
|
4017
|
+
Asset,
|
|
4018
|
+
Block,
|
|
4019
|
+
BlockDef,
|
|
4020
|
+
Camera,
|
|
4021
|
+
CanvasComponentDef,
|
|
4022
|
+
CanvasSingletonDef,
|
|
4023
|
+
Color,
|
|
4024
|
+
CommandMarker,
|
|
4025
|
+
ComponentDef,
|
|
4026
|
+
Connector,
|
|
4027
|
+
Controls,
|
|
4028
|
+
ControlsOptions,
|
|
4029
|
+
CorePlugin,
|
|
4030
|
+
Cursor,
|
|
4031
|
+
CursorDef,
|
|
4032
|
+
Edited,
|
|
4033
|
+
Editor,
|
|
4034
|
+
EditorStateDef,
|
|
4035
|
+
EventType,
|
|
4036
|
+
FontFamily,
|
|
4037
|
+
FontLoader,
|
|
4038
|
+
Frame,
|
|
4039
|
+
Grid,
|
|
4040
|
+
GridOptions,
|
|
4041
|
+
Held,
|
|
4042
|
+
HitGeometry,
|
|
4043
|
+
Hovered,
|
|
4044
|
+
Image,
|
|
4045
|
+
Intersect,
|
|
4046
|
+
Key,
|
|
4047
|
+
Keybind,
|
|
4048
|
+
Keyboard,
|
|
4049
|
+
MAX_HIT_ARCS,
|
|
4050
|
+
MAX_HIT_CAPSULES,
|
|
4051
|
+
MainThreadSystem,
|
|
4052
|
+
Mouse,
|
|
4053
|
+
Opacity,
|
|
4054
|
+
Pointer,
|
|
4055
|
+
PointerButton,
|
|
4056
|
+
PointerType,
|
|
4057
|
+
RankBounds,
|
|
4058
|
+
Redo,
|
|
4059
|
+
SINGLETON_ENTITY_ID,
|
|
4060
|
+
STRATUM_ORDER,
|
|
4061
|
+
ScaleWithZoom,
|
|
4062
|
+
ScaleWithZoomState,
|
|
4063
|
+
Screen,
|
|
4064
|
+
Shape,
|
|
4065
|
+
Stratum,
|
|
4066
|
+
StrokeKind,
|
|
4067
|
+
Synced,
|
|
4068
|
+
Text,
|
|
4069
|
+
TextAlignment,
|
|
4070
|
+
Undo,
|
|
4071
|
+
UploadState,
|
|
4072
|
+
User,
|
|
4073
|
+
UserData,
|
|
4074
|
+
VerticalAlign,
|
|
4075
|
+
VerticalAlignment,
|
|
4076
|
+
addComponent,
|
|
4077
|
+
canBlockEdit,
|
|
4078
|
+
clearPointerTrackingState,
|
|
4079
|
+
createEntity,
|
|
4080
|
+
defineCanvasComponent,
|
|
4081
|
+
defineCanvasSingleton,
|
|
4082
|
+
defineCommand,
|
|
4083
|
+
defineEditorState,
|
|
4084
|
+
defineEditorSystem,
|
|
4085
|
+
defineQuery,
|
|
4086
|
+
defineSystem,
|
|
4087
|
+
field,
|
|
4088
|
+
getBackrefs,
|
|
4089
|
+
getBlockDef,
|
|
4090
|
+
getFrameInput,
|
|
4091
|
+
getKeyboardInput,
|
|
4092
|
+
getMouseInput,
|
|
4093
|
+
getPluginResources,
|
|
4094
|
+
getPointerInput,
|
|
4095
|
+
getResources,
|
|
4096
|
+
getTopmostBlockAtPoint,
|
|
4097
|
+
hasComponent,
|
|
4098
|
+
intersectAabb,
|
|
4099
|
+
intersectCapsule,
|
|
4100
|
+
intersectPoint,
|
|
4101
|
+
isAlive,
|
|
4102
|
+
isHeldByRemote,
|
|
4103
|
+
on,
|
|
4104
|
+
parsePlugin,
|
|
4105
|
+
removeComponent,
|
|
4106
|
+
removeEntity,
|
|
4107
|
+
runMachine
|
|
4108
|
+
});
|
|
4109
|
+
//# sourceMappingURL=index.cjs.map
|