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 (
+ <>
+
+
+ >
+ );
+}
+
+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;