Added logic gates to logic sim app

This commit is contained in:
Prozilla 2024-06-02 21:27:50 +02:00
parent 54249b4ba6
commit 445c10d68d
No known key found for this signature in database
GPG key ID: 5858DFE71CAF31EE
10 changed files with 222 additions and 68 deletions

View file

@ -70,7 +70,6 @@ export function Actions({ children, className, onAnyTrigger, triggerParams, avoi
actionId,
children: iterateOverChildren((child.props as ActionProps).children),
onTrigger: (event, ...args) => {
console.log(event);
onAnyTrigger?.(event, triggerParams, ...args);
onTrigger?.(event, triggerParams, ...args);
}

View file

@ -1,9 +1,13 @@
import { useEffect, useRef, useState } from "react";
import { Circuit } from "../../../features/apps/logic-sim/circuit";
import styles from "./CircuitView.module.css";
import { DropdownAction } from "../../actions/actions/DropdownAction";
import { HeaderMenu } from "../_utils/header-menu/HeaderMenu";
import { ClickAction } from "../../actions/actions/ClickAction";
import { ChipsManager } from "../../../features/apps/logic-sim/chipsManager";
export function CircuitView() {
const [circuit, setCircuit] = useState(new Circuit("Chip", "#000", 2, 1));
const [circuit] = useState(new Circuit("Chip", "#000", 2, 1));
const canvasRef = useRef(null);
useEffect(() => {
@ -17,5 +21,13 @@ export function CircuitView() {
};
}, [canvasRef, circuit]);
return <canvas ref={canvasRef} className={styles.Canvas}/>;
return <>
<HeaderMenu>
<DropdownAction label="Add" showOnHover={false}>
<ClickAction label="AND gate" onTrigger={() => { circuit.addChip(ChipsManager.CHIPS.AND); }}/>
<ClickAction label="NOT gate" onTrigger={() => { circuit.addChip(ChipsManager.CHIPS.NOT); }}/>
</DropdownAction>
</HeaderMenu>
<canvas ref={canvasRef} className={styles.Canvas}/>
</>;
}

View file

@ -127,9 +127,7 @@ export function TextEditor({ file, setTitle, setIconUrl, close, mode, app, modal
return (
<div className={styles.Container} style={{ fontSize: zoom }}>
<HeaderMenu onAnyTrigger={(event) => {
console.log(event);
}}>
<HeaderMenu>
<DropdownAction label="File" showOnHover={false}>
<ClickAction label="New" onTrigger={() => { newText(); }} shortcut={["Control", "e"]}/>
<ClickAction label="Open" onTrigger={() => {

View file

@ -3,6 +3,8 @@ export const CURSORS = {
pointer: "pointer"
};
export const FONT = "outfit";
export const BACKGROUND = {
padding: 40,
borderWidth: 8,
@ -20,7 +22,14 @@ export const WIRE = {
};
export const PIN = {
radius: 10,
radius: 8,
};
export const CHIP = {
width: 140,
height: 72,
padding: 12,
fontSize: 36,
};
export const COLORS = {
@ -44,4 +53,7 @@ export const COLORS = {
wire: {
placing: "dark-grey-ca",
},
chip: {
text: "dark-grey-f"
}
};

View file

@ -1,3 +1,4 @@
import { CHIP, COLORS } from "../../../config/apps/logicSim.config";
import Vector2 from "../../math/vector2";
import { Circuit } from "./circuit";
import { Pin } from "./pin";
@ -7,7 +8,9 @@ export class Chip {
color: string;
name: string;
position: Vector2;
size: Vector2;
circuit: Circuit;
isCircuit = true;
inputCount: number;
outputCount: number;
@ -15,8 +18,8 @@ export class Chip {
outputPins: Pin[];
logic: (inputStates: State[]) => State[];
constructor(circuit: Circuit | null, name: string, color: string, inputCount: number, outputCount: number) {
Object.assign(this, { circuit, name, color, inputCount, outputCount });
constructor(circuit: Circuit | null, name: string, color: string, size: Vector2, inputCount: number, outputCount: number) {
Object.assign(this, { circuit, name, color, size, inputCount, outputCount });
if (this.circuit == null)
this.circuit = this as unknown as Circuit;
@ -32,15 +35,21 @@ export class Chip {
}
}
setInputState(index: number, state: State) {
if (this.inputPins[index].state.isEqual(state))
return;
setCircuit(circuit: Circuit) {
this.circuit = circuit;
this.inputPins[index].state = state;
this.update();
this.inputPins.concat(this.outputPins).forEach((pin) => { pin.circuit = circuit; });
}
setLogic(logic: (inputStates: State[]) => State[]) {
this.logic = logic;
return this;
}
update() {
if (this.logic == null)
return;
const inputStates: State[] = [];
for (let i = 0; i < this.inputCount; i++) {
const state = this.inputPins[i].state ?? State.LOW;
@ -50,16 +59,52 @@ export class Chip {
const outputStates: State[] = this.logic(inputStates);
for (let i = 0; i < this.outputCount; i++) {
this.outputPins[i].state = outputStates[i];
this.outputPins[i].setState(outputStates[i]);
}
}
drawPins() {
this.inputPins.forEach((pin) => pin.draw());
this.outputPins.forEach((pin) => pin.draw());
drawPins(reposition = true) {
this.inputPins.forEach((pin, index) => {
if (reposition) {
const gap = this.size.y / (this.inputCount + 1);
pin.position.x = this.position.x;
pin.position.y = this.position.y + gap * (index + 1);
}
pin.draw();
});
this.outputPins.forEach((pin, index) => {
if (reposition) {
const gap = this.size.y / (this.outputCount + 1);
pin.position.x = this.position.x + this.size.x;
pin.position.y = this.position.y + gap * (index + 1);
}
pin.draw();
});
}
draw() {
this.circuit.drawRect(
this.circuit.getColor(this.color + "-b"),
this.position.x, this.position.y,
this.size.x, this.size.y
);
this.circuit.drawRect(
this.circuit.getColor(this.color + "-a"),
this.position.x + CHIP.padding, this.position.y + CHIP.padding,
this.size.x - CHIP.padding * 2, this.size.y - CHIP.padding * 2
);
this.circuit.drawText(
this.circuit.getColor(COLORS.chip.text),
"center",
this.position.x + this.size.x / 2, this.position.y + this.size.y / 2,
CHIP.fontSize,
this.name
);
this.drawPins();
}
}

View file

@ -0,0 +1,19 @@
import { CHIP } from "../../../config/apps/logicSim.config";
import Vector2 from "../../math/vector2";
import { Chip } from "./chip";
import { State } from "./state";
export class ChipsManager {
static CHIPS = {
AND: new Chip(null, "AND", "cyan", new Vector2(CHIP.width, CHIP.height), 2, 1).setLogic((inputStates: State[]) => {
if (inputStates[0].value === 1 && inputStates[1].value === 1) {
return [State.HIGH];
} else {
return [State.LOW];
}
}),
NOT: new Chip(null, "NOT", "yellow", new Vector2(CHIP.width, CHIP.height), 1, 1).setLogic((inputStates: State[]) => {
return [State.invert(inputStates[0])];
}),
};
}

View file

@ -1,4 +1,4 @@
import { BACKGROUND, COLORS, CURSORS, INPUT_OUTPUT, PIN, WIRE } from "../../../config/apps/logicSim.config";
import { BACKGROUND, COLORS, CURSORS, FONT, INPUT_OUTPUT, PIN } from "../../../config/apps/logicSim.config";
import Vector2 from "../../math/vector2";
import { Chip } from "./chip";
import { Pin } from "./pin";
@ -11,16 +11,19 @@ export class Circuit extends Chip {
context: CanvasRenderingContext2D;
colors: { [key: string]: string } = {};
mousePosition = Vector2.ZERO;
isCircuit = true;
isPlacing = false;
snapping = false;
placingWire: Wire;
wires: Wire[] = [];
placingChip: Chip;
chips: Chip[] = [];
cursor = CURSORS.default;
constructor(name: string, color: string, inputCount: number, outputCount: number) {
super(null, name, color, inputCount, outputCount);
super(null, name, color, null, inputCount, outputCount);
}
resize() {
@ -111,11 +114,18 @@ export class Circuit extends Chip {
}
}
if (this.placingChip != null) {
this.placingChip.position.x = this.mousePosition.x - this.placingChip.size.x / 2;
this.placingChip.position.y = this.mousePosition.y - this.placingChip.size.y / 2;
}
};
onClickPin(pin: Pin) {
onClickPin(pin: Pin, isInputOutput = false) {
const isInputPin = isInputOutput ? !pin.isInput : pin.isInput;
if (this.placingWire != null) {
if (!pin.isInput) {
if (isInputPin) {
this.placingWire.outputPin = pin;
this.placingWire.anchorPoints.pop();
@ -125,14 +135,15 @@ export class Circuit extends Chip {
this.placingWire = null;
this.isPlacing = false;
}
return;
}
const inputConnection = pin.isInput ? pin : null;
const outputConnection = pin.isInput ? null : pin;
const inputConnection = isInputPin ? null : pin;
const outputConnection = isInputPin ? pin : null;
const anchorPoint = this.mousePosition.clone;
this.placingWire = new Wire("blue", inputConnection, outputConnection, [anchorPoint]);
this.placingWire = new Wire(this, "red", inputConnection, outputConnection, [anchorPoint]);
this.wires.push(this.placingWire);
}
@ -147,6 +158,8 @@ export class Circuit extends Chip {
this.wires.pop();
return;
}
console.log(this);
} else if (event.button === 0) {
let eventComplete = false;
@ -155,7 +168,7 @@ export class Circuit extends Chip {
pin.setState(State.invert(pin.state));
eventComplete = true;
} else if (this.mousePosition.getDistance(pin.position.x, pin.position.y) <= PIN.radius) {
this.onClickPin(pin);
this.onClickPin(pin, true);
eventComplete = true;
}
});
@ -165,16 +178,37 @@ export class Circuit extends Chip {
this.outputPins.forEach((pin: Pin) => {
if (this.mousePosition.getDistance(pin.position.x, pin.position.y) <= PIN.radius) {
this.onClickPin(pin);
this.onClickPin(pin, true);
eventComplete = true;
}
});
if (eventComplete)
return;
this.chips.forEach((chip) => {
chip.inputPins.concat(chip.outputPins).forEach((pin) => {
if (this.mousePosition.getDistance(pin.position.x, pin.position.y) <= PIN.radius) {
this.onClickPin(pin);
eventComplete = true;
}
});
});
if (eventComplete)
return;
// Create wire anchor point
if (this.placingWire != null) {
this.placingWire.anchorPoints.push(this.mousePosition.clone);
return;
}
// Place chip
if (this.placingChip != null) {
this.placingChip = null;
this.isPlacing = false;
return;
}
}
};
@ -197,6 +231,19 @@ export class Circuit extends Chip {
}
};
addChip(chip: Chip) {
const newChip = new Chip(this, chip.name, chip.color, chip.size, chip.inputCount, chip.outputCount);
newChip.setLogic(chip.logic);
newChip.position = new Vector2(
this.mousePosition.x - newChip.size.x / 2,
this.mousePosition.y - newChip.size.y / 2
);
this.placingChip = newChip;
this.isPlacing = true;
this.chips.push(newChip);
}
getColor(key: string) {
if (this.colors[key] != null)
return this.colors[key];
@ -239,6 +286,14 @@ export class Circuit extends Chip {
this.context.stroke();
}
drawText(style: string, align: CanvasTextAlign, positionX: number, positionY: number, size: number, content: string) {
this.context.fillStyle = style;
this.context.textAlign = align;
this.context.textBaseline = "middle";
this.context.font = `bold ${size}px ${FONT}`;
this.context.fillText(content, positionX, positionY);
}
drawBackground() {
let offset = 0;
this.drawRect(this.getColor(COLORS.background.outer), offset, offset, this.size.x, this.size.y);
@ -260,28 +315,15 @@ export class Circuit extends Chip {
drawWires() {
this.wires.forEach((wire, index) => {
const positions = [...wire.anchorPoints];
if (wire.inputPin != null)
positions.unshift(wire.inputPin.position);
if (wire.outputPin != null)
positions.push(wire.outputPin.position);
let color: string;
const isPlacingWire = this.placingWire != null && index == this.wires.length - 1;
if (isPlacingWire) {
color = COLORS.wire.placing;
} else if (wire.state.value === 1) {
color = `${wire.color}-a`;
} else {
color = `${wire.color}-b`;
}
this.drawLine(this.getColor(color), positions, WIRE.width);
wire.draw(isPlacingWire);
});
}
drawChips() {
this.chips.forEach((chip) => { chip.draw(); });
}
drawInputOutput(state: State, isInteractable: boolean, positionX: number, positionY: number) {
let color: string;
@ -338,12 +380,13 @@ export class Circuit extends Chip {
this.drawInputOutput(pin.state, false, positionX, pin.position.y);
});
super.drawPins();
super.drawPins(false);
}
draw() {
this.drawBackground();
this.drawWires();
this.drawChips();
this.drawPins();
}

View file

@ -7,7 +7,7 @@ import { Wire } from "./wire";
export class Pin {
name: string;
position: Vector2;
position = Vector2.ZERO;
attachedChip: Chip;
index: number;
circuit: Circuit;
@ -34,9 +34,9 @@ export class Pin {
}
update() {
if (this.isInput) {
this.outputWire?.setState(this.state);
}
// TO DO: clean this mess
this.outputWire?.setState(this.state);
this.attachedChip?.update();
}
draw() {

View file

@ -1,4 +1,6 @@
import { WIRE } from "../../../config/apps/logicSim.config";
import Vector2 from "../../math/vector2";
import { Circuit } from "./circuit";
import { Pin } from "./pin";
import { State } from "./state";
@ -8,9 +10,10 @@ export class Wire {
inputPin: Pin;
outputPin: Pin;
anchorPoints: Vector2[];
circuit: Circuit;
constructor(color: string, inputPin?: Pin, outputPin?: Pin, anchorPoints?: Vector2[]) {
Object.assign(this, { color, inputPin, outputPin, anchorPoints });
constructor(circuit: Circuit, color: string, inputPin?: Pin, outputPin?: Pin, anchorPoints?: Vector2[]) {
Object.assign(this, { circuit, color, inputPin, outputPin, anchorPoints });
}
setState(state: State) {
@ -27,4 +30,25 @@ export class Wire {
this.outputPin.setState(this.state);
}
draw(isPlacingWire: boolean) {
const positions = [...this.anchorPoints];
if (this.inputPin != null)
positions.unshift(this.inputPin.position);
if (this.outputPin != null)
positions.push(this.outputPin.position);
let color: string;
if (isPlacingWire) {
color = `${this.color}-b`;
} else if (this.state.value === 1) {
color = `${this.color}-a`;
} else {
color = `${this.color}-d`;
}
this.circuit.drawLine(this.circuit.getColor(color), positions, WIRE.width);
}
}

View file

@ -1,22 +1,24 @@
:root {
--white-a: hsl(210, 54%, 95%);
--white-b: hsl(210, 45%, 90%);
--pink-a: #ff9ff3;
--pink-b: #f368e0;
--yellow-a: #feca57;
--yellow-b: #ff9f43;
--red-a: #ff6b6b;
--red-b: #ee5253;
--light-blue-a: #48dbfb;
--light-blue-b: #0abde3;
--green-a: #1dd1a1;
--green-b: #10ac84;
--cyan-a: #00d2d3;
--cyan-b: #01a3a4;
--blue-a: #54a0ff;
--blue-b: #2e86de;
--purple-a: #5f27cd;
--purple-b: #341f97;
--pink-a: hsl(308, 100%, 81%);
--pink-b: hsl(308, 85%, 68%);
--yellow-a: hsl(41, 99%, 67%);
--yellow-b: hsl(29, 100%, 63%);
--red-a: hsl(0, 100%, 71%);
--red-b: hsl(360, 82%, 63%);
--red-c: hsl(360, 64%, 55%);
--red-d: hsl(360, 46%, 47%);
--light-blue-a: hsl(191, 96%, 63%);
--light-blue-b: hsl(191, 92%, 46%);
--green-a: hsl(164, 76%, 47%);
--green-b: hsl(165, 83%, 37%);
--cyan-a: hsl(180, 100%, 41%);
--cyan-b: hsl(180, 99%, 32%);
--blue-a: hsl(213, 100%, 66%);
--blue-b: hsl(210, 73%, 53%);
--purple-a: hsl(260, 68%, 48%);
--purple-b: hsl(251, 66%, 36%);
--grey-a: hsl(210, 36%, 85%);
--grey-aa: hsl(210, 27%, 72.5%);
--grey-b: hsl(210, 18%, 60%);