feat: editable labels for INPUT/OUTPUT/CLOCK gates
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 <noreply@anthropic.com>
This commit is contained in:
14
js/events.js
14
js/events.js
@@ -147,6 +147,20 @@ export function initEvents() {
|
|||||||
dragStartPos = null;
|
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 => {
|
canvas.addEventListener('contextmenu', e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const world = screenToWorld(e.offsetX, e.offsetY);
|
const world = screenToWorld(e.offsetX, e.offsetY);
|
||||||
|
|||||||
@@ -20,16 +20,16 @@ const GAP_Y = 100;
|
|||||||
function srFlipFlop() {
|
function srFlipFlop() {
|
||||||
const gates = [
|
const gates = [
|
||||||
// Inputs
|
// Inputs
|
||||||
{ id: 1, type: 'INPUT', x: 50, y: 80, value: 0 }, // S (Set)
|
{ id: 1, type: 'INPUT', x: 50, y: 80, value: 0, label: 'S' },
|
||||||
{ id: 2, type: 'INPUT', x: 50, y: 280, value: 0 }, // R (Reset)
|
{ id: 2, type: 'INPUT', x: 50, y: 280, value: 0, label: 'R' },
|
||||||
|
|
||||||
// Cross-coupled NOR gates
|
// Cross-coupled NOR gates
|
||||||
{ id: 3, type: 'NOR', x: 300, y: 80, value: 0 }, // Top NOR → Q
|
{ id: 3, type: 'NOR', x: 300, y: 80, value: 0 },
|
||||||
{ id: 4, type: 'NOR', x: 300, y: 280, value: 0 }, // Bottom NOR → Q̅
|
{ id: 4, type: 'NOR', x: 300, y: 280, value: 0 },
|
||||||
|
|
||||||
// Outputs
|
// Outputs
|
||||||
{ id: 5, type: 'OUTPUT', x: 550, y: 80, 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 } // Q̅
|
{ id: 6, type: 'OUTPUT', x: 550, y: 280, value: 0, label: 'Q̅' }
|
||||||
];
|
];
|
||||||
|
|
||||||
const connections = [
|
const connections = [
|
||||||
@@ -70,16 +70,16 @@ function srFlipFlop() {
|
|||||||
function srFlipFlopNand() {
|
function srFlipFlopNand() {
|
||||||
const gates = [
|
const gates = [
|
||||||
// Inputs (active low for NAND SR)
|
// Inputs (active low for NAND SR)
|
||||||
{ id: 1, type: 'INPUT', x: 50, y: 80, value: 1 }, // S̅
|
{ id: 1, type: 'INPUT', x: 50, y: 80, value: 1, label: 'S̅' },
|
||||||
{ id: 2, type: 'INPUT', x: 50, y: 280, value: 1 }, // R̅
|
{ id: 2, type: 'INPUT', x: 50, y: 280, value: 1, label: 'R̅' },
|
||||||
|
|
||||||
// Cross-coupled NAND gates
|
// Cross-coupled NAND gates
|
||||||
{ id: 3, type: 'NAND', x: 300, y: 80, value: 0 }, // Top NAND → Q
|
{ id: 3, type: 'NAND', x: 300, y: 80, value: 0 },
|
||||||
{ id: 4, type: 'NAND', x: 300, y: 280, value: 0 }, // Bottom NAND → Q̅
|
{ id: 4, type: 'NAND', x: 300, y: 280, value: 0 },
|
||||||
|
|
||||||
// Outputs
|
// Outputs
|
||||||
{ id: 5, type: 'OUTPUT', x: 550, y: 80, 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 } // Q̅
|
{ id: 6, type: 'OUTPUT', x: 550, y: 280, value: 0, label: 'Q̅' }
|
||||||
];
|
];
|
||||||
|
|
||||||
const connections = [
|
const connections = [
|
||||||
@@ -124,8 +124,8 @@ function srFlipFlopNand() {
|
|||||||
function dLatch() {
|
function dLatch() {
|
||||||
const gates = [
|
const gates = [
|
||||||
// Inputs
|
// Inputs
|
||||||
{ id: 1, type: 'INPUT', x: 50, y: 100, value: 0 }, // D (Data)
|
{ id: 1, type: 'INPUT', x: 50, y: 100, value: 0, label: 'D' },
|
||||||
{ id: 2, type: 'INPUT', x: 50, y: 280, value: 0 }, // E (Enable)
|
{ id: 2, type: 'INPUT', x: 50, y: 280, value: 0, label: 'EN' },
|
||||||
|
|
||||||
// NOT gate to invert D
|
// NOT gate to invert D
|
||||||
{ id: 3, type: 'NOT', x: 200, y: 340, value: 0 },
|
{ 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̅
|
{ id: 7, type: 'NOR', x: 550, y: 340, value: 0 }, // → Q̅
|
||||||
|
|
||||||
// Outputs
|
// Outputs
|
||||||
{ id: 8, type: 'OUTPUT', x: 750, y: 60, 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 } // Q̅
|
{ id: 9, type: 'OUTPUT', x: 750, y: 340, value: 0, label: 'Q̅' }
|
||||||
];
|
];
|
||||||
|
|
||||||
const connections = [
|
const connections = [
|
||||||
@@ -189,8 +189,8 @@ function dLatch() {
|
|||||||
function dFlipFlop() {
|
function dFlipFlop() {
|
||||||
const gates = [
|
const gates = [
|
||||||
// Inputs
|
// Inputs
|
||||||
{ id: 1, type: 'INPUT', x: 30, y: 100, value: 0 }, // D
|
{ id: 1, type: 'INPUT', x: 30, y: 100, value: 0, label: 'D' },
|
||||||
{ id: 2, type: 'CLOCK', x: 30, y: 350, value: 0 }, // CLK
|
{ id: 2, type: 'CLOCK', x: 30, y: 350, value: 0, label: 'CLK' },
|
||||||
|
|
||||||
// CLK inverter (for master latch)
|
// CLK inverter (for master latch)
|
||||||
{ id: 3, type: 'NOT', x: 170, y: 350, value: 0 },
|
{ 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̅
|
{ id: 13, type: 'NOR', x: 830, y: 240, value: 0 }, // Slave Q̅
|
||||||
|
|
||||||
// Outputs
|
// Outputs
|
||||||
{ id: 14, type: 'OUTPUT', x: 1010, y: 60, 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 } // Q̅
|
{ id: 15, type: 'OUTPUT', x: 1010, y: 240, value: 0, label: 'Q̅' }
|
||||||
];
|
];
|
||||||
|
|
||||||
const connections = [
|
const connections = [
|
||||||
|
|||||||
@@ -63,8 +63,18 @@ function drawGate(gate) {
|
|||||||
ctx.textBaseline = 'middle';
|
ctx.textBaseline = 'middle';
|
||||||
|
|
||||||
const isIOType = gate.type === 'INPUT' || gate.type === 'OUTPUT' || gate.type === 'CLOCK';
|
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(
|
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.x + GATE_W / 2,
|
||||||
gate.y + GATE_H / 2 - (isIOType ? 8 : 0)
|
gate.y + GATE_H / 2 - (isIOType ? 8 : 0)
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ export function getTrackedGates() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getGateLabel(gate) {
|
export function getGateLabel(gate) {
|
||||||
|
if (gate.label) return gate.label;
|
||||||
const sameType = state.gates.filter(g => g.type === gate.type);
|
const sameType = state.gates.filter(g => g.type === gate.type);
|
||||||
const idx = sameType.indexOf(gate);
|
const idx = sameType.indexOf(gate);
|
||||||
if (gate.type === 'CLOCK') return `CLK_${idx}`;
|
if (gate.type === 'CLOCK') return `CLK_${idx}`;
|
||||||
|
|||||||
Reference in New Issue
Block a user