refactor: bus terminals with single-sided pins only
BUS_IN has input pins only (left side), BUS_OUT has output pins only (right side). No internal connections between them — BUS_OUT reads values directly from its paired BUS_IN via busPairId. The bus cable between them is purely visual, representing the grouped signal bundle. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -3,7 +3,7 @@ import { GATE_W, GATE_H, COMP_W, PORT_R, GATE_COLORS } from './constants.js';
|
||||
import { state } from './state.js';
|
||||
import { getInputPorts, getOutputPorts, getComponentWidth, getComponentHeight } from './gates.js';
|
||||
import { getGateLabel, drawWaveLabels, drawWaveforms } from './waveform.js';
|
||||
import { isBusInternalConnection, getBusPairs } from './bus.js';
|
||||
import { getBusPairs } from './bus.js';
|
||||
|
||||
let canvas, ctx;
|
||||
|
||||
@@ -32,9 +32,13 @@ export function screenToWorld(sx, sy) {
|
||||
};
|
||||
}
|
||||
|
||||
function isBusType(type) {
|
||||
return type.startsWith('BUS_IN:') || type.startsWith('BUS_OUT:');
|
||||
}
|
||||
|
||||
function drawSelectionHighlight(gate) {
|
||||
if (!state.selectedGates.includes(gate.id)) return;
|
||||
const isDynamic = gate.type.startsWith('COMPONENT:') || gate.type.startsWith('BUS:');
|
||||
const isDynamic = gate.type.startsWith('COMPONENT:') || isBusType(gate.type);
|
||||
const w = isDynamic ? getComponentWidth(gate) : GATE_W;
|
||||
const h = isDynamic ? getComponentHeight(gate) : GATE_H;
|
||||
const pad = 4;
|
||||
@@ -49,7 +53,7 @@ function drawSelectionHighlight(gate) {
|
||||
|
||||
function drawGate(gate) {
|
||||
// Special gate types have different rendering
|
||||
if (gate.type.startsWith('BUS:')) { drawBusGate(gate); drawSelectionHighlight(gate); return; }
|
||||
if (isBusType(gate.type)) { drawBusGate(gate); drawSelectionHighlight(gate); return; }
|
||||
if (gate.type.startsWith('COMPONENT:')) { drawComponentGate(gate); drawSelectionHighlight(gate); return; }
|
||||
|
||||
const color = GATE_COLORS[gate.type];
|
||||
@@ -149,8 +153,14 @@ function drawBusGate(gate) {
|
||||
const w = getComponentWidth(gate); // 30
|
||||
const h = getComponentHeight(gate);
|
||||
const color = '#44ddff';
|
||||
const n = parseInt(gate.type.substring(4)) || 1;
|
||||
const hasActive = gate.outputValues?.some(v => v === 1);
|
||||
const isIn = gate.type.startsWith('BUS_IN:');
|
||||
const n = isIn
|
||||
? parseInt(gate.type.substring(7)) || 1
|
||||
: parseInt(gate.type.substring(8)) || 1;
|
||||
|
||||
// Check if any channel is active
|
||||
const values = isIn ? gate.busValues : gate.outputValues;
|
||||
const hasActive = values?.some(v => v === 1);
|
||||
|
||||
if (hasActive) {
|
||||
ctx.shadowColor = color;
|
||||
@@ -180,43 +190,48 @@ function drawBusGate(gate) {
|
||||
ctx.fillStyle = color;
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
const roleIcon = gate.busRole === 'in' ? '▶' : gate.busRole === 'out' ? '◀' : '';
|
||||
const roleIcon = isIn ? '▶' : '◀';
|
||||
ctx.fillText(`${roleIcon} ${n}`, gate.x + w / 2, gate.y + h + 10);
|
||||
|
||||
// Input ports (left)
|
||||
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;
|
||||
// BUS_IN: only input ports (left side)
|
||||
if (isIn) {
|
||||
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 srcGate = conn ? state.gates.find(g => g.id === conn.from) : null;
|
||||
const portActive = srcGate ? (srcGate.outputValues ? (srcGate.outputValues[conn.fromPort] || 0) : srcGate.value) : 0;
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.arc(p.x, p.y, PORT_R - 1, 0, Math.PI * 2);
|
||||
ctx.fillStyle = isPortHovered ? '#fff' : (portActive ? '#00ff88' : '#1a1a2e');
|
||||
ctx.fill();
|
||||
ctx.strokeStyle = isPortHovered ? '#fff' : '#555';
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.stroke();
|
||||
});
|
||||
ctx.beginPath();
|
||||
ctx.arc(p.x, p.y, PORT_R - 1, 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 (right)
|
||||
getOutputPorts(gate).forEach(p => {
|
||||
const isPortHovered = state.hoveredPort &&
|
||||
state.hoveredPort.gate === gate &&
|
||||
state.hoveredPort.index === p.index &&
|
||||
state.hoveredPort.type === 'output';
|
||||
const portVal = gate.outputValues ? (gate.outputValues[p.index] || 0) : 0;
|
||||
// BUS_OUT: only output ports (right side)
|
||||
if (!isIn) {
|
||||
getOutputPorts(gate).forEach(p => {
|
||||
const isPortHovered = state.hoveredPort &&
|
||||
state.hoveredPort.gate === gate &&
|
||||
state.hoveredPort.index === p.index &&
|
||||
state.hoveredPort.type === 'output';
|
||||
const portVal = gate.outputValues ? (gate.outputValues[p.index] || 0) : 0;
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.arc(p.x, p.y, PORT_R - 1, 0, Math.PI * 2);
|
||||
ctx.fillStyle = isPortHovered ? '#fff' : (portVal ? '#00ff88' : '#1a1a2e');
|
||||
ctx.fill();
|
||||
ctx.strokeStyle = isPortHovered ? '#fff' : '#555';
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.stroke();
|
||||
});
|
||||
ctx.beginPath();
|
||||
ctx.arc(p.x, p.y, PORT_R - 1, 0, Math.PI * 2);
|
||||
ctx.fillStyle = isPortHovered ? '#fff' : (portVal ? '#00ff88' : '#1a1a2e');
|
||||
ctx.fill();
|
||||
ctx.strokeStyle = isPortHovered ? '#fff' : '#555';
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.stroke();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function drawBusCables() {
|
||||
@@ -233,8 +248,8 @@ function drawBusCables() {
|
||||
const y2 = outGate.y + outH / 2;
|
||||
|
||||
// Check if any channel is active
|
||||
const hasActive = inGate.outputValues?.some(v => v === 1);
|
||||
const n = parseInt(inGate.type.substring(4)) || 1;
|
||||
const hasActive = inGate.busValues?.some(v => v === 1);
|
||||
const n = parseInt(inGate.type.substring(7)) || 1;
|
||||
|
||||
// Outer thick cable (bus background)
|
||||
const cableWidth = Math.max(6, n * 2.5);
|
||||
@@ -384,9 +399,6 @@ function drawComponentGate(gate) {
|
||||
}
|
||||
|
||||
function drawConnection(conn) {
|
||||
// Skip internal bus connections (rendered as bus cable instead)
|
||||
if (isBusInternalConnection(conn)) return;
|
||||
|
||||
const fromGate = state.gates.find(g => g.id === conn.from);
|
||||
const toGate = state.gates.find(g => g.id === conn.to);
|
||||
if (!fromGate || !toGate) return;
|
||||
|
||||
Reference in New Issue
Block a user