fix: allow dragging all gates + stop waveform recording on edits

- INPUT/CLOCK gates can now be dragged (click-without-move = toggle,
  click-and-drag = move). Uses 4px movement threshold.
- Waveform only records samples on intentional actions (INPUT toggle,
  manual step, simulation tick), not on gate placement/movement/connections.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jose Luis
2026-03-20 02:42:00 +01:00
parent 3f1daa77bd
commit 3bff1fd4b4
2 changed files with 33 additions and 14 deletions

View File

@@ -30,9 +30,19 @@ export function initEvents() {
state.hoveredGate = state.hoveredPort ? state.hoveredPort.gate : findGateAt(world.x, world.y); state.hoveredGate = state.hoveredPort ? state.hoveredPort.gate : findGateAt(world.x, world.y);
if (state.dragging) { if (state.dragging) {
state.dragging.x = world.x - state.dragOffset.x; // Detect if mouse actually moved (threshold of 4px to distinguish click vs drag)
state.dragging.y = world.y - state.dragOffset.y; if (dragStartPos && !dragMoved) {
evaluateAll(); const dx = e.offsetX - dragStartPos.x;
const dy = e.offsetY - dragStartPos.y;
if (Math.abs(dx) > 4 || Math.abs(dy) > 4) {
dragMoved = true;
}
}
if (dragMoved) {
state.dragging.x = world.x - state.dragOffset.x;
state.dragging.y = world.y - state.dragOffset.y;
evaluateAll();
}
} }
canvas.style.cursor = state.placingGate ? 'crosshair' canvas.style.cursor = state.placingGate ? 'crosshair'
@@ -41,9 +51,14 @@ export function initEvents() {
: 'default'; : 'default';
}); });
let dragStartPos = null;
let dragMoved = false;
canvas.addEventListener('mousedown', e => { canvas.addEventListener('mousedown', e => {
if (e.button !== 0) return; if (e.button !== 0) return;
const world = screenToWorld(e.offsetX, e.offsetY); const world = screenToWorld(e.offsetX, e.offsetY);
dragStartPos = { x: e.offsetX, y: e.offsetY };
dragMoved = false;
// Placing a new gate // Placing a new gate
if (state.placingGate) { if (state.placingGate) {
@@ -81,15 +96,8 @@ export function initEvents() {
if (state.connecting) { state.connecting = null; return; } if (state.connecting) { state.connecting = null; return; }
// Toggle INPUT/CLOCK // Drag any gate (including INPUT/CLOCK)
const gate = findGateAt(world.x, world.y); const gate = findGateAt(world.x, world.y);
if (gate && (gate.type === 'INPUT' || gate.type === 'CLOCK')) {
gate.value = gate.value ? 0 : 1;
evaluateAll();
return;
}
// Drag gate
if (gate) { if (gate) {
state.dragging = gate; state.dragging = gate;
state.dragOffset = { x: world.x - gate.x, y: world.y - gate.y }; state.dragOffset = { x: world.x - gate.x, y: world.y - gate.y };
@@ -97,7 +105,18 @@ export function initEvents() {
} }
}); });
canvas.addEventListener('mouseup', () => { state.dragging = null; }); canvas.addEventListener('mouseup', e => {
// Toggle INPUT/CLOCK only on click (no drag movement)
if (state.dragging && !dragMoved) {
const gate = state.dragging;
if (gate.type === 'INPUT' || gate.type === 'CLOCK') {
gate.value = gate.value ? 0 : 1;
evaluateAll(true); // record waveform on intentional toggle
}
}
state.dragging = null;
dragStartPos = null;
});
canvas.addEventListener('contextmenu', e => { canvas.addEventListener('contextmenu', e => {
e.preventDefault(); e.preventDefault();

View File

@@ -53,12 +53,12 @@ export function evaluate(gate, visited = new Set()) {
return result; return result;
} }
export function evaluateAll() { export function evaluateAll(recordWave = false) {
state.gates.forEach(g => { state.gates.forEach(g => {
if (g.type !== 'INPUT' && g.type !== 'CLOCK') g.value = 0; if (g.type !== 'INPUT' && g.type !== 'CLOCK') g.value = 0;
}); });
state.gates.forEach(g => evaluate(g)); state.gates.forEach(g => evaluate(g));
if (state.recording && state.waveformVisible) recordSample(); if (recordWave && state.recording && state.waveformVisible) recordSample();
} }
// Register evaluateAll in waveform to break circular dependency // Register evaluateAll in waveform to break circular dependency