@fieldnotes/core 0.37.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 +19 -0
- package/dist/index.cjs +564 -509
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +16 -18
- package/dist/index.d.ts +16 -18
- package/dist/index.js +564 -509
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -6120,6 +6120,32 @@ var Viewport = class {
|
|
|
6120
6120
|
this.requestRender();
|
|
6121
6121
|
return el.id;
|
|
6122
6122
|
}
|
|
6123
|
+
addShape(opts = {}) {
|
|
6124
|
+
const size = opts.size ?? { w: 100, h: 100 };
|
|
6125
|
+
const position = opts.position ?? this.centeredPosition(size);
|
|
6126
|
+
const shape = createShape({
|
|
6127
|
+
position,
|
|
6128
|
+
size,
|
|
6129
|
+
shape: opts.shape,
|
|
6130
|
+
strokeColor: opts.strokeColor,
|
|
6131
|
+
strokeWidth: opts.strokeWidth,
|
|
6132
|
+
fillColor: opts.fillColor,
|
|
6133
|
+
layerId: this.layerManager.activeLayerId
|
|
6134
|
+
});
|
|
6135
|
+
this.historyRecorder.begin();
|
|
6136
|
+
this.store.add(shape);
|
|
6137
|
+
this.historyRecorder.commit();
|
|
6138
|
+
this.getSelectTool()?.setSelection([shape.id]);
|
|
6139
|
+
this.requestRender();
|
|
6140
|
+
return shape.id;
|
|
6141
|
+
}
|
|
6142
|
+
centeredPosition(size) {
|
|
6143
|
+
const c = this.camera.screenToWorld({
|
|
6144
|
+
x: this.wrapper.clientWidth / 2,
|
|
6145
|
+
y: this.wrapper.clientHeight / 2
|
|
6146
|
+
});
|
|
6147
|
+
return { x: c.x - size.w / 2, y: c.y - size.h / 2 };
|
|
6148
|
+
}
|
|
6123
6149
|
removeLayer(id) {
|
|
6124
6150
|
this.historyRecorder.begin();
|
|
6125
6151
|
this.layerManager.removeLayer(id);
|
|
@@ -7169,14 +7195,11 @@ function computeSnapGuides(moving, targets, threshold) {
|
|
|
7169
7195
|
return { dx: xSnap?.delta ?? 0, dy: ySnap?.delta ?? 0, guides };
|
|
7170
7196
|
}
|
|
7171
7197
|
|
|
7172
|
-
// src/tools/select-
|
|
7198
|
+
// src/tools/select-overlay.ts
|
|
7173
7199
|
var HANDLE_SIZE = 8;
|
|
7174
|
-
var SNAP_PX = 6;
|
|
7175
7200
|
var HANDLE_HIT_PADDING2 = 4;
|
|
7176
7201
|
var SELECTION_PAD = 4;
|
|
7177
|
-
var MIN_ELEMENT_SIZE = 20;
|
|
7178
7202
|
var ROTATE_HANDLE_OFFSET = 24;
|
|
7179
|
-
var ROTATE_SNAP = Math.PI / 12;
|
|
7180
7203
|
var ROTATABLE_TYPES = /* @__PURE__ */ new Set(["note", "text", "image", "html", "shape", "stroke"]);
|
|
7181
7204
|
var HANDLE_CURSORS = {
|
|
7182
7205
|
nw: "nwse-resize",
|
|
@@ -7184,6 +7207,486 @@ var HANDLE_CURSORS = {
|
|
|
7184
7207
|
ne: "nesw-resize",
|
|
7185
7208
|
sw: "nesw-resize"
|
|
7186
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;
|
|
7187
7690
|
var SelectTool = class {
|
|
7188
7691
|
name = "select";
|
|
7189
7692
|
_selectedIds = [];
|
|
@@ -7219,7 +7722,7 @@ var SelectTool = class {
|
|
|
7219
7722
|
this.ctx?.requestRender();
|
|
7220
7723
|
}
|
|
7221
7724
|
selectAtPoint(world, ctx) {
|
|
7222
|
-
const hit =
|
|
7725
|
+
const hit = hitTest(world, ctx);
|
|
7223
7726
|
if (!hit) {
|
|
7224
7727
|
this.setSelectedIds([]);
|
|
7225
7728
|
return;
|
|
@@ -7263,19 +7766,19 @@ var SelectTool = class {
|
|
|
7263
7766
|
ctx.requestRender();
|
|
7264
7767
|
return;
|
|
7265
7768
|
}
|
|
7266
|
-
const lineHit =
|
|
7769
|
+
const lineHit = hitTestLineHandles(world, ctx, this._selectedIds);
|
|
7267
7770
|
if (lineHit) {
|
|
7268
7771
|
this.mode = { type: "line-handle", elementId: lineHit.elementId, fixed: lineHit.fixed };
|
|
7269
7772
|
ctx.requestRender();
|
|
7270
7773
|
return;
|
|
7271
7774
|
}
|
|
7272
|
-
const templateResizeHit =
|
|
7775
|
+
const templateResizeHit = hitTestTemplateResizeHandle(world, ctx, this._selectedIds);
|
|
7273
7776
|
if (templateResizeHit) {
|
|
7274
7777
|
this.mode = { type: "resizing-template", elementId: templateResizeHit };
|
|
7275
7778
|
ctx.requestRender();
|
|
7276
7779
|
return;
|
|
7277
7780
|
}
|
|
7278
|
-
const rotateHit =
|
|
7781
|
+
const rotateHit = hitTestRotateHandle(world, ctx, this._selectedIds);
|
|
7279
7782
|
if (rotateHit) {
|
|
7280
7783
|
const el = ctx.store.getById(rotateHit.elementId);
|
|
7281
7784
|
const layout = el ? this.getOverlayLayout(el, ctx.camera.zoom) : null;
|
|
@@ -7291,7 +7794,7 @@ var SelectTool = class {
|
|
|
7291
7794
|
return;
|
|
7292
7795
|
}
|
|
7293
7796
|
}
|
|
7294
|
-
const resizeHit =
|
|
7797
|
+
const resizeHit = hitTestResizeHandle(world, ctx, this._selectedIds);
|
|
7295
7798
|
if (resizeHit) {
|
|
7296
7799
|
const el = ctx.store.getById(resizeHit.elementId);
|
|
7297
7800
|
if (el && "size" in el) {
|
|
@@ -7307,7 +7810,7 @@ var SelectTool = class {
|
|
|
7307
7810
|
}
|
|
7308
7811
|
this.pendingSingleSelectId = null;
|
|
7309
7812
|
this.hasDragged = false;
|
|
7310
|
-
const hit =
|
|
7813
|
+
const hit = hitTest(world, ctx);
|
|
7311
7814
|
if (hit) {
|
|
7312
7815
|
const all = ctx.store.getAll();
|
|
7313
7816
|
const alreadySelected = this._selectedIds.includes(hit.id);
|
|
@@ -7445,7 +7948,7 @@ var SelectTool = class {
|
|
|
7445
7948
|
if (this.mode.type === "marquee") {
|
|
7446
7949
|
const rect = this.getMarqueeRect();
|
|
7447
7950
|
if (rect) {
|
|
7448
|
-
this.setSelectedIds(expandToGroups(
|
|
7951
|
+
this.setSelectedIds(expandToGroups(findElementsInRect(rect, ctx), ctx.store.getAll()));
|
|
7449
7952
|
}
|
|
7450
7953
|
ctx.requestRender();
|
|
7451
7954
|
}
|
|
@@ -7472,8 +7975,16 @@ var SelectTool = class {
|
|
|
7472
7975
|
this.setHovered(hoverId, ctx);
|
|
7473
7976
|
}
|
|
7474
7977
|
renderOverlay(canvasCtx) {
|
|
7475
|
-
this.
|
|
7476
|
-
|
|
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
|
+
});
|
|
7477
7988
|
if (this.mode.type === "arrow-handle" && this.ctx) {
|
|
7478
7989
|
const target = getArrowHandleDragTarget(
|
|
7479
7990
|
this.mode.handle,
|
|
@@ -7507,32 +8018,13 @@ var SelectTool = class {
|
|
|
7507
8018
|
}
|
|
7508
8019
|
}
|
|
7509
8020
|
}
|
|
7510
|
-
this.
|
|
7511
|
-
|
|
7512
|
-
|
|
7513
|
-
|
|
7514
|
-
|
|
7515
|
-
|
|
7516
|
-
|
|
7517
|
-
canvasCtx.strokeStyle = "#FF4081";
|
|
7518
|
-
canvasCtx.lineWidth = 1 / zoom;
|
|
7519
|
-
canvasCtx.setLineDash([]);
|
|
7520
|
-
for (const g of this.activeGuides) {
|
|
7521
|
-
canvasCtx.beginPath();
|
|
7522
|
-
if (g.axis === "x") {
|
|
7523
|
-
const y0 = rect ? rect.y : this.currentWorld.y - 1e5;
|
|
7524
|
-
const y1 = rect ? rect.y + rect.h : this.currentWorld.y + 1e5;
|
|
7525
|
-
canvasCtx.moveTo(g.position, y0);
|
|
7526
|
-
canvasCtx.lineTo(g.position, y1);
|
|
7527
|
-
} else {
|
|
7528
|
-
const x0 = rect ? rect.x : this.currentWorld.x - 1e5;
|
|
7529
|
-
const x1 = rect ? rect.x + rect.w : this.currentWorld.x + 1e5;
|
|
7530
|
-
canvasCtx.moveTo(x0, g.position);
|
|
7531
|
-
canvasCtx.lineTo(x1, g.position);
|
|
7532
|
-
}
|
|
7533
|
-
canvasCtx.stroke();
|
|
7534
|
-
}
|
|
7535
|
-
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
|
+
});
|
|
7536
8028
|
}
|
|
7537
8029
|
updateArrowsBoundTo(ids, ctx) {
|
|
7538
8030
|
updateArrowsBoundToElements(ids, ctx.store);
|
|
@@ -7558,25 +8050,25 @@ var SelectTool = class {
|
|
|
7558
8050
|
ctx.setCursor?.(getArrowHandleCursor(arrowHit.handle, false));
|
|
7559
8051
|
return null;
|
|
7560
8052
|
}
|
|
7561
|
-
if (
|
|
8053
|
+
if (hitTestLineHandles(world, ctx, this._selectedIds)) {
|
|
7562
8054
|
ctx.setCursor?.("grab");
|
|
7563
8055
|
return null;
|
|
7564
8056
|
}
|
|
7565
|
-
const templateResizeHit =
|
|
8057
|
+
const templateResizeHit = hitTestTemplateResizeHandle(world, ctx, this._selectedIds);
|
|
7566
8058
|
if (templateResizeHit) {
|
|
7567
8059
|
ctx.setCursor?.("nwse-resize");
|
|
7568
8060
|
return null;
|
|
7569
8061
|
}
|
|
7570
|
-
if (
|
|
8062
|
+
if (hitTestRotateHandle(world, ctx, this._selectedIds)) {
|
|
7571
8063
|
ctx.setCursor?.("grab");
|
|
7572
8064
|
return null;
|
|
7573
8065
|
}
|
|
7574
|
-
const resizeHit =
|
|
8066
|
+
const resizeHit = hitTestResizeHandle(world, ctx, this._selectedIds);
|
|
7575
8067
|
if (resizeHit) {
|
|
7576
8068
|
ctx.setCursor?.(HANDLE_CURSORS[resizeHit.handle]);
|
|
7577
8069
|
return null;
|
|
7578
8070
|
}
|
|
7579
|
-
const hit =
|
|
8071
|
+
const hit = hitTest(world, ctx);
|
|
7580
8072
|
ctx.setCursor?.(hit ? "move" : "default");
|
|
7581
8073
|
return hit ? hit.id : null;
|
|
7582
8074
|
}
|
|
@@ -7590,421 +8082,43 @@ var SelectTool = class {
|
|
|
7590
8082
|
const el = ctx.store.getById(this.mode.elementId);
|
|
7591
8083
|
if (!el || !("size" in el) || el.locked) return;
|
|
7592
8084
|
const angle = el.rotation ?? 0;
|
|
7593
|
-
|
|
7594
|
-
|
|
7595
|
-
|
|
7596
|
-
|
|
7597
|
-
|
|
7598
|
-
|
|
7599
|
-
|
|
7600
|
-
|
|
7601
|
-
|
|
7602
|
-
|
|
7603
|
-
|
|
7604
|
-
|
|
7605
|
-
|
|
7606
|
-
|
|
7607
|
-
|
|
7608
|
-
x += dx;
|
|
7609
|
-
w -= dx;
|
|
7610
|
-
h += dy;
|
|
7611
|
-
break;
|
|
7612
|
-
case "ne":
|
|
7613
|
-
y += dy;
|
|
7614
|
-
w += dx;
|
|
7615
|
-
h -= dy;
|
|
7616
|
-
break;
|
|
7617
|
-
case "nw":
|
|
7618
|
-
x += dx;
|
|
7619
|
-
y += dy;
|
|
7620
|
-
w -= dx;
|
|
7621
|
-
h -= dy;
|
|
7622
|
-
break;
|
|
7623
|
-
}
|
|
7624
|
-
if (shiftKey && this.resizeAspectRatio > 0) {
|
|
7625
|
-
const absDw = Math.abs(w - el.size.w);
|
|
7626
|
-
const absDh = Math.abs(h - el.size.h);
|
|
7627
|
-
if (absDw >= absDh) {
|
|
7628
|
-
h = w / this.resizeAspectRatio;
|
|
7629
|
-
} else {
|
|
7630
|
-
w = h * this.resizeAspectRatio;
|
|
7631
|
-
}
|
|
7632
|
-
if (handle === "nw" || handle === "sw") {
|
|
7633
|
-
x = el.position.x + el.size.w - w;
|
|
7634
|
-
}
|
|
7635
|
-
if (handle === "nw" || handle === "ne") {
|
|
7636
|
-
y = el.position.y + el.size.h - h;
|
|
7637
|
-
}
|
|
7638
|
-
}
|
|
7639
|
-
if (w < MIN_ELEMENT_SIZE) {
|
|
7640
|
-
if (handle === "nw" || handle === "sw") x = el.position.x + el.size.w - MIN_ELEMENT_SIZE;
|
|
7641
|
-
w = MIN_ELEMENT_SIZE;
|
|
7642
|
-
}
|
|
7643
|
-
if (h < MIN_ELEMENT_SIZE) {
|
|
7644
|
-
if (handle === "nw" || handle === "ne") y = el.position.y + el.size.h - MIN_ELEMENT_SIZE;
|
|
7645
|
-
h = MIN_ELEMENT_SIZE;
|
|
7646
|
-
}
|
|
7647
|
-
ctx.store.update(this.mode.elementId, {
|
|
7648
|
-
position: { x, y },
|
|
7649
|
-
size: { w, h }
|
|
7650
|
-
});
|
|
7651
|
-
this.updateArrowsBoundTo([this.mode.elementId], ctx);
|
|
7652
|
-
ctx.requestRender();
|
|
7653
|
-
}
|
|
7654
|
-
anchorOffset(handle, w, h) {
|
|
7655
|
-
switch (handle) {
|
|
7656
|
-
case "se":
|
|
7657
|
-
return { x: -w / 2, y: -h / 2 };
|
|
7658
|
-
case "sw":
|
|
7659
|
-
return { x: w / 2, y: -h / 2 };
|
|
7660
|
-
case "ne":
|
|
7661
|
-
return { x: -w / 2, y: h / 2 };
|
|
7662
|
-
case "nw":
|
|
7663
|
-
return { x: w / 2, y: h / 2 };
|
|
7664
|
-
default:
|
|
7665
|
-
return { x: 0, y: 0 };
|
|
7666
|
-
}
|
|
7667
|
-
}
|
|
7668
|
-
handleRotatedResize(world, el, angle, ctx, shiftKey) {
|
|
7669
|
-
if (this.mode.type !== "resizing") return;
|
|
7670
|
-
const { handle } = this.mode;
|
|
7671
|
-
const wdx = world.x - this.lastWorld.x;
|
|
7672
|
-
const wdy = world.y - this.lastWorld.y;
|
|
7673
|
-
this.lastWorld = world;
|
|
7674
|
-
const cosN = Math.cos(-angle);
|
|
7675
|
-
const sinN = Math.sin(-angle);
|
|
7676
|
-
const ldx = wdx * cosN - wdy * sinN;
|
|
7677
|
-
const ldy = wdx * sinN + wdy * cosN;
|
|
7678
|
-
let w = el.size.w;
|
|
7679
|
-
let h = el.size.h;
|
|
7680
|
-
switch (handle) {
|
|
7681
|
-
case "se":
|
|
7682
|
-
w += ldx;
|
|
7683
|
-
h += ldy;
|
|
7684
|
-
break;
|
|
7685
|
-
case "sw":
|
|
7686
|
-
w -= ldx;
|
|
7687
|
-
h += ldy;
|
|
7688
|
-
break;
|
|
7689
|
-
case "ne":
|
|
7690
|
-
w += ldx;
|
|
7691
|
-
h -= ldy;
|
|
7692
|
-
break;
|
|
7693
|
-
case "nw":
|
|
7694
|
-
w -= ldx;
|
|
7695
|
-
h -= ldy;
|
|
7696
|
-
break;
|
|
7697
|
-
}
|
|
7698
|
-
if (shiftKey && this.resizeAspectRatio > 0) {
|
|
7699
|
-
const absDw = Math.abs(w - el.size.w);
|
|
7700
|
-
const absDh = Math.abs(h - el.size.h);
|
|
7701
|
-
if (absDw >= absDh) h = w / this.resizeAspectRatio;
|
|
7702
|
-
else w = h * this.resizeAspectRatio;
|
|
7703
|
-
}
|
|
7704
|
-
w = Math.max(w, MIN_ELEMENT_SIZE);
|
|
7705
|
-
h = Math.max(h, MIN_ELEMENT_SIZE);
|
|
7706
|
-
const oldCenter = { x: el.position.x + el.size.w / 2, y: el.position.y + el.size.h / 2 };
|
|
7707
|
-
const oldAnchorLocal = this.anchorOffset(handle, el.size.w, el.size.h);
|
|
7708
|
-
const anchorWorld = rotatePoint(
|
|
7709
|
-
{ x: oldCenter.x + oldAnchorLocal.x, y: oldCenter.y + oldAnchorLocal.y },
|
|
7710
|
-
oldCenter,
|
|
7711
|
-
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
|
|
7712
8100
|
);
|
|
7713
|
-
|
|
7714
|
-
|
|
7715
|
-
const sin = Math.sin(angle);
|
|
7716
|
-
const rotatedAnchor = {
|
|
7717
|
-
x: newAnchorLocal.x * cos - newAnchorLocal.y * sin,
|
|
7718
|
-
y: newAnchorLocal.x * sin + newAnchorLocal.y * cos
|
|
7719
|
-
};
|
|
7720
|
-
const newCenter = { x: anchorWorld.x - rotatedAnchor.x, y: anchorWorld.y - rotatedAnchor.y };
|
|
7721
|
-
const position = { x: newCenter.x - w / 2, y: newCenter.y - h / 2 };
|
|
7722
|
-
ctx.store.update(this.mode.elementId, { position, size: { w, h } });
|
|
8101
|
+
this.lastWorld = world;
|
|
8102
|
+
ctx.store.update(this.mode.elementId, patch);
|
|
7723
8103
|
this.updateArrowsBoundTo([this.mode.elementId], ctx);
|
|
7724
8104
|
ctx.requestRender();
|
|
7725
8105
|
}
|
|
7726
|
-
hitTestResizeHandle(world, ctx) {
|
|
7727
|
-
if (this._selectedIds.length === 0) return null;
|
|
7728
|
-
const zoom = ctx.camera.zoom;
|
|
7729
|
-
const handleHalf = (HANDLE_SIZE / 2 + HANDLE_HIT_PADDING2) / zoom;
|
|
7730
|
-
for (const id of this._selectedIds) {
|
|
7731
|
-
const el = ctx.store.getById(id);
|
|
7732
|
-
if (!el || !("size" in el)) continue;
|
|
7733
|
-
if (el.locked) continue;
|
|
7734
|
-
if (el.type === "shape" && el.shape === "line") continue;
|
|
7735
|
-
const layout = this.getOverlayLayout(el, zoom);
|
|
7736
|
-
if (!layout) continue;
|
|
7737
|
-
for (const [handle, pos] of layout.corners) {
|
|
7738
|
-
if (Math.abs(world.x - pos.x) <= handleHalf && Math.abs(world.y - pos.y) <= handleHalf) {
|
|
7739
|
-
return { elementId: id, handle };
|
|
7740
|
-
}
|
|
7741
|
-
}
|
|
7742
|
-
}
|
|
7743
|
-
return null;
|
|
7744
|
-
}
|
|
7745
|
-
hitTestRotateHandle(world, ctx) {
|
|
7746
|
-
if (this._selectedIds.length !== 1) return null;
|
|
7747
|
-
const id = this._selectedIds[0];
|
|
7748
|
-
if (!id) return null;
|
|
7749
|
-
const el = ctx.store.getById(id);
|
|
7750
|
-
if (!el || el.locked || !ROTATABLE_TYPES.has(el.type)) return null;
|
|
7751
|
-
const layout = this.getOverlayLayout(el, ctx.camera.zoom);
|
|
7752
|
-
if (!layout) return null;
|
|
7753
|
-
const r = (HANDLE_SIZE / 2 + HANDLE_HIT_PADDING2) / ctx.camera.zoom;
|
|
7754
|
-
const dx = world.x - layout.rotateHandle.x;
|
|
7755
|
-
const dy = world.y - layout.rotateHandle.y;
|
|
7756
|
-
return dx * dx + dy * dy <= r * r ? { elementId: id } : null;
|
|
7757
|
-
}
|
|
7758
|
-
hitTestLineHandles(world, ctx) {
|
|
7759
|
-
if (this._selectedIds.length === 0) return null;
|
|
7760
|
-
const zoom = ctx.camera.zoom;
|
|
7761
|
-
const r = (HANDLE_SIZE / 2 + HANDLE_HIT_PADDING2) / zoom;
|
|
7762
|
-
const r2 = r * r;
|
|
7763
|
-
for (const id of this._selectedIds) {
|
|
7764
|
-
const el = ctx.store.getById(id);
|
|
7765
|
-
if (!el || el.type !== "shape" || el.shape !== "line") continue;
|
|
7766
|
-
const [a, b] = lineEndpoints(el);
|
|
7767
|
-
if ((world.x - a.x) ** 2 + (world.y - a.y) ** 2 <= r2) return { elementId: id, fixed: b };
|
|
7768
|
-
if ((world.x - b.x) ** 2 + (world.y - b.y) ** 2 <= r2) return { elementId: id, fixed: a };
|
|
7769
|
-
}
|
|
7770
|
-
return null;
|
|
7771
|
-
}
|
|
7772
|
-
getHandlePositions(bounds) {
|
|
7773
|
-
return [
|
|
7774
|
-
["nw", { x: bounds.x, y: bounds.y }],
|
|
7775
|
-
["ne", { x: bounds.x + bounds.w, y: bounds.y }],
|
|
7776
|
-
["sw", { x: bounds.x, y: bounds.y + bounds.h }],
|
|
7777
|
-
["se", { x: bounds.x + bounds.w, y: bounds.y + bounds.h }]
|
|
7778
|
-
];
|
|
7779
|
-
}
|
|
7780
8106
|
getOverlayLayout(el, zoom) {
|
|
7781
|
-
|
|
7782
|
-
if (!bounds) return null;
|
|
7783
|
-
const angle = el.rotation ?? 0;
|
|
7784
|
-
const pad = SELECTION_PAD / zoom;
|
|
7785
|
-
const center2 = { x: bounds.x + bounds.w / 2, y: bounds.y + bounds.h / 2 };
|
|
7786
|
-
const raw = [
|
|
7787
|
-
["nw", { x: bounds.x - pad, y: bounds.y - pad }],
|
|
7788
|
-
["ne", { x: bounds.x + bounds.w + pad, y: bounds.y - pad }],
|
|
7789
|
-
["sw", { x: bounds.x - pad, y: bounds.y + bounds.h + pad }],
|
|
7790
|
-
["se", { x: bounds.x + bounds.w + pad, y: bounds.y + bounds.h + pad }]
|
|
7791
|
-
];
|
|
7792
|
-
const corners = raw.map(
|
|
7793
|
-
([h, p]) => [h, rotatePoint(p, center2, angle)]
|
|
7794
|
-
);
|
|
7795
|
-
const topMid = { x: center2.x, y: bounds.y - pad - ROTATE_HANDLE_OFFSET / zoom };
|
|
7796
|
-
const rotateHandle = rotatePoint(topMid, center2, angle);
|
|
7797
|
-
return { center: center2, corners, rotateHandle, angle };
|
|
7798
|
-
}
|
|
7799
|
-
topMidpoint(layout) {
|
|
7800
|
-
const nw = layout.corners.find(([h]) => h === "nw")?.[1] ?? { x: 0, y: 0 };
|
|
7801
|
-
const ne = layout.corners.find(([h]) => h === "ne")?.[1] ?? { x: 0, y: 0 };
|
|
7802
|
-
return { x: (nw.x + ne.x) / 2, y: (nw.y + ne.y) / 2 };
|
|
7803
|
-
}
|
|
7804
|
-
renderMarquee(canvasCtx) {
|
|
7805
|
-
if (this.mode.type !== "marquee") return;
|
|
7806
|
-
const rect = this.getMarqueeRect();
|
|
7807
|
-
if (!rect) return;
|
|
7808
|
-
canvasCtx.save();
|
|
7809
|
-
canvasCtx.strokeStyle = "#2196F3";
|
|
7810
|
-
canvasCtx.fillStyle = "rgba(33, 150, 243, 0.08)";
|
|
7811
|
-
canvasCtx.lineWidth = 1;
|
|
7812
|
-
canvasCtx.setLineDash([4, 4]);
|
|
7813
|
-
canvasCtx.strokeRect(rect.x, rect.y, rect.w, rect.h);
|
|
7814
|
-
canvasCtx.fillRect(rect.x, rect.y, rect.w, rect.h);
|
|
7815
|
-
canvasCtx.restore();
|
|
7816
|
-
}
|
|
7817
|
-
renderSelectionBoxes(canvasCtx) {
|
|
7818
|
-
if (this._selectedIds.length === 0 || !this.ctx) return;
|
|
7819
|
-
const zoom = this.ctx.camera.zoom;
|
|
7820
|
-
const handleWorldSize = HANDLE_SIZE / zoom;
|
|
7821
|
-
canvasCtx.save();
|
|
7822
|
-
canvasCtx.strokeStyle = "#2196F3";
|
|
7823
|
-
canvasCtx.lineWidth = 1.5 / zoom;
|
|
7824
|
-
canvasCtx.setLineDash([4 / zoom, 4 / zoom]);
|
|
7825
|
-
for (const id of this._selectedIds) {
|
|
7826
|
-
const el = this.ctx.store.getById(id);
|
|
7827
|
-
if (!el) continue;
|
|
7828
|
-
if (el.type === "arrow") {
|
|
7829
|
-
renderArrowHandles(canvasCtx, el, zoom);
|
|
7830
|
-
this.renderBindingHighlights(canvasCtx, el, zoom);
|
|
7831
|
-
continue;
|
|
7832
|
-
}
|
|
7833
|
-
if (el.type === "shape" && el.shape === "line") {
|
|
7834
|
-
canvasCtx.setLineDash([]);
|
|
7835
|
-
canvasCtx.fillStyle = "#ffffff";
|
|
7836
|
-
const r = handleWorldSize / 2;
|
|
7837
|
-
for (const p of lineEndpoints(el)) {
|
|
7838
|
-
canvasCtx.beginPath();
|
|
7839
|
-
canvasCtx.arc(p.x, p.y, r, 0, Math.PI * 2);
|
|
7840
|
-
canvasCtx.fill();
|
|
7841
|
-
canvasCtx.stroke();
|
|
7842
|
-
}
|
|
7843
|
-
canvasCtx.setLineDash([4 / zoom, 4 / zoom]);
|
|
7844
|
-
continue;
|
|
7845
|
-
}
|
|
7846
|
-
const bounds = getElementBounds(el);
|
|
7847
|
-
if (!bounds) continue;
|
|
7848
|
-
const layout = this.getOverlayLayout(el, zoom);
|
|
7849
|
-
if (!layout) continue;
|
|
7850
|
-
const pad = SELECTION_PAD / zoom;
|
|
7851
|
-
if (layout.angle === 0) {
|
|
7852
|
-
canvasCtx.strokeRect(
|
|
7853
|
-
bounds.x - pad,
|
|
7854
|
-
bounds.y - pad,
|
|
7855
|
-
bounds.w + pad * 2,
|
|
7856
|
-
bounds.h + pad * 2
|
|
7857
|
-
);
|
|
7858
|
-
} else {
|
|
7859
|
-
const ordered = ["nw", "ne", "se", "sw"].map((h) => layout.corners.find(([c]) => c === h)?.[1]).filter((p) => !!p);
|
|
7860
|
-
const [p0, ...others] = ordered;
|
|
7861
|
-
if (p0) {
|
|
7862
|
-
canvasCtx.beginPath();
|
|
7863
|
-
canvasCtx.moveTo(p0.x, p0.y);
|
|
7864
|
-
for (const p of others) canvasCtx.lineTo(p.x, p.y);
|
|
7865
|
-
canvasCtx.closePath();
|
|
7866
|
-
canvasCtx.stroke();
|
|
7867
|
-
}
|
|
7868
|
-
}
|
|
7869
|
-
if (!el.locked) {
|
|
7870
|
-
if ("size" in el) {
|
|
7871
|
-
canvasCtx.setLineDash([]);
|
|
7872
|
-
canvasCtx.fillStyle = "#ffffff";
|
|
7873
|
-
const corners = layout.angle === 0 ? this.getHandlePositions(bounds) : layout.corners;
|
|
7874
|
-
for (const [, pos] of corners) {
|
|
7875
|
-
canvasCtx.fillRect(
|
|
7876
|
-
pos.x - handleWorldSize / 2,
|
|
7877
|
-
pos.y - handleWorldSize / 2,
|
|
7878
|
-
handleWorldSize,
|
|
7879
|
-
handleWorldSize
|
|
7880
|
-
);
|
|
7881
|
-
canvasCtx.strokeRect(
|
|
7882
|
-
pos.x - handleWorldSize / 2,
|
|
7883
|
-
pos.y - handleWorldSize / 2,
|
|
7884
|
-
handleWorldSize,
|
|
7885
|
-
handleWorldSize
|
|
7886
|
-
);
|
|
7887
|
-
}
|
|
7888
|
-
canvasCtx.setLineDash([4 / zoom, 4 / zoom]);
|
|
7889
|
-
} else if (el.type === "template") {
|
|
7890
|
-
canvasCtx.setLineDash([]);
|
|
7891
|
-
canvasCtx.fillStyle = "#ffffff";
|
|
7892
|
-
const hx = bounds.x + bounds.w;
|
|
7893
|
-
const hy = bounds.y + bounds.h;
|
|
7894
|
-
canvasCtx.fillRect(
|
|
7895
|
-
hx - handleWorldSize / 2,
|
|
7896
|
-
hy - handleWorldSize / 2,
|
|
7897
|
-
handleWorldSize,
|
|
7898
|
-
handleWorldSize
|
|
7899
|
-
);
|
|
7900
|
-
canvasCtx.strokeRect(
|
|
7901
|
-
hx - handleWorldSize / 2,
|
|
7902
|
-
hy - handleWorldSize / 2,
|
|
7903
|
-
handleWorldSize,
|
|
7904
|
-
handleWorldSize
|
|
7905
|
-
);
|
|
7906
|
-
canvasCtx.setLineDash([4 / zoom, 4 / zoom]);
|
|
7907
|
-
}
|
|
7908
|
-
if (this._selectedIds.length === 1 && ROTATABLE_TYPES.has(el.type)) {
|
|
7909
|
-
const stemStart = this.topMidpoint(layout);
|
|
7910
|
-
const stemEnd = layout.rotateHandle;
|
|
7911
|
-
canvasCtx.beginPath();
|
|
7912
|
-
canvasCtx.moveTo(stemStart.x, stemStart.y);
|
|
7913
|
-
canvasCtx.lineTo(stemEnd.x, stemEnd.y);
|
|
7914
|
-
canvasCtx.stroke();
|
|
7915
|
-
canvasCtx.setLineDash([]);
|
|
7916
|
-
canvasCtx.fillStyle = "#ffffff";
|
|
7917
|
-
canvasCtx.beginPath();
|
|
7918
|
-
canvasCtx.arc(stemEnd.x, stemEnd.y, handleWorldSize / 2, 0, Math.PI * 2);
|
|
7919
|
-
canvasCtx.fill();
|
|
7920
|
-
canvasCtx.stroke();
|
|
7921
|
-
canvasCtx.setLineDash([4 / zoom, 4 / zoom]);
|
|
7922
|
-
}
|
|
7923
|
-
}
|
|
7924
|
-
if (el.locked) {
|
|
7925
|
-
const ne = layout.corners.find(([h]) => h === "ne")?.[1];
|
|
7926
|
-
if (ne) this.drawLockBadge(canvasCtx, ne, zoom);
|
|
7927
|
-
}
|
|
7928
|
-
}
|
|
7929
|
-
canvasCtx.restore();
|
|
7930
|
-
}
|
|
7931
|
-
drawLockBadge(ctx, at, zoom) {
|
|
7932
|
-
const r = 9 / zoom;
|
|
7933
|
-
ctx.save();
|
|
7934
|
-
ctx.setLineDash([]);
|
|
7935
|
-
ctx.beginPath();
|
|
7936
|
-
ctx.arc(at.x, at.y, r, 0, Math.PI * 2);
|
|
7937
|
-
ctx.fillStyle = "#ffffff";
|
|
7938
|
-
ctx.fill();
|
|
7939
|
-
ctx.strokeStyle = "#2196F3";
|
|
7940
|
-
ctx.lineWidth = 1.5 / zoom;
|
|
7941
|
-
ctx.stroke();
|
|
7942
|
-
const bw = 8 / zoom;
|
|
7943
|
-
const bh = 6 / zoom;
|
|
7944
|
-
ctx.fillStyle = "#2196F3";
|
|
7945
|
-
ctx.fillRect(at.x - bw / 2, at.y - bh / 2 + 1 / zoom, bw, bh);
|
|
7946
|
-
ctx.beginPath();
|
|
7947
|
-
ctx.arc(at.x, at.y - bh / 2 + 1 / zoom, 2.5 / zoom, Math.PI, 0);
|
|
7948
|
-
ctx.lineWidth = 1.4 / zoom;
|
|
7949
|
-
ctx.stroke();
|
|
7950
|
-
ctx.restore();
|
|
7951
|
-
}
|
|
7952
|
-
renderBindingHighlights(canvasCtx, arrow, zoom) {
|
|
7953
|
-
if (!this.ctx) return;
|
|
7954
|
-
if (!arrow.fromBinding && !arrow.toBinding) return;
|
|
7955
|
-
const pad = SELECTION_PAD / zoom;
|
|
7956
|
-
canvasCtx.save();
|
|
7957
|
-
canvasCtx.strokeStyle = "#2196F3";
|
|
7958
|
-
canvasCtx.lineWidth = 2 / zoom;
|
|
7959
|
-
canvasCtx.setLineDash([]);
|
|
7960
|
-
const drawn = /* @__PURE__ */ new Set();
|
|
7961
|
-
for (const binding of [arrow.fromBinding, arrow.toBinding]) {
|
|
7962
|
-
if (!binding || drawn.has(binding.elementId)) continue;
|
|
7963
|
-
drawn.add(binding.elementId);
|
|
7964
|
-
const target = this.ctx.store.getById(binding.elementId);
|
|
7965
|
-
if (!target) continue;
|
|
7966
|
-
const bounds = getElementBounds(target);
|
|
7967
|
-
if (!bounds) continue;
|
|
7968
|
-
canvasCtx.strokeRect(bounds.x - pad, bounds.y - pad, bounds.w + pad * 2, bounds.h + pad * 2);
|
|
7969
|
-
}
|
|
7970
|
-
canvasCtx.restore();
|
|
7971
|
-
}
|
|
7972
|
-
hitTestTemplateResizeHandle(world, ctx) {
|
|
7973
|
-
if (this._selectedIds.length === 0) return null;
|
|
7974
|
-
const zoom = ctx.camera.zoom;
|
|
7975
|
-
const handleHalf = (HANDLE_SIZE / 2 + HANDLE_HIT_PADDING2) / zoom;
|
|
7976
|
-
for (const id of this._selectedIds) {
|
|
7977
|
-
const el = ctx.store.getById(id);
|
|
7978
|
-
if (!el || el.type !== "template") continue;
|
|
7979
|
-
const bounds = getElementBounds(el);
|
|
7980
|
-
if (!bounds) continue;
|
|
7981
|
-
const hx = bounds.x + bounds.w;
|
|
7982
|
-
const hy = bounds.y + bounds.h;
|
|
7983
|
-
if (Math.abs(world.x - hx) <= handleHalf && Math.abs(world.y - hy) <= handleHalf) {
|
|
7984
|
-
return id;
|
|
7985
|
-
}
|
|
7986
|
-
}
|
|
7987
|
-
return null;
|
|
8107
|
+
return getOverlayLayout(el, zoom);
|
|
7988
8108
|
}
|
|
7989
8109
|
handleTemplateResize(world, ctx) {
|
|
7990
8110
|
if (this.mode.type !== "resizing-template") return;
|
|
7991
8111
|
const el = ctx.store.getById(this.mode.elementId);
|
|
7992
8112
|
if (!el || el.type !== "template" || el.locked) return;
|
|
7993
|
-
const
|
|
7994
|
-
|
|
7995
|
-
|
|
7996
|
-
|
|
7997
|
-
|
|
7998
|
-
|
|
7999
|
-
|
|
8000
|
-
|
|
8001
|
-
|
|
8002
|
-
if (el.feetPerCell != null && ctx.gridSize && ctx.gridSize > 0) {
|
|
8003
|
-
const snapUnit = ctx.gridType === "hex" ? Math.sqrt(3) * ctx.gridSize : ctx.gridSize;
|
|
8004
|
-
updates.radiusFeet = newRadius / snapUnit * el.feetPerCell;
|
|
8005
|
-
}
|
|
8006
|
-
ctx.store.update(this.mode.elementId, updates);
|
|
8007
|
-
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
|
+
}
|
|
8008
8122
|
}
|
|
8009
8123
|
getMarqueeRect() {
|
|
8010
8124
|
if (this.mode.type !== "marquee") return null;
|
|
@@ -8017,65 +8131,6 @@ var SelectTool = class {
|
|
|
8017
8131
|
if (w === 0 && h === 0) return null;
|
|
8018
8132
|
return { x, y, w, h };
|
|
8019
8133
|
}
|
|
8020
|
-
findElementsInRect(marquee, ctx) {
|
|
8021
|
-
const candidates = ctx.store.queryRect(marquee);
|
|
8022
|
-
const ids = [];
|
|
8023
|
-
for (const el of candidates) {
|
|
8024
|
-
if (ctx.isLayerVisible && !ctx.isLayerVisible(el.layerId)) continue;
|
|
8025
|
-
if (ctx.isLayerLocked && ctx.isLayerLocked(el.layerId)) continue;
|
|
8026
|
-
if (el.type === "grid") continue;
|
|
8027
|
-
const bounds = getElementBounds(el);
|
|
8028
|
-
if (bounds && this.rectsOverlap(marquee, rotatedAABB(bounds, el.rotation ?? 0))) {
|
|
8029
|
-
ids.push(el.id);
|
|
8030
|
-
}
|
|
8031
|
-
}
|
|
8032
|
-
return ids;
|
|
8033
|
-
}
|
|
8034
|
-
rectsOverlap(a, b) {
|
|
8035
|
-
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;
|
|
8036
|
-
}
|
|
8037
|
-
hitTest(world, ctx) {
|
|
8038
|
-
const r = 10;
|
|
8039
|
-
const candidates = ctx.store.queryRect({ x: world.x - r, y: world.y - r, w: r * 2, h: r * 2 }).reverse();
|
|
8040
|
-
for (const el of candidates) {
|
|
8041
|
-
if (ctx.isLayerVisible && !ctx.isLayerVisible(el.layerId)) continue;
|
|
8042
|
-
if (ctx.isLayerLocked && ctx.isLayerLocked(el.layerId)) continue;
|
|
8043
|
-
if (el.type === "grid") continue;
|
|
8044
|
-
if (this.isInsideBounds(world, el)) return el;
|
|
8045
|
-
}
|
|
8046
|
-
return null;
|
|
8047
|
-
}
|
|
8048
|
-
isInsideBounds(point, el) {
|
|
8049
|
-
if (el.type === "grid") return false;
|
|
8050
|
-
const angle = el.rotation ?? 0;
|
|
8051
|
-
if (angle !== 0) {
|
|
8052
|
-
const b = getElementBounds(el);
|
|
8053
|
-
if (b) {
|
|
8054
|
-
point = rotatePoint(point, { x: b.x + b.w / 2, y: b.y + b.h / 2 }, -angle);
|
|
8055
|
-
}
|
|
8056
|
-
}
|
|
8057
|
-
if (el.type === "shape" && el.shape === "line") {
|
|
8058
|
-
const [a, b] = lineEndpoints(el);
|
|
8059
|
-
const threshold = Math.max(el.strokeWidth / 2, 6);
|
|
8060
|
-
return distSqToSegment(point, a, b) <= threshold * threshold;
|
|
8061
|
-
}
|
|
8062
|
-
if ("size" in el) {
|
|
8063
|
-
const s = el.size;
|
|
8064
|
-
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;
|
|
8065
|
-
}
|
|
8066
|
-
if (el.type === "stroke") {
|
|
8067
|
-
return hitTestStroke(el, point, 10);
|
|
8068
|
-
}
|
|
8069
|
-
if (el.type === "arrow") {
|
|
8070
|
-
return isNearBezier(point, el.from, el.to, el.bend, 10);
|
|
8071
|
-
}
|
|
8072
|
-
if (el.type === "template") {
|
|
8073
|
-
const bounds = getElementBounds(el);
|
|
8074
|
-
if (!bounds) return false;
|
|
8075
|
-
return point.x >= bounds.x && point.x <= bounds.x + bounds.w && point.y >= bounds.y && point.y <= bounds.y + bounds.h;
|
|
8076
|
-
}
|
|
8077
|
-
return false;
|
|
8078
|
-
}
|
|
8079
8134
|
};
|
|
8080
8135
|
|
|
8081
8136
|
// src/tools/arrow-tool.ts
|
|
@@ -8921,7 +8976,7 @@ var TemplateTool = class {
|
|
|
8921
8976
|
};
|
|
8922
8977
|
|
|
8923
8978
|
// src/index.ts
|
|
8924
|
-
var VERSION = "0.
|
|
8979
|
+
var VERSION = "0.38.0";
|
|
8925
8980
|
// Annotate the CommonJS export names for ESM import in node:
|
|
8926
8981
|
0 && (module.exports = {
|
|
8927
8982
|
ArrowTool,
|