fix project
This commit is contained in:
parent
ae14bc3724
commit
c1e72f9ae2
@ -11,13 +11,12 @@ function App(): ReactElement {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h1>nandsim</h1>
|
|
||||||
<div className="Editor">
|
<div className="Editor">
|
||||||
<div>
|
<div>
|
||||||
<Toolbar editor={editor} canvasRef={canvasRef} />
|
<Toolbar editor={editor} canvasRef={canvasRef} />
|
||||||
</div>
|
</div>
|
||||||
<main>
|
<main>
|
||||||
<Tabbar editor={editor} />
|
<Tabbar editor={editor} canvasRef={canvasRef} />
|
||||||
<Canvas editor={editor} canvasRef={canvasRef} />
|
<Canvas editor={editor} canvasRef={canvasRef} />
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -9,9 +9,28 @@ type Props = {
|
|||||||
|
|
||||||
function Canvas({ editor, canvasRef }: Props): ReactElement {
|
function Canvas({ editor, canvasRef }: Props): ReactElement {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!canvasRef.current) return;
|
if (canvasRef.current) {
|
||||||
|
|
||||||
editor.render(canvasRef.current);
|
editor.render(canvasRef.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
const unsubscribe = editor.events.subscribe(["RenderRequest"], (ev) => {
|
||||||
|
if (canvasRef.current) {
|
||||||
|
editor.render(canvasRef.current);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function onResize() {
|
||||||
|
if (canvasRef.current) {
|
||||||
|
editor.render(canvasRef.current);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener("resize", onResize);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("resize", onResize);
|
||||||
|
unsubscribe();
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -24,30 +43,24 @@ function Canvas({ editor, canvasRef }: Props): ReactElement {
|
|||||||
onMouseDown={(ev) => {
|
onMouseDown={(ev) => {
|
||||||
const pos = v2(ev.nativeEvent.offsetX, ev.nativeEvent.offsetY);
|
const pos = v2(ev.nativeEvent.offsetX, ev.nativeEvent.offsetY);
|
||||||
editor.events.send({ tag: "MouseDown", pos });
|
editor.events.send({ tag: "MouseDown", pos });
|
||||||
editor.renderIfNeeded(ev.target as HTMLCanvasElement);
|
|
||||||
}}
|
}}
|
||||||
onMouseUp={(ev) => {
|
onMouseUp={(ev) => {
|
||||||
const pos = v2(ev.nativeEvent.offsetX, ev.nativeEvent.offsetY);
|
const pos = v2(ev.nativeEvent.offsetX, ev.nativeEvent.offsetY);
|
||||||
editor.events.send({ tag: "MouseUp", pos });
|
editor.events.send({ tag: "MouseUp", pos });
|
||||||
editor.renderIfNeeded(ev.target as HTMLCanvasElement);
|
|
||||||
}}
|
}}
|
||||||
onMouseMove={(ev) => {
|
onMouseMove={(ev) => {
|
||||||
const deltaPos = v2(ev.movementX, ev.movementY);
|
const deltaPos = v2(ev.movementX, ev.movementY);
|
||||||
const pos = v2(ev.nativeEvent.offsetX, ev.nativeEvent.offsetY);
|
const pos = v2(ev.nativeEvent.offsetX, ev.nativeEvent.offsetY);
|
||||||
editor.events.send({ tag: "MouseMove", pos, deltaPos });
|
editor.events.send({ tag: "MouseMove", pos, deltaPos });
|
||||||
editor.renderIfNeeded(ev.target as HTMLCanvasElement);
|
|
||||||
}}
|
}}
|
||||||
onMouseLeave={(ev) => {
|
onMouseLeave={(_ev) => {
|
||||||
editor.events.send({ tag: "MouseLeave" });
|
editor.events.send({ tag: "MouseLeave" });
|
||||||
editor.renderIfNeeded(ev.target as HTMLCanvasElement);
|
|
||||||
}}
|
}}
|
||||||
onKeyDown={(ev) => {
|
onKeyDown={(ev) => {
|
||||||
editor.events.send({ tag: "KeyDown", key: ev.key });
|
editor.events.send({ tag: "KeyDown", key: ev.key });
|
||||||
editor.renderIfNeeded(ev.target as HTMLCanvasElement);
|
|
||||||
}}
|
}}
|
||||||
onKeyUp={(ev) => {
|
onKeyUp={(ev) => {
|
||||||
editor.events.send({ tag: "KeyUp", key: ev.key });
|
editor.events.send({ tag: "KeyUp", key: ev.key });
|
||||||
editor.renderIfNeeded(ev.target as HTMLCanvasElement);
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,17 +1,63 @@
|
|||||||
import { useEffect, useState, type ReactElement } from "react";
|
import { useEffect, useState, type ReactElement, type RefObject } from "react";
|
||||||
import type { Editor } from "./editor/Editor";
|
import type { Editor } from "./editor/Editor";
|
||||||
|
|
||||||
type Props = { editor: Editor };
|
type Props = { editor: Editor; canvasRef: RefObject<HTMLCanvasElement | null> };
|
||||||
|
|
||||||
function Tabbar({ editor }: Props): ReactElement {
|
function Tabbar({ editor, canvasRef }: Props): ReactElement {
|
||||||
const [selectedTool, setSelectedTool] = useState("select");
|
const [updateId, update] = useState(0);
|
||||||
|
const [selectedTab, setSelectedTab] = useState(0);
|
||||||
|
|
||||||
|
useEffect(() =>
|
||||||
|
editor.events.subscribe(["ShowSelectedTab"], (ev) => {
|
||||||
|
setSelectedTab(ev.idx);
|
||||||
|
update(updateId + 1);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="Tabbar">
|
<div className="Tabbar">
|
||||||
<button className="active"><unnamed></button>
|
<div>
|
||||||
<button>Component one</button>
|
{editor.availableBoardEditors().map((tab, idx) => (
|
||||||
<button>Another components</button>
|
<button
|
||||||
|
key={`${idx}${updateId}`}
|
||||||
|
className={selectedTab === idx ? "active" : ""}
|
||||||
|
onClick={() => {
|
||||||
|
editor.events.send({ tag: "SelectTab", idx });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{tab}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
<button
|
||||||
|
className="add"
|
||||||
|
onClick={() => {
|
||||||
|
editor.events.send({ tag: "CreateTab" });
|
||||||
|
canvasRef.current?.focus();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
+
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
editor.events.send({ tag: "SaveComponent" });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
const name = prompt("New component name:");
|
||||||
|
if (!name) return;
|
||||||
|
editor.events.send({ tag: "RenameComponent", newName: name });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Rename
|
||||||
|
</button>
|
||||||
|
<button onClick={() => {}}>Close</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -17,9 +17,9 @@ function Toolbar({ editor, canvasRef }: Props): ReactElement {
|
|||||||
<div className="Toolbar">
|
<div className="Toolbar">
|
||||||
<h2>Toolbar</h2>
|
<h2>Toolbar</h2>
|
||||||
<div>
|
<div>
|
||||||
{editor.tools().map((tool, key) => (
|
{editor.availableTools().map((tool, key) => (
|
||||||
<button
|
<button
|
||||||
key={`${key}`}
|
key={key}
|
||||||
className={selectedTool === tool ? "active" : ""}
|
className={selectedTool === tool ? "active" : ""}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
editor.events.send({ tag: "SelectTool", tool });
|
editor.events.send({ tag: "SelectTool", tool });
|
||||||
@ -30,9 +30,7 @@ function Toolbar({ editor, canvasRef }: Props): ReactElement {
|
|||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div></div>
|
||||||
<button className="add">+</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -21,6 +21,27 @@ export class Board {
|
|||||||
|
|
||||||
constructor() {}
|
constructor() {}
|
||||||
|
|
||||||
|
static withExample(repo: ComponentRepo): Board {
|
||||||
|
const board = new Board();
|
||||||
|
board.placeComponent(repo.get("input"), v2(100, 100));
|
||||||
|
board.placeComponent(repo.get("input"), v2(100, 200));
|
||||||
|
board.placeComponent(repo.get("and"), v2(300, 150));
|
||||||
|
board.placeComponent(repo.get("output"), v2(500, 150));
|
||||||
|
board.addWire(
|
||||||
|
{ tag: "OutputPin", comp: board.components[0], i: 0 },
|
||||||
|
{ tag: "InputPin", comp: board.components[2], i: 0 },
|
||||||
|
);
|
||||||
|
board.addWire(
|
||||||
|
{ tag: "OutputPin", comp: board.components[1], i: 0 },
|
||||||
|
{ tag: "InputPin", comp: board.components[2], i: 1 },
|
||||||
|
);
|
||||||
|
board.addWire(
|
||||||
|
{ tag: "OutputPin", comp: board.components[2], i: 0 },
|
||||||
|
{ tag: "InputPin", comp: board.components[3], i: 0 },
|
||||||
|
);
|
||||||
|
return board;
|
||||||
|
}
|
||||||
|
|
||||||
canPlaceComponent(kind: ComponentKind, pos: V2): boolean {
|
canPlaceComponent(kind: ComponentKind, pos: V2): boolean {
|
||||||
return !this.components.some((comp) =>
|
return !this.components.some((comp) =>
|
||||||
rectsCollide(comp.pos, comp.kind.size, pos, kind.size),
|
rectsCollide(comp.pos, comp.kind.size, pos, kind.size),
|
||||||
@ -194,6 +215,22 @@ export class Board {
|
|||||||
this.wires = this.wires.filter((wire) => !wire.isSelected(selection));
|
this.wires = this.wires.filter((wire) => !wire.isSelected(selection));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toComponentKind(name: string): ComponentKind {
|
||||||
|
const inputCount = this.components.filter(
|
||||||
|
(comp) => comp.kind.label === "input",
|
||||||
|
).length;
|
||||||
|
const outputCount = this.components.filter(
|
||||||
|
(comp) => comp.kind.label === "output",
|
||||||
|
).length;
|
||||||
|
const pinMax = Math.max(inputCount, outputCount);
|
||||||
|
return new ComponentKind(
|
||||||
|
v2(60 + name.length * 5, 40 + 10 * pinMax),
|
||||||
|
name,
|
||||||
|
new Array<null>(inputCount).fill(null),
|
||||||
|
new Array<null>(outputCount).fill(null),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
toIr(): ir.Component {
|
toIr(): ir.Component {
|
||||||
console.log("Lowering to IR");
|
console.log("Lowering to IR");
|
||||||
|
|
||||||
@ -337,6 +374,10 @@ export class ComponentRepo {
|
|||||||
return repo;
|
return repo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
available(): string[] {
|
||||||
|
return [...this.defs.keys()];
|
||||||
|
}
|
||||||
|
|
||||||
add(ident: string, kind: ComponentKind) {
|
add(ident: string, kind: ComponentKind) {
|
||||||
this.defs.set(ident, kind);
|
this.defs.set(ident, kind);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,31 +10,39 @@ import { v2, type V2 } from "./V2";
|
|||||||
import { ViewPos } from "./ViewPos";
|
import { ViewPos } from "./ViewPos";
|
||||||
import { type ComponentKind } from "./Board";
|
import { type ComponentKind } from "./Board";
|
||||||
import type { EventUnsub } from "./events";
|
import type { EventUnsub } from "./events";
|
||||||
|
import { Project } from "./Project";
|
||||||
|
|
||||||
export class Editor {
|
export class Editor {
|
||||||
public events = new EventBus();
|
public events = new EventBus();
|
||||||
public viewpos = new ViewPos(this.events);
|
public viewpos = new ViewPos(this.events);
|
||||||
private renderNeeded = false;
|
public mouse = new Mouse(this.events);
|
||||||
|
|
||||||
private state: State = new Normal(this);
|
public project = Project.loadLocalStoreOrInitNew(this.events);
|
||||||
|
public board = this.project.currentBoard();
|
||||||
|
|
||||||
public selectionBox: SelectionBox | null = null;
|
public selectionBox: SelectionBox | null = null;
|
||||||
private componentPlacer: ComponentPlacer | null = null;
|
private componentPlacer: ComponentPlacer | null = null;
|
||||||
public selection: Selection | null = null;
|
public selection: Selection | null = null;
|
||||||
public connectingWire: ConnectingWire | null = null;
|
public connectingWire: ConnectingWire | null = null;
|
||||||
|
|
||||||
public board = new Board();
|
|
||||||
public componentRepo = ComponentRepo.withDefaults();
|
|
||||||
|
|
||||||
public keysPressed = new Set<string>();
|
public keysPressed = new Set<string>();
|
||||||
|
|
||||||
public mouse = new Mouse(this.events);
|
private state: State = new Normal(this);
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.state.enter();
|
|
||||||
|
|
||||||
this.events.subscribe(
|
this.events.subscribe(
|
||||||
["MouseDown", "MouseUp", "MouseMove", "KeyDown", "KeyUp", "SelectTool"],
|
[
|
||||||
|
"MouseDown",
|
||||||
|
"MouseUp",
|
||||||
|
"MouseMove",
|
||||||
|
"KeyDown",
|
||||||
|
"KeyUp",
|
||||||
|
"SelectTool",
|
||||||
|
"CreateTab",
|
||||||
|
"SelectTab",
|
||||||
|
"SaveComponent",
|
||||||
|
"RenameComponent",
|
||||||
|
],
|
||||||
(ev) => {
|
(ev) => {
|
||||||
switch (ev.tag) {
|
switch (ev.tag) {
|
||||||
case "KeyDown":
|
case "KeyDown":
|
||||||
@ -45,10 +53,30 @@ export class Editor {
|
|||||||
break;
|
break;
|
||||||
case "SelectTool":
|
case "SelectTool":
|
||||||
this.onSelectTool(ev.tool);
|
this.onSelectTool(ev.tool);
|
||||||
|
break;
|
||||||
|
case "CreateTab": {
|
||||||
|
const idx = this.project.newTab();
|
||||||
|
this.switchTab(idx);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
this.renderNeeded = true;
|
case "SelectTab": {
|
||||||
|
this.switchTab(ev.idx);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "SaveComponent": {
|
||||||
|
this.project.saveComponent();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "RenameComponent": {
|
||||||
|
this.project.renameComponent(ev.newName);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.events.send({ tag: "RenderRequest" });
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.state.enter();
|
||||||
}
|
}
|
||||||
|
|
||||||
render(canvas: HTMLCanvasElement) {
|
render(canvas: HTMLCanvasElement) {
|
||||||
@ -62,29 +90,12 @@ export class Editor {
|
|||||||
this.connectingWire?.render(r);
|
this.connectingWire?.render(r);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderIfNeeded(canvas: HTMLCanvasElement) {
|
availableBoardEditors(): string[] {
|
||||||
if (this.renderNeeded) {
|
return this.project.availableBoardEditors();
|
||||||
this.render(canvas);
|
|
||||||
this.renderNeeded = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private onSelectTool(tool: string) {
|
availableTools(): string[] {
|
||||||
switch (tool) {
|
return this.project.availableTools();
|
||||||
case "pan":
|
|
||||||
this.transitionTo(new Panning(this));
|
|
||||||
break;
|
|
||||||
case "input":
|
|
||||||
case "output":
|
|
||||||
case "and":
|
|
||||||
case "or":
|
|
||||||
case "not":
|
|
||||||
this.transitionTo(new Placing(this, tool));
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
this.transitionTo(new Normal(this));
|
|
||||||
}
|
|
||||||
this.events.send({ tag: "ShowSelectedTool", tool });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
transitionTo(newState: State) {
|
transitionTo(newState: State) {
|
||||||
@ -118,9 +129,34 @@ export class Editor {
|
|||||||
// const sim = new Sim(comp, [], []);
|
// const sim = new Sim(comp, [], []);
|
||||||
// sim.simulate();
|
// sim.simulate();
|
||||||
}
|
}
|
||||||
|
private onSelectTool(tool: string) {
|
||||||
|
switch (tool) {
|
||||||
|
case "pan":
|
||||||
|
this.transitionTo(new Panning(this));
|
||||||
|
break;
|
||||||
|
case "input":
|
||||||
|
case "output":
|
||||||
|
case "and":
|
||||||
|
case "or":
|
||||||
|
case "not":
|
||||||
|
this.transitionTo(new Placing(this, tool));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this.transitionTo(new Normal(this));
|
||||||
|
}
|
||||||
|
this.events.send({ tag: "ShowSelectedTool", tool });
|
||||||
|
}
|
||||||
|
|
||||||
tools(): string[] {
|
private switchTab(idx: number) {
|
||||||
return ["select", "pan", "input", "output", "and", "or", "not"];
|
this.project.switchTab(idx);
|
||||||
|
this.events.send({ tag: "ShowSelectedTab", idx });
|
||||||
|
this.selectionBox = null;
|
||||||
|
this.componentPlacer = null;
|
||||||
|
this.selection = null;
|
||||||
|
this.connectingWire = null;
|
||||||
|
this.viewpos.offset.assign(v2(0, 0));
|
||||||
|
this.board = this.project.currentBoard();
|
||||||
|
this.transitionTo(new Normal(this));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,7 +172,13 @@ class Normal implements State {
|
|||||||
|
|
||||||
enter(): void {
|
enter(): void {
|
||||||
this.unsubscribe = this.cx.events.subscribe(
|
this.unsubscribe = this.cx.events.subscribe(
|
||||||
["MouseDownOffset", "MouseMoveOffset", "MouseDragBegin", "KeyDown"],
|
[
|
||||||
|
"MouseDownOffset",
|
||||||
|
"MouseMoveOffset",
|
||||||
|
"MouseDragBegin",
|
||||||
|
"KeyDown",
|
||||||
|
"MouseDoubleClick",
|
||||||
|
],
|
||||||
(ev) => {
|
(ev) => {
|
||||||
switch (ev.tag) {
|
switch (ev.tag) {
|
||||||
case "MouseDownOffset":
|
case "MouseDownOffset":
|
||||||
@ -157,6 +199,15 @@ class Normal implements State {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case "MouseDoubleClick": {
|
||||||
|
this.cx.board.handleMouseClick(ev.pos, {
|
||||||
|
onComponentClicked: (comp) => {
|
||||||
|
if (comp.kind.label === "input") {
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -256,7 +307,7 @@ class Placing implements State {
|
|||||||
private cx: Editor,
|
private cx: Editor,
|
||||||
private tool: string,
|
private tool: string,
|
||||||
) {
|
) {
|
||||||
this.compDef = this.cx.componentRepo.get(this.tool);
|
this.compDef = this.cx.project.componentRepo.get(this.tool);
|
||||||
}
|
}
|
||||||
|
|
||||||
enter(): void {
|
enter(): void {
|
||||||
|
|||||||
100
editor/src/editor/Project.ts
Normal file
100
editor/src/editor/Project.ts
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
import { Board, ComponentRepo, type Component } from "./Board";
|
||||||
|
import { type EventBus } from "./events";
|
||||||
|
|
||||||
|
export class Project {
|
||||||
|
private current: BoardEditor;
|
||||||
|
private selectedIdx = 0;
|
||||||
|
|
||||||
|
private constructor(
|
||||||
|
private events: EventBus,
|
||||||
|
private boardEditors: BoardEditor[],
|
||||||
|
private components: Component[],
|
||||||
|
public componentRepo: ComponentRepo,
|
||||||
|
) {
|
||||||
|
this.current = boardEditors[this.selectedIdx];
|
||||||
|
}
|
||||||
|
|
||||||
|
static loadLocalStoreOrInitNew(events: EventBus): Project {
|
||||||
|
if (globalThis.localStorage.getItem("nandsim")) {
|
||||||
|
return this.loadLocalStorage();
|
||||||
|
} else {
|
||||||
|
return this.initNew(events);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static initNew(events: EventBus): Project {
|
||||||
|
const repo = ComponentRepo.withDefaults();
|
||||||
|
return new Project(
|
||||||
|
events,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: "(Unnamed)",
|
||||||
|
board: Board.withExample(repo),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[],
|
||||||
|
repo,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static loadLocalStorage(): Project {
|
||||||
|
throw new Error("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
currentBoard(): Board {
|
||||||
|
return this.current.board;
|
||||||
|
}
|
||||||
|
|
||||||
|
availableBoardEditors(): string[] {
|
||||||
|
return this.boardEditors.map((e) => e.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
availableTools(): string[] {
|
||||||
|
return this.componentRepo
|
||||||
|
.available()
|
||||||
|
.filter((e) => e !== this.current.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
newTab(): number {
|
||||||
|
this.boardEditors.push({
|
||||||
|
name: `(Unnamed ${this.boardEditors.length})`,
|
||||||
|
board: new Board(),
|
||||||
|
});
|
||||||
|
this.events.send({ tag: "ShowSelectedTab", idx: this.selectedIdx });
|
||||||
|
return this.boardEditors.length - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
switchTab(idx: number) {
|
||||||
|
this.selectedIdx = idx;
|
||||||
|
this.current = this.boardEditors[this.selectedIdx];
|
||||||
|
this.events.send({ tag: "ShowSelectedTab", idx: this.selectedIdx });
|
||||||
|
this.events.send({ tag: "ShowSelectedTool", tool: this.current.name });
|
||||||
|
}
|
||||||
|
|
||||||
|
closeTab(idx: number) {
|
||||||
|
this.boardEditors.splice(idx, 1);
|
||||||
|
if (this.boardEditors.length === 0) {
|
||||||
|
this.newTab();
|
||||||
|
}
|
||||||
|
this.selectedIdx = 0;
|
||||||
|
this.current = this.boardEditors[this.selectedIdx];
|
||||||
|
this.events.send({ tag: "ShowSelectedTab", idx: this.selectedIdx });
|
||||||
|
}
|
||||||
|
|
||||||
|
renameComponent(newName: string) {
|
||||||
|
this.current.name = newName;
|
||||||
|
this.events.send({ tag: "ShowSelectedTab", idx: this.selectedIdx });
|
||||||
|
}
|
||||||
|
|
||||||
|
saveComponent() {
|
||||||
|
this.componentRepo.add(
|
||||||
|
this.current.name,
|
||||||
|
this.current.board.toComponentKind(this.current.name),
|
||||||
|
);
|
||||||
|
this.events.send({ tag: "ShowSelectedTool", tool: this.current.name });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type BoardEditor = {
|
||||||
|
name: string;
|
||||||
|
board: Board;
|
||||||
|
};
|
||||||
@ -25,6 +25,11 @@ export class V2 {
|
|||||||
return new V2(Math.abs(this.x), Math.abs(this.y));
|
return new V2(Math.abs(this.x), Math.abs(this.y));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assign(rhs: V2) {
|
||||||
|
this.x = rhs.x;
|
||||||
|
this.y = rhs.y;
|
||||||
|
}
|
||||||
|
|
||||||
toString(): string {
|
toString(): string {
|
||||||
return `V2(${this.x}, ${this.y})`;
|
return `V2(${this.x}, ${this.y})`;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,8 +16,13 @@ export type Event =
|
|||||||
}
|
}
|
||||||
| { tag: "KeyDown" | "KeyUp"; key: string }
|
| { tag: "KeyDown" | "KeyUp"; key: string }
|
||||||
| { tag: "SelectTool" | "ShowSelectedTool"; tool: string }
|
| { tag: "SelectTool" | "ShowSelectedTool"; tool: string }
|
||||||
|
| { tag: "CreateTab" }
|
||||||
|
| { tag: "SelectTab" | "ShowSelectedTab"; idx: number }
|
||||||
| { tag: "MouseDownOffset"; pos: V2; absPos: V2 }
|
| { tag: "MouseDownOffset"; pos: V2; absPos: V2 }
|
||||||
| { tag: "MouseMoveOffset"; pos: V2; deltaPos: V2 };
|
| { tag: "MouseMoveOffset"; pos: V2; deltaPos: V2 }
|
||||||
|
| { tag: "RenderRequest" }
|
||||||
|
| { tag: "SaveComponent" | "CloseComponent" }
|
||||||
|
| { tag: "RenameComponent"; newName: string };
|
||||||
|
|
||||||
export type EventOf<Tag extends Event["tag"]> = Event & { tag: Tag };
|
export type EventOf<Tag extends Event["tag"]> = Event & { tag: Tag };
|
||||||
|
|
||||||
|
|||||||
@ -45,7 +45,14 @@
|
|||||||
.Tabbar {
|
.Tabbar {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
gap: 2px;
|
justify-content: space-between;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
padding-left: 5px;
|
padding-left: 5px;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user