Add marquee selection: click and drag on empty space to select multiple symbols
- Blue dashed rectangle drawn while dragging - Selects all visible symbols whose bounding box intersects the marquee - Respects hidden symbols and hidden groups Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
3c532b8cfe
commit
18c0e03287
@ -548,6 +548,31 @@ function onMousemove(e: MouseEvent) {
|
|||||||
}
|
}
|
||||||
layout.markDirty();
|
layout.markDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (dragState.type === 'marquee') {
|
||||||
|
const pos = screenToCanvas(e.clientX, e.clientY);
|
||||||
|
if (!dragState.dragActivated) {
|
||||||
|
if (!pastDragThreshold(pos.x, pos.y, dragState.startX!, dragState.startY!, DRAG_THRESHOLD)) return;
|
||||||
|
dragState.dragActivated = true;
|
||||||
|
}
|
||||||
|
const x1 = Math.min(dragState.startX!, pos.x);
|
||||||
|
const y1 = Math.min(dragState.startY!, pos.y);
|
||||||
|
const x2 = Math.max(dragState.startX!, pos.x);
|
||||||
|
const y2 = Math.max(dragState.startY!, pos.y);
|
||||||
|
marqueeRect = { x: x1, y: y1, w: x2 - x1, h: y2 - y1 };
|
||||||
|
|
||||||
|
// Select all visible symbols whose AABB intersects the marquee
|
||||||
|
const selected = new Set<number>();
|
||||||
|
for (const sym of layout.symbols) {
|
||||||
|
if (sym.hidden || layout.hiddenGroups.has(getSymbolGroup(sym.symbolId))) continue;
|
||||||
|
const bb = getAABB(sym.x, sym.y, sym.w, sym.h, sym.rotation);
|
||||||
|
if (bb.x + bb.w >= x1 && bb.x <= x2 && bb.y + bb.h >= y1 && bb.y <= y2) {
|
||||||
|
selected.add(sym.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
layout.selectedIds = selected;
|
||||||
|
layout.markDirty();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onMouseup(e: MouseEvent) {
|
function onMouseup(e: MouseEvent) {
|
||||||
@ -643,6 +668,11 @@ function onMouseup(e: MouseEvent) {
|
|||||||
layout.saveMcmState();
|
layout.saveMcmState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (dragState.type === 'marquee') {
|
||||||
|
marqueeRect = null;
|
||||||
|
layout.markDirty();
|
||||||
|
}
|
||||||
|
|
||||||
dragState = null;
|
dragState = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { layout } from '../stores/layout.svelte.js';
|
import { layout } from '../stores/layout.svelte.js';
|
||||||
import { getSymbolImage, isResizable, isCurvedType, isSpurType, isEpcType, isInductionType, isPhotoeyeType, getCurveGeometry, getSymbolGroup, SPACING_EXEMPT, EPC_CONFIG, INDUCTION_CONFIG, PHOTOEYE_CONFIG } from '../symbols.js';
|
import { getSymbolImage, isResizable, isCurvedType, isSpurType, isEpcType, isInductionType, isPhotoeyeType, getCurveGeometry, getSymbolGroup, SPACING_EXEMPT, EPC_CONFIG, INDUCTION_CONFIG, PHOTOEYE_CONFIG } from '../symbols.js';
|
||||||
import { checkSpacingViolation } from './collision.js';
|
import { checkSpacingViolation } from './collision.js';
|
||||||
|
import { marqueeRect } from './interactions.js';
|
||||||
import { THEME } from './render-theme.js';
|
import { THEME } from './render-theme.js';
|
||||||
import type { PlacedSymbol } from '../types.js';
|
import type { PlacedSymbol } from '../types.js';
|
||||||
|
|
||||||
@ -79,6 +80,17 @@ export function render() {
|
|||||||
if (SPACING_EXEMPT.has(sym.symbolId)) drawSymbol(ctx, sym as PlacedSymbol);
|
if (SPACING_EXEMPT.has(sym.symbolId)) drawSymbol(ctx, sym as PlacedSymbol);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Marquee selection rectangle
|
||||||
|
if (marqueeRect) {
|
||||||
|
ctx.strokeStyle = '#4a9eff';
|
||||||
|
ctx.lineWidth = 1;
|
||||||
|
ctx.setLineDash([4, 3]);
|
||||||
|
ctx.fillStyle = 'rgba(74, 158, 255, 0.1)';
|
||||||
|
ctx.fillRect(marqueeRect.x, marqueeRect.y, marqueeRect.w, marqueeRect.h);
|
||||||
|
ctx.strokeRect(marqueeRect.x, marqueeRect.y, marqueeRect.w, marqueeRect.h);
|
||||||
|
ctx.setLineDash([]);
|
||||||
|
}
|
||||||
|
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user