feat: drag selection box to select, move, and delete multiple gates

Click and drag on empty space to draw a selection rectangle. Gates
inside the box get selected (cyan dashed outline). Drag any selected
gate to move all of them together. Delete/Backspace removes all
selected gates and their connections. Escape clears the selection.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jose Luis
2026-03-20 04:47:34 +01:00
parent 12d7331d2c
commit 9ec3367253
3 changed files with 148 additions and 6 deletions

View File

@@ -32,10 +32,25 @@ export function screenToWorld(sx, sy) {
};
}
function drawSelectionHighlight(gate) {
if (!state.selectedGates.includes(gate.id)) return;
const isDynamic = gate.type.startsWith('COMPONENT:') || gate.type.startsWith('BUS:');
const w = isDynamic ? getComponentWidth(gate) : GATE_W;
const h = isDynamic ? getComponentHeight(gate) : GATE_H;
const pad = 4;
ctx.strokeStyle = '#44ddff';
ctx.lineWidth = 2;
ctx.setLineDash([5, 3]);
ctx.beginPath();
ctx.roundRect(gate.x - pad, gate.y - pad, w + pad * 2, h + pad * 2, 10);
ctx.stroke();
ctx.setLineDash([]);
}
function drawGate(gate) {
// Special gate types have different rendering
if (gate.type.startsWith('BUS:')) return drawBusGate(gate);
if (gate.type.startsWith('COMPONENT:')) return drawComponentGate(gate);
if (gate.type.startsWith('BUS:')) { drawBusGate(gate); drawSelectionHighlight(gate); return; }
if (gate.type.startsWith('COMPONENT:')) { drawComponentGate(gate); drawSelectionHighlight(gate); return; }
const color = GATE_COLORS[gate.type];
const isHovered = state.hoveredGate === gate;
@@ -125,6 +140,8 @@ function drawGate(gate) {
ctx.lineWidth = 1.5;
ctx.stroke();
});
drawSelectionHighlight(gate);
}
function drawBusGate(gate) {
@@ -519,6 +536,23 @@ function segsHit(ax1, ay1, ax2, ay2, bx1, by1, bx2, by2) {
return t >= 0 && t <= 1 && u >= 0 && u <= 1;
}
function drawSelectionBox() {
if (!state.selectionBox) return;
const box = state.selectionBox;
const x = Math.min(box.startX, box.endX);
const y = Math.min(box.startY, box.endY);
const w = Math.abs(box.endX - box.startX);
const h = Math.abs(box.endY - box.startY);
ctx.fillStyle = '#44ddff0a';
ctx.fillRect(x, y, w, h);
ctx.strokeStyle = '#44ddff88';
ctx.lineWidth = 1.5;
ctx.setLineDash([6, 3]);
ctx.strokeRect(x, y, w, h);
ctx.setLineDash([]);
}
function drawPlacingGhost() {
if (!state.placingGate) return;
ctx.globalAlpha = 0.5;
@@ -572,6 +606,7 @@ function draw() {
state.gates.forEach(drawGate);
drawConnectingWire();
drawBusCutLine();
drawSelectionBox();
drawPlacingGhost();
ctx.restore();