diff --git a/editor/src/App.tsx b/editor/src/App.tsx index 9f6c942..00365d6 100644 --- a/editor/src/App.tsx +++ b/editor/src/App.tsx @@ -1,12 +1,17 @@ -import { type ReactElement } from "react"; +import { useState, type ReactElement } from "react"; import "./App.css"; -import Editor from "./Editor"; +import Canvas from "./Canvas"; +import { Editor } from "./Editor"; +import Toolbar from "./Toolbar"; function App(): ReactElement { + const [editor] = useState(new Editor()); + return ( <>

nandsim

- + + ); } diff --git a/editor/src/Editor.css b/editor/src/Canvas.css similarity index 80% rename from editor/src/Editor.css rename to editor/src/Canvas.css index 51015f8..6d8e94e 100644 --- a/editor/src/Editor.css +++ b/editor/src/Canvas.css @@ -1,5 +1,5 @@ -.Editor { +.EditorView { canvas { image-rendering: pixelated; } diff --git a/editor/src/Canvas.tsx b/editor/src/Canvas.tsx new file mode 100644 index 0000000..44efc05 --- /dev/null +++ b/editor/src/Canvas.tsx @@ -0,0 +1,51 @@ +import { useEffect, useRef, type ReactElement } from "react"; +import "./Canvas.css"; +import { V2, type Editor } from "./Editor"; + +type Props = { editor: Editor }; + +function Canvas({ editor }: Props): ReactElement { + const ref = useRef(null); + + useEffect(() => { + if (!ref.current) return; + + editor.render(ref.current); + }); + + return ( + <> +
+ { + const pos = V2(ev.nativeEvent.offsetX, ev.nativeEvent.offsetY); + editor.mouseDown(pos); + editor.renderIfNeeded(ev.target as HTMLCanvasElement); + }} + onMouseUp={(ev) => { + const pos = V2(ev.nativeEvent.offsetX, ev.nativeEvent.offsetY); + editor.mouseUp(pos); + editor.renderIfNeeded(ev.target as HTMLCanvasElement); + }} + onMouseMove={(ev) => { + editor.mouseMove(V2(ev.movementX, ev.movementY)); + editor.renderIfNeeded(ev.target as HTMLCanvasElement); + }} + onKeyDown={(ev) => { + console.log(ev.key); + }} + onKeyUp={(ev) => { + console.log(ev.key); + }} + /> +
+ + ); +} + +export default Canvas; diff --git a/editor/src/Editor.ts b/editor/src/Editor.ts new file mode 100644 index 0000000..31fe37f --- /dev/null +++ b/editor/src/Editor.ts @@ -0,0 +1,58 @@ +export type V2 = { x: number; y: number }; +export const V2 = (x: number, y: number): V2 => ({ x, y }); + +export class Editor { + private offset = V2(0, 0); + private dragging = false; + private renderNeeded = false; + + render(canvas: HTMLCanvasElement) { + const cx = canvas.getContext("2d")!; + + cx.imageSmoothingEnabled = false; + cx.fillStyle = "#666"; + cx.fillRect(0, 0, canvas.width, canvas.height); + + const gridSize = { x: 20, y: 20 }; + const dotSize = { x: 2, y: 2 }; + + cx.fillStyle = "#111"; + for (let y = 0; y < canvas.width / gridSize.x + 1; ++y) { + for (let x = 0; x < canvas.height / gridSize.y + 1; ++x) { + cx.fillRect( + (this.offset.x % gridSize.x) + x * gridSize.x - dotSize.x / 2, + (this.offset.y % gridSize.y) + y * gridSize.y - dotSize.y / 2, + dotSize.x, + dotSize.y, + ); + } + } + } + + renderIfNeeded(canvas: HTMLCanvasElement) { + if (this.renderNeeded) { + this.render(canvas); + this.renderNeeded = false; + } + } + + mouseDown(pos: V2) { + this.dragging = true; + } + + mouseUp(pos: V2) { + this.dragging = false; + } + + mouseMove(deltaPos: V2) { + if (this.dragging) { + this.offset.x += deltaPos.x; + this.offset.y += deltaPos.y; + this.renderNeeded = true; + } + } + + selectTool(tool: Tool) {} +} + +type Tool = "and" | "not" | "pin in" | "pin out"; diff --git a/editor/src/Editor.tsx b/editor/src/Editor.tsx deleted file mode 100644 index af27945..0000000 --- a/editor/src/Editor.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import { useEffect, useRef, type ReactElement } from "react"; -import "./Editor.css"; - -function Editor(): ReactElement { - const ref = useRef(null); - - useEffect(() => { - if (!ref.current) return; - - let offset = { x: 0, y: 0 }; - let dragging = false; - - const canvas = ref.current; - const cx = canvas.getContext("2d")!; - cx.imageSmoothingEnabled = false; - - const render = () => { - cx.fillStyle = "white"; - cx.fillRect(0, 0, canvas.width, canvas.height); - - const gridSize = { x: 20, y: 20 }; - const dotSize = { x: 4, y: 4 }; - - cx.fillStyle = "gray"; - for (let y = 0; y < canvas.width / gridSize.x + 1; ++y) { - for (let x = 0; x < canvas.height / gridSize.y + 1; ++x) { - cx.fillRect( - (offset.x % gridSize.x) + x * gridSize.x - dotSize.x / 2, - (offset.y % gridSize.y) + y * gridSize.y - dotSize.y / 2, - dotSize.x, - dotSize.y, - ); - } - } - }; - - function mousedownHandler(ev: MouseEvent) { - dragging = true; - } - function mouseupHandler(ev: MouseEvent) { - dragging = false; - } - function mousemoveHandler(ev: MouseEvent) { - if (dragging) { - offset.x += ev.movementX; - offset.y += ev.movementY; - render(); - } - } - - canvas.addEventListener("mousedown", mousedownHandler); - canvas.addEventListener("mouseup", mouseupHandler); - canvas.addEventListener("mousemove", mousemoveHandler); - - render(); - - return () => { - canvas.removeEventListener("mousedown", mousedownHandler); - canvas.removeEventListener("mouseup", mouseupHandler); - canvas.removeEventListener("mousemove", mousemoveHandler); - }; - }); - - return ( - <> -
- -
- - ); -} - -export default Editor; diff --git a/editor/src/Toolbar.tsx b/editor/src/Toolbar.tsx new file mode 100644 index 0000000..0334570 --- /dev/null +++ b/editor/src/Toolbar.tsx @@ -0,0 +1,19 @@ +import type { ReactElement } from "react"; +import type { Editor } from "./Editor"; + +type Props = { editor: Editor }; + +function Toolbar({ editor }: Props): ReactElement { + return ( + <> +
+ + + + +
+ + ); +} + +export default Toolbar;