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