// Canvas rendering — gates, connections, grid import { GATE_W, GATE_H, PORT_R, GATE_COLORS } from './constants.js'; import { state } from './state.js'; import { getInputPorts, getOutputPorts } from './gates.js'; import { getGateLabel, drawWaveLabels, drawWaveforms } from './waveform.js'; let canvas, ctx; export function initRenderer() { canvas = document.getElementById('canvas'); ctx = canvas.getContext('2d'); resize(); window.addEventListener('resize', resize); requestAnimationFrame(draw); } export function resize() { canvas.width = window.innerWidth; const waveH = state.waveformVisible ? state.waveformHeight : 0; canvas.height = window.innerHeight - 48 - waveH; } function drawGate(gate) { const color = GATE_COLORS[gate.type]; const isHovered = state.hoveredGate === gate; const isActive = gate.value === 1; if (isActive) { ctx.shadowColor = color; ctx.shadowBlur = 20; } ctx.fillStyle = isActive ? color + '22' : '#14141e'; ctx.strokeStyle = isHovered ? '#fff' : color; ctx.lineWidth = isHovered ? 2.5 : 1.5; ctx.beginPath(); ctx.roundRect(gate.x, gate.y, GATE_W, GATE_H, 8); ctx.fill(); ctx.stroke(); ctx.shadowBlur = 0; // Label ctx.fillStyle = isActive ? '#fff' : color; ctx.font = 'bold 14px "Segoe UI", system-ui, sans-serif'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; const isIOType = gate.type === 'INPUT' || gate.type === 'OUTPUT' || gate.type === 'CLOCK'; ctx.fillText( gate.type === 'CLOCK' ? '⏱ CLK' : gate.type, gate.x + GATE_W / 2, gate.y + GATE_H / 2 - (isIOType ? 8 : 0) ); // Small ID label ctx.font = '9px monospace'; ctx.fillStyle = '#444'; ctx.fillText(getGateLabel(gate), gate.x + GATE_W / 2, gate.y + GATE_H - 6); // Value for INPUT/OUTPUT/CLOCK if (isIOType) { ctx.font = 'bold 16px monospace'; ctx.fillStyle = gate.value ? '#00ff88' : '#ff4444'; ctx.fillText(gate.value ? '1' : '0', gate.x + GATE_W / 2, gate.y + GATE_H / 2 + 10); } // Input ports getInputPorts(gate).forEach(p => { const isPortHovered = state.hoveredPort && state.hoveredPort.gate === gate && state.hoveredPort.index === p.index && state.hoveredPort.type === 'input'; const conn = state.connections.find(c => c.to === gate.id && c.toPort === p.index); const portActive = conn ? state.gates.find(g => g.id === conn.from)?.value : 0; ctx.beginPath(); ctx.arc(p.x, p.y, PORT_R, 0, Math.PI * 2); ctx.fillStyle = isPortHovered ? '#fff' : (portActive ? '#00ff88' : '#1a1a2e'); ctx.fill(); ctx.strokeStyle = isPortHovered ? '#fff' : '#555'; ctx.lineWidth = 1.5; ctx.stroke(); }); // Output ports getOutputPorts(gate).forEach(p => { const isPortHovered = state.hoveredPort && state.hoveredPort.gate === gate && state.hoveredPort.index === p.index && state.hoveredPort.type === 'output'; ctx.beginPath(); ctx.arc(p.x, p.y, PORT_R, 0, Math.PI * 2); ctx.fillStyle = isPortHovered ? '#fff' : (gate.value ? '#00ff88' : '#1a1a2e'); ctx.fill(); ctx.strokeStyle = isPortHovered ? '#fff' : '#555'; ctx.lineWidth = 1.5; ctx.stroke(); }); } function drawConnection(conn) { const fromGate = state.gates.find(g => g.id === conn.from); const toGate = state.gates.find(g => g.id === conn.to); if (!fromGate || !toGate) return; const fromPort = getOutputPorts(fromGate)[conn.fromPort]; const toPort = getInputPorts(toGate)[conn.toPort]; if (!fromPort || !toPort) return; const active = fromGate.value === 1; const midX = (fromPort.x + toPort.x) / 2; ctx.beginPath(); ctx.moveTo(fromPort.x, fromPort.y); ctx.bezierCurveTo(midX, fromPort.y, midX, toPort.y, toPort.x, toPort.y); ctx.strokeStyle = active ? '#00ff88' : '#333'; ctx.lineWidth = active ? 2.5 : 1.5; ctx.stroke(); if (active) { ctx.strokeStyle = '#00ff8844'; ctx.lineWidth = 6; ctx.stroke(); } } function drawGrid() { ctx.strokeStyle = '#111118'; ctx.lineWidth = 1; for (let x = 0; x < canvas.width; x += 40) { ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, canvas.height); ctx.stroke(); } for (let y = 0; y < canvas.height; y += 40) { ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(canvas.width, y); ctx.stroke(); } } function drawConnectingWire() { if (!state.connecting) return; const gate = state.connecting.gate; const port = state.connecting.portType === 'output' ? getOutputPorts(gate)[state.connecting.portIndex] : getInputPorts(gate)[state.connecting.portIndex]; if (!port) return; const midX = (port.x + state.mouseX) / 2; ctx.beginPath(); ctx.moveTo(port.x, port.y); ctx.bezierCurveTo(midX, port.y, midX, state.mouseY, state.mouseX, state.mouseY); ctx.strokeStyle = '#00e59988'; ctx.lineWidth = 2; ctx.setLineDash([6, 4]); ctx.stroke(); ctx.setLineDash([]); } function drawPlacingGhost() { if (!state.placingGate) return; ctx.globalAlpha = 0.5; const ghost = { x: state.mouseX - GATE_W / 2, y: state.mouseY - GATE_H / 2, type: state.placingGate, value: 0, id: -1 }; drawGate(ghost); ctx.globalAlpha = 1; } function draw() { ctx.clearRect(0, 0, canvas.width, canvas.height); drawGrid(); state.connections.forEach(drawConnection); state.gates.forEach(drawGate); drawConnectingWire(); drawPlacingGhost(); if (state.waveformVisible) { drawWaveLabels(); drawWaveforms(); } requestAnimationFrame(draw); }