From 3bff1fd4b44c1f128f93b902b7ac274660a77209 Mon Sep 17 00:00:00 2001 From: Jose Luis Date: Fri, 20 Mar 2026 02:42:00 +0100 Subject: [PATCH] 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 --- js/events.js | 43 +++++++++++++++++++++++++++++++------------ js/gates.js | 4 ++-- 2 files changed, 33 insertions(+), 14 deletions(-) diff --git a/js/events.js b/js/events.js index 832c31a..1db80f4 100644 --- a/js/events.js +++ b/js/events.js @@ -30,9 +30,19 @@ export function initEvents() { state.hoveredGate = state.hoveredPort ? state.hoveredPort.gate : findGateAt(world.x, world.y); if (state.dragging) { - state.dragging.x = world.x - state.dragOffset.x; - state.dragging.y = world.y - state.dragOffset.y; - evaluateAll(); + // Detect if mouse actually moved (threshold of 4px to distinguish click vs drag) + if (dragStartPos && !dragMoved) { + 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' @@ -41,9 +51,14 @@ export function initEvents() { : 'default'; }); + let dragStartPos = null; + let dragMoved = false; + canvas.addEventListener('mousedown', e => { if (e.button !== 0) return; const world = screenToWorld(e.offsetX, e.offsetY); + dragStartPos = { x: e.offsetX, y: e.offsetY }; + dragMoved = false; // Placing a new gate if (state.placingGate) { @@ -81,15 +96,8 @@ export function initEvents() { if (state.connecting) { state.connecting = null; return; } - // Toggle INPUT/CLOCK + // Drag any gate (including INPUT/CLOCK) 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) { state.dragging = gate; 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 => { e.preventDefault(); diff --git a/js/gates.js b/js/gates.js index a973587..2fbc12a 100644 --- a/js/gates.js +++ b/js/gates.js @@ -53,12 +53,12 @@ export function evaluate(gate, visited = new Set()) { return result; } -export function evaluateAll() { +export function evaluateAll(recordWave = false) { state.gates.forEach(g => { if (g.type !== 'INPUT' && g.type !== 'CLOCK') g.value = 0; }); 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