refactor states

This commit is contained in:
sfja 2026-05-19 16:40:20 +02:00
parent bdf4a01e7f
commit 2a172f687c
8 changed files with 157 additions and 179 deletions

View File

@ -1,15 +1,12 @@
import { Board, ComponentRepo } from "./Board"; import { Board, ComponentRepo } from "./Board";
import { Renderer } from "./Renderer"; import { Renderer } from "./Renderer";
import type { State } from "./State"; import * as states from "./states";
import { Normal } from "./states/Normal";
import { Panning } from "./states/Panning";
import { Placing } from "./states/Placing";
import { v2, V2 } from "./V2"; import { v2, V2 } from "./V2";
export class Cx { export class Cx {
public offset = v2(0, 0); public offset = v2(0, 0);
private renderNeeded = false; private renderNeeded = false;
private state = new Normal(this) as State; private state = new states.Normal(this) as states.State;
private updateActions: (() => void)[] = []; private updateActions: (() => void)[] = [];
private selectionBox: SelectionBox | null = null; private selectionBox: SelectionBox | null = null;
@ -57,17 +54,17 @@ export class Cx {
selectTool(tool: Tool) { selectTool(tool: Tool) {
switch (tool) { switch (tool) {
case "pan": case "pan":
this.transitionTo(new Panning(this)); this.transitionTo(new states.Panning(this));
break; break;
case "input": case "input":
case "output": case "output":
case "and": case "and":
case "or": case "or":
case "not": case "not":
this.transitionTo(new Placing(this, tool)); this.transitionTo(new states.Placing(this, tool));
break; break;
default: default:
this.transitionTo(new Normal(this)); this.transitionTo(new states.Normal(this));
} }
} }
@ -82,7 +79,7 @@ export class Cx {
); );
} }
transitionTo(newState: State) { transitionTo(newState: states.State) {
this.state.leaveState?.(); this.state.leaveState?.();
this.state = newState; this.state = newState;
this.state.enterState?.(); this.state.enterState?.();

View File

@ -1,13 +0,0 @@
import type { Tool } from "./Cx";
import type { V2 } from "./V2";
export interface State {
enterState?(): void;
leaveState?(): void;
onMouseDown?(pos: V2): void;
onMouseUp?(pos: V2): void;
onMouseMove?(deltaPos: V2, pos: V2): void;
onKeyDown?(key: string): void;
onKeyUp?(key: string): void;
selectedTool?(): Tool | null;
}

151
editor/src/editor/states.ts Normal file
View File

@ -0,0 +1,151 @@
import type { ComponentDef } from "./Board";
import type { Cx, Tool } from "./Cx";
import { v2, type V2 } from "./V2";
export interface State {
enterState?(): void;
leaveState?(): void;
onMouseDown?(pos: V2): void;
onMouseUp?(pos: V2): void;
onMouseMove?(deltaPos: V2, pos: V2): void;
onKeyDown?(key: string): void;
onKeyUp?(key: string): void;
selectedTool?(): Tool | null;
}
export class Normal implements State {
constructor(private cx: Cx) {}
onMouseDown(pos: V2): void {
if (
this.cx.board.handleMouseClick(
pos.sub(this.cx.offset),
(comp, i) => {},
(comp, i) => {},
(comp) => {},
) === "handled"
) {
return;
} else {
this.cx.addSelectionRect(pos);
this.cx.transitionTo(new SelectingBox(this.cx));
}
}
onMouseMove(_deltaPos: V2, pos: V2): void {
this.cx.board.updateMouseHover(pos.sub(this.cx.offset));
this.cx.setRenderNeeded();
}
onKeyDown(key: string): void {
if (key === "Shift") {
this.cx.transitionTo(new Panning(this.cx));
return;
}
}
selectedTool(): Tool | null {
return "select";
}
}
export class Panning implements State {
private dragging = false;
constructor(private cx: Cx) {}
onMouseDown(_pos: V2): void {
this.dragging = true;
}
onMouseUp(_pos: V2): void {
this.dragging = false;
}
onMouseMove(deltaPos: V2): void {
if (this.dragging) {
this.cx.moveOffset(deltaPos);
}
}
onKeyDown(key: string): void {
if (key === "Escape") {
this.cx.transitionTo(new Normal(this.cx));
return;
}
}
onKeyUp(key: string): void {
if (key === "Shift") {
this.cx.transitionTo(new Normal(this.cx));
return;
}
}
selectedTool(): Tool | null {
return "pan";
}
}
export class Placing implements State {
private compDef: ComponentDef;
constructor(
private cx: Cx,
private tool: Tool,
) {
this.compDef = this.cx.componentRepo.get(this.tool);
}
enterState(): void {
this.cx.addComponentPlacer(v2(0, 0), this.compDef.size);
}
leaveState(): void {
this.cx.removeComponentPlacer();
}
onMouseDown(pos: V2): void {
const boardPos = this.cx.canvasPosToBoard(pos);
if (this.cx.board.canPlaceComponent(this.compDef, boardPos)) {
this.cx.board.placeComponent(this.compDef, boardPos);
this.cx.transitionTo(new Normal(this.cx));
}
}
onMouseMove(_deltaPos: V2, pos: V2): void {
this.cx.setComponentPlacerPos(pos);
}
onKeyDown(key: string): void {
if (key === "Escape") {
this.cx.transitionTo(new Normal(this.cx));
return;
}
}
selectedTool(): Tool | null {
return this.tool;
}
}
export class Selecting implements State {
constructor(private cx: Cx) {}
}
export class SelectingBox implements State {
constructor(private cx: Cx) {}
onMouseUp(_pos: V2): void {
this.cx.removeSelectionRect();
this.cx.transitionTo(new Normal(this.cx));
}
onMouseMove(deltaPos: V2): void {
this.cx.moveSelectionRect(deltaPos);
}
selectedTool(): Tool | null {
return "select";
}
}

View File

@ -1,41 +0,0 @@
import type { Cx, Tool } from "../Cx";
import type { V2 } from "../V2";
import type { State } from "../State";
import { Panning } from "./Panning";
import { SelectingBox } from "./SelectingBox";
export class Normal implements State {
constructor(private cx: Cx) {}
onMouseDown(pos: V2): void {
if (
this.cx.board.handleMouseClick(
pos.sub(this.cx.offset),
(comp, i) => {},
(comp, i) => {},
(comp) => {},
) === "handled"
) {
return;
} else {
this.cx.addSelectionRect(pos);
this.cx.transitionTo(new SelectingBox(this.cx));
}
}
onMouseMove(_deltaPos: V2, pos: V2): void {
this.cx.board.updateMouseHover(pos.sub(this.cx.offset));
this.cx.setRenderNeeded();
}
onKeyDown(key: string): void {
if (key === "Shift") {
this.cx.transitionTo(new Panning(this.cx));
return;
}
}
selectedTool(): Tool | null {
return "select";
}
}

View File

@ -1,42 +0,0 @@
import type { Cx, Tool } from "../Cx";
import type { V2_ } from "../V2";
import type { State } from "../State";
import { Normal } from "./Normal";
export class Panning implements State {
private dragging = false;
constructor(private cx: Cx) {}
onMouseDown(_pos: V2_): void {
this.dragging = true;
}
onMouseUp(_pos: V2_): void {
this.dragging = false;
}
onMouseMove(deltaPos: V2_): void {
if (this.dragging) {
this.cx.moveOffset(deltaPos);
}
}
onKeyDown(key: string): void {
if (key === "Escape") {
this.cx.transitionTo(new Normal(this.cx));
return;
}
}
onKeyUp(key: string): void {
if (key === "Shift") {
this.cx.transitionTo(new Normal(this.cx));
return;
}
}
selectedTool(): Tool | null {
return "pan";
}
}

View File

@ -1,47 +0,0 @@
import { type Cx, type Tool } from "../Cx";
import { V2, v2 } from "../V2";
import type { State } from "../State";
import { Normal } from "./Normal";
import type { ComponentDef } from "../Board";
export class Placing implements State {
private compDef: ComponentDef;
constructor(
private cx: Cx,
private tool: Tool,
) {
this.compDef = this.cx.componentRepo.get(this.tool);
}
enterState(): void {
this.cx.addComponentPlacer(v2(0, 0), this.compDef.size);
}
leaveState(): void {
this.cx.removeComponentPlacer();
}
onMouseDown(pos: V2): void {
const boardPos = this.cx.canvasPosToBoard(pos);
if (this.cx.board.canPlaceComponent(this.compDef, boardPos)) {
this.cx.board.placeComponent(this.compDef, boardPos);
this.cx.transitionTo(new Normal(this.cx));
}
}
onMouseMove(_deltaPos: V2, pos: V2): void {
this.cx.setComponentPlacerPos(pos);
}
onKeyDown(key: string): void {
if (key === "Escape") {
this.cx.transitionTo(new Normal(this.cx));
return;
}
}
selectedTool(): Tool | null {
return this.tool;
}
}

View File

@ -1,6 +0,0 @@
import type { Cx } from "../Cx";
import type { State } from "../State";
export class Selecting implements State {
constructor(private cx: Cx) {}
}

View File

@ -1,21 +0,0 @@
import type { Cx, Tool } from "../Cx";
import type { V2 } from "../V2";
import type { State } from "../State";
import { Normal } from "./Normal";
export class SelectingBox implements State {
constructor(private cx: Cx) {}
onMouseUp(_pos: V2): void {
this.cx.removeSelectionRect();
this.cx.transitionTo(new Normal(this.cx));
}
onMouseMove(deltaPos: V2): void {
this.cx.moveSelectionRect(deltaPos);
}
selectedTool(): Tool | null {
return "select";
}
}