From bc8823bcd44ebf85f4547548eb539da6f11f15e0 Mon Sep 17 00:00:00 2001 From: Jose Luis Date: Fri, 20 Mar 2026 04:06:50 +0100 Subject: [PATCH] feat: editable labels for INPUT/OUTPUT/CLOCK gates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Double-click any INPUT, OUTPUT, or CLOCK gate to assign a custom label. Labels are shown inside the gate and used in waveform viewer instead of generic IN_0/OUT_0 names. Example circuits now ship with meaningful labels (S, R, D, EN, Q, Q̅, CLK). Co-Authored-By: Claude Opus 4.6 --- js/events.js | 14 ++++++++++++++ js/examples.js | 40 ++++++++++++++++++++-------------------- js/renderer.js | 12 +++++++++++- js/waveform.js | 1 + 4 files changed, 46 insertions(+), 21 deletions(-) diff --git a/js/events.js b/js/events.js index e275d0e..117a0b2 100644 --- a/js/events.js +++ b/js/events.js @@ -147,6 +147,20 @@ export function initEvents() { dragStartPos = null; }); + // Double-click to rename INPUT/OUTPUT/CLOCK gates + canvas.addEventListener('dblclick', e => { + const world = screenToWorld(e.offsetX, e.offsetY); + const gate = findGateAt(world.x, world.y); + if (gate && (gate.type === 'INPUT' || gate.type === 'OUTPUT' || gate.type === 'CLOCK')) { + const current = gate.label || ''; + const label = prompt(`Label for ${gate.type}#${gate.id}:`, current); + if (label !== null) { + gate.label = label.trim() || undefined; + console.log(`[label] ${gate.type}#${gate.id} → "${gate.label || ''}"`); + } + } + }); + canvas.addEventListener('contextmenu', e => { e.preventDefault(); const world = screenToWorld(e.offsetX, e.offsetY); diff --git a/js/examples.js b/js/examples.js index f2e5dec..83139fa 100644 --- a/js/examples.js +++ b/js/examples.js @@ -20,16 +20,16 @@ const GAP_Y = 100; function srFlipFlop() { const gates = [ // Inputs - { id: 1, type: 'INPUT', x: 50, y: 80, value: 0 }, // S (Set) - { id: 2, type: 'INPUT', x: 50, y: 280, value: 0 }, // R (Reset) + { id: 1, type: 'INPUT', x: 50, y: 80, value: 0, label: 'S' }, + { id: 2, type: 'INPUT', x: 50, y: 280, value: 0, label: 'R' }, // Cross-coupled NOR gates - { id: 3, type: 'NOR', x: 300, y: 80, value: 0 }, // Top NOR → Q - { id: 4, type: 'NOR', x: 300, y: 280, value: 0 }, // Bottom NOR → Q̅ + { id: 3, type: 'NOR', x: 300, y: 80, value: 0 }, + { id: 4, type: 'NOR', x: 300, y: 280, value: 0 }, // Outputs - { id: 5, type: 'OUTPUT', x: 550, y: 80, value: 0 }, // Q - { id: 6, type: 'OUTPUT', x: 550, y: 280, value: 0 } // Q̅ + { id: 5, type: 'OUTPUT', x: 550, y: 80, value: 0, label: 'Q' }, + { id: 6, type: 'OUTPUT', x: 550, y: 280, value: 0, label: 'Q̅' } ]; const connections = [ @@ -70,16 +70,16 @@ function srFlipFlop() { function srFlipFlopNand() { const gates = [ // Inputs (active low for NAND SR) - { id: 1, type: 'INPUT', x: 50, y: 80, value: 1 }, // S̅ - { id: 2, type: 'INPUT', x: 50, y: 280, value: 1 }, // R̅ + { id: 1, type: 'INPUT', x: 50, y: 80, value: 1, label: 'S̅' }, + { id: 2, type: 'INPUT', x: 50, y: 280, value: 1, label: 'R̅' }, // Cross-coupled NAND gates - { id: 3, type: 'NAND', x: 300, y: 80, value: 0 }, // Top NAND → Q - { id: 4, type: 'NAND', x: 300, y: 280, value: 0 }, // Bottom NAND → Q̅ + { id: 3, type: 'NAND', x: 300, y: 80, value: 0 }, + { id: 4, type: 'NAND', x: 300, y: 280, value: 0 }, // Outputs - { id: 5, type: 'OUTPUT', x: 550, y: 80, value: 0 }, // Q - { id: 6, type: 'OUTPUT', x: 550, y: 280, value: 0 } // Q̅ + { id: 5, type: 'OUTPUT', x: 550, y: 80, value: 0, label: 'Q' }, + { id: 6, type: 'OUTPUT', x: 550, y: 280, value: 0, label: 'Q̅' } ]; const connections = [ @@ -124,8 +124,8 @@ function srFlipFlopNand() { function dLatch() { const gates = [ // Inputs - { id: 1, type: 'INPUT', x: 50, y: 100, value: 0 }, // D (Data) - { id: 2, type: 'INPUT', x: 50, y: 280, value: 0 }, // E (Enable) + { id: 1, type: 'INPUT', x: 50, y: 100, value: 0, label: 'D' }, + { id: 2, type: 'INPUT', x: 50, y: 280, value: 0, label: 'EN' }, // NOT gate to invert D { id: 3, type: 'NOT', x: 200, y: 340, value: 0 }, @@ -139,8 +139,8 @@ function dLatch() { { id: 7, type: 'NOR', x: 550, y: 340, value: 0 }, // → Q̅ // Outputs - { id: 8, type: 'OUTPUT', x: 750, y: 60, value: 0 }, // Q - { id: 9, type: 'OUTPUT', x: 750, y: 340, value: 0 } // Q̅ + { id: 8, type: 'OUTPUT', x: 750, y: 60, value: 0, label: 'Q' }, + { id: 9, type: 'OUTPUT', x: 750, y: 340, value: 0, label: 'Q̅' } ]; const connections = [ @@ -189,8 +189,8 @@ function dLatch() { function dFlipFlop() { const gates = [ // Inputs - { id: 1, type: 'INPUT', x: 30, y: 100, value: 0 }, // D - { id: 2, type: 'CLOCK', x: 30, y: 350, value: 0 }, // CLK + { id: 1, type: 'INPUT', x: 30, y: 100, value: 0, label: 'D' }, + { id: 2, type: 'CLOCK', x: 30, y: 350, value: 0, label: 'CLK' }, // CLK inverter (for master latch) { id: 3, type: 'NOT', x: 170, y: 350, value: 0 }, @@ -210,8 +210,8 @@ function dFlipFlop() { { id: 13, type: 'NOR', x: 830, y: 240, value: 0 }, // Slave Q̅ // Outputs - { id: 14, type: 'OUTPUT', x: 1010, y: 60, value: 0 }, // Q - { id: 15, type: 'OUTPUT', x: 1010, y: 240, value: 0 } // Q̅ + { id: 14, type: 'OUTPUT', x: 1010, y: 60, value: 0, label: 'Q' }, + { id: 15, type: 'OUTPUT', x: 1010, y: 240, value: 0, label: 'Q̅' } ]; const connections = [ diff --git a/js/renderer.js b/js/renderer.js index d8b1390..87ffbeb 100644 --- a/js/renderer.js +++ b/js/renderer.js @@ -63,8 +63,18 @@ function drawGate(gate) { ctx.textBaseline = 'middle'; const isIOType = gate.type === 'INPUT' || gate.type === 'OUTPUT' || gate.type === 'CLOCK'; + + // Show custom label above the gate if it has one + if (gate.label && isIOType) { + ctx.font = 'bold 10px "Segoe UI", system-ui, sans-serif'; + ctx.fillStyle = '#888'; + ctx.fillText(gate.label, gate.x + GATE_W / 2, gate.y - 8); + } + + ctx.font = `bold 14px "Segoe UI", system-ui, sans-serif`; + ctx.fillStyle = isActive ? '#fff' : color; ctx.fillText( - gate.type === 'CLOCK' ? '⏱ CLK' : gate.type, + gate.label && isIOType ? gate.label : (gate.type === 'CLOCK' ? '⏱ CLK' : gate.type), gate.x + GATE_W / 2, gate.y + GATE_H / 2 - (isIOType ? 8 : 0) ); diff --git a/js/waveform.js b/js/waveform.js index 1c0a152..6c47823 100644 --- a/js/waveform.js +++ b/js/waveform.js @@ -12,6 +12,7 @@ export function getTrackedGates() { } export function getGateLabel(gate) { + if (gate.label) return gate.label; const sameType = state.gates.filter(g => g.type === gate.type); const idx = sameType.indexOf(gate); if (gate.type === 'CLOCK') return `CLK_${idx}`;