feat: shift+drag to cut wires and create bus connectors

Hold Shift and drag across wires to create a BUS gate that groups
them together. The cut line shows a live preview with wire count.
BUS gates are pass-through (each input maps to its output) and
render as a thin cyan bar with ports on each side.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jose Luis
2026-03-20 04:39:00 +01:00
parent 89d118f738
commit 99f0fefe5c
6 changed files with 376 additions and 14 deletions

View File

@@ -4,13 +4,14 @@ import { state } from './state.js';
import { recordSample, setEvaluateAll } from './waveform.js';
import { evaluateComponent } from './components.js';
// Wrappers that handle component types
// Wrappers that handle component and BUS types
export function gateInputCount(type) {
if (type.startsWith('COMPONENT:')) {
const componentId = type.substring(10);
const component = state.customComponents?.[componentId];
return component ? component.inputCount : 0;
}
if (type.startsWith('BUS:')) return parseInt(type.substring(4)) || 0;
return baseGateInputCount(type);
}
@@ -20,10 +21,12 @@ export function gateOutputCount(type) {
const component = state.customComponents?.[componentId];
return component ? component.outputCount : 0;
}
if (type.startsWith('BUS:')) return parseInt(type.substring(4)) || 0;
return baseGateOutputCount(type);
}
export function getComponentWidth(gate) {
if (gate.type.startsWith('BUS:')) return 30;
if (gate.type.startsWith('COMPONENT:')) {
const count = Math.max(gate.component?.inputCount || 1, gate.component?.outputCount || 1);
return Math.max(120, (count + 1) * 25);
@@ -32,6 +35,10 @@ export function getComponentWidth(gate) {
}
export function getComponentHeight(gate) {
if (gate.type.startsWith('BUS:')) {
const n = parseInt(gate.type.substring(4)) || 1;
return Math.max(40, (n + 1) * 22);
}
if (gate.type.startsWith('COMPONENT:')) {
const count = Math.max(gate.component?.inputCount || 1, gate.component?.outputCount || 1);
return Math.max(60, (count + 1) * 25);
@@ -42,8 +49,8 @@ export function getComponentHeight(gate) {
export function getInputPorts(gate) {
const count = gateInputCount(gate.type);
const ports = [];
const isComponent = gate.type.startsWith('COMPONENT:');
const gateHeight = isComponent ? getComponentHeight(gate) : GATE_H;
const isDynamic = gate.type.startsWith('COMPONENT:') || gate.type.startsWith('BUS:');
const gateHeight = isDynamic ? getComponentHeight(gate) : GATE_H;
for (let i = 0; i < count; i++) {
const spacing = gateHeight / (count + 1);
@@ -55,9 +62,9 @@ export function getInputPorts(gate) {
export function getOutputPorts(gate) {
const count = gateOutputCount(gate.type);
const ports = [];
const isComponent = gate.type.startsWith('COMPONENT:');
const gateWidth = isComponent ? getComponentWidth(gate) : GATE_W;
const gateHeight = isComponent ? getComponentHeight(gate) : GATE_H;
const isDynamic = gate.type.startsWith('COMPONENT:') || gate.type.startsWith('BUS:');
const gateWidth = isDynamic ? getComponentWidth(gate) : GATE_W;
const gateHeight = isDynamic ? getComponentHeight(gate) : GATE_H;
for (let i = 0; i < count; i++) {
const spacing = gateHeight / (count + 1);
@@ -105,6 +112,12 @@ function computeGate(gate) {
return outputs[0] || 0;
}
// BUS: pass-through, each input maps to corresponding output
if (gate.type.startsWith('BUS:')) {
gate.outputValues = [...inputs];
return inputs[0] || 0;
}
switch (gate.type) {
case 'AND': return (inputs[0] && inputs[1]) ? 1 : 0;
case 'OR': return (inputs[0] || inputs[1]) ? 1 : 0;
@@ -168,8 +181,9 @@ setEvaluateAll(evaluateAll);
export function findGateAt(x, y) {
return state.gates.find(g => {
const w = g.type.startsWith('COMPONENT:') ? getComponentWidth(g) : GATE_W;
const h = g.type.startsWith('COMPONENT:') ? getComponentHeight(g) : GATE_H;
const isDynamic = g.type.startsWith('COMPONENT:') || g.type.startsWith('BUS:');
const w = isDynamic ? getComponentWidth(g) : GATE_W;
const h = isDynamic ? getComponentHeight(g) : GATE_H;
return x >= g.x && x <= g.x + w && y >= g.y && y <= g.y + h;
});
}