add component defs

This commit is contained in:
sfja 2026-05-13 03:17:16 +02:00
parent 51934b7cb9
commit 0343ded6c8
5 changed files with 77 additions and 47 deletions

View File

@ -1,59 +1,88 @@
import { rectsCollide, type V2 } from "./V2"; import { rectsCollide, V2 } from "./V2";
export class Board { export class Board {
private components: Component[] = []; private components: Component[] = [];
canPlaceComponent(pos: V2, size: V2): boolean { canPlaceComponent(def: ComponentDef, pos: V2): boolean {
return !this.components.some((comp) => return !this.components.some((comp) =>
rectsCollide(comp.pos, comp.size, pos, size), rectsCollide(comp.pos, comp.def.size, pos, def.size),
); );
} }
placeComponent(pos: V2, size: V2, label: string) { placeComponent(def: ComponentDef, pos: V2) {
this.components.push({ pos, size, label }); this.components.push({ def, pos });
} }
render( render(_canvas: HTMLCanvasElement, c: CanvasRenderingContext2D, offset: V2) {
canvas: HTMLCanvasElement,
c: CanvasRenderingContext2D,
offset: V2,
gridSize: Readonly<V2>,
) {
for (const comp of this.components) { for (const comp of this.components) {
const { const {
def: {
size: { x: w, y: h },
label,
},
pos: { x, y }, pos: { x, y },
size: { x: w, y: h },
} = comp; } = comp;
c.fillStyle = `#0088cc`; c.fillStyle = `#6abbde`;
c.fillRect( c.fillRect(x + offset.x, y + offset.y, w, h);
x * gridSize.x + offset.x,
y * gridSize.y + offset.y,
w * gridSize.x,
h * gridSize.y,
);
c.strokeStyle = `#333333`; c.strokeStyle = `#333333`;
c.lineWidth = 2; c.lineWidth = 2;
c.strokeRect( c.strokeRect(x + offset.x, y + offset.y, w, h);
x * gridSize.x + offset.x,
y * gridSize.y + offset.y,
w * gridSize.x,
h * gridSize.y,
);
c.fillStyle = `#333333`; c.fillStyle = `#333333`;
c.font = "bold 16px monospace"; c.font = "bold 16px monospace";
const textMetrix = c.measureText(comp.label); const textMetrix = c.measureText(label);
c.fillText( c.fillText(
comp.label, label,
x * gridSize.x + offset.x + (w * gridSize.x) / 2 - textMetrix.width / 2, x + offset.x + w / 2 - textMetrix.width / 2,
y * gridSize.y + offset.y + 13 + (h * gridSize.y) / 2 - 16 / 2, y + offset.y + 13 + h / 2 - 16 / 2,
); );
} }
} }
} }
export class ComponentRepo {
private defs = new Map<string, ComponentDef>();
static withDefaults(): ComponentRepo {
const repo = new ComponentRepo();
repo.add("and", {
label: "and",
size: V2(80, 40),
inputs: [null, null],
outputs: [null],
});
repo.add("or", {
label: "or",
size: V2(80, 40),
inputs: [null, null],
outputs: [null],
});
return repo;
}
add(ident: string, def: ComponentDef) {
this.defs.set(ident, def);
}
get(ident: string): ComponentDef {
const def = this.defs.get(ident);
if (!def) {
throw new Error("should be defined");
}
return def;
}
}
type Component = { type Component = {
def: ComponentDef;
pos: V2; pos: V2;
};
export type ComponentDef = {
size: V2; size: V2;
label: string; label: string;
inputs: (string | null)[];
outputs: (string | null)[];
}; };

View File

@ -1,4 +1,4 @@
import { Board } from "./Board"; import { Board, ComponentRepo } from "./Board";
import type { State } from "./State"; import type { State } from "./State";
import { Normal } from "./states/Normal"; import { Normal } from "./states/Normal";
import { V2 } from "./V2"; import { V2 } from "./V2";
@ -9,12 +9,11 @@ export class Cx {
private state = new Normal(this) as State; private state = new Normal(this) as State;
private updateActions: (() => void)[] = []; private updateActions: (() => void)[] = [];
public gridSize = Object.freeze(V2(20, 20));
private selectionRect: SelectionRect | null = null; private selectionRect: SelectionRect | null = null;
private componentPlacer: ComponentPlacer | null = null; private componentPlacer: ComponentPlacer | null = null;
public board = new Board(); public board = new Board();
public componentRepo = ComponentRepo.withDefaults();
render(canvas: HTMLCanvasElement) { render(canvas: HTMLCanvasElement) {
const c = canvas.getContext("2d")!; const c = canvas.getContext("2d")!;
@ -23,8 +22,8 @@ export class Cx {
c.fillStyle = "#666"; c.fillStyle = "#666";
c.fillRect(0, 0, canvas.width, canvas.height); c.fillRect(0, 0, canvas.width, canvas.height);
const gridSize = this.gridSize;
const dotSize = { x: 2, y: 2 }; const dotSize = { x: 2, y: 2 };
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.width / gridSize.x + 1; ++y) {
@ -38,7 +37,7 @@ export class Cx {
} }
} }
this.board.render(canvas, c, this.offset, gridSize); this.board.render(canvas, c, this.offset);
if (this.selectionRect) { if (this.selectionRect) {
const { const {
@ -61,7 +60,7 @@ export class Cx {
c.strokeStyle = `#ffffff`; c.strokeStyle = `#ffffff`;
c.lineWidth = 2; c.lineWidth = 2;
c.strokeRect(x - (x % gridSize.x), y - (y % gridSize.y), w, h); c.strokeRect(x, y, w, h);
} }
} }
@ -168,10 +167,7 @@ export class Cx {
canvasPosToBoard(pos: V2): V2 { canvasPosToBoard(pos: V2): V2 {
const absX = pos.x - this.offset.x; const absX = pos.x - this.offset.x;
const absY = pos.y - this.offset.y; const absY = pos.y - this.offset.y;
return V2( return V2(absX, absY);
(absX - (absX % this.gridSize.x)) / this.gridSize.x,
(absY - (absY % this.gridSize.y)) / this.gridSize.y,
);
} }
} }
@ -185,4 +181,4 @@ export type ComponentPlacer = {
size: V2; size: V2;
}; };
export type Tool = "select" | "pan" | "and"; export type Tool = "select" | "pan" | "and" | "or";

View File

@ -41,7 +41,7 @@ export class Editor {
} }
tools(): Tool[] { tools(): Tool[] {
return ["select", "pan", "and"]; return ["select", "pan", "and", "or"];
} }
addUpdateAction(action: () => void): object { addUpdateAction(action: () => void): object {

View File

@ -14,7 +14,8 @@ export class Normal implements State {
this.cx.transitionTo(new Panning(this.cx)); this.cx.transitionTo(new Panning(this.cx));
break; break;
case "and": case "and":
this.cx.transitionTo(new Placing(this.cx, "and")); case "or":
this.cx.transitionTo(new Placing(this.cx, tool));
} }
} }

View File

@ -2,15 +2,20 @@ import { type Cx, type Tool } from "../Cx";
import { V2 } from "../V2"; import { V2 } from "../V2";
import type { State } from "../State"; import type { State } from "../State";
import { Normal } from "./Normal"; import { Normal } from "./Normal";
import type { ComponentDef } from "../Board";
export class Placing implements State { export class Placing implements State {
private compDef: ComponentDef;
constructor( constructor(
private cx: Cx, private cx: Cx,
private tool: Tool, private tool: Tool,
) {} ) {
this.compDef = this.cx.componentRepo.get(this.tool);
}
enterState(): void { enterState(): void {
this.cx.addComponentPlacer(V2(0, 0), V2(20 * 4, 20 * 2)); this.cx.addComponentPlacer(V2(0, 0), this.compDef.size);
} }
leaveState(): void { leaveState(): void {
@ -19,9 +24,8 @@ export class Placing implements State {
onMouseDown(pos: V2): void { onMouseDown(pos: V2): void {
const boardPos = this.cx.canvasPosToBoard(pos); const boardPos = this.cx.canvasPosToBoard(pos);
if (this.cx.board.canPlaceComponent(boardPos, V2(4, 2))) { if (this.cx.board.canPlaceComponent(this.compDef, boardPos)) {
console.log("place"); this.cx.board.placeComponent(this.compDef, boardPos);
this.cx.board.placeComponent(boardPos, V2(4, 2), "AND");
this.cx.transitionTo(new Normal(this.cx)); this.cx.transitionTo(new Normal(this.cx));
} }
} }