add tabbar

This commit is contained in:
sfja 2026-06-10 05:19:22 +02:00
parent 022e8b89f7
commit a0f0486590
8 changed files with 148 additions and 51 deletions

View File

@ -3,6 +3,7 @@ import "./style.css";
import Canvas from "./Canvas"; import Canvas from "./Canvas";
import { Editor } from "./editor/Editor"; import { Editor } from "./editor/Editor";
import Toolbar from "./Toolbar"; import Toolbar from "./Toolbar";
import Tabbar from "./Tabbar";
function App(): ReactElement { function App(): ReactElement {
const [editor] = useState(() => new Editor()); const [editor] = useState(() => new Editor());
@ -12,8 +13,13 @@ function App(): ReactElement {
<> <>
<h1>nandsim</h1> <h1>nandsim</h1>
<div className="Editor"> <div className="Editor">
<Toolbar editor={editor} canvasRef={canvasRef} /> <div>
<Canvas editor={editor} canvasRef={canvasRef} width={800} height={800} /> <Toolbar editor={editor} canvasRef={canvasRef} />
</div>
<main>
<Tabbar editor={editor} />
<Canvas editor={editor} canvasRef={canvasRef} />
</main>
</div> </div>
</> </>
); );

View File

@ -5,11 +5,9 @@ import { v2 } from "./editor/V2";
type Props = { type Props = {
editor: Editor; editor: Editor;
canvasRef: RefObject<HTMLCanvasElement | null>; canvasRef: RefObject<HTMLCanvasElement | null>;
width: number;
height: number;
}; };
function Canvas({ editor, canvasRef, width, height }: Props): ReactElement { function Canvas({ editor, canvasRef }: Props): ReactElement {
useEffect(() => { useEffect(() => {
if (!canvasRef.current) return; if (!canvasRef.current) return;
@ -21,9 +19,7 @@ function Canvas({ editor, canvasRef, width, height }: Props): ReactElement {
<div className="Canvas"> <div className="Canvas">
<canvas <canvas
ref={canvasRef} ref={canvasRef}
width={width} style={{ backgroundColor: "black" }}
height={height}
style={{ width, height, backgroundColor: "black" }}
tabIndex={0} tabIndex={0}
onMouseDown={(ev) => { onMouseDown={(ev) => {
const pos = v2(ev.nativeEvent.offsetX, ev.nativeEvent.offsetY); const pos = v2(ev.nativeEvent.offsetX, ev.nativeEvent.offsetY);

20
editor/src/Tabbar.tsx Normal file
View File

@ -0,0 +1,20 @@
import { useEffect, useState, type ReactElement } from "react";
import type { Editor } from "./editor/Editor";
type Props = { editor: Editor };
function Tabbar({ editor }: Props): ReactElement {
const [selectedTool, setSelectedTool] = useState("select");
return (
<>
<div className="Tabbar">
<button className="active">&lt;unnamed&gt;</button>
<button>Component one</button>
<button>Another components</button>
</div>
</>
);
}
export default Tabbar;

View File

@ -15,18 +15,24 @@ function Toolbar({ editor, canvasRef }: Props): ReactElement {
return ( return (
<> <>
<div className="Toolbar"> <div className="Toolbar">
{editor.tools().map((tool, key) => ( <h2>Toolbar</h2>
<button <div>
key={`${key}`} {editor.tools().map((tool, key) => (
className={selectedTool === tool ? "active" : ""} <button
onClick={() => { key={`${key}`}
editor.events.send({ tag: "SelectTool", tool }); className={selectedTool === tool ? "active" : ""}
canvasRef.current?.focus(); onClick={() => {
}} editor.events.send({ tag: "SelectTool", tool });
> canvasRef.current?.focus();
{tool} }}
</button> >
))} {tool}
</button>
))}
</div>
<div>
<button className="add">+</button>
</div>
</div> </div>
</> </>
); );

View File

@ -1,6 +1,5 @@
import { Cx, type Tool } from "./Cx"; import { Cx, type Tool } from "./Cx";
import { EventBus } from "./events"; import { EventBus } from "./events";
import { V2 } from "./V2";
export class Editor { export class Editor {
public events = new EventBus(); public events = new EventBus();

View File

@ -14,6 +14,13 @@ export class Renderer {
clear() { clear() {
const { canvas, c } = this; const { canvas, c } = this;
const width = canvas.offsetWidth;
const height = canvas.offsetHeight;
canvas.width = width;
canvas.height = height;
c.fillStyle = "#666"; c.fillStyle = "#666";
c.fillRect(0, 0, canvas.width, canvas.height); c.fillRect(0, 0, canvas.width, canvas.height);
} }
@ -25,8 +32,8 @@ export class Renderer {
const gridSize = v2(20, 20); const gridSize = v2(20, 20);
c.fillStyle = "#111"; c.fillStyle = "#111";
for (let y = 0; y < canvas.width / gridSize.x + 1; ++y) { for (let y = 0; y < canvas.height / gridSize.y + 1; ++y) {
for (let x = 0; x < canvas.height / gridSize.y + 1; ++x) { for (let x = 0; x < canvas.width / gridSize.x + 1; ++x) {
c.fillRect( c.fillRect(
(this.offset.x % gridSize.x) + x * gridSize.x - dotSize.x / 2, (this.offset.x % gridSize.x) + x * gridSize.x - dotSize.x / 2,
(this.offset.y % gridSize.y) + y * gridSize.y - dotSize.y / 2, (this.offset.y % gridSize.y) + y * gridSize.y - dotSize.y / 2,

View File

@ -2,14 +2,21 @@
color-scheme: light dark; color-scheme: light dark;
} }
* {
font-family: sans;
}
#root { #root {
margin: 0 auto; margin: 0 auto;
min-height: 100svh; height: 100svh;
text-align: center; text-align: center;
display: flex;
flex-direction: column;
} }
body { body {
margin: 0; margin: 0;
height: 100vh;
} }
h1 { h1 {

View File

@ -1,37 +1,93 @@
.Editor { .Editor {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: center; justify-content: center;
height: 100%;
.Toolbar {
.Toolbar {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 2px; gap: 2px;
max-width: 300px;
height: 100%;
background-color: #2b2a33;
button { h2 {
padding-left: 5px; margin: 0;
padding-right: 5px;
padding-top: 5px;
padding-bottom: 5px;
font-size: 1rem;
text-transform: capitalize;
width: 200px;
text-align: left;
border: 2px solid gray;
border-radius: 5px;
}
button.active {
border: 2px solid #ff8800;
}
} }
.Canvas { button {
canvas { padding-left: 20px;
image-rendering: pixelated; padding-right: 20px;
} padding-top: 5px;
padding-bottom: 5px;
font-size: 1rem;
text-transform: capitalize;
font-weight: bold;
min-width: 200px;
width: 100%;
text-align: left;
border: none;
border-bottom: 2px solid #2b2a33;
} }
button.active {
background-color: #373541;
border-bottom: 2px solid #ff8800;
}
button.add {
text-align: center;
}
}
.Tabbar {
display: flex;
flex-direction: row;
gap: 2px;
button {
padding-left: 5px;
padding-right: 5px;
padding-top: 5px;
padding-bottom: 5px;
font-size: 1rem;
text-transform: capitalize;
max-width: 200px;
text-align: left;
border-radius: 10px 10px 0 0;
border: none;
border-bottom: 2px solid #2b2a33;
}
button.active {
border-bottom: 2px solid #ff8800;
}
}
.Canvas {
width: 100%;
height: 100%;
canvas {
width: 100%;
height: 100%;
image-rendering: pixelated;
}
}
> div,
> main {
display: flex;
flex-direction: column;
}
main {
height: 100%;
width: 100%;
}
} }