Added logic gates to logic sim app
This commit is contained in:
parent
54249b4ba6
commit
445c10d68d
10 changed files with 222 additions and 68 deletions
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}/>
|
||||
</>;
|
||||
}
|
||||
|
|
@ -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={() => {
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
};
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
19
src/features/apps/logic-sim/chipsManager.ts
Normal file
19
src/features/apps/logic-sim/chipsManager.ts
Normal 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])];
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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%);
|
||||
|
|
|
|||
Loading…
Reference in a new issue