feat: CLK toggles on Step, add pan/zoom (arrows, +/-, wheel, 0=reset)

This commit is contained in:
Jose Luis Montañes
2026-03-19 22:06:03 +01:00
parent 7409a96cf1
commit d5de328898
5 changed files with 141 additions and 27 deletions

View File

@@ -2,9 +2,11 @@
import { GATE_W, GATE_H } from './constants.js';
import { state } from './state.js';
import { evaluateAll, findGateAt, findPortAt } from './gates.js';
import { manualStep, clearWaveData, updateWaveInfo } from './waveform.js';
import { manualStep, clearWaveData } from './waveform.js';
import { startSim, stopSim, adjustSpeed } from './simulation.js';
import { resize } from './renderer.js';
import { resize, screenToWorld } from './renderer.js';
const PAN_SPEED = 40;
export function initEvents() {
const canvas = document.getElementById('canvas');
@@ -13,12 +15,15 @@ export function initEvents() {
canvas.addEventListener('mousemove', e => {
state.mouseX = e.offsetX;
state.mouseY = e.offsetY;
state.hoveredPort = findPortAt(state.mouseX, state.mouseY);
state.hoveredGate = state.hoveredPort ? state.hoveredPort.gate : findGateAt(state.mouseX, state.mouseY);
// Convert to world coords for gate/port detection
const world = screenToWorld(e.offsetX, e.offsetY);
state.hoveredPort = findPortAt(world.x, world.y);
state.hoveredGate = state.hoveredPort ? state.hoveredPort.gate : findGateAt(world.x, world.y);
if (state.dragging) {
state.dragging.x = state.mouseX - state.dragOffset.x;
state.dragging.y = state.mouseY - state.dragOffset.y;
state.dragging.x = world.x - state.dragOffset.x;
state.dragging.y = world.y - state.dragOffset.y;
evaluateAll();
}
@@ -30,15 +35,15 @@ export function initEvents() {
canvas.addEventListener('mousedown', e => {
if (e.button !== 0) return;
const x = e.offsetX, y = e.offsetY;
const world = screenToWorld(e.offsetX, e.offsetY);
// Placing a new gate
if (state.placingGate) {
state.gates.push({
id: state.nextId++,
type: state.placingGate,
x: x - GATE_W / 2,
y: y - GATE_H / 2,
x: world.x - GATE_W / 2,
y: world.y - GATE_H / 2,
value: 0
});
evaluateAll();
@@ -47,7 +52,7 @@ export function initEvents() {
}
// Port click — connecting
const port = findPortAt(x, y);
const port = findPortAt(world.x, world.y);
if (port) {
if (state.connecting) {
if (state.connecting.portType === 'output' && port.type === 'input') {
@@ -69,7 +74,7 @@ export function initEvents() {
if (state.connecting) { state.connecting = null; return; }
// Toggle INPUT/CLOCK
const gate = findGateAt(x, y);
const gate = findGateAt(world.x, world.y);
if (gate && (gate.type === 'INPUT' || gate.type === 'CLOCK')) {
gate.value = gate.value ? 0 : 1;
evaluateAll();
@@ -79,7 +84,7 @@ export function initEvents() {
// Drag gate
if (gate) {
state.dragging = gate;
state.dragOffset = { x: x - gate.x, y: y - gate.y };
state.dragOffset = { x: world.x - gate.x, y: world.y - gate.y };
canvas.style.cursor = 'grabbing';
}
});
@@ -88,8 +93,8 @@ export function initEvents() {
canvas.addEventListener('contextmenu', e => {
e.preventDefault();
const x = e.offsetX, y = e.offsetY;
const port = findPortAt(x, y);
const world = screenToWorld(e.offsetX, e.offsetY);
const port = findPortAt(world.x, world.y);
if (port && port.type === 'input') {
state.connections = state.connections.filter(c => !(c.to === port.gate.id && c.toPort === port.index));
evaluateAll();
@@ -99,8 +104,27 @@ export function initEvents() {
}
});
// ==================== MOUSE WHEEL ZOOM ====================
canvas.addEventListener('wheel', e => {
e.preventDefault();
const delta = e.deltaY > 0 ? -0.1 : 0.1;
const newZoom = Math.max(0.2, Math.min(3, state.zoom + delta));
// Zoom towards mouse position
const worldBefore = screenToWorld(e.offsetX, e.offsetY);
state.zoom = newZoom;
const worldAfter = screenToWorld(e.offsetX, e.offsetY);
state.camX += (worldAfter.x - worldBefore.x) * state.zoom;
state.camY += (worldAfter.y - worldBefore.y) * state.zoom;
}, { passive: false });
// ==================== KEYBOARD ====================
const keysDown = new Set();
document.addEventListener('keydown', e => {
keysDown.add(e.key);
if (e.key === 'Delete' || e.key === 'Backspace') {
if (state.hoveredGate && document.activeElement === document.body) {
e.preventDefault();
@@ -116,6 +140,33 @@ export function initEvents() {
state.placingGate = null;
state.connecting = null;
}
// Pan with arrow keys
if (e.key === 'ArrowLeft') { state.camX += PAN_SPEED; e.preventDefault(); }
if (e.key === 'ArrowRight') { state.camX -= PAN_SPEED; e.preventDefault(); }
if (e.key === 'ArrowUp') { state.camY += PAN_SPEED; e.preventDefault(); }
if (e.key === 'ArrowDown') { state.camY -= PAN_SPEED; e.preventDefault(); }
// Zoom with +/- keys
if ((e.key === '+' || e.key === '=') && document.activeElement === document.body) {
state.zoom = Math.min(3, state.zoom + 0.1);
e.preventDefault();
}
if ((e.key === '-' || e.key === '_') && document.activeElement === document.body) {
state.zoom = Math.max(0.2, state.zoom - 0.1);
e.preventDefault();
}
// Reset view with 0
if (e.key === '0' && document.activeElement === document.body) {
state.camX = 0;
state.camY = 0;
state.zoom = 1;
}
});
document.addEventListener('keyup', e => {
keysDown.delete(e.key);
});
// ==================== TOOLBAR ====================
@@ -156,7 +207,7 @@ export function initEvents() {
document.getElementById('wave-record').addEventListener('click', function() {
state.recording = !state.recording;
this.classList.toggle('active', state.recording);
this.textContent = state.recording ? 'Record' : 'Paused';
this.textContent = state.recording ? 'Record' : 'Paused';
});
document.getElementById('wave-clear').addEventListener('click', clearWaveData);