add editor
This commit is contained in:
parent
4640b579ec
commit
eb40f5fba9
@ -1,12 +1,17 @@
|
|||||||
import { type ReactElement } from "react";
|
import { useState, type ReactElement } from "react";
|
||||||
import "./App.css";
|
import "./App.css";
|
||||||
import Editor from "./Editor";
|
import Canvas from "./Canvas";
|
||||||
|
import { Editor } from "./Editor";
|
||||||
|
import Toolbar from "./Toolbar";
|
||||||
|
|
||||||
function App(): ReactElement {
|
function App(): ReactElement {
|
||||||
|
const [editor] = useState(new Editor());
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h1>nandsim</h1>
|
<h1>nandsim</h1>
|
||||||
<Editor></Editor>
|
<Canvas editor={editor} />
|
||||||
|
<Toolbar editor={editor} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
|
|
||||||
.Editor {
|
.EditorView {
|
||||||
canvas {
|
canvas {
|
||||||
image-rendering: pixelated;
|
image-rendering: pixelated;
|
||||||
}
|
}
|
||||||
51
editor/src/Canvas.tsx
Normal file
51
editor/src/Canvas.tsx
Normal file
@ -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<HTMLCanvasElement | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!ref.current) return;
|
||||||
|
|
||||||
|
editor.render(ref.current);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="EditorView">
|
||||||
|
<canvas
|
||||||
|
ref={ref}
|
||||||
|
width={1000}
|
||||||
|
height={1000}
|
||||||
|
style={{ width: 1000, height: 1000, backgroundColor: "black" }}
|
||||||
|
tabIndex={0}
|
||||||
|
onMouseDown={(ev) => {
|
||||||
|
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);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Canvas;
|
||||||
58
editor/src/Editor.ts
Normal file
58
editor/src/Editor.ts
Normal file
@ -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";
|
||||||
@ -1,78 +0,0 @@
|
|||||||
import { useEffect, useRef, type ReactElement } from "react";
|
|
||||||
import "./Editor.css";
|
|
||||||
|
|
||||||
function Editor(): ReactElement {
|
|
||||||
const ref = useRef<HTMLCanvasElement | null>(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 (
|
|
||||||
<>
|
|
||||||
<div className="Editor">
|
|
||||||
<canvas
|
|
||||||
ref={ref}
|
|
||||||
width={1000}
|
|
||||||
height={1000}
|
|
||||||
style={{ width: 1000, height: 1000, backgroundColor: "black" }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Editor;
|
|
||||||
19
editor/src/Toolbar.tsx
Normal file
19
editor/src/Toolbar.tsx
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import type { ReactElement } from "react";
|
||||||
|
import type { Editor } from "./Editor";
|
||||||
|
|
||||||
|
type Props = { editor: Editor };
|
||||||
|
|
||||||
|
function Toolbar({ editor }: Props): ReactElement {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div>
|
||||||
|
<button onClick={() => editor.selectTool("and")}>and</button>
|
||||||
|
<button onClick={() => editor.selectTool("not")}>not</button>
|
||||||
|
<button onClick={() => editor.selectTool("pin in")}>pin in</button>
|
||||||
|
<button onClick={() => editor.selectTool("pin out")}>pin out</button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Toolbar;
|
||||||
Loading…
x
Reference in New Issue
Block a user