@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.js
CHANGED
|
@@ -6039,6 +6039,32 @@ var Viewport = class {
|
|
|
6039
6039
|
this.requestRender();
|
|
6040
6040
|
return el.id;
|
|
6041
6041
|
}
|
|
6042
|
+
addShape(opts = {}) {
|
|
6043
|
+
const size = opts.size ?? { w: 100, h: 100 };
|
|
6044
|
+
const position = opts.position ?? this.centeredPosition(size);
|
|
6045
|
+
const shape = createShape({
|
|
6046
|
+
position,
|
|
6047
|
+
size,
|
|
6048
|
+
shape: opts.shape,
|
|
6049
|
+
strokeColor: opts.strokeColor,
|
|
6050
|
+
strokeWidth: opts.strokeWidth,
|
|
6051
|
+
fillColor: opts.fillColor,
|
|
6052
|
+
layerId: this.layerManager.activeLayerId
|
|
6053
|
+
});
|
|
6054
|
+
this.historyRecorder.begin();
|
|
6055
|
+
this.store.add(shape);
|
|
6056
|
+
this.historyRecorder.commit();
|
|
6057
|
+
this.getSelectTool()?.setSelection([shape.id]);
|
|
6058
|
+
this.requestRender();
|
|
6059
|
+
return shape.id;
|
|
6060
|
+
}
|
|
6061
|
+
centeredPosition(size) {
|
|
6062
|
+
const c = this.camera.screenToWorld({
|
|
6063
|
+
x: this.wrapper.clientWidth / 2,
|
|
6064
|
+
y: this.wrapper.clientHeight / 2
|
|
6065
|
+
});
|
|
6066
|
+
return { x: c.x - size.w / 2, y: c.y - size.h / 2 };
|
|
6067
|
+
}
|
|
6042
6068
|
removeLayer(id) {
|
|
6043
6069
|
this.historyRecorder.begin();
|
|
6044
6070
|
this.layerManager.removeLayer(id);
|
|
@@ -7088,14 +7114,11 @@ function computeSnapGuides(moving, targets, threshold) {
|
|
|
7088
7114
|
return { dx: xSnap?.delta ?? 0, dy: ySnap?.delta ?? 0, guides };
|
|
7089
7115
|
}
|
|
7090
7116
|
|
|
7091
|
-
// src/tools/select-
|
|
7117
|
+
// src/tools/select-overlay.ts
|
|
7092
7118
|
var HANDLE_SIZE = 8;
|
|
7093
|
-
var SNAP_PX = 6;
|
|
7094
7119
|
var HANDLE_HIT_PADDING2 = 4;
|
|
7095
7120
|
var SELECTION_PAD = 4;
|
|
7096
|
-
var MIN_ELEMENT_SIZE = 20;
|
|
7097
7121
|
var ROTATE_HANDLE_OFFSET = 24;
|
|
7098
|
-
var ROTATE_SNAP = Math.PI / 12;
|
|
7099
7122
|
var ROTATABLE_TYPES = /* @__PURE__ */ new Set(["note", "text", "image", "html", "shape", "stroke"]);
|
|
7100
7123
|
var HANDLE_CURSORS = {
|
|
7101
7124
|
nw: "nwse-resize",
|
|
@@ -7103,6 +7126,486 @@ var HANDLE_CURSORS = {
|
|
|
7103
7126
|
ne: "nesw-resize",
|
|
7104
7127
|
sw: "nesw-resize"
|
|
7105
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;
|
|
7106
7609
|
var SelectTool = class {
|
|
7107
7610
|
name = "select";
|
|
7108
7611
|
_selectedIds = [];
|
|
@@ -7138,7 +7641,7 @@ var SelectTool = class {
|
|
|
7138
7641
|
this.ctx?.requestRender();
|
|
7139
7642
|
}
|
|
7140
7643
|
selectAtPoint(world, ctx) {
|
|
7141
|
-
const hit =
|
|
7644
|
+
const hit = hitTest(world, ctx);
|
|
7142
7645
|
if (!hit) {
|
|
7143
7646
|
this.setSelectedIds([]);
|
|
7144
7647
|
return;
|
|
@@ -7182,19 +7685,19 @@ var SelectTool = class {
|
|
|
7182
7685
|
ctx.requestRender();
|
|
7183
7686
|
return;
|
|
7184
7687
|
}
|
|
7185
|
-
const lineHit =
|
|
7688
|
+
const lineHit = hitTestLineHandles(world, ctx, this._selectedIds);
|
|
7186
7689
|
if (lineHit) {
|
|
7187
7690
|
this.mode = { type: "line-handle", elementId: lineHit.elementId, fixed: lineHit.fixed };
|
|
7188
7691
|
ctx.requestRender();
|
|
7189
7692
|
return;
|
|
7190
7693
|
}
|
|
7191
|
-
const templateResizeHit =
|
|
7694
|
+
const templateResizeHit = hitTestTemplateResizeHandle(world, ctx, this._selectedIds);
|
|
7192
7695
|
if (templateResizeHit) {
|
|
7193
7696
|
this.mode = { type: "resizing-template", elementId: templateResizeHit };
|
|
7194
7697
|
ctx.requestRender();
|
|
7195
7698
|
return;
|
|
7196
7699
|
}
|
|
7197
|
-
const rotateHit =
|
|
7700
|
+
const rotateHit = hitTestRotateHandle(world, ctx, this._selectedIds);
|
|
7198
7701
|
if (rotateHit) {
|
|
7199
7702
|
const el = ctx.store.getById(rotateHit.elementId);
|
|
7200
7703
|
const layout = el ? this.getOverlayLayout(el, ctx.camera.zoom) : null;
|
|
@@ -7210,7 +7713,7 @@ var SelectTool = class {
|
|
|
7210
7713
|
return;
|
|
7211
7714
|
}
|
|
7212
7715
|
}
|
|
7213
|
-
const resizeHit =
|
|
7716
|
+
const resizeHit = hitTestResizeHandle(world, ctx, this._selectedIds);
|
|
7214
7717
|
if (resizeHit) {
|
|
7215
7718
|
const el = ctx.store.getById(resizeHit.elementId);
|
|
7216
7719
|
if (el && "size" in el) {
|
|
@@ -7226,7 +7729,7 @@ var SelectTool = class {
|
|
|
7226
7729
|
}
|
|
7227
7730
|
this.pendingSingleSelectId = null;
|
|
7228
7731
|
this.hasDragged = false;
|
|
7229
|
-
const hit =
|
|
7732
|
+
const hit = hitTest(world, ctx);
|
|
7230
7733
|
if (hit) {
|
|
7231
7734
|
const all = ctx.store.getAll();
|
|
7232
7735
|
const alreadySelected = this._selectedIds.includes(hit.id);
|
|
@@ -7364,7 +7867,7 @@ var SelectTool = class {
|
|
|
7364
7867
|
if (this.mode.type === "marquee") {
|
|
7365
7868
|
const rect = this.getMarqueeRect();
|
|
7366
7869
|
if (rect) {
|
|
7367
|
-
this.setSelectedIds(expandToGroups(
|
|
7870
|
+
this.setSelectedIds(expandToGroups(findElementsInRect(rect, ctx), ctx.store.getAll()));
|
|
7368
7871
|
}
|
|
7369
7872
|
ctx.requestRender();
|
|
7370
7873
|
}
|
|
@@ -7391,8 +7894,16 @@ var SelectTool = class {
|
|
|
7391
7894
|
this.setHovered(hoverId, ctx);
|
|
7392
7895
|
}
|
|
7393
7896
|
renderOverlay(canvasCtx) {
|
|
7394
|
-
this.
|
|
7395
|
-
|
|
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
|
+
});
|
|
7396
7907
|
if (this.mode.type === "arrow-handle" && this.ctx) {
|
|
7397
7908
|
const target = getArrowHandleDragTarget(
|
|
7398
7909
|
this.mode.handle,
|
|
@@ -7426,32 +7937,13 @@ var SelectTool = class {
|
|
|
7426
7937
|
}
|
|
7427
7938
|
}
|
|
7428
7939
|
}
|
|
7429
|
-
this.
|
|
7430
|
-
|
|
7431
|
-
|
|
7432
|
-
|
|
7433
|
-
|
|
7434
|
-
|
|
7435
|
-
|
|
7436
|
-
canvasCtx.strokeStyle = "#FF4081";
|
|
7437
|
-
canvasCtx.lineWidth = 1 / zoom;
|
|
7438
|
-
canvasCtx.setLineDash([]);
|
|
7439
|
-
for (const g of this.activeGuides) {
|
|
7440
|
-
canvasCtx.beginPath();
|
|
7441
|
-
if (g.axis === "x") {
|
|
7442
|
-
const y0 = rect ? rect.y : this.currentWorld.y - 1e5;
|
|
7443
|
-
const y1 = rect ? rect.y + rect.h : this.currentWorld.y + 1e5;
|
|
7444
|
-
canvasCtx.moveTo(g.position, y0);
|
|
7445
|
-
canvasCtx.lineTo(g.position, y1);
|
|
7446
|
-
} else {
|
|
7447
|
-
const x0 = rect ? rect.x : this.currentWorld.x - 1e5;
|
|
7448
|
-
const x1 = rect ? rect.x + rect.w : this.currentWorld.x + 1e5;
|
|
7449
|
-
canvasCtx.moveTo(x0, g.position);
|
|
7450
|
-
canvasCtx.lineTo(x1, g.position);
|
|
7451
|
-
}
|
|
7452
|
-
canvasCtx.stroke();
|
|
7453
|
-
}
|
|
7454
|
-
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
|
+
});
|
|
7455
7947
|
}
|
|
7456
7948
|
updateArrowsBoundTo(ids, ctx) {
|
|
7457
7949
|
updateArrowsBoundToElements(ids, ctx.store);
|
|
@@ -7477,25 +7969,25 @@ var SelectTool = class {
|
|
|
7477
7969
|
ctx.setCursor?.(getArrowHandleCursor(arrowHit.handle, false));
|
|
7478
7970
|
return null;
|
|
7479
7971
|
}
|
|
7480
|
-
if (
|
|
7972
|
+
if (hitTestLineHandles(world, ctx, this._selectedIds)) {
|
|
7481
7973
|
ctx.setCursor?.("grab");
|
|
7482
7974
|
return null;
|
|
7483
7975
|
}
|
|
7484
|
-
const templateResizeHit =
|
|
7976
|
+
const templateResizeHit = hitTestTemplateResizeHandle(world, ctx, this._selectedIds);
|
|
7485
7977
|
if (templateResizeHit) {
|
|
7486
7978
|
ctx.setCursor?.("nwse-resize");
|
|
7487
7979
|
return null;
|
|
7488
7980
|
}
|
|
7489
|
-
if (
|
|
7981
|
+
if (hitTestRotateHandle(world, ctx, this._selectedIds)) {
|
|
7490
7982
|
ctx.setCursor?.("grab");
|
|
7491
7983
|
return null;
|
|
7492
7984
|
}
|
|
7493
|
-
const resizeHit =
|
|
7985
|
+
const resizeHit = hitTestResizeHandle(world, ctx, this._selectedIds);
|
|
7494
7986
|
if (resizeHit) {
|
|
7495
7987
|
ctx.setCursor?.(HANDLE_CURSORS[resizeHit.handle]);
|
|
7496
7988
|
return null;
|
|
7497
7989
|
}
|
|
7498
|
-
const hit =
|
|
7990
|
+
const hit = hitTest(world, ctx);
|
|
7499
7991
|
ctx.setCursor?.(hit ? "move" : "default");
|
|
7500
7992
|
return hit ? hit.id : null;
|
|
7501
7993
|
}
|
|
@@ -7509,421 +8001,43 @@ var SelectTool = class {
|
|
|
7509
8001
|
const el = ctx.store.getById(this.mode.elementId);
|
|
7510
8002
|
if (!el || !("size" in el) || el.locked) return;
|
|
7511
8003
|
const angle = el.rotation ?? 0;
|
|
7512
|
-
|
|
7513
|
-
|
|
7514
|
-
|
|
7515
|
-
|
|
7516
|
-
|
|
7517
|
-
|
|
7518
|
-
|
|
7519
|
-
|
|
7520
|
-
|
|
7521
|
-
|
|
7522
|
-
|
|
7523
|
-
|
|
7524
|
-
|
|
7525
|
-
|
|
7526
|
-
|
|
7527
|
-
x += dx;
|
|
7528
|
-
w -= dx;
|
|
7529
|
-
h += dy;
|
|
7530
|
-
break;
|
|
7531
|
-
case "ne":
|
|
7532
|
-
y += dy;
|
|
7533
|
-
w += dx;
|
|
7534
|
-
h -= dy;
|
|
7535
|
-
break;
|
|
7536
|
-
case "nw":
|
|
7537
|
-
x += dx;
|
|
7538
|
-
y += dy;
|
|
7539
|
-
w -= dx;
|
|
7540
|
-
h -= dy;
|
|
7541
|
-
break;
|
|
7542
|
-
}
|
|
7543
|
-
if (shiftKey && this.resizeAspectRatio > 0) {
|
|
7544
|
-
const absDw = Math.abs(w - el.size.w);
|
|
7545
|
-
const absDh = Math.abs(h - el.size.h);
|
|
7546
|
-
if (absDw >= absDh) {
|
|
7547
|
-
h = w / this.resizeAspectRatio;
|
|
7548
|
-
} else {
|
|
7549
|
-
w = h * this.resizeAspectRatio;
|
|
7550
|
-
}
|
|
7551
|
-
if (handle === "nw" || handle === "sw") {
|
|
7552
|
-
x = el.position.x + el.size.w - w;
|
|
7553
|
-
}
|
|
7554
|
-
if (handle === "nw" || handle === "ne") {
|
|
7555
|
-
y = el.position.y + el.size.h - h;
|
|
7556
|
-
}
|
|
7557
|
-
}
|
|
7558
|
-
if (w < MIN_ELEMENT_SIZE) {
|
|
7559
|
-
if (handle === "nw" || handle === "sw") x = el.position.x + el.size.w - MIN_ELEMENT_SIZE;
|
|
7560
|
-
w = MIN_ELEMENT_SIZE;
|
|
7561
|
-
}
|
|
7562
|
-
if (h < MIN_ELEMENT_SIZE) {
|
|
7563
|
-
if (handle === "nw" || handle === "ne") y = el.position.y + el.size.h - MIN_ELEMENT_SIZE;
|
|
7564
|
-
h = MIN_ELEMENT_SIZE;
|
|
7565
|
-
}
|
|
7566
|
-
ctx.store.update(this.mode.elementId, {
|
|
7567
|
-
position: { x, y },
|
|
7568
|
-
size: { w, h }
|
|
7569
|
-
});
|
|
7570
|
-
this.updateArrowsBoundTo([this.mode.elementId], ctx);
|
|
7571
|
-
ctx.requestRender();
|
|
7572
|
-
}
|
|
7573
|
-
anchorOffset(handle, w, h) {
|
|
7574
|
-
switch (handle) {
|
|
7575
|
-
case "se":
|
|
7576
|
-
return { x: -w / 2, y: -h / 2 };
|
|
7577
|
-
case "sw":
|
|
7578
|
-
return { x: w / 2, y: -h / 2 };
|
|
7579
|
-
case "ne":
|
|
7580
|
-
return { x: -w / 2, y: h / 2 };
|
|
7581
|
-
case "nw":
|
|
7582
|
-
return { x: w / 2, y: h / 2 };
|
|
7583
|
-
default:
|
|
7584
|
-
return { x: 0, y: 0 };
|
|
7585
|
-
}
|
|
7586
|
-
}
|
|
7587
|
-
handleRotatedResize(world, el, angle, ctx, shiftKey) {
|
|
7588
|
-
if (this.mode.type !== "resizing") return;
|
|
7589
|
-
const { handle } = this.mode;
|
|
7590
|
-
const wdx = world.x - this.lastWorld.x;
|
|
7591
|
-
const wdy = world.y - this.lastWorld.y;
|
|
7592
|
-
this.lastWorld = world;
|
|
7593
|
-
const cosN = Math.cos(-angle);
|
|
7594
|
-
const sinN = Math.sin(-angle);
|
|
7595
|
-
const ldx = wdx * cosN - wdy * sinN;
|
|
7596
|
-
const ldy = wdx * sinN + wdy * cosN;
|
|
7597
|
-
let w = el.size.w;
|
|
7598
|
-
let h = el.size.h;
|
|
7599
|
-
switch (handle) {
|
|
7600
|
-
case "se":
|
|
7601
|
-
w += ldx;
|
|
7602
|
-
h += ldy;
|
|
7603
|
-
break;
|
|
7604
|
-
case "sw":
|
|
7605
|
-
w -= ldx;
|
|
7606
|
-
h += ldy;
|
|
7607
|
-
break;
|
|
7608
|
-
case "ne":
|
|
7609
|
-
w += ldx;
|
|
7610
|
-
h -= ldy;
|
|
7611
|
-
break;
|
|
7612
|
-
case "nw":
|
|
7613
|
-
w -= ldx;
|
|
7614
|
-
h -= ldy;
|
|
7615
|
-
break;
|
|
7616
|
-
}
|
|
7617
|
-
if (shiftKey && this.resizeAspectRatio > 0) {
|
|
7618
|
-
const absDw = Math.abs(w - el.size.w);
|
|
7619
|
-
const absDh = Math.abs(h - el.size.h);
|
|
7620
|
-
if (absDw >= absDh) h = w / this.resizeAspectRatio;
|
|
7621
|
-
else w = h * this.resizeAspectRatio;
|
|
7622
|
-
}
|
|
7623
|
-
w = Math.max(w, MIN_ELEMENT_SIZE);
|
|
7624
|
-
h = Math.max(h, MIN_ELEMENT_SIZE);
|
|
7625
|
-
const oldCenter = { x: el.position.x + el.size.w / 2, y: el.position.y + el.size.h / 2 };
|
|
7626
|
-
const oldAnchorLocal = this.anchorOffset(handle, el.size.w, el.size.h);
|
|
7627
|
-
const anchorWorld = rotatePoint(
|
|
7628
|
-
{ x: oldCenter.x + oldAnchorLocal.x, y: oldCenter.y + oldAnchorLocal.y },
|
|
7629
|
-
oldCenter,
|
|
7630
|
-
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
|
|
7631
8019
|
);
|
|
7632
|
-
|
|
7633
|
-
|
|
7634
|
-
const sin = Math.sin(angle);
|
|
7635
|
-
const rotatedAnchor = {
|
|
7636
|
-
x: newAnchorLocal.x * cos - newAnchorLocal.y * sin,
|
|
7637
|
-
y: newAnchorLocal.x * sin + newAnchorLocal.y * cos
|
|
7638
|
-
};
|
|
7639
|
-
const newCenter = { x: anchorWorld.x - rotatedAnchor.x, y: anchorWorld.y - rotatedAnchor.y };
|
|
7640
|
-
const position = { x: newCenter.x - w / 2, y: newCenter.y - h / 2 };
|
|
7641
|
-
ctx.store.update(this.mode.elementId, { position, size: { w, h } });
|
|
8020
|
+
this.lastWorld = world;
|
|
8021
|
+
ctx.store.update(this.mode.elementId, patch);
|
|
7642
8022
|
this.updateArrowsBoundTo([this.mode.elementId], ctx);
|
|
7643
8023
|
ctx.requestRender();
|
|
7644
8024
|
}
|
|
7645
|
-
hitTestResizeHandle(world, ctx) {
|
|
7646
|
-
if (this._selectedIds.length === 0) return null;
|
|
7647
|
-
const zoom = ctx.camera.zoom;
|
|
7648
|
-
const handleHalf = (HANDLE_SIZE / 2 + HANDLE_HIT_PADDING2) / zoom;
|
|
7649
|
-
for (const id of this._selectedIds) {
|
|
7650
|
-
const el = ctx.store.getById(id);
|
|
7651
|
-
if (!el || !("size" in el)) continue;
|
|
7652
|
-
if (el.locked) continue;
|
|
7653
|
-
if (el.type === "shape" && el.shape === "line") continue;
|
|
7654
|
-
const layout = this.getOverlayLayout(el, zoom);
|
|
7655
|
-
if (!layout) continue;
|
|
7656
|
-
for (const [handle, pos] of layout.corners) {
|
|
7657
|
-
if (Math.abs(world.x - pos.x) <= handleHalf && Math.abs(world.y - pos.y) <= handleHalf) {
|
|
7658
|
-
return { elementId: id, handle };
|
|
7659
|
-
}
|
|
7660
|
-
}
|
|
7661
|
-
}
|
|
7662
|
-
return null;
|
|
7663
|
-
}
|
|
7664
|
-
hitTestRotateHandle(world, ctx) {
|
|
7665
|
-
if (this._selectedIds.length !== 1) return null;
|
|
7666
|
-
const id = this._selectedIds[0];
|
|
7667
|
-
if (!id) return null;
|
|
7668
|
-
const el = ctx.store.getById(id);
|
|
7669
|
-
if (!el || el.locked || !ROTATABLE_TYPES.has(el.type)) return null;
|
|
7670
|
-
const layout = this.getOverlayLayout(el, ctx.camera.zoom);
|
|
7671
|
-
if (!layout) return null;
|
|
7672
|
-
const r = (HANDLE_SIZE / 2 + HANDLE_HIT_PADDING2) / ctx.camera.zoom;
|
|
7673
|
-
const dx = world.x - layout.rotateHandle.x;
|
|
7674
|
-
const dy = world.y - layout.rotateHandle.y;
|
|
7675
|
-
return dx * dx + dy * dy <= r * r ? { elementId: id } : null;
|
|
7676
|
-
}
|
|
7677
|
-
hitTestLineHandles(world, ctx) {
|
|
7678
|
-
if (this._selectedIds.length === 0) return null;
|
|
7679
|
-
const zoom = ctx.camera.zoom;
|
|
7680
|
-
const r = (HANDLE_SIZE / 2 + HANDLE_HIT_PADDING2) / zoom;
|
|
7681
|
-
const r2 = r * r;
|
|
7682
|
-
for (const id of this._selectedIds) {
|
|
7683
|
-
const el = ctx.store.getById(id);
|
|
7684
|
-
if (!el || el.type !== "shape" || el.shape !== "line") continue;
|
|
7685
|
-
const [a, b] = lineEndpoints(el);
|
|
7686
|
-
if ((world.x - a.x) ** 2 + (world.y - a.y) ** 2 <= r2) return { elementId: id, fixed: b };
|
|
7687
|
-
if ((world.x - b.x) ** 2 + (world.y - b.y) ** 2 <= r2) return { elementId: id, fixed: a };
|
|
7688
|
-
}
|
|
7689
|
-
return null;
|
|
7690
|
-
}
|
|
7691
|
-
getHandlePositions(bounds) {
|
|
7692
|
-
return [
|
|
7693
|
-
["nw", { x: bounds.x, y: bounds.y }],
|
|
7694
|
-
["ne", { x: bounds.x + bounds.w, y: bounds.y }],
|
|
7695
|
-
["sw", { x: bounds.x, y: bounds.y + bounds.h }],
|
|
7696
|
-
["se", { x: bounds.x + bounds.w, y: bounds.y + bounds.h }]
|
|
7697
|
-
];
|
|
7698
|
-
}
|
|
7699
8025
|
getOverlayLayout(el, zoom) {
|
|
7700
|
-
|
|
7701
|
-
if (!bounds) return null;
|
|
7702
|
-
const angle = el.rotation ?? 0;
|
|
7703
|
-
const pad = SELECTION_PAD / zoom;
|
|
7704
|
-
const center2 = { x: bounds.x + bounds.w / 2, y: bounds.y + bounds.h / 2 };
|
|
7705
|
-
const raw = [
|
|
7706
|
-
["nw", { x: bounds.x - pad, y: bounds.y - pad }],
|
|
7707
|
-
["ne", { x: bounds.x + bounds.w + pad, y: bounds.y - pad }],
|
|
7708
|
-
["sw", { x: bounds.x - pad, y: bounds.y + bounds.h + pad }],
|
|
7709
|
-
["se", { x: bounds.x + bounds.w + pad, y: bounds.y + bounds.h + pad }]
|
|
7710
|
-
];
|
|
7711
|
-
const corners = raw.map(
|
|
7712
|
-
([h, p]) => [h, rotatePoint(p, center2, angle)]
|
|
7713
|
-
);
|
|
7714
|
-
const topMid = { x: center2.x, y: bounds.y - pad - ROTATE_HANDLE_OFFSET / zoom };
|
|
7715
|
-
const rotateHandle = rotatePoint(topMid, center2, angle);
|
|
7716
|
-
return { center: center2, corners, rotateHandle, angle };
|
|
7717
|
-
}
|
|
7718
|
-
topMidpoint(layout) {
|
|
7719
|
-
const nw = layout.corners.find(([h]) => h === "nw")?.[1] ?? { x: 0, y: 0 };
|
|
7720
|
-
const ne = layout.corners.find(([h]) => h === "ne")?.[1] ?? { x: 0, y: 0 };
|
|
7721
|
-
return { x: (nw.x + ne.x) / 2, y: (nw.y + ne.y) / 2 };
|
|
7722
|
-
}
|
|
7723
|
-
renderMarquee(canvasCtx) {
|
|
7724
|
-
if (this.mode.type !== "marquee") return;
|
|
7725
|
-
const rect = this.getMarqueeRect();
|
|
7726
|
-
if (!rect) return;
|
|
7727
|
-
canvasCtx.save();
|
|
7728
|
-
canvasCtx.strokeStyle = "#2196F3";
|
|
7729
|
-
canvasCtx.fillStyle = "rgba(33, 150, 243, 0.08)";
|
|
7730
|
-
canvasCtx.lineWidth = 1;
|
|
7731
|
-
canvasCtx.setLineDash([4, 4]);
|
|
7732
|
-
canvasCtx.strokeRect(rect.x, rect.y, rect.w, rect.h);
|
|
7733
|
-
canvasCtx.fillRect(rect.x, rect.y, rect.w, rect.h);
|
|
7734
|
-
canvasCtx.restore();
|
|
7735
|
-
}
|
|
7736
|
-
renderSelectionBoxes(canvasCtx) {
|
|
7737
|
-
if (this._selectedIds.length === 0 || !this.ctx) return;
|
|
7738
|
-
const zoom = this.ctx.camera.zoom;
|
|
7739
|
-
const handleWorldSize = HANDLE_SIZE / zoom;
|
|
7740
|
-
canvasCtx.save();
|
|
7741
|
-
canvasCtx.strokeStyle = "#2196F3";
|
|
7742
|
-
canvasCtx.lineWidth = 1.5 / zoom;
|
|
7743
|
-
canvasCtx.setLineDash([4 / zoom, 4 / zoom]);
|
|
7744
|
-
for (const id of this._selectedIds) {
|
|
7745
|
-
const el = this.ctx.store.getById(id);
|
|
7746
|
-
if (!el) continue;
|
|
7747
|
-
if (el.type === "arrow") {
|
|
7748
|
-
renderArrowHandles(canvasCtx, el, zoom);
|
|
7749
|
-
this.renderBindingHighlights(canvasCtx, el, zoom);
|
|
7750
|
-
continue;
|
|
7751
|
-
}
|
|
7752
|
-
if (el.type === "shape" && el.shape === "line") {
|
|
7753
|
-
canvasCtx.setLineDash([]);
|
|
7754
|
-
canvasCtx.fillStyle = "#ffffff";
|
|
7755
|
-
const r = handleWorldSize / 2;
|
|
7756
|
-
for (const p of lineEndpoints(el)) {
|
|
7757
|
-
canvasCtx.beginPath();
|
|
7758
|
-
canvasCtx.arc(p.x, p.y, r, 0, Math.PI * 2);
|
|
7759
|
-
canvasCtx.fill();
|
|
7760
|
-
canvasCtx.stroke();
|
|
7761
|
-
}
|
|
7762
|
-
canvasCtx.setLineDash([4 / zoom, 4 / zoom]);
|
|
7763
|
-
continue;
|
|
7764
|
-
}
|
|
7765
|
-
const bounds = getElementBounds(el);
|
|
7766
|
-
if (!bounds) continue;
|
|
7767
|
-
const layout = this.getOverlayLayout(el, zoom);
|
|
7768
|
-
if (!layout) continue;
|
|
7769
|
-
const pad = SELECTION_PAD / zoom;
|
|
7770
|
-
if (layout.angle === 0) {
|
|
7771
|
-
canvasCtx.strokeRect(
|
|
7772
|
-
bounds.x - pad,
|
|
7773
|
-
bounds.y - pad,
|
|
7774
|
-
bounds.w + pad * 2,
|
|
7775
|
-
bounds.h + pad * 2
|
|
7776
|
-
);
|
|
7777
|
-
} else {
|
|
7778
|
-
const ordered = ["nw", "ne", "se", "sw"].map((h) => layout.corners.find(([c]) => c === h)?.[1]).filter((p) => !!p);
|
|
7779
|
-
const [p0, ...others] = ordered;
|
|
7780
|
-
if (p0) {
|
|
7781
|
-
canvasCtx.beginPath();
|
|
7782
|
-
canvasCtx.moveTo(p0.x, p0.y);
|
|
7783
|
-
for (const p of others) canvasCtx.lineTo(p.x, p.y);
|
|
7784
|
-
canvasCtx.closePath();
|
|
7785
|
-
canvasCtx.stroke();
|
|
7786
|
-
}
|
|
7787
|
-
}
|
|
7788
|
-
if (!el.locked) {
|
|
7789
|
-
if ("size" in el) {
|
|
7790
|
-
canvasCtx.setLineDash([]);
|
|
7791
|
-
canvasCtx.fillStyle = "#ffffff";
|
|
7792
|
-
const corners = layout.angle === 0 ? this.getHandlePositions(bounds) : layout.corners;
|
|
7793
|
-
for (const [, pos] of corners) {
|
|
7794
|
-
canvasCtx.fillRect(
|
|
7795
|
-
pos.x - handleWorldSize / 2,
|
|
7796
|
-
pos.y - handleWorldSize / 2,
|
|
7797
|
-
handleWorldSize,
|
|
7798
|
-
handleWorldSize
|
|
7799
|
-
);
|
|
7800
|
-
canvasCtx.strokeRect(
|
|
7801
|
-
pos.x - handleWorldSize / 2,
|
|
7802
|
-
pos.y - handleWorldSize / 2,
|
|
7803
|
-
handleWorldSize,
|
|
7804
|
-
handleWorldSize
|
|
7805
|
-
);
|
|
7806
|
-
}
|
|
7807
|
-
canvasCtx.setLineDash([4 / zoom, 4 / zoom]);
|
|
7808
|
-
} else if (el.type === "template") {
|
|
7809
|
-
canvasCtx.setLineDash([]);
|
|
7810
|
-
canvasCtx.fillStyle = "#ffffff";
|
|
7811
|
-
const hx = bounds.x + bounds.w;
|
|
7812
|
-
const hy = bounds.y + bounds.h;
|
|
7813
|
-
canvasCtx.fillRect(
|
|
7814
|
-
hx - handleWorldSize / 2,
|
|
7815
|
-
hy - handleWorldSize / 2,
|
|
7816
|
-
handleWorldSize,
|
|
7817
|
-
handleWorldSize
|
|
7818
|
-
);
|
|
7819
|
-
canvasCtx.strokeRect(
|
|
7820
|
-
hx - handleWorldSize / 2,
|
|
7821
|
-
hy - handleWorldSize / 2,
|
|
7822
|
-
handleWorldSize,
|
|
7823
|
-
handleWorldSize
|
|
7824
|
-
);
|
|
7825
|
-
canvasCtx.setLineDash([4 / zoom, 4 / zoom]);
|
|
7826
|
-
}
|
|
7827
|
-
if (this._selectedIds.length === 1 && ROTATABLE_TYPES.has(el.type)) {
|
|
7828
|
-
const stemStart = this.topMidpoint(layout);
|
|
7829
|
-
const stemEnd = layout.rotateHandle;
|
|
7830
|
-
canvasCtx.beginPath();
|
|
7831
|
-
canvasCtx.moveTo(stemStart.x, stemStart.y);
|
|
7832
|
-
canvasCtx.lineTo(stemEnd.x, stemEnd.y);
|
|
7833
|
-
canvasCtx.stroke();
|
|
7834
|
-
canvasCtx.setLineDash([]);
|
|
7835
|
-
canvasCtx.fillStyle = "#ffffff";
|
|
7836
|
-
canvasCtx.beginPath();
|
|
7837
|
-
canvasCtx.arc(stemEnd.x, stemEnd.y, handleWorldSize / 2, 0, Math.PI * 2);
|
|
7838
|
-
canvasCtx.fill();
|
|
7839
|
-
canvasCtx.stroke();
|
|
7840
|
-
canvasCtx.setLineDash([4 / zoom, 4 / zoom]);
|
|
7841
|
-
}
|
|
7842
|
-
}
|
|
7843
|
-
if (el.locked) {
|
|
7844
|
-
const ne = layout.corners.find(([h]) => h === "ne")?.[1];
|
|
7845
|
-
if (ne) this.drawLockBadge(canvasCtx, ne, zoom);
|
|
7846
|
-
}
|
|
7847
|
-
}
|
|
7848
|
-
canvasCtx.restore();
|
|
7849
|
-
}
|
|
7850
|
-
drawLockBadge(ctx, at, zoom) {
|
|
7851
|
-
const r = 9 / zoom;
|
|
7852
|
-
ctx.save();
|
|
7853
|
-
ctx.setLineDash([]);
|
|
7854
|
-
ctx.beginPath();
|
|
7855
|
-
ctx.arc(at.x, at.y, r, 0, Math.PI * 2);
|
|
7856
|
-
ctx.fillStyle = "#ffffff";
|
|
7857
|
-
ctx.fill();
|
|
7858
|
-
ctx.strokeStyle = "#2196F3";
|
|
7859
|
-
ctx.lineWidth = 1.5 / zoom;
|
|
7860
|
-
ctx.stroke();
|
|
7861
|
-
const bw = 8 / zoom;
|
|
7862
|
-
const bh = 6 / zoom;
|
|
7863
|
-
ctx.fillStyle = "#2196F3";
|
|
7864
|
-
ctx.fillRect(at.x - bw / 2, at.y - bh / 2 + 1 / zoom, bw, bh);
|
|
7865
|
-
ctx.beginPath();
|
|
7866
|
-
ctx.arc(at.x, at.y - bh / 2 + 1 / zoom, 2.5 / zoom, Math.PI, 0);
|
|
7867
|
-
ctx.lineWidth = 1.4 / zoom;
|
|
7868
|
-
ctx.stroke();
|
|
7869
|
-
ctx.restore();
|
|
7870
|
-
}
|
|
7871
|
-
renderBindingHighlights(canvasCtx, arrow, zoom) {
|
|
7872
|
-
if (!this.ctx) return;
|
|
7873
|
-
if (!arrow.fromBinding && !arrow.toBinding) return;
|
|
7874
|
-
const pad = SELECTION_PAD / zoom;
|
|
7875
|
-
canvasCtx.save();
|
|
7876
|
-
canvasCtx.strokeStyle = "#2196F3";
|
|
7877
|
-
canvasCtx.lineWidth = 2 / zoom;
|
|
7878
|
-
canvasCtx.setLineDash([]);
|
|
7879
|
-
const drawn = /* @__PURE__ */ new Set();
|
|
7880
|
-
for (const binding of [arrow.fromBinding, arrow.toBinding]) {
|
|
7881
|
-
if (!binding || drawn.has(binding.elementId)) continue;
|
|
7882
|
-
drawn.add(binding.elementId);
|
|
7883
|
-
const target = this.ctx.store.getById(binding.elementId);
|
|
7884
|
-
if (!target) continue;
|
|
7885
|
-
const bounds = getElementBounds(target);
|
|
7886
|
-
if (!bounds) continue;
|
|
7887
|
-
canvasCtx.strokeRect(bounds.x - pad, bounds.y - pad, bounds.w + pad * 2, bounds.h + pad * 2);
|
|
7888
|
-
}
|
|
7889
|
-
canvasCtx.restore();
|
|
7890
|
-
}
|
|
7891
|
-
hitTestTemplateResizeHandle(world, ctx) {
|
|
7892
|
-
if (this._selectedIds.length === 0) return null;
|
|
7893
|
-
const zoom = ctx.camera.zoom;
|
|
7894
|
-
const handleHalf = (HANDLE_SIZE / 2 + HANDLE_HIT_PADDING2) / zoom;
|
|
7895
|
-
for (const id of this._selectedIds) {
|
|
7896
|
-
const el = ctx.store.getById(id);
|
|
7897
|
-
if (!el || el.type !== "template") continue;
|
|
7898
|
-
const bounds = getElementBounds(el);
|
|
7899
|
-
if (!bounds) continue;
|
|
7900
|
-
const hx = bounds.x + bounds.w;
|
|
7901
|
-
const hy = bounds.y + bounds.h;
|
|
7902
|
-
if (Math.abs(world.x - hx) <= handleHalf && Math.abs(world.y - hy) <= handleHalf) {
|
|
7903
|
-
return id;
|
|
7904
|
-
}
|
|
7905
|
-
}
|
|
7906
|
-
return null;
|
|
8026
|
+
return getOverlayLayout(el, zoom);
|
|
7907
8027
|
}
|
|
7908
8028
|
handleTemplateResize(world, ctx) {
|
|
7909
8029
|
if (this.mode.type !== "resizing-template") return;
|
|
7910
8030
|
const el = ctx.store.getById(this.mode.elementId);
|
|
7911
8031
|
if (!el || el.type !== "template" || el.locked) return;
|
|
7912
|
-
const
|
|
7913
|
-
|
|
7914
|
-
|
|
7915
|
-
|
|
7916
|
-
|
|
7917
|
-
|
|
7918
|
-
|
|
7919
|
-
|
|
7920
|
-
|
|
7921
|
-
if (el.feetPerCell != null && ctx.gridSize && ctx.gridSize > 0) {
|
|
7922
|
-
const snapUnit = ctx.gridType === "hex" ? Math.sqrt(3) * ctx.gridSize : ctx.gridSize;
|
|
7923
|
-
updates.radiusFeet = newRadius / snapUnit * el.feetPerCell;
|
|
7924
|
-
}
|
|
7925
|
-
ctx.store.update(this.mode.elementId, updates);
|
|
7926
|
-
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
|
+
}
|
|
7927
8041
|
}
|
|
7928
8042
|
getMarqueeRect() {
|
|
7929
8043
|
if (this.mode.type !== "marquee") return null;
|
|
@@ -7936,65 +8050,6 @@ var SelectTool = class {
|
|
|
7936
8050
|
if (w === 0 && h === 0) return null;
|
|
7937
8051
|
return { x, y, w, h };
|
|
7938
8052
|
}
|
|
7939
|
-
findElementsInRect(marquee, ctx) {
|
|
7940
|
-
const candidates = ctx.store.queryRect(marquee);
|
|
7941
|
-
const ids = [];
|
|
7942
|
-
for (const el of candidates) {
|
|
7943
|
-
if (ctx.isLayerVisible && !ctx.isLayerVisible(el.layerId)) continue;
|
|
7944
|
-
if (ctx.isLayerLocked && ctx.isLayerLocked(el.layerId)) continue;
|
|
7945
|
-
if (el.type === "grid") continue;
|
|
7946
|
-
const bounds = getElementBounds(el);
|
|
7947
|
-
if (bounds && this.rectsOverlap(marquee, rotatedAABB(bounds, el.rotation ?? 0))) {
|
|
7948
|
-
ids.push(el.id);
|
|
7949
|
-
}
|
|
7950
|
-
}
|
|
7951
|
-
return ids;
|
|
7952
|
-
}
|
|
7953
|
-
rectsOverlap(a, b) {
|
|
7954
|
-
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;
|
|
7955
|
-
}
|
|
7956
|
-
hitTest(world, ctx) {
|
|
7957
|
-
const r = 10;
|
|
7958
|
-
const candidates = ctx.store.queryRect({ x: world.x - r, y: world.y - r, w: r * 2, h: r * 2 }).reverse();
|
|
7959
|
-
for (const el of candidates) {
|
|
7960
|
-
if (ctx.isLayerVisible && !ctx.isLayerVisible(el.layerId)) continue;
|
|
7961
|
-
if (ctx.isLayerLocked && ctx.isLayerLocked(el.layerId)) continue;
|
|
7962
|
-
if (el.type === "grid") continue;
|
|
7963
|
-
if (this.isInsideBounds(world, el)) return el;
|
|
7964
|
-
}
|
|
7965
|
-
return null;
|
|
7966
|
-
}
|
|
7967
|
-
isInsideBounds(point, el) {
|
|
7968
|
-
if (el.type === "grid") return false;
|
|
7969
|
-
const angle = el.rotation ?? 0;
|
|
7970
|
-
if (angle !== 0) {
|
|
7971
|
-
const b = getElementBounds(el);
|
|
7972
|
-
if (b) {
|
|
7973
|
-
point = rotatePoint(point, { x: b.x + b.w / 2, y: b.y + b.h / 2 }, -angle);
|
|
7974
|
-
}
|
|
7975
|
-
}
|
|
7976
|
-
if (el.type === "shape" && el.shape === "line") {
|
|
7977
|
-
const [a, b] = lineEndpoints(el);
|
|
7978
|
-
const threshold = Math.max(el.strokeWidth / 2, 6);
|
|
7979
|
-
return distSqToSegment(point, a, b) <= threshold * threshold;
|
|
7980
|
-
}
|
|
7981
|
-
if ("size" in el) {
|
|
7982
|
-
const s = el.size;
|
|
7983
|
-
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;
|
|
7984
|
-
}
|
|
7985
|
-
if (el.type === "stroke") {
|
|
7986
|
-
return hitTestStroke(el, point, 10);
|
|
7987
|
-
}
|
|
7988
|
-
if (el.type === "arrow") {
|
|
7989
|
-
return isNearBezier(point, el.from, el.to, el.bend, 10);
|
|
7990
|
-
}
|
|
7991
|
-
if (el.type === "template") {
|
|
7992
|
-
const bounds = getElementBounds(el);
|
|
7993
|
-
if (!bounds) return false;
|
|
7994
|
-
return point.x >= bounds.x && point.x <= bounds.x + bounds.w && point.y >= bounds.y && point.y <= bounds.y + bounds.h;
|
|
7995
|
-
}
|
|
7996
|
-
return false;
|
|
7997
|
-
}
|
|
7998
8053
|
};
|
|
7999
8054
|
|
|
8000
8055
|
// src/tools/arrow-tool.ts
|
|
@@ -8840,7 +8895,7 @@ var TemplateTool = class {
|
|
|
8840
8895
|
};
|
|
8841
8896
|
|
|
8842
8897
|
// src/index.ts
|
|
8843
|
-
var VERSION = "0.
|
|
8898
|
+
var VERSION = "0.38.0";
|
|
8844
8899
|
export {
|
|
8845
8900
|
ArrowTool,
|
|
8846
8901
|
AutoSave,
|