add placing

This commit is contained in:
sfja 2026-05-11 23:36:53 +02:00
parent 3a38e90178
commit 70ad562bde
4 changed files with 52 additions and 25 deletions

View File

@ -1,18 +1,19 @@
import { useState, type ReactElement } from "react"; import { useRef, useState, type ReactElement } from "react";
import "./style.css"; import "./style.css";
import Canvas from "./Canvas"; import Canvas from "./Canvas";
import { Editor } from "./Editor"; import { Editor } from "./Editor";
import Toolbar from "./Toolbar"; import Toolbar from "./Toolbar";
function App(): ReactElement { function App(): ReactElement {
const [editor] = useState(new Editor()); const [editor] = useState(() => new Editor());
const canvasRef = useRef<HTMLCanvasElement | null>(null);
return ( return (
<> <>
<h1>nandsim</h1> <h1>nandsim</h1>
<div className="Editor"> <div className="Editor">
<Toolbar editor={editor} /> <Toolbar editor={editor} canvasRef={canvasRef} />
<Canvas editor={editor} /> <Canvas editor={editor} canvasRef={canvasRef} />
</div> </div>
</> </>
); );

View File

@ -1,22 +1,20 @@
import { useEffect, useRef, type ReactElement } from "react"; import { useEffect, useRef, type ReactElement, type RefObject } from "react";
import { V2, type Editor } from "./Editor"; import { V2, type Editor } from "./Editor";
type Props = { editor: Editor }; type Props = { editor: Editor; canvasRef: RefObject<HTMLCanvasElement | null> };
function Canvas({ editor }: Props): ReactElement {
const ref = useRef<HTMLCanvasElement | null>(null);
function Canvas({ editor, canvasRef }: Props): ReactElement {
useEffect(() => { useEffect(() => {
if (!ref.current) return; if (!canvasRef.current) return;
editor.render(ref.current); editor.render(canvasRef.current);
}); });
return ( return (
<> <>
<div className="Canvas"> <div className="Canvas">
<canvas <canvas
ref={ref} ref={canvasRef}
width={1000} width={1000}
height={1000} height={1000}
style={{ width: 1000, height: 1000, backgroundColor: "black" }} style={{ width: 1000, height: 1000, backgroundColor: "black" }}

View File

@ -136,8 +136,8 @@ class Cx {
); );
} }
transitionTo<S extends { new (cx: Cx): State }>(S: S) { transitionTo(newState: State) {
this.state = new S(this); this.state = newState;
this.notifyListeners(); this.notifyListeners();
} }
@ -188,19 +188,21 @@ class Normal implements State {
selectTool(tool: Tool): void { selectTool(tool: Tool): void {
switch (tool) { switch (tool) {
case "pan": case "pan":
this.cx.transitionTo(Panning); this.cx.transitionTo(new Panning(this.cx));
break; break;
case "and":
this.cx.transitionTo(new Placing(this.cx, "and"));
} }
} }
onMouseDown(pos: V2): void { onMouseDown(pos: V2): void {
this.cx.addSelectionRect(pos); this.cx.addSelectionRect(pos);
this.cx.transitionTo(Selecting); this.cx.transitionTo(new Selecting(this.cx));
} }
onKeyDown(key: string): void { onKeyDown(key: string): void {
if (key === "Shift") { if (key === "Shift") {
this.cx.transitionTo(Panning); this.cx.transitionTo(new Panning(this.cx));
return; return;
} }
} }
@ -231,20 +233,20 @@ class Panning implements State {
onKeyDown(key: string): void { onKeyDown(key: string): void {
if (key === "Escape") { if (key === "Escape") {
this.cx.transitionTo(Normal); this.cx.transitionTo(new Normal(this.cx));
return; return;
} }
} }
onKeyUp(key: string): void { onKeyUp(key: string): void {
if (key === "Shift") { if (key === "Shift") {
this.cx.transitionTo(Normal); this.cx.transitionTo(new Normal(this.cx));
return; return;
} }
} }
selectTool(tool: Tool): void { selectTool(tool: Tool): void {
this.cx.transitionTo(Normal); this.cx.transitionTo(new Normal(this.cx));
this.cx.selectTool(tool); this.cx.selectTool(tool);
} }
@ -263,7 +265,7 @@ class Selecting implements State {
onMouseUp(_pos: V2): void { onMouseUp(_pos: V2): void {
this.cx.removeSelectionRect(); this.cx.removeSelectionRect();
this.cx.transitionTo(Normal); this.cx.transitionTo(new Normal(this.cx));
} }
onMouseMove(deltaPos: V2): void { onMouseMove(deltaPos: V2): void {
@ -275,4 +277,27 @@ class Selecting implements State {
} }
} }
class Placing implements State {
constructor(
private cx: Cx,
private tool: Tool,
) {}
onMouseUp(pos: V2): void {
this.cx.transitionTo(new Normal(this.cx));
console.log("place");
}
onKeyDown(key: string): void {
if (key === "Escape") {
this.cx.transitionTo(new Normal(this.cx));
return;
}
}
selectedTool(): Tool | null {
return this.tool;
}
}
type Tool = "select" | "pan" | "and"; type Tool = "select" | "pan" | "and";

View File

@ -1,14 +1,14 @@
import { useEffect, useState, type ReactElement } from "react"; import { useEffect, useState, type ReactElement, type RefObject } from "react";
import type { Editor } from "./Editor"; import type { Editor } from "./Editor";
type Props = { editor: Editor }; type Props = { editor: Editor; canvasRef: RefObject<HTMLCanvasElement | null> };
function useUpdate(): [number, () => void] { function useUpdate(): [number, () => void] {
const [value, setValue] = useState(0); const [value, setValue] = useState(0);
return [value, () => setValue(value + 1)] as const; return [value, () => setValue(value + 1)] as const;
} }
function Toolbar({ editor }: Props): ReactElement { function Toolbar({ editor, canvasRef }: Props): ReactElement {
const [uid, update] = useUpdate(); const [uid, update] = useUpdate();
useEffect(() => { useEffect(() => {
@ -23,7 +23,10 @@ function Toolbar({ editor }: Props): ReactElement {
<button <button
key={`${uid}${key}`} key={`${uid}${key}`}
className={editor.selectedTool() === tool ? "active" : ""} className={editor.selectedTool() === tool ? "active" : ""}
onClick={() => editor.selectTool(tool)} onClick={() => {
editor.selectTool(tool);
canvasRef.current?.focus();
}}
> >
{tool} {tool}
</button> </button>