From 445c10d68d9d1121685b92f5ac5fcca2063a20c2 Mon Sep 17 00:00:00 2001 From: Prozilla Date: Sun, 2 Jun 2024 21:27:50 +0200 Subject: [PATCH] Added logic gates to logic sim app --- src/components/actions/Actions.tsx | 1 - src/components/apps/logic-sim/CircuitView.tsx | 16 ++- .../apps/text-editor/TextEditor.tsx | 4 +- src/config/apps/logicSim.config.ts | 14 ++- src/features/apps/logic-sim/chip.ts | 67 ++++++++++--- src/features/apps/logic-sim/chipsManager.ts | 19 ++++ src/features/apps/logic-sim/circuit.ts | 99 +++++++++++++------ src/features/apps/logic-sim/pin.ts | 8 +- src/features/apps/logic-sim/wire.ts | 28 +++++- src/styles/global/variables.css | 34 ++++--- 10 files changed, 222 insertions(+), 68 deletions(-) create mode 100644 src/features/apps/logic-sim/chipsManager.ts diff --git a/src/components/actions/Actions.tsx b/src/components/actions/Actions.tsx index 2a86e13..d0fe1d5 100644 --- a/src/components/actions/Actions.tsx +++ b/src/components/actions/Actions.tsx @@ -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); } diff --git a/src/components/apps/logic-sim/CircuitView.tsx b/src/components/apps/logic-sim/CircuitView.tsx index 1afd41a..226b5d2 100644 --- a/src/components/apps/logic-sim/CircuitView.tsx +++ b/src/components/apps/logic-sim/CircuitView.tsx @@ -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 ; + return <> + + + { circuit.addChip(ChipsManager.CHIPS.AND); }}/> + { circuit.addChip(ChipsManager.CHIPS.NOT); }}/> + + + + ; } \ No newline at end of file diff --git a/src/components/apps/text-editor/TextEditor.tsx b/src/components/apps/text-editor/TextEditor.tsx index 6f4a1e5..13b0eb0 100644 --- a/src/components/apps/text-editor/TextEditor.tsx +++ b/src/components/apps/text-editor/TextEditor.tsx @@ -127,9 +127,7 @@ export function TextEditor({ file, setTitle, setIconUrl, close, mode, app, modal return (
- { - console.log(event); - }}> + { newText(); }} shortcut={["Control", "e"]}/> { diff --git a/src/config/apps/logicSim.config.ts b/src/config/apps/logicSim.config.ts index fb58dcf..6507f14 100644 --- a/src/config/apps/logicSim.config.ts +++ b/src/config/apps/logicSim.config.ts @@ -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" + } }; \ No newline at end of file diff --git a/src/features/apps/logic-sim/chip.ts b/src/features/apps/logic-sim/chip.ts index 3d3c615..d4599b2 100644 --- a/src/features/apps/logic-sim/chip.ts +++ b/src/features/apps/logic-sim/chip.ts @@ -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(); } } \ No newline at end of file diff --git a/src/features/apps/logic-sim/chipsManager.ts b/src/features/apps/logic-sim/chipsManager.ts new file mode 100644 index 0000000..16d7fef --- /dev/null +++ b/src/features/apps/logic-sim/chipsManager.ts @@ -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])]; + }), + }; +} \ No newline at end of file diff --git a/src/features/apps/logic-sim/circuit.ts b/src/features/apps/logic-sim/circuit.ts index 1d80282..0f2baf3 100644 --- a/src/features/apps/logic-sim/circuit.ts +++ b/src/features/apps/logic-sim/circuit.ts @@ -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(); } diff --git a/src/features/apps/logic-sim/pin.ts b/src/features/apps/logic-sim/pin.ts index 4f51edf..52a98ba 100644 --- a/src/features/apps/logic-sim/pin.ts +++ b/src/features/apps/logic-sim/pin.ts @@ -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() { diff --git a/src/features/apps/logic-sim/wire.ts b/src/features/apps/logic-sim/wire.ts index 0915d8f..4876205 100644 --- a/src/features/apps/logic-sim/wire.ts +++ b/src/features/apps/logic-sim/wire.ts @@ -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); + } } \ No newline at end of file diff --git a/src/styles/global/variables.css b/src/styles/global/variables.css index d93e8e0..ffdfb1e 100644 --- a/src/styles/global/variables.css +++ b/src/styles/global/variables.css @@ -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%);