@webwriter/logic-circuit
Version:
Create and simulate circuits with logic gates (AND, OR, XOR, etc.).
283 lines (240 loc) • 10.3 kB
text/typescript
export function getConnectorCoordinates(svgCanvas, connector, zoom) {
const connectorRect = connector?.getBoundingClientRect();
const offsetX = connector?.getBoundingClientRect().width / 2;
const offsetY = connector?.getBoundingClientRect().height / 2;
const x = (connectorRect?.left - svgCanvas?.getBoundingClientRect()?.left + offsetX - (9*zoom)) / zoom;
const y = (connectorRect?.top - svgCanvas?.getBoundingClientRect()?.top + offsetY - (9*zoom)) / zoom;
return { x, y };
}
export function getMouseCoordinates(svgCanvas, mouseX, mouseY, zoom) {
const workspaceX = svgCanvas.getBoundingClientRect().left;
const workspaceY = svgCanvas.getBoundingClientRect().top;
const relativeX = mouseX - workspaceX;
const relativeY = mouseY - workspaceY;
const scaledRelativeX = relativeX / zoom;
const scaledRelativeY = relativeY / zoom;
return { x: scaledRelativeX, y: scaledRelativeY };
}
export function getConnectorCoordinatesGhost(svgCanvas, connector) {
const connectorRect = connector?.getBoundingClientRect();
const offsetX = connector?.getBoundingClientRect().width / 2;
const offsetY = connector?.getBoundingClientRect().height / 2;
const x = connectorRect?.left - svgCanvas?.getBoundingClientRect()?.left + offsetX - 9;
const y = connectorRect?.top - svgCanvas?.getBoundingClientRect()?.top + offsetY - 9;
return { x, y };
}
export function calculatePath(svgCanvas, startCon, endCon, zoom) {
let startConnector;
let endConnector;
if (startCon.type === 'input' && endCon.type === 'output') {
startConnector = endCon;
endConnector = startCon;
} else if (startCon.type === 'output' && endCon.type === 'input') {
startConnector = startCon;
endConnector = endCon;
} else {
return;
}
const points = [];
const padding = 15;
const offsetX = 5;
const start = {
x: getConnectorCoordinates(svgCanvas, startConnector, zoom).x,
y: getConnectorCoordinates(svgCanvas, startConnector, zoom).y,
type: startConnector?.id?.substring(startConnector.id.length - 3),
};
const end = {
x: getConnectorCoordinates(svgCanvas, endConnector, zoom).x ,
y: getConnectorCoordinates(svgCanvas, endConnector, zoom).y,
type: endConnector?.id?.substring(endConnector.id.length - 3),
};
points.push({ x: start.x, y: start.y });
const isAbove = end.y < start.y;
const isLeft = end.x < start.x + offsetX + padding;
if (isLeft) {
if (start.type === 'Out' || start.type === 'ut2' || start.type === 'ut3') {
points.push({ x: start.x + padding, y: start.y });
if (end.type === 'In1' || end.type === 'In2') {
points.push({ x: start.x + padding, y: (start.y + end.y) / 2 });
points.push({ x: end.x - padding, y: (start.y + end.y) / 2 });
points.push({ x: end.x - padding, y: end.y });
}
}
} else {
if (start.type === 'Out' || start.type === 'ut2' || start.type === 'ut3') {
if (isAbove) {
points.push({ x: start.x + padding - offsetX, y: start.y });
if (end.type === 'In1') {
points.pop();
points.push({ x: (start.x + end.x) / 2 - offsetX, y: start.y });
points.push({ x: (start.x + end.x) / 2 - offsetX, y: end.y });
} else if (end.type === 'In2') {
points.pop();
points.push({ x: (start.x + end.x) / 2 + offsetX, y: start.y });
points.push({ x: (start.x + end.x) / 2 + offsetX, y: end.y });
}
} else {
points.push({ x: start.x + padding + offsetX, y: start.y });
if (end.type === 'In1') {
points.pop();
points.push({ x: (start.x + end.x) / 2 + offsetX, y: start.y });
points.push({ x: (start.x + end.x) / 2 + offsetX, y: end.y });
} else if (end.type === 'In2') {
points.pop();
points.push({ x: (start.x + end.x) / 2 - offsetX, y: start.y });
points.push({ x: (start.x + end.x) / 2 - offsetX, y: end.y });
}
}
}
}
points.push({ x: end.x, y: end.y });
return points;
}
export function calculatePathToMouse(svgCanvas, connector, zoom, mouseX, mouseY, x1 = -9, y1 = -9, x2 = -9, y2 = -9){
let start;
let end;
if (connector.type === 'input') {
start = {
x: mouseX,
y: mouseY,
type: 'Out',
};
end = {
x: getConnectorCoordinates(svgCanvas, connector, zoom).x,
y: getConnectorCoordinates(svgCanvas, connector, zoom).y,
type: connector?.id?.substring(connector.id.length - 3),
};
} else if (connector.type === 'output') {
start = {
x: getConnectorCoordinates(svgCanvas, connector, zoom).x,
y: getConnectorCoordinates(svgCanvas, connector, zoom).y,
type: connector?.id?.substring(connector.id.length - 3),
};
end = {
x: mouseX,
y: mouseY,
type: 'In1',
};
} else {
return;
}
const points = [];
const padding = 15;
points.push({ x: start.x, y: start.y });
const isLeft = end.x < start.x;
if (start.type === 'Out' || start.type === 'ut2' || start.type === 'ut3') {
points.push({ x: start.x + padding, y: start.y});
if (end.type === 'In1' || end.type === 'In2') {
if (start.y === end.y && isLeft) {
points.push({ x: start.x + padding, y: start.y + padding });
points.push({ x: end.x - padding, y: start.y + padding });
points.push({ x: end.x - padding, y: end.y });
} else if (isLeft) {
points.push({ x: start.x + padding, y: (start.y + end.y) / 2 });
points.push({ x: end.x - padding, y: (start.y + end.y) / 2 });
points.push({ x: end.x - padding, y: end.y });
} else {
points.pop();
points.push({ x: (start.x + end.x) / 2, y: start.y });
points.push({ x: (start.x + end.x) / 2, y: end.y });
}
}
}
points.push({ x: end.x, y: end.y });
return points;
}
export function updateLines(widget, movedGate) {
const moveLines = [];
widget.lineElements.forEach((lineObject) => {
const startConnector = lineObject.start;
const endConnector = lineObject.end;
if (
startConnector.id === movedGate.conIn1?.id ||
startConnector.id === movedGate.conIn2?.id ||
startConnector.id === movedGate.conOut?.id ||
startConnector.id === movedGate.conOut2?.id ||
endConnector.id === movedGate.conIn1?.id ||
endConnector.id === movedGate.conIn2?.id ||
endConnector.id === movedGate.conOut?.id ||
endConnector.id === movedGate.conOut2?.id
) {
moveLines.push(lineObject);
}
});
moveLines.forEach((line) => {
const startConnector = line.start;
const endConnector = line.end;
const svgPath = line.lineSVG;
const points = calculatePath(widget.svgCanvas, startConnector, endConnector, widget.zoom);
let path = `M ${points[0].x} ${points[0].y}`;
for (let i = 1; i < points.length; i++) {
path += ` L ${points[i].x} ${points[i].y}`;
}
svgPath.setAttribute('d', path);
});
}
export function createLine(widget, startCon, endCon) {
let startConnector;
let endConnector;
if (startCon.type === 'input' && endCon.type === 'output') {
startConnector = endCon;
endConnector = startCon;
} else if (startCon.type === 'output' && endCon.type === 'input') {
startConnector = startCon;
endConnector = endCon;
} else {
return;
}
const points = calculatePath(widget.svgCanvas, startCon, endCon, widget.zoom);
let path = `M ${points[0].x} ${points[0].y}`;
for (let i = 1; i < points.length; i++) {
path += ` L ${points[i].x} ${points[i].y}`;
}
const svgPath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
svgPath.setAttribute('d', path);
svgPath.setAttribute('id', 'line' + widget.lineID);
svgPath.classList.add('svgLine');
widget.lineID++;
const lineObject = {
start: startConnector,
end: endConnector,
lineSVG: svgPath,
};
widget.lineElements = [...widget.lineElements, lineObject];
svgPath.addEventListener('contextmenu', (event) => {
event.preventDefault();
const deletePathID = svgPath.getAttribute('id');
widget.lineElements.forEach((line, index) => {
if (line.lineSVG.id === deletePathID) {
const deletePath = svgPath;
deletePath.remove();
widget.lineElements.splice(index, 1);
}
});
});
widget.svgCanvas.appendChild(svgPath);
console.log(widget.lineElements)
let entry: string = startCon.id+"|"+endCon.id
console.log(entry)
if(!widget.reflectCons.includes(entry)){
widget.reflectCons+= widget.reflectCons != "" ? "," : ""
widget.reflectCons+= entry
}
}
export function updateLineColor(widget) {
const lineElements = widget.getLineElements();
let svgPath;
lineElements.forEach((line) => {
svgPath = line.lineSVG;
if (line.start.value === true || line.end.value === true) {
svgPath.classList.add('svgLineTrue');
} else {
svgPath.classList.remove('svgLineTrue');
}
});
}
export function resetLines(widget) {
widget.lineElements.forEach((line) => {
line.lineSVG.classList.remove('svgLineTrue');
});
}