@fieldnotes/core 0.38.0 → 0.38.1
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/README.md +675 -675
- package/dist/index.cjs +537 -508
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +0 -17
- package/dist/index.d.ts +0 -17
- package/dist/index.js +537 -508
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -7195,14 +7195,11 @@ function computeSnapGuides(moving, targets, threshold) {
|
|
|
7195
7195
|
return { dx: xSnap?.delta ?? 0, dy: ySnap?.delta ?? 0, guides };
|
|
7196
7196
|
}
|
|
7197
7197
|
|
|
7198
|
-
// src/tools/select-
|
|
7198
|
+
// src/tools/select-overlay.ts
|
|
7199
7199
|
var HANDLE_SIZE = 8;
|
|
7200
|
-
var SNAP_PX = 6;
|
|
7201
7200
|
var HANDLE_HIT_PADDING2 = 4;
|
|
7202
7201
|
var SELECTION_PAD = 4;
|
|
7203
|
-
var MIN_ELEMENT_SIZE = 20;
|
|
7204
7202
|
var ROTATE_HANDLE_OFFSET = 24;
|
|
7205
|
-
var ROTATE_SNAP = Math.PI / 12;
|
|
7206
7203
|
var ROTATABLE_TYPES = /* @__PURE__ */ new Set(["note", "text", "image", "html", "shape", "stroke"]);
|
|
7207
7204
|
var HANDLE_CURSORS = {
|
|
7208
7205
|
nw: "nwse-resize",
|
|
@@ -7210,6 +7207,486 @@ var HANDLE_CURSORS = {
|
|
|
7210
7207
|
ne: "nesw-resize",
|
|
7211
7208
|
sw: "nesw-resize"
|
|
7212
7209
|
};
|
|
7210
|
+
function getOverlayLayout(el, zoom) {
|
|
7211
|
+
const bounds = getElementBounds(el);
|
|
7212
|
+
if (!bounds) return null;
|
|
7213
|
+
const angle = el.rotation ?? 0;
|
|
7214
|
+
const pad = SELECTION_PAD / zoom;
|
|
7215
|
+
const center2 = { x: bounds.x + bounds.w / 2, y: bounds.y + bounds.h / 2 };
|
|
7216
|
+
const raw = [
|
|
7217
|
+
["nw", { x: bounds.x - pad, y: bounds.y - pad }],
|
|
7218
|
+
["ne", { x: bounds.x + bounds.w + pad, y: bounds.y - pad }],
|
|
7219
|
+
["sw", { x: bounds.x - pad, y: bounds.y + bounds.h + pad }],
|
|
7220
|
+
["se", { x: bounds.x + bounds.w + pad, y: bounds.y + bounds.h + pad }]
|
|
7221
|
+
];
|
|
7222
|
+
const corners = raw.map(
|
|
7223
|
+
([h, p]) => [h, rotatePoint(p, center2, angle)]
|
|
7224
|
+
);
|
|
7225
|
+
const topMid = { x: center2.x, y: bounds.y - pad - ROTATE_HANDLE_OFFSET / zoom };
|
|
7226
|
+
const rotateHandle = rotatePoint(topMid, center2, angle);
|
|
7227
|
+
return { center: center2, corners, rotateHandle, angle };
|
|
7228
|
+
}
|
|
7229
|
+
function getHandlePositions(bounds) {
|
|
7230
|
+
return [
|
|
7231
|
+
["nw", { x: bounds.x, y: bounds.y }],
|
|
7232
|
+
["ne", { x: bounds.x + bounds.w, y: bounds.y }],
|
|
7233
|
+
["sw", { x: bounds.x, y: bounds.y + bounds.h }],
|
|
7234
|
+
["se", { x: bounds.x + bounds.w, y: bounds.y + bounds.h }]
|
|
7235
|
+
];
|
|
7236
|
+
}
|
|
7237
|
+
function topMidpoint(layout) {
|
|
7238
|
+
const nw = layout.corners.find(([h]) => h === "nw")?.[1] ?? { x: 0, y: 0 };
|
|
7239
|
+
const ne = layout.corners.find(([h]) => h === "ne")?.[1] ?? { x: 0, y: 0 };
|
|
7240
|
+
return { x: (nw.x + ne.x) / 2, y: (nw.y + ne.y) / 2 };
|
|
7241
|
+
}
|
|
7242
|
+
function drawLockBadge(ctx, at, zoom) {
|
|
7243
|
+
const r = 9 / zoom;
|
|
7244
|
+
ctx.save();
|
|
7245
|
+
ctx.setLineDash([]);
|
|
7246
|
+
ctx.beginPath();
|
|
7247
|
+
ctx.arc(at.x, at.y, r, 0, Math.PI * 2);
|
|
7248
|
+
ctx.fillStyle = "#ffffff";
|
|
7249
|
+
ctx.fill();
|
|
7250
|
+
ctx.strokeStyle = "#2196F3";
|
|
7251
|
+
ctx.lineWidth = 1.5 / zoom;
|
|
7252
|
+
ctx.stroke();
|
|
7253
|
+
const bw = 8 / zoom;
|
|
7254
|
+
const bh = 6 / zoom;
|
|
7255
|
+
ctx.fillStyle = "#2196F3";
|
|
7256
|
+
ctx.fillRect(at.x - bw / 2, at.y - bh / 2 + 1 / zoom, bw, bh);
|
|
7257
|
+
ctx.beginPath();
|
|
7258
|
+
ctx.arc(at.x, at.y - bh / 2 + 1 / zoom, 2.5 / zoom, Math.PI, 0);
|
|
7259
|
+
ctx.lineWidth = 1.4 / zoom;
|
|
7260
|
+
ctx.stroke();
|
|
7261
|
+
ctx.restore();
|
|
7262
|
+
}
|
|
7263
|
+
function renderMarquee(ctx, rect) {
|
|
7264
|
+
ctx.save();
|
|
7265
|
+
ctx.strokeStyle = "#2196F3";
|
|
7266
|
+
ctx.fillStyle = "rgba(33, 150, 243, 0.08)";
|
|
7267
|
+
ctx.lineWidth = 1;
|
|
7268
|
+
ctx.setLineDash([4, 4]);
|
|
7269
|
+
ctx.strokeRect(rect.x, rect.y, rect.w, rect.h);
|
|
7270
|
+
ctx.fillRect(rect.x, rect.y, rect.w, rect.h);
|
|
7271
|
+
ctx.restore();
|
|
7272
|
+
}
|
|
7273
|
+
function renderBindingHighlights(ctx, arrow, zoom, store) {
|
|
7274
|
+
if (!arrow.fromBinding && !arrow.toBinding) return;
|
|
7275
|
+
const pad = SELECTION_PAD / zoom;
|
|
7276
|
+
ctx.save();
|
|
7277
|
+
ctx.strokeStyle = "#2196F3";
|
|
7278
|
+
ctx.lineWidth = 2 / zoom;
|
|
7279
|
+
ctx.setLineDash([]);
|
|
7280
|
+
const drawn = /* @__PURE__ */ new Set();
|
|
7281
|
+
for (const binding of [arrow.fromBinding, arrow.toBinding]) {
|
|
7282
|
+
if (!binding || drawn.has(binding.elementId)) continue;
|
|
7283
|
+
drawn.add(binding.elementId);
|
|
7284
|
+
const target = store.getById(binding.elementId);
|
|
7285
|
+
if (!target) continue;
|
|
7286
|
+
const bounds = getElementBounds(target);
|
|
7287
|
+
if (!bounds) continue;
|
|
7288
|
+
ctx.strokeRect(bounds.x - pad, bounds.y - pad, bounds.w + pad * 2, bounds.h + pad * 2);
|
|
7289
|
+
}
|
|
7290
|
+
ctx.restore();
|
|
7291
|
+
}
|
|
7292
|
+
function renderSelectionBoxes(ctx, p) {
|
|
7293
|
+
if (p.selectedIds.length === 0) return;
|
|
7294
|
+
const zoom = p.zoom;
|
|
7295
|
+
const handleWorldSize = HANDLE_SIZE / zoom;
|
|
7296
|
+
ctx.save();
|
|
7297
|
+
ctx.strokeStyle = "#2196F3";
|
|
7298
|
+
ctx.lineWidth = 1.5 / zoom;
|
|
7299
|
+
ctx.setLineDash([4 / zoom, 4 / zoom]);
|
|
7300
|
+
for (const id of p.selectedIds) {
|
|
7301
|
+
const el = p.store.getById(id);
|
|
7302
|
+
if (!el) continue;
|
|
7303
|
+
if (el.type === "arrow") {
|
|
7304
|
+
renderArrowHandles(ctx, el, zoom);
|
|
7305
|
+
renderBindingHighlights(ctx, el, zoom, p.store);
|
|
7306
|
+
continue;
|
|
7307
|
+
}
|
|
7308
|
+
if (el.type === "shape" && el.shape === "line") {
|
|
7309
|
+
ctx.setLineDash([]);
|
|
7310
|
+
ctx.fillStyle = "#ffffff";
|
|
7311
|
+
const r = handleWorldSize / 2;
|
|
7312
|
+
for (const pt of lineEndpoints(el)) {
|
|
7313
|
+
ctx.beginPath();
|
|
7314
|
+
ctx.arc(pt.x, pt.y, r, 0, Math.PI * 2);
|
|
7315
|
+
ctx.fill();
|
|
7316
|
+
ctx.stroke();
|
|
7317
|
+
}
|
|
7318
|
+
ctx.setLineDash([4 / zoom, 4 / zoom]);
|
|
7319
|
+
continue;
|
|
7320
|
+
}
|
|
7321
|
+
const bounds = getElementBounds(el);
|
|
7322
|
+
if (!bounds) continue;
|
|
7323
|
+
const layout = getOverlayLayout(el, zoom);
|
|
7324
|
+
if (!layout) continue;
|
|
7325
|
+
const pad = SELECTION_PAD / zoom;
|
|
7326
|
+
if (layout.angle === 0) {
|
|
7327
|
+
ctx.strokeRect(bounds.x - pad, bounds.y - pad, bounds.w + pad * 2, bounds.h + pad * 2);
|
|
7328
|
+
} else {
|
|
7329
|
+
const ordered = ["nw", "ne", "se", "sw"].map((h) => layout.corners.find(([c]) => c === h)?.[1]).filter((pp) => !!pp);
|
|
7330
|
+
const [p0, ...others] = ordered;
|
|
7331
|
+
if (p0) {
|
|
7332
|
+
ctx.beginPath();
|
|
7333
|
+
ctx.moveTo(p0.x, p0.y);
|
|
7334
|
+
for (const pp of others) ctx.lineTo(pp.x, pp.y);
|
|
7335
|
+
ctx.closePath();
|
|
7336
|
+
ctx.stroke();
|
|
7337
|
+
}
|
|
7338
|
+
}
|
|
7339
|
+
if (!el.locked) {
|
|
7340
|
+
if ("size" in el) {
|
|
7341
|
+
ctx.setLineDash([]);
|
|
7342
|
+
ctx.fillStyle = "#ffffff";
|
|
7343
|
+
const corners = layout.angle === 0 ? getHandlePositions(bounds) : layout.corners;
|
|
7344
|
+
for (const [, pos] of corners) {
|
|
7345
|
+
ctx.fillRect(
|
|
7346
|
+
pos.x - handleWorldSize / 2,
|
|
7347
|
+
pos.y - handleWorldSize / 2,
|
|
7348
|
+
handleWorldSize,
|
|
7349
|
+
handleWorldSize
|
|
7350
|
+
);
|
|
7351
|
+
ctx.strokeRect(
|
|
7352
|
+
pos.x - handleWorldSize / 2,
|
|
7353
|
+
pos.y - handleWorldSize / 2,
|
|
7354
|
+
handleWorldSize,
|
|
7355
|
+
handleWorldSize
|
|
7356
|
+
);
|
|
7357
|
+
}
|
|
7358
|
+
ctx.setLineDash([4 / zoom, 4 / zoom]);
|
|
7359
|
+
} else if (el.type === "template") {
|
|
7360
|
+
ctx.setLineDash([]);
|
|
7361
|
+
ctx.fillStyle = "#ffffff";
|
|
7362
|
+
const hx = bounds.x + bounds.w;
|
|
7363
|
+
const hy = bounds.y + bounds.h;
|
|
7364
|
+
ctx.fillRect(
|
|
7365
|
+
hx - handleWorldSize / 2,
|
|
7366
|
+
hy - handleWorldSize / 2,
|
|
7367
|
+
handleWorldSize,
|
|
7368
|
+
handleWorldSize
|
|
7369
|
+
);
|
|
7370
|
+
ctx.strokeRect(
|
|
7371
|
+
hx - handleWorldSize / 2,
|
|
7372
|
+
hy - handleWorldSize / 2,
|
|
7373
|
+
handleWorldSize,
|
|
7374
|
+
handleWorldSize
|
|
7375
|
+
);
|
|
7376
|
+
ctx.setLineDash([4 / zoom, 4 / zoom]);
|
|
7377
|
+
}
|
|
7378
|
+
if (p.selectedIds.length === 1 && ROTATABLE_TYPES.has(el.type)) {
|
|
7379
|
+
const stemStart = topMidpoint(layout);
|
|
7380
|
+
const stemEnd = layout.rotateHandle;
|
|
7381
|
+
ctx.beginPath();
|
|
7382
|
+
ctx.moveTo(stemStart.x, stemStart.y);
|
|
7383
|
+
ctx.lineTo(stemEnd.x, stemEnd.y);
|
|
7384
|
+
ctx.stroke();
|
|
7385
|
+
ctx.setLineDash([]);
|
|
7386
|
+
ctx.fillStyle = "#ffffff";
|
|
7387
|
+
ctx.beginPath();
|
|
7388
|
+
ctx.arc(stemEnd.x, stemEnd.y, handleWorldSize / 2, 0, Math.PI * 2);
|
|
7389
|
+
ctx.fill();
|
|
7390
|
+
ctx.stroke();
|
|
7391
|
+
ctx.setLineDash([4 / zoom, 4 / zoom]);
|
|
7392
|
+
}
|
|
7393
|
+
}
|
|
7394
|
+
if (el.locked) {
|
|
7395
|
+
const ne = layout.corners.find(([h]) => h === "ne")?.[1];
|
|
7396
|
+
if (ne) drawLockBadge(ctx, ne, zoom);
|
|
7397
|
+
}
|
|
7398
|
+
}
|
|
7399
|
+
ctx.restore();
|
|
7400
|
+
}
|
|
7401
|
+
function renderGuideLines(ctx, p) {
|
|
7402
|
+
const zoom = p.zoom;
|
|
7403
|
+
const rect = p.rect;
|
|
7404
|
+
ctx.save();
|
|
7405
|
+
ctx.strokeStyle = "#FF4081";
|
|
7406
|
+
ctx.lineWidth = 1 / zoom;
|
|
7407
|
+
ctx.setLineDash([]);
|
|
7408
|
+
for (const g of p.guides) {
|
|
7409
|
+
ctx.beginPath();
|
|
7410
|
+
if (g.axis === "x") {
|
|
7411
|
+
const y0 = rect ? rect.y : p.currentWorld.y - 1e5;
|
|
7412
|
+
const y1 = rect ? rect.y + rect.h : p.currentWorld.y + 1e5;
|
|
7413
|
+
ctx.moveTo(g.position, y0);
|
|
7414
|
+
ctx.lineTo(g.position, y1);
|
|
7415
|
+
} else {
|
|
7416
|
+
const x0 = rect ? rect.x : p.currentWorld.x - 1e5;
|
|
7417
|
+
const x1 = rect ? rect.x + rect.w : p.currentWorld.x + 1e5;
|
|
7418
|
+
ctx.moveTo(x0, g.position);
|
|
7419
|
+
ctx.lineTo(x1, g.position);
|
|
7420
|
+
}
|
|
7421
|
+
ctx.stroke();
|
|
7422
|
+
}
|
|
7423
|
+
ctx.restore();
|
|
7424
|
+
}
|
|
7425
|
+
|
|
7426
|
+
// src/tools/select-hit.ts
|
|
7427
|
+
function hitTest(world, ctx) {
|
|
7428
|
+
const r = 10;
|
|
7429
|
+
const candidates = ctx.store.queryRect({ x: world.x - r, y: world.y - r, w: r * 2, h: r * 2 }).reverse();
|
|
7430
|
+
for (const el of candidates) {
|
|
7431
|
+
if (ctx.isLayerVisible && !ctx.isLayerVisible(el.layerId)) continue;
|
|
7432
|
+
if (ctx.isLayerLocked && ctx.isLayerLocked(el.layerId)) continue;
|
|
7433
|
+
if (el.type === "grid") continue;
|
|
7434
|
+
if (isInsideBounds(world, el)) return el;
|
|
7435
|
+
}
|
|
7436
|
+
return null;
|
|
7437
|
+
}
|
|
7438
|
+
function isInsideBounds(point, el) {
|
|
7439
|
+
if (el.type === "grid") return false;
|
|
7440
|
+
const angle = el.rotation ?? 0;
|
|
7441
|
+
if (angle !== 0) {
|
|
7442
|
+
const b = getElementBounds(el);
|
|
7443
|
+
if (b) {
|
|
7444
|
+
point = rotatePoint(point, { x: b.x + b.w / 2, y: b.y + b.h / 2 }, -angle);
|
|
7445
|
+
}
|
|
7446
|
+
}
|
|
7447
|
+
if (el.type === "shape" && el.shape === "line") {
|
|
7448
|
+
const [a, b] = lineEndpoints(el);
|
|
7449
|
+
const threshold = Math.max(el.strokeWidth / 2, 6);
|
|
7450
|
+
return distSqToSegment(point, a, b) <= threshold * threshold;
|
|
7451
|
+
}
|
|
7452
|
+
if ("size" in el) {
|
|
7453
|
+
const s = el.size;
|
|
7454
|
+
return point.x >= el.position.x && point.x <= el.position.x + s.w && point.y >= el.position.y && point.y <= el.position.y + s.h;
|
|
7455
|
+
}
|
|
7456
|
+
if (el.type === "stroke") {
|
|
7457
|
+
return hitTestStroke(el, point, 10);
|
|
7458
|
+
}
|
|
7459
|
+
if (el.type === "arrow") {
|
|
7460
|
+
return isNearBezier(point, el.from, el.to, el.bend, 10);
|
|
7461
|
+
}
|
|
7462
|
+
if (el.type === "template") {
|
|
7463
|
+
const bounds = getElementBounds(el);
|
|
7464
|
+
if (!bounds) return false;
|
|
7465
|
+
return point.x >= bounds.x && point.x <= bounds.x + bounds.w && point.y >= bounds.y && point.y <= bounds.y + bounds.h;
|
|
7466
|
+
}
|
|
7467
|
+
return false;
|
|
7468
|
+
}
|
|
7469
|
+
function hitTestResizeHandle(world, ctx, selectedIds) {
|
|
7470
|
+
if (selectedIds.length === 0) return null;
|
|
7471
|
+
const zoom = ctx.camera.zoom;
|
|
7472
|
+
const handleHalf = (HANDLE_SIZE / 2 + HANDLE_HIT_PADDING2) / zoom;
|
|
7473
|
+
for (const id of selectedIds) {
|
|
7474
|
+
const el = ctx.store.getById(id);
|
|
7475
|
+
if (!el || !("size" in el)) continue;
|
|
7476
|
+
if (el.locked) continue;
|
|
7477
|
+
if (el.type === "shape" && el.shape === "line") continue;
|
|
7478
|
+
const layout = getOverlayLayout(el, zoom);
|
|
7479
|
+
if (!layout) continue;
|
|
7480
|
+
for (const [handle, pos] of layout.corners) {
|
|
7481
|
+
if (Math.abs(world.x - pos.x) <= handleHalf && Math.abs(world.y - pos.y) <= handleHalf) {
|
|
7482
|
+
return { elementId: id, handle };
|
|
7483
|
+
}
|
|
7484
|
+
}
|
|
7485
|
+
}
|
|
7486
|
+
return null;
|
|
7487
|
+
}
|
|
7488
|
+
function hitTestRotateHandle(world, ctx, selectedIds) {
|
|
7489
|
+
if (selectedIds.length !== 1) return null;
|
|
7490
|
+
const id = selectedIds[0];
|
|
7491
|
+
if (!id) return null;
|
|
7492
|
+
const el = ctx.store.getById(id);
|
|
7493
|
+
if (!el || el.locked || !ROTATABLE_TYPES.has(el.type)) return null;
|
|
7494
|
+
const layout = getOverlayLayout(el, ctx.camera.zoom);
|
|
7495
|
+
if (!layout) return null;
|
|
7496
|
+
const r = (HANDLE_SIZE / 2 + HANDLE_HIT_PADDING2) / ctx.camera.zoom;
|
|
7497
|
+
const dx = world.x - layout.rotateHandle.x;
|
|
7498
|
+
const dy = world.y - layout.rotateHandle.y;
|
|
7499
|
+
return dx * dx + dy * dy <= r * r ? { elementId: id } : null;
|
|
7500
|
+
}
|
|
7501
|
+
function hitTestLineHandles(world, ctx, selectedIds) {
|
|
7502
|
+
if (selectedIds.length === 0) return null;
|
|
7503
|
+
const zoom = ctx.camera.zoom;
|
|
7504
|
+
const r = (HANDLE_SIZE / 2 + HANDLE_HIT_PADDING2) / zoom;
|
|
7505
|
+
const r2 = r * r;
|
|
7506
|
+
for (const id of selectedIds) {
|
|
7507
|
+
const el = ctx.store.getById(id);
|
|
7508
|
+
if (!el || el.type !== "shape" || el.shape !== "line") continue;
|
|
7509
|
+
const [a, b] = lineEndpoints(el);
|
|
7510
|
+
if ((world.x - a.x) ** 2 + (world.y - a.y) ** 2 <= r2) return { elementId: id, fixed: b };
|
|
7511
|
+
if ((world.x - b.x) ** 2 + (world.y - b.y) ** 2 <= r2) return { elementId: id, fixed: a };
|
|
7512
|
+
}
|
|
7513
|
+
return null;
|
|
7514
|
+
}
|
|
7515
|
+
function hitTestTemplateResizeHandle(world, ctx, selectedIds) {
|
|
7516
|
+
if (selectedIds.length === 0) return null;
|
|
7517
|
+
const zoom = ctx.camera.zoom;
|
|
7518
|
+
const handleHalf = (HANDLE_SIZE / 2 + HANDLE_HIT_PADDING2) / zoom;
|
|
7519
|
+
for (const id of selectedIds) {
|
|
7520
|
+
const el = ctx.store.getById(id);
|
|
7521
|
+
if (!el || el.type !== "template") continue;
|
|
7522
|
+
const bounds = getElementBounds(el);
|
|
7523
|
+
if (!bounds) continue;
|
|
7524
|
+
const hx = bounds.x + bounds.w;
|
|
7525
|
+
const hy = bounds.y + bounds.h;
|
|
7526
|
+
if (Math.abs(world.x - hx) <= handleHalf && Math.abs(world.y - hy) <= handleHalf) {
|
|
7527
|
+
return id;
|
|
7528
|
+
}
|
|
7529
|
+
}
|
|
7530
|
+
return null;
|
|
7531
|
+
}
|
|
7532
|
+
function findElementsInRect(marquee, ctx) {
|
|
7533
|
+
const candidates = ctx.store.queryRect(marquee);
|
|
7534
|
+
const ids = [];
|
|
7535
|
+
for (const el of candidates) {
|
|
7536
|
+
if (ctx.isLayerVisible && !ctx.isLayerVisible(el.layerId)) continue;
|
|
7537
|
+
if (ctx.isLayerLocked && ctx.isLayerLocked(el.layerId)) continue;
|
|
7538
|
+
if (el.type === "grid") continue;
|
|
7539
|
+
const bounds = getElementBounds(el);
|
|
7540
|
+
if (bounds && rectsOverlap(marquee, rotatedAABB(bounds, el.rotation ?? 0))) {
|
|
7541
|
+
ids.push(el.id);
|
|
7542
|
+
}
|
|
7543
|
+
}
|
|
7544
|
+
return ids;
|
|
7545
|
+
}
|
|
7546
|
+
function rectsOverlap(a, b) {
|
|
7547
|
+
return a.x <= b.x + b.w && a.x + a.w >= b.x && a.y <= b.y + b.h && a.y + a.h >= b.y;
|
|
7548
|
+
}
|
|
7549
|
+
|
|
7550
|
+
// src/tools/select-resize.ts
|
|
7551
|
+
var MIN_ELEMENT_SIZE = 20;
|
|
7552
|
+
function anchorOffset(handle, w, h) {
|
|
7553
|
+
switch (handle) {
|
|
7554
|
+
case "se":
|
|
7555
|
+
return { x: -w / 2, y: -h / 2 };
|
|
7556
|
+
case "sw":
|
|
7557
|
+
return { x: w / 2, y: -h / 2 };
|
|
7558
|
+
case "ne":
|
|
7559
|
+
return { x: -w / 2, y: h / 2 };
|
|
7560
|
+
case "nw":
|
|
7561
|
+
return { x: w / 2, y: h / 2 };
|
|
7562
|
+
default:
|
|
7563
|
+
return { x: 0, y: 0 };
|
|
7564
|
+
}
|
|
7565
|
+
}
|
|
7566
|
+
function computeResize(el, handle, world, lastWorld, aspectRatio, shiftKey) {
|
|
7567
|
+
const dx = world.x - lastWorld.x;
|
|
7568
|
+
const dy = world.y - lastWorld.y;
|
|
7569
|
+
let { x, y, w, h } = { x: el.position.x, y: el.position.y, w: el.size.w, h: el.size.h };
|
|
7570
|
+
switch (handle) {
|
|
7571
|
+
case "se":
|
|
7572
|
+
w += dx;
|
|
7573
|
+
h += dy;
|
|
7574
|
+
break;
|
|
7575
|
+
case "sw":
|
|
7576
|
+
x += dx;
|
|
7577
|
+
w -= dx;
|
|
7578
|
+
h += dy;
|
|
7579
|
+
break;
|
|
7580
|
+
case "ne":
|
|
7581
|
+
y += dy;
|
|
7582
|
+
w += dx;
|
|
7583
|
+
h -= dy;
|
|
7584
|
+
break;
|
|
7585
|
+
case "nw":
|
|
7586
|
+
x += dx;
|
|
7587
|
+
y += dy;
|
|
7588
|
+
w -= dx;
|
|
7589
|
+
h -= dy;
|
|
7590
|
+
break;
|
|
7591
|
+
}
|
|
7592
|
+
if (shiftKey && aspectRatio > 0) {
|
|
7593
|
+
const absDw = Math.abs(w - el.size.w);
|
|
7594
|
+
const absDh = Math.abs(h - el.size.h);
|
|
7595
|
+
if (absDw >= absDh) {
|
|
7596
|
+
h = w / aspectRatio;
|
|
7597
|
+
} else {
|
|
7598
|
+
w = h * aspectRatio;
|
|
7599
|
+
}
|
|
7600
|
+
if (handle === "nw" || handle === "sw") {
|
|
7601
|
+
x = el.position.x + el.size.w - w;
|
|
7602
|
+
}
|
|
7603
|
+
if (handle === "nw" || handle === "ne") {
|
|
7604
|
+
y = el.position.y + el.size.h - h;
|
|
7605
|
+
}
|
|
7606
|
+
}
|
|
7607
|
+
if (w < MIN_ELEMENT_SIZE) {
|
|
7608
|
+
if (handle === "nw" || handle === "sw") x = el.position.x + el.size.w - MIN_ELEMENT_SIZE;
|
|
7609
|
+
w = MIN_ELEMENT_SIZE;
|
|
7610
|
+
}
|
|
7611
|
+
if (h < MIN_ELEMENT_SIZE) {
|
|
7612
|
+
if (handle === "nw" || handle === "ne") y = el.position.y + el.size.h - MIN_ELEMENT_SIZE;
|
|
7613
|
+
h = MIN_ELEMENT_SIZE;
|
|
7614
|
+
}
|
|
7615
|
+
return { position: { x, y }, size: { w, h } };
|
|
7616
|
+
}
|
|
7617
|
+
function computeRotatedResize(el, handle, angle, world, lastWorld, aspectRatio, shiftKey) {
|
|
7618
|
+
const wdx = world.x - lastWorld.x;
|
|
7619
|
+
const wdy = world.y - lastWorld.y;
|
|
7620
|
+
const cosN = Math.cos(-angle);
|
|
7621
|
+
const sinN = Math.sin(-angle);
|
|
7622
|
+
const ldx = wdx * cosN - wdy * sinN;
|
|
7623
|
+
const ldy = wdx * sinN + wdy * cosN;
|
|
7624
|
+
let w = el.size.w;
|
|
7625
|
+
let h = el.size.h;
|
|
7626
|
+
switch (handle) {
|
|
7627
|
+
case "se":
|
|
7628
|
+
w += ldx;
|
|
7629
|
+
h += ldy;
|
|
7630
|
+
break;
|
|
7631
|
+
case "sw":
|
|
7632
|
+
w -= ldx;
|
|
7633
|
+
h += ldy;
|
|
7634
|
+
break;
|
|
7635
|
+
case "ne":
|
|
7636
|
+
w += ldx;
|
|
7637
|
+
h -= ldy;
|
|
7638
|
+
break;
|
|
7639
|
+
case "nw":
|
|
7640
|
+
w -= ldx;
|
|
7641
|
+
h -= ldy;
|
|
7642
|
+
break;
|
|
7643
|
+
}
|
|
7644
|
+
if (shiftKey && aspectRatio > 0) {
|
|
7645
|
+
const absDw = Math.abs(w - el.size.w);
|
|
7646
|
+
const absDh = Math.abs(h - el.size.h);
|
|
7647
|
+
if (absDw >= absDh) h = w / aspectRatio;
|
|
7648
|
+
else w = h * aspectRatio;
|
|
7649
|
+
}
|
|
7650
|
+
w = Math.max(w, MIN_ELEMENT_SIZE);
|
|
7651
|
+
h = Math.max(h, MIN_ELEMENT_SIZE);
|
|
7652
|
+
const oldCenter = { x: el.position.x + el.size.w / 2, y: el.position.y + el.size.h / 2 };
|
|
7653
|
+
const oldAnchorLocal = anchorOffset(handle, el.size.w, el.size.h);
|
|
7654
|
+
const anchorWorld = rotatePoint(
|
|
7655
|
+
{ x: oldCenter.x + oldAnchorLocal.x, y: oldCenter.y + oldAnchorLocal.y },
|
|
7656
|
+
oldCenter,
|
|
7657
|
+
angle
|
|
7658
|
+
);
|
|
7659
|
+
const newAnchorLocal = anchorOffset(handle, w, h);
|
|
7660
|
+
const cos = Math.cos(angle);
|
|
7661
|
+
const sin = Math.sin(angle);
|
|
7662
|
+
const rotatedAnchor = {
|
|
7663
|
+
x: newAnchorLocal.x * cos - newAnchorLocal.y * sin,
|
|
7664
|
+
y: newAnchorLocal.x * sin + newAnchorLocal.y * cos
|
|
7665
|
+
};
|
|
7666
|
+
const newCenter = { x: anchorWorld.x - rotatedAnchor.x, y: anchorWorld.y - rotatedAnchor.y };
|
|
7667
|
+
const position = { x: newCenter.x - w / 2, y: newCenter.y - h / 2 };
|
|
7668
|
+
return { position, size: { w, h } };
|
|
7669
|
+
}
|
|
7670
|
+
function computeTemplateResize(el, world, opts) {
|
|
7671
|
+
const dx = world.x - el.position.x;
|
|
7672
|
+
const dy = world.y - el.position.y;
|
|
7673
|
+
let newRadius = Math.sqrt(dx * dx + dy * dy);
|
|
7674
|
+
if (opts.snapToGrid && opts.gridSize && opts.gridSize > 0) {
|
|
7675
|
+
const snapUnit = opts.gridType === "hex" ? Math.sqrt(3) * opts.gridSize : opts.gridSize;
|
|
7676
|
+
newRadius = Math.max(snapUnit, Math.round(newRadius / snapUnit) * snapUnit);
|
|
7677
|
+
}
|
|
7678
|
+
newRadius = Math.max(MIN_ELEMENT_SIZE, newRadius);
|
|
7679
|
+
const updates = { radius: newRadius };
|
|
7680
|
+
if (el.feetPerCell != null && opts.gridSize && opts.gridSize > 0) {
|
|
7681
|
+
const snapUnit = opts.gridType === "hex" ? Math.sqrt(3) * opts.gridSize : opts.gridSize;
|
|
7682
|
+
updates.radiusFeet = newRadius / snapUnit * el.feetPerCell;
|
|
7683
|
+
}
|
|
7684
|
+
return updates;
|
|
7685
|
+
}
|
|
7686
|
+
|
|
7687
|
+
// src/tools/select-tool.ts
|
|
7688
|
+
var SNAP_PX = 6;
|
|
7689
|
+
var ROTATE_SNAP = Math.PI / 12;
|
|
7213
7690
|
var SelectTool = class {
|
|
7214
7691
|
name = "select";
|
|
7215
7692
|
_selectedIds = [];
|
|
@@ -7245,7 +7722,7 @@ var SelectTool = class {
|
|
|
7245
7722
|
this.ctx?.requestRender();
|
|
7246
7723
|
}
|
|
7247
7724
|
selectAtPoint(world, ctx) {
|
|
7248
|
-
const hit =
|
|
7725
|
+
const hit = hitTest(world, ctx);
|
|
7249
7726
|
if (!hit) {
|
|
7250
7727
|
this.setSelectedIds([]);
|
|
7251
7728
|
return;
|
|
@@ -7289,19 +7766,19 @@ var SelectTool = class {
|
|
|
7289
7766
|
ctx.requestRender();
|
|
7290
7767
|
return;
|
|
7291
7768
|
}
|
|
7292
|
-
const lineHit =
|
|
7769
|
+
const lineHit = hitTestLineHandles(world, ctx, this._selectedIds);
|
|
7293
7770
|
if (lineHit) {
|
|
7294
7771
|
this.mode = { type: "line-handle", elementId: lineHit.elementId, fixed: lineHit.fixed };
|
|
7295
7772
|
ctx.requestRender();
|
|
7296
7773
|
return;
|
|
7297
7774
|
}
|
|
7298
|
-
const templateResizeHit =
|
|
7775
|
+
const templateResizeHit = hitTestTemplateResizeHandle(world, ctx, this._selectedIds);
|
|
7299
7776
|
if (templateResizeHit) {
|
|
7300
7777
|
this.mode = { type: "resizing-template", elementId: templateResizeHit };
|
|
7301
7778
|
ctx.requestRender();
|
|
7302
7779
|
return;
|
|
7303
7780
|
}
|
|
7304
|
-
const rotateHit =
|
|
7781
|
+
const rotateHit = hitTestRotateHandle(world, ctx, this._selectedIds);
|
|
7305
7782
|
if (rotateHit) {
|
|
7306
7783
|
const el = ctx.store.getById(rotateHit.elementId);
|
|
7307
7784
|
const layout = el ? this.getOverlayLayout(el, ctx.camera.zoom) : null;
|
|
@@ -7317,7 +7794,7 @@ var SelectTool = class {
|
|
|
7317
7794
|
return;
|
|
7318
7795
|
}
|
|
7319
7796
|
}
|
|
7320
|
-
const resizeHit =
|
|
7797
|
+
const resizeHit = hitTestResizeHandle(world, ctx, this._selectedIds);
|
|
7321
7798
|
if (resizeHit) {
|
|
7322
7799
|
const el = ctx.store.getById(resizeHit.elementId);
|
|
7323
7800
|
if (el && "size" in el) {
|
|
@@ -7333,7 +7810,7 @@ var SelectTool = class {
|
|
|
7333
7810
|
}
|
|
7334
7811
|
this.pendingSingleSelectId = null;
|
|
7335
7812
|
this.hasDragged = false;
|
|
7336
|
-
const hit =
|
|
7813
|
+
const hit = hitTest(world, ctx);
|
|
7337
7814
|
if (hit) {
|
|
7338
7815
|
const all = ctx.store.getAll();
|
|
7339
7816
|
const alreadySelected = this._selectedIds.includes(hit.id);
|
|
@@ -7471,7 +7948,7 @@ var SelectTool = class {
|
|
|
7471
7948
|
if (this.mode.type === "marquee") {
|
|
7472
7949
|
const rect = this.getMarqueeRect();
|
|
7473
7950
|
if (rect) {
|
|
7474
|
-
this.setSelectedIds(expandToGroups(
|
|
7951
|
+
this.setSelectedIds(expandToGroups(findElementsInRect(rect, ctx), ctx.store.getAll()));
|
|
7475
7952
|
}
|
|
7476
7953
|
ctx.requestRender();
|
|
7477
7954
|
}
|
|
@@ -7498,8 +7975,16 @@ var SelectTool = class {
|
|
|
7498
7975
|
this.setHovered(hoverId, ctx);
|
|
7499
7976
|
}
|
|
7500
7977
|
renderOverlay(canvasCtx) {
|
|
7501
|
-
this.
|
|
7502
|
-
|
|
7978
|
+
if (this.mode.type === "marquee") {
|
|
7979
|
+
const rect = this.getMarqueeRect();
|
|
7980
|
+
if (rect) renderMarquee(canvasCtx, rect);
|
|
7981
|
+
}
|
|
7982
|
+
if (this.ctx)
|
|
7983
|
+
renderSelectionBoxes(canvasCtx, {
|
|
7984
|
+
selectedIds: this._selectedIds,
|
|
7985
|
+
store: this.ctx.store,
|
|
7986
|
+
zoom: this.ctx.camera.zoom
|
|
7987
|
+
});
|
|
7503
7988
|
if (this.mode.type === "arrow-handle" && this.ctx) {
|
|
7504
7989
|
const target = getArrowHandleDragTarget(
|
|
7505
7990
|
this.mode.handle,
|
|
@@ -7533,32 +8018,13 @@ var SelectTool = class {
|
|
|
7533
8018
|
}
|
|
7534
8019
|
}
|
|
7535
8020
|
}
|
|
7536
|
-
this.
|
|
7537
|
-
|
|
7538
|
-
|
|
7539
|
-
|
|
7540
|
-
|
|
7541
|
-
|
|
7542
|
-
|
|
7543
|
-
canvasCtx.strokeStyle = "#FF4081";
|
|
7544
|
-
canvasCtx.lineWidth = 1 / zoom;
|
|
7545
|
-
canvasCtx.setLineDash([]);
|
|
7546
|
-
for (const g of this.activeGuides) {
|
|
7547
|
-
canvasCtx.beginPath();
|
|
7548
|
-
if (g.axis === "x") {
|
|
7549
|
-
const y0 = rect ? rect.y : this.currentWorld.y - 1e5;
|
|
7550
|
-
const y1 = rect ? rect.y + rect.h : this.currentWorld.y + 1e5;
|
|
7551
|
-
canvasCtx.moveTo(g.position, y0);
|
|
7552
|
-
canvasCtx.lineTo(g.position, y1);
|
|
7553
|
-
} else {
|
|
7554
|
-
const x0 = rect ? rect.x : this.currentWorld.x - 1e5;
|
|
7555
|
-
const x1 = rect ? rect.x + rect.w : this.currentWorld.x + 1e5;
|
|
7556
|
-
canvasCtx.moveTo(x0, g.position);
|
|
7557
|
-
canvasCtx.lineTo(x1, g.position);
|
|
7558
|
-
}
|
|
7559
|
-
canvasCtx.stroke();
|
|
7560
|
-
}
|
|
7561
|
-
canvasCtx.restore();
|
|
8021
|
+
if (this.mode.type === "dragging" && this.ctx && this.activeGuides.length)
|
|
8022
|
+
renderGuideLines(canvasCtx, {
|
|
8023
|
+
guides: this.activeGuides,
|
|
8024
|
+
rect: this.dragVisibleRect,
|
|
8025
|
+
currentWorld: this.currentWorld,
|
|
8026
|
+
zoom: this.ctx.camera.zoom
|
|
8027
|
+
});
|
|
7562
8028
|
}
|
|
7563
8029
|
updateArrowsBoundTo(ids, ctx) {
|
|
7564
8030
|
updateArrowsBoundToElements(ids, ctx.store);
|
|
@@ -7584,25 +8050,25 @@ var SelectTool = class {
|
|
|
7584
8050
|
ctx.setCursor?.(getArrowHandleCursor(arrowHit.handle, false));
|
|
7585
8051
|
return null;
|
|
7586
8052
|
}
|
|
7587
|
-
if (
|
|
8053
|
+
if (hitTestLineHandles(world, ctx, this._selectedIds)) {
|
|
7588
8054
|
ctx.setCursor?.("grab");
|
|
7589
8055
|
return null;
|
|
7590
8056
|
}
|
|
7591
|
-
const templateResizeHit =
|
|
8057
|
+
const templateResizeHit = hitTestTemplateResizeHandle(world, ctx, this._selectedIds);
|
|
7592
8058
|
if (templateResizeHit) {
|
|
7593
8059
|
ctx.setCursor?.("nwse-resize");
|
|
7594
8060
|
return null;
|
|
7595
8061
|
}
|
|
7596
|
-
if (
|
|
8062
|
+
if (hitTestRotateHandle(world, ctx, this._selectedIds)) {
|
|
7597
8063
|
ctx.setCursor?.("grab");
|
|
7598
8064
|
return null;
|
|
7599
8065
|
}
|
|
7600
|
-
const resizeHit =
|
|
8066
|
+
const resizeHit = hitTestResizeHandle(world, ctx, this._selectedIds);
|
|
7601
8067
|
if (resizeHit) {
|
|
7602
8068
|
ctx.setCursor?.(HANDLE_CURSORS[resizeHit.handle]);
|
|
7603
8069
|
return null;
|
|
7604
8070
|
}
|
|
7605
|
-
const hit =
|
|
8071
|
+
const hit = hitTest(world, ctx);
|
|
7606
8072
|
ctx.setCursor?.(hit ? "move" : "default");
|
|
7607
8073
|
return hit ? hit.id : null;
|
|
7608
8074
|
}
|
|
@@ -7616,421 +8082,43 @@ var SelectTool = class {
|
|
|
7616
8082
|
const el = ctx.store.getById(this.mode.elementId);
|
|
7617
8083
|
if (!el || !("size" in el) || el.locked) return;
|
|
7618
8084
|
const angle = el.rotation ?? 0;
|
|
7619
|
-
|
|
7620
|
-
|
|
7621
|
-
|
|
7622
|
-
|
|
7623
|
-
|
|
7624
|
-
|
|
7625
|
-
|
|
7626
|
-
|
|
7627
|
-
|
|
7628
|
-
|
|
7629
|
-
|
|
7630
|
-
|
|
7631
|
-
|
|
7632
|
-
|
|
7633
|
-
|
|
7634
|
-
x += dx;
|
|
7635
|
-
w -= dx;
|
|
7636
|
-
h += dy;
|
|
7637
|
-
break;
|
|
7638
|
-
case "ne":
|
|
7639
|
-
y += dy;
|
|
7640
|
-
w += dx;
|
|
7641
|
-
h -= dy;
|
|
7642
|
-
break;
|
|
7643
|
-
case "nw":
|
|
7644
|
-
x += dx;
|
|
7645
|
-
y += dy;
|
|
7646
|
-
w -= dx;
|
|
7647
|
-
h -= dy;
|
|
7648
|
-
break;
|
|
7649
|
-
}
|
|
7650
|
-
if (shiftKey && this.resizeAspectRatio > 0) {
|
|
7651
|
-
const absDw = Math.abs(w - el.size.w);
|
|
7652
|
-
const absDh = Math.abs(h - el.size.h);
|
|
7653
|
-
if (absDw >= absDh) {
|
|
7654
|
-
h = w / this.resizeAspectRatio;
|
|
7655
|
-
} else {
|
|
7656
|
-
w = h * this.resizeAspectRatio;
|
|
7657
|
-
}
|
|
7658
|
-
if (handle === "nw" || handle === "sw") {
|
|
7659
|
-
x = el.position.x + el.size.w - w;
|
|
7660
|
-
}
|
|
7661
|
-
if (handle === "nw" || handle === "ne") {
|
|
7662
|
-
y = el.position.y + el.size.h - h;
|
|
7663
|
-
}
|
|
7664
|
-
}
|
|
7665
|
-
if (w < MIN_ELEMENT_SIZE) {
|
|
7666
|
-
if (handle === "nw" || handle === "sw") x = el.position.x + el.size.w - MIN_ELEMENT_SIZE;
|
|
7667
|
-
w = MIN_ELEMENT_SIZE;
|
|
7668
|
-
}
|
|
7669
|
-
if (h < MIN_ELEMENT_SIZE) {
|
|
7670
|
-
if (handle === "nw" || handle === "ne") y = el.position.y + el.size.h - MIN_ELEMENT_SIZE;
|
|
7671
|
-
h = MIN_ELEMENT_SIZE;
|
|
7672
|
-
}
|
|
7673
|
-
ctx.store.update(this.mode.elementId, {
|
|
7674
|
-
position: { x, y },
|
|
7675
|
-
size: { w, h }
|
|
7676
|
-
});
|
|
7677
|
-
this.updateArrowsBoundTo([this.mode.elementId], ctx);
|
|
7678
|
-
ctx.requestRender();
|
|
7679
|
-
}
|
|
7680
|
-
anchorOffset(handle, w, h) {
|
|
7681
|
-
switch (handle) {
|
|
7682
|
-
case "se":
|
|
7683
|
-
return { x: -w / 2, y: -h / 2 };
|
|
7684
|
-
case "sw":
|
|
7685
|
-
return { x: w / 2, y: -h / 2 };
|
|
7686
|
-
case "ne":
|
|
7687
|
-
return { x: -w / 2, y: h / 2 };
|
|
7688
|
-
case "nw":
|
|
7689
|
-
return { x: w / 2, y: h / 2 };
|
|
7690
|
-
default:
|
|
7691
|
-
return { x: 0, y: 0 };
|
|
7692
|
-
}
|
|
7693
|
-
}
|
|
7694
|
-
handleRotatedResize(world, el, angle, ctx, shiftKey) {
|
|
7695
|
-
if (this.mode.type !== "resizing") return;
|
|
7696
|
-
const { handle } = this.mode;
|
|
7697
|
-
const wdx = world.x - this.lastWorld.x;
|
|
7698
|
-
const wdy = world.y - this.lastWorld.y;
|
|
7699
|
-
this.lastWorld = world;
|
|
7700
|
-
const cosN = Math.cos(-angle);
|
|
7701
|
-
const sinN = Math.sin(-angle);
|
|
7702
|
-
const ldx = wdx * cosN - wdy * sinN;
|
|
7703
|
-
const ldy = wdx * sinN + wdy * cosN;
|
|
7704
|
-
let w = el.size.w;
|
|
7705
|
-
let h = el.size.h;
|
|
7706
|
-
switch (handle) {
|
|
7707
|
-
case "se":
|
|
7708
|
-
w += ldx;
|
|
7709
|
-
h += ldy;
|
|
7710
|
-
break;
|
|
7711
|
-
case "sw":
|
|
7712
|
-
w -= ldx;
|
|
7713
|
-
h += ldy;
|
|
7714
|
-
break;
|
|
7715
|
-
case "ne":
|
|
7716
|
-
w += ldx;
|
|
7717
|
-
h -= ldy;
|
|
7718
|
-
break;
|
|
7719
|
-
case "nw":
|
|
7720
|
-
w -= ldx;
|
|
7721
|
-
h -= ldy;
|
|
7722
|
-
break;
|
|
7723
|
-
}
|
|
7724
|
-
if (shiftKey && this.resizeAspectRatio > 0) {
|
|
7725
|
-
const absDw = Math.abs(w - el.size.w);
|
|
7726
|
-
const absDh = Math.abs(h - el.size.h);
|
|
7727
|
-
if (absDw >= absDh) h = w / this.resizeAspectRatio;
|
|
7728
|
-
else w = h * this.resizeAspectRatio;
|
|
7729
|
-
}
|
|
7730
|
-
w = Math.max(w, MIN_ELEMENT_SIZE);
|
|
7731
|
-
h = Math.max(h, MIN_ELEMENT_SIZE);
|
|
7732
|
-
const oldCenter = { x: el.position.x + el.size.w / 2, y: el.position.y + el.size.h / 2 };
|
|
7733
|
-
const oldAnchorLocal = this.anchorOffset(handle, el.size.w, el.size.h);
|
|
7734
|
-
const anchorWorld = rotatePoint(
|
|
7735
|
-
{ x: oldCenter.x + oldAnchorLocal.x, y: oldCenter.y + oldAnchorLocal.y },
|
|
7736
|
-
oldCenter,
|
|
7737
|
-
angle
|
|
8085
|
+
const patch = angle !== 0 ? computeRotatedResize(
|
|
8086
|
+
el,
|
|
8087
|
+
this.mode.handle,
|
|
8088
|
+
angle,
|
|
8089
|
+
world,
|
|
8090
|
+
this.lastWorld,
|
|
8091
|
+
this.resizeAspectRatio,
|
|
8092
|
+
shiftKey
|
|
8093
|
+
) : computeResize(
|
|
8094
|
+
el,
|
|
8095
|
+
this.mode.handle,
|
|
8096
|
+
world,
|
|
8097
|
+
this.lastWorld,
|
|
8098
|
+
this.resizeAspectRatio,
|
|
8099
|
+
shiftKey
|
|
7738
8100
|
);
|
|
7739
|
-
|
|
7740
|
-
|
|
7741
|
-
const sin = Math.sin(angle);
|
|
7742
|
-
const rotatedAnchor = {
|
|
7743
|
-
x: newAnchorLocal.x * cos - newAnchorLocal.y * sin,
|
|
7744
|
-
y: newAnchorLocal.x * sin + newAnchorLocal.y * cos
|
|
7745
|
-
};
|
|
7746
|
-
const newCenter = { x: anchorWorld.x - rotatedAnchor.x, y: anchorWorld.y - rotatedAnchor.y };
|
|
7747
|
-
const position = { x: newCenter.x - w / 2, y: newCenter.y - h / 2 };
|
|
7748
|
-
ctx.store.update(this.mode.elementId, { position, size: { w, h } });
|
|
8101
|
+
this.lastWorld = world;
|
|
8102
|
+
ctx.store.update(this.mode.elementId, patch);
|
|
7749
8103
|
this.updateArrowsBoundTo([this.mode.elementId], ctx);
|
|
7750
8104
|
ctx.requestRender();
|
|
7751
8105
|
}
|
|
7752
|
-
hitTestResizeHandle(world, ctx) {
|
|
7753
|
-
if (this._selectedIds.length === 0) return null;
|
|
7754
|
-
const zoom = ctx.camera.zoom;
|
|
7755
|
-
const handleHalf = (HANDLE_SIZE / 2 + HANDLE_HIT_PADDING2) / zoom;
|
|
7756
|
-
for (const id of this._selectedIds) {
|
|
7757
|
-
const el = ctx.store.getById(id);
|
|
7758
|
-
if (!el || !("size" in el)) continue;
|
|
7759
|
-
if (el.locked) continue;
|
|
7760
|
-
if (el.type === "shape" && el.shape === "line") continue;
|
|
7761
|
-
const layout = this.getOverlayLayout(el, zoom);
|
|
7762
|
-
if (!layout) continue;
|
|
7763
|
-
for (const [handle, pos] of layout.corners) {
|
|
7764
|
-
if (Math.abs(world.x - pos.x) <= handleHalf && Math.abs(world.y - pos.y) <= handleHalf) {
|
|
7765
|
-
return { elementId: id, handle };
|
|
7766
|
-
}
|
|
7767
|
-
}
|
|
7768
|
-
}
|
|
7769
|
-
return null;
|
|
7770
|
-
}
|
|
7771
|
-
hitTestRotateHandle(world, ctx) {
|
|
7772
|
-
if (this._selectedIds.length !== 1) return null;
|
|
7773
|
-
const id = this._selectedIds[0];
|
|
7774
|
-
if (!id) return null;
|
|
7775
|
-
const el = ctx.store.getById(id);
|
|
7776
|
-
if (!el || el.locked || !ROTATABLE_TYPES.has(el.type)) return null;
|
|
7777
|
-
const layout = this.getOverlayLayout(el, ctx.camera.zoom);
|
|
7778
|
-
if (!layout) return null;
|
|
7779
|
-
const r = (HANDLE_SIZE / 2 + HANDLE_HIT_PADDING2) / ctx.camera.zoom;
|
|
7780
|
-
const dx = world.x - layout.rotateHandle.x;
|
|
7781
|
-
const dy = world.y - layout.rotateHandle.y;
|
|
7782
|
-
return dx * dx + dy * dy <= r * r ? { elementId: id } : null;
|
|
7783
|
-
}
|
|
7784
|
-
hitTestLineHandles(world, ctx) {
|
|
7785
|
-
if (this._selectedIds.length === 0) return null;
|
|
7786
|
-
const zoom = ctx.camera.zoom;
|
|
7787
|
-
const r = (HANDLE_SIZE / 2 + HANDLE_HIT_PADDING2) / zoom;
|
|
7788
|
-
const r2 = r * r;
|
|
7789
|
-
for (const id of this._selectedIds) {
|
|
7790
|
-
const el = ctx.store.getById(id);
|
|
7791
|
-
if (!el || el.type !== "shape" || el.shape !== "line") continue;
|
|
7792
|
-
const [a, b] = lineEndpoints(el);
|
|
7793
|
-
if ((world.x - a.x) ** 2 + (world.y - a.y) ** 2 <= r2) return { elementId: id, fixed: b };
|
|
7794
|
-
if ((world.x - b.x) ** 2 + (world.y - b.y) ** 2 <= r2) return { elementId: id, fixed: a };
|
|
7795
|
-
}
|
|
7796
|
-
return null;
|
|
7797
|
-
}
|
|
7798
|
-
getHandlePositions(bounds) {
|
|
7799
|
-
return [
|
|
7800
|
-
["nw", { x: bounds.x, y: bounds.y }],
|
|
7801
|
-
["ne", { x: bounds.x + bounds.w, y: bounds.y }],
|
|
7802
|
-
["sw", { x: bounds.x, y: bounds.y + bounds.h }],
|
|
7803
|
-
["se", { x: bounds.x + bounds.w, y: bounds.y + bounds.h }]
|
|
7804
|
-
];
|
|
7805
|
-
}
|
|
7806
8106
|
getOverlayLayout(el, zoom) {
|
|
7807
|
-
|
|
7808
|
-
if (!bounds) return null;
|
|
7809
|
-
const angle = el.rotation ?? 0;
|
|
7810
|
-
const pad = SELECTION_PAD / zoom;
|
|
7811
|
-
const center2 = { x: bounds.x + bounds.w / 2, y: bounds.y + bounds.h / 2 };
|
|
7812
|
-
const raw = [
|
|
7813
|
-
["nw", { x: bounds.x - pad, y: bounds.y - pad }],
|
|
7814
|
-
["ne", { x: bounds.x + bounds.w + pad, y: bounds.y - pad }],
|
|
7815
|
-
["sw", { x: bounds.x - pad, y: bounds.y + bounds.h + pad }],
|
|
7816
|
-
["se", { x: bounds.x + bounds.w + pad, y: bounds.y + bounds.h + pad }]
|
|
7817
|
-
];
|
|
7818
|
-
const corners = raw.map(
|
|
7819
|
-
([h, p]) => [h, rotatePoint(p, center2, angle)]
|
|
7820
|
-
);
|
|
7821
|
-
const topMid = { x: center2.x, y: bounds.y - pad - ROTATE_HANDLE_OFFSET / zoom };
|
|
7822
|
-
const rotateHandle = rotatePoint(topMid, center2, angle);
|
|
7823
|
-
return { center: center2, corners, rotateHandle, angle };
|
|
7824
|
-
}
|
|
7825
|
-
topMidpoint(layout) {
|
|
7826
|
-
const nw = layout.corners.find(([h]) => h === "nw")?.[1] ?? { x: 0, y: 0 };
|
|
7827
|
-
const ne = layout.corners.find(([h]) => h === "ne")?.[1] ?? { x: 0, y: 0 };
|
|
7828
|
-
return { x: (nw.x + ne.x) / 2, y: (nw.y + ne.y) / 2 };
|
|
7829
|
-
}
|
|
7830
|
-
renderMarquee(canvasCtx) {
|
|
7831
|
-
if (this.mode.type !== "marquee") return;
|
|
7832
|
-
const rect = this.getMarqueeRect();
|
|
7833
|
-
if (!rect) return;
|
|
7834
|
-
canvasCtx.save();
|
|
7835
|
-
canvasCtx.strokeStyle = "#2196F3";
|
|
7836
|
-
canvasCtx.fillStyle = "rgba(33, 150, 243, 0.08)";
|
|
7837
|
-
canvasCtx.lineWidth = 1;
|
|
7838
|
-
canvasCtx.setLineDash([4, 4]);
|
|
7839
|
-
canvasCtx.strokeRect(rect.x, rect.y, rect.w, rect.h);
|
|
7840
|
-
canvasCtx.fillRect(rect.x, rect.y, rect.w, rect.h);
|
|
7841
|
-
canvasCtx.restore();
|
|
7842
|
-
}
|
|
7843
|
-
renderSelectionBoxes(canvasCtx) {
|
|
7844
|
-
if (this._selectedIds.length === 0 || !this.ctx) return;
|
|
7845
|
-
const zoom = this.ctx.camera.zoom;
|
|
7846
|
-
const handleWorldSize = HANDLE_SIZE / zoom;
|
|
7847
|
-
canvasCtx.save();
|
|
7848
|
-
canvasCtx.strokeStyle = "#2196F3";
|
|
7849
|
-
canvasCtx.lineWidth = 1.5 / zoom;
|
|
7850
|
-
canvasCtx.setLineDash([4 / zoom, 4 / zoom]);
|
|
7851
|
-
for (const id of this._selectedIds) {
|
|
7852
|
-
const el = this.ctx.store.getById(id);
|
|
7853
|
-
if (!el) continue;
|
|
7854
|
-
if (el.type === "arrow") {
|
|
7855
|
-
renderArrowHandles(canvasCtx, el, zoom);
|
|
7856
|
-
this.renderBindingHighlights(canvasCtx, el, zoom);
|
|
7857
|
-
continue;
|
|
7858
|
-
}
|
|
7859
|
-
if (el.type === "shape" && el.shape === "line") {
|
|
7860
|
-
canvasCtx.setLineDash([]);
|
|
7861
|
-
canvasCtx.fillStyle = "#ffffff";
|
|
7862
|
-
const r = handleWorldSize / 2;
|
|
7863
|
-
for (const p of lineEndpoints(el)) {
|
|
7864
|
-
canvasCtx.beginPath();
|
|
7865
|
-
canvasCtx.arc(p.x, p.y, r, 0, Math.PI * 2);
|
|
7866
|
-
canvasCtx.fill();
|
|
7867
|
-
canvasCtx.stroke();
|
|
7868
|
-
}
|
|
7869
|
-
canvasCtx.setLineDash([4 / zoom, 4 / zoom]);
|
|
7870
|
-
continue;
|
|
7871
|
-
}
|
|
7872
|
-
const bounds = getElementBounds(el);
|
|
7873
|
-
if (!bounds) continue;
|
|
7874
|
-
const layout = this.getOverlayLayout(el, zoom);
|
|
7875
|
-
if (!layout) continue;
|
|
7876
|
-
const pad = SELECTION_PAD / zoom;
|
|
7877
|
-
if (layout.angle === 0) {
|
|
7878
|
-
canvasCtx.strokeRect(
|
|
7879
|
-
bounds.x - pad,
|
|
7880
|
-
bounds.y - pad,
|
|
7881
|
-
bounds.w + pad * 2,
|
|
7882
|
-
bounds.h + pad * 2
|
|
7883
|
-
);
|
|
7884
|
-
} else {
|
|
7885
|
-
const ordered = ["nw", "ne", "se", "sw"].map((h) => layout.corners.find(([c]) => c === h)?.[1]).filter((p) => !!p);
|
|
7886
|
-
const [p0, ...others] = ordered;
|
|
7887
|
-
if (p0) {
|
|
7888
|
-
canvasCtx.beginPath();
|
|
7889
|
-
canvasCtx.moveTo(p0.x, p0.y);
|
|
7890
|
-
for (const p of others) canvasCtx.lineTo(p.x, p.y);
|
|
7891
|
-
canvasCtx.closePath();
|
|
7892
|
-
canvasCtx.stroke();
|
|
7893
|
-
}
|
|
7894
|
-
}
|
|
7895
|
-
if (!el.locked) {
|
|
7896
|
-
if ("size" in el) {
|
|
7897
|
-
canvasCtx.setLineDash([]);
|
|
7898
|
-
canvasCtx.fillStyle = "#ffffff";
|
|
7899
|
-
const corners = layout.angle === 0 ? this.getHandlePositions(bounds) : layout.corners;
|
|
7900
|
-
for (const [, pos] of corners) {
|
|
7901
|
-
canvasCtx.fillRect(
|
|
7902
|
-
pos.x - handleWorldSize / 2,
|
|
7903
|
-
pos.y - handleWorldSize / 2,
|
|
7904
|
-
handleWorldSize,
|
|
7905
|
-
handleWorldSize
|
|
7906
|
-
);
|
|
7907
|
-
canvasCtx.strokeRect(
|
|
7908
|
-
pos.x - handleWorldSize / 2,
|
|
7909
|
-
pos.y - handleWorldSize / 2,
|
|
7910
|
-
handleWorldSize,
|
|
7911
|
-
handleWorldSize
|
|
7912
|
-
);
|
|
7913
|
-
}
|
|
7914
|
-
canvasCtx.setLineDash([4 / zoom, 4 / zoom]);
|
|
7915
|
-
} else if (el.type === "template") {
|
|
7916
|
-
canvasCtx.setLineDash([]);
|
|
7917
|
-
canvasCtx.fillStyle = "#ffffff";
|
|
7918
|
-
const hx = bounds.x + bounds.w;
|
|
7919
|
-
const hy = bounds.y + bounds.h;
|
|
7920
|
-
canvasCtx.fillRect(
|
|
7921
|
-
hx - handleWorldSize / 2,
|
|
7922
|
-
hy - handleWorldSize / 2,
|
|
7923
|
-
handleWorldSize,
|
|
7924
|
-
handleWorldSize
|
|
7925
|
-
);
|
|
7926
|
-
canvasCtx.strokeRect(
|
|
7927
|
-
hx - handleWorldSize / 2,
|
|
7928
|
-
hy - handleWorldSize / 2,
|
|
7929
|
-
handleWorldSize,
|
|
7930
|
-
handleWorldSize
|
|
7931
|
-
);
|
|
7932
|
-
canvasCtx.setLineDash([4 / zoom, 4 / zoom]);
|
|
7933
|
-
}
|
|
7934
|
-
if (this._selectedIds.length === 1 && ROTATABLE_TYPES.has(el.type)) {
|
|
7935
|
-
const stemStart = this.topMidpoint(layout);
|
|
7936
|
-
const stemEnd = layout.rotateHandle;
|
|
7937
|
-
canvasCtx.beginPath();
|
|
7938
|
-
canvasCtx.moveTo(stemStart.x, stemStart.y);
|
|
7939
|
-
canvasCtx.lineTo(stemEnd.x, stemEnd.y);
|
|
7940
|
-
canvasCtx.stroke();
|
|
7941
|
-
canvasCtx.setLineDash([]);
|
|
7942
|
-
canvasCtx.fillStyle = "#ffffff";
|
|
7943
|
-
canvasCtx.beginPath();
|
|
7944
|
-
canvasCtx.arc(stemEnd.x, stemEnd.y, handleWorldSize / 2, 0, Math.PI * 2);
|
|
7945
|
-
canvasCtx.fill();
|
|
7946
|
-
canvasCtx.stroke();
|
|
7947
|
-
canvasCtx.setLineDash([4 / zoom, 4 / zoom]);
|
|
7948
|
-
}
|
|
7949
|
-
}
|
|
7950
|
-
if (el.locked) {
|
|
7951
|
-
const ne = layout.corners.find(([h]) => h === "ne")?.[1];
|
|
7952
|
-
if (ne) this.drawLockBadge(canvasCtx, ne, zoom);
|
|
7953
|
-
}
|
|
7954
|
-
}
|
|
7955
|
-
canvasCtx.restore();
|
|
7956
|
-
}
|
|
7957
|
-
drawLockBadge(ctx, at, zoom) {
|
|
7958
|
-
const r = 9 / zoom;
|
|
7959
|
-
ctx.save();
|
|
7960
|
-
ctx.setLineDash([]);
|
|
7961
|
-
ctx.beginPath();
|
|
7962
|
-
ctx.arc(at.x, at.y, r, 0, Math.PI * 2);
|
|
7963
|
-
ctx.fillStyle = "#ffffff";
|
|
7964
|
-
ctx.fill();
|
|
7965
|
-
ctx.strokeStyle = "#2196F3";
|
|
7966
|
-
ctx.lineWidth = 1.5 / zoom;
|
|
7967
|
-
ctx.stroke();
|
|
7968
|
-
const bw = 8 / zoom;
|
|
7969
|
-
const bh = 6 / zoom;
|
|
7970
|
-
ctx.fillStyle = "#2196F3";
|
|
7971
|
-
ctx.fillRect(at.x - bw / 2, at.y - bh / 2 + 1 / zoom, bw, bh);
|
|
7972
|
-
ctx.beginPath();
|
|
7973
|
-
ctx.arc(at.x, at.y - bh / 2 + 1 / zoom, 2.5 / zoom, Math.PI, 0);
|
|
7974
|
-
ctx.lineWidth = 1.4 / zoom;
|
|
7975
|
-
ctx.stroke();
|
|
7976
|
-
ctx.restore();
|
|
7977
|
-
}
|
|
7978
|
-
renderBindingHighlights(canvasCtx, arrow, zoom) {
|
|
7979
|
-
if (!this.ctx) return;
|
|
7980
|
-
if (!arrow.fromBinding && !arrow.toBinding) return;
|
|
7981
|
-
const pad = SELECTION_PAD / zoom;
|
|
7982
|
-
canvasCtx.save();
|
|
7983
|
-
canvasCtx.strokeStyle = "#2196F3";
|
|
7984
|
-
canvasCtx.lineWidth = 2 / zoom;
|
|
7985
|
-
canvasCtx.setLineDash([]);
|
|
7986
|
-
const drawn = /* @__PURE__ */ new Set();
|
|
7987
|
-
for (const binding of [arrow.fromBinding, arrow.toBinding]) {
|
|
7988
|
-
if (!binding || drawn.has(binding.elementId)) continue;
|
|
7989
|
-
drawn.add(binding.elementId);
|
|
7990
|
-
const target = this.ctx.store.getById(binding.elementId);
|
|
7991
|
-
if (!target) continue;
|
|
7992
|
-
const bounds = getElementBounds(target);
|
|
7993
|
-
if (!bounds) continue;
|
|
7994
|
-
canvasCtx.strokeRect(bounds.x - pad, bounds.y - pad, bounds.w + pad * 2, bounds.h + pad * 2);
|
|
7995
|
-
}
|
|
7996
|
-
canvasCtx.restore();
|
|
7997
|
-
}
|
|
7998
|
-
hitTestTemplateResizeHandle(world, ctx) {
|
|
7999
|
-
if (this._selectedIds.length === 0) return null;
|
|
8000
|
-
const zoom = ctx.camera.zoom;
|
|
8001
|
-
const handleHalf = (HANDLE_SIZE / 2 + HANDLE_HIT_PADDING2) / zoom;
|
|
8002
|
-
for (const id of this._selectedIds) {
|
|
8003
|
-
const el = ctx.store.getById(id);
|
|
8004
|
-
if (!el || el.type !== "template") continue;
|
|
8005
|
-
const bounds = getElementBounds(el);
|
|
8006
|
-
if (!bounds) continue;
|
|
8007
|
-
const hx = bounds.x + bounds.w;
|
|
8008
|
-
const hy = bounds.y + bounds.h;
|
|
8009
|
-
if (Math.abs(world.x - hx) <= handleHalf && Math.abs(world.y - hy) <= handleHalf) {
|
|
8010
|
-
return id;
|
|
8011
|
-
}
|
|
8012
|
-
}
|
|
8013
|
-
return null;
|
|
8107
|
+
return getOverlayLayout(el, zoom);
|
|
8014
8108
|
}
|
|
8015
8109
|
handleTemplateResize(world, ctx) {
|
|
8016
8110
|
if (this.mode.type !== "resizing-template") return;
|
|
8017
8111
|
const el = ctx.store.getById(this.mode.elementId);
|
|
8018
8112
|
if (!el || el.type !== "template" || el.locked) return;
|
|
8019
|
-
const
|
|
8020
|
-
|
|
8021
|
-
|
|
8022
|
-
|
|
8023
|
-
|
|
8024
|
-
|
|
8025
|
-
|
|
8026
|
-
|
|
8027
|
-
|
|
8028
|
-
if (el.feetPerCell != null && ctx.gridSize && ctx.gridSize > 0) {
|
|
8029
|
-
const snapUnit = ctx.gridType === "hex" ? Math.sqrt(3) * ctx.gridSize : ctx.gridSize;
|
|
8030
|
-
updates.radiusFeet = newRadius / snapUnit * el.feetPerCell;
|
|
8031
|
-
}
|
|
8032
|
-
ctx.store.update(this.mode.elementId, updates);
|
|
8033
|
-
ctx.requestRender();
|
|
8113
|
+
const patch = computeTemplateResize(el, world, {
|
|
8114
|
+
snapToGrid: ctx.snapToGrid,
|
|
8115
|
+
gridSize: ctx.gridSize,
|
|
8116
|
+
gridType: ctx.gridType
|
|
8117
|
+
});
|
|
8118
|
+
if (patch) {
|
|
8119
|
+
ctx.store.update(this.mode.elementId, patch);
|
|
8120
|
+
ctx.requestRender();
|
|
8121
|
+
}
|
|
8034
8122
|
}
|
|
8035
8123
|
getMarqueeRect() {
|
|
8036
8124
|
if (this.mode.type !== "marquee") return null;
|
|
@@ -8043,65 +8131,6 @@ var SelectTool = class {
|
|
|
8043
8131
|
if (w === 0 && h === 0) return null;
|
|
8044
8132
|
return { x, y, w, h };
|
|
8045
8133
|
}
|
|
8046
|
-
findElementsInRect(marquee, ctx) {
|
|
8047
|
-
const candidates = ctx.store.queryRect(marquee);
|
|
8048
|
-
const ids = [];
|
|
8049
|
-
for (const el of candidates) {
|
|
8050
|
-
if (ctx.isLayerVisible && !ctx.isLayerVisible(el.layerId)) continue;
|
|
8051
|
-
if (ctx.isLayerLocked && ctx.isLayerLocked(el.layerId)) continue;
|
|
8052
|
-
if (el.type === "grid") continue;
|
|
8053
|
-
const bounds = getElementBounds(el);
|
|
8054
|
-
if (bounds && this.rectsOverlap(marquee, rotatedAABB(bounds, el.rotation ?? 0))) {
|
|
8055
|
-
ids.push(el.id);
|
|
8056
|
-
}
|
|
8057
|
-
}
|
|
8058
|
-
return ids;
|
|
8059
|
-
}
|
|
8060
|
-
rectsOverlap(a, b) {
|
|
8061
|
-
return a.x <= b.x + b.w && a.x + a.w >= b.x && a.y <= b.y + b.h && a.y + a.h >= b.y;
|
|
8062
|
-
}
|
|
8063
|
-
hitTest(world, ctx) {
|
|
8064
|
-
const r = 10;
|
|
8065
|
-
const candidates = ctx.store.queryRect({ x: world.x - r, y: world.y - r, w: r * 2, h: r * 2 }).reverse();
|
|
8066
|
-
for (const el of candidates) {
|
|
8067
|
-
if (ctx.isLayerVisible && !ctx.isLayerVisible(el.layerId)) continue;
|
|
8068
|
-
if (ctx.isLayerLocked && ctx.isLayerLocked(el.layerId)) continue;
|
|
8069
|
-
if (el.type === "grid") continue;
|
|
8070
|
-
if (this.isInsideBounds(world, el)) return el;
|
|
8071
|
-
}
|
|
8072
|
-
return null;
|
|
8073
|
-
}
|
|
8074
|
-
isInsideBounds(point, el) {
|
|
8075
|
-
if (el.type === "grid") return false;
|
|
8076
|
-
const angle = el.rotation ?? 0;
|
|
8077
|
-
if (angle !== 0) {
|
|
8078
|
-
const b = getElementBounds(el);
|
|
8079
|
-
if (b) {
|
|
8080
|
-
point = rotatePoint(point, { x: b.x + b.w / 2, y: b.y + b.h / 2 }, -angle);
|
|
8081
|
-
}
|
|
8082
|
-
}
|
|
8083
|
-
if (el.type === "shape" && el.shape === "line") {
|
|
8084
|
-
const [a, b] = lineEndpoints(el);
|
|
8085
|
-
const threshold = Math.max(el.strokeWidth / 2, 6);
|
|
8086
|
-
return distSqToSegment(point, a, b) <= threshold * threshold;
|
|
8087
|
-
}
|
|
8088
|
-
if ("size" in el) {
|
|
8089
|
-
const s = el.size;
|
|
8090
|
-
return point.x >= el.position.x && point.x <= el.position.x + s.w && point.y >= el.position.y && point.y <= el.position.y + s.h;
|
|
8091
|
-
}
|
|
8092
|
-
if (el.type === "stroke") {
|
|
8093
|
-
return hitTestStroke(el, point, 10);
|
|
8094
|
-
}
|
|
8095
|
-
if (el.type === "arrow") {
|
|
8096
|
-
return isNearBezier(point, el.from, el.to, el.bend, 10);
|
|
8097
|
-
}
|
|
8098
|
-
if (el.type === "template") {
|
|
8099
|
-
const bounds = getElementBounds(el);
|
|
8100
|
-
if (!bounds) return false;
|
|
8101
|
-
return point.x >= bounds.x && point.x <= bounds.x + bounds.w && point.y >= bounds.y && point.y <= bounds.y + bounds.h;
|
|
8102
|
-
}
|
|
8103
|
-
return false;
|
|
8104
|
-
}
|
|
8105
8134
|
};
|
|
8106
8135
|
|
|
8107
8136
|
// src/tools/arrow-tool.ts
|