refactor to use new events
This commit is contained in:
parent
cc1ef05b95
commit
9def12cad9
@ -27,30 +27,30 @@ function Canvas({ editor, canvasRef, width, height }: Props): ReactElement {
|
||||
tabIndex={0}
|
||||
onMouseDown={(ev) => {
|
||||
const pos = v2(ev.nativeEvent.offsetX, ev.nativeEvent.offsetY);
|
||||
editor.mouseDown(pos);
|
||||
editor.events.send({ tag: "MouseDown", pos });
|
||||
editor.renderIfNeeded(ev.target as HTMLCanvasElement);
|
||||
}}
|
||||
onMouseUp={(ev) => {
|
||||
const pos = v2(ev.nativeEvent.offsetX, ev.nativeEvent.offsetY);
|
||||
editor.mouseUp(pos);
|
||||
editor.events.send({ tag: "MouseUp", pos });
|
||||
editor.renderIfNeeded(ev.target as HTMLCanvasElement);
|
||||
}}
|
||||
onMouseMove={(ev) => {
|
||||
const deltaPos = v2(ev.movementX, ev.movementY);
|
||||
const pos = v2(ev.nativeEvent.offsetX, ev.nativeEvent.offsetY);
|
||||
editor.mouseMove(deltaPos, pos);
|
||||
editor.events.send({ tag: "MouseMove", pos, deltaPos });
|
||||
editor.renderIfNeeded(ev.target as HTMLCanvasElement);
|
||||
}}
|
||||
onMouseLeave={(ev) => {
|
||||
editor.mouseLeave();
|
||||
editor.events.send({ tag: "MouseLeave" });
|
||||
editor.renderIfNeeded(ev.target as HTMLCanvasElement);
|
||||
}}
|
||||
onKeyDown={(ev) => {
|
||||
editor.keyDown(ev.key);
|
||||
editor.events.send({ tag: "KeyDown", key: ev.key });
|
||||
editor.renderIfNeeded(ev.target as HTMLCanvasElement);
|
||||
}}
|
||||
onKeyUp={(ev) => {
|
||||
editor.keyUp(ev.key);
|
||||
editor.events.send({ tag: "KeyUp", key: ev.key });
|
||||
editor.renderIfNeeded(ev.target as HTMLCanvasElement);
|
||||
}}
|
||||
/>
|
||||
|
||||
@ -3,28 +3,24 @@ import type { Editor } from "./editor/Editor";
|
||||
|
||||
type Props = { editor: Editor; canvasRef: RefObject<HTMLCanvasElement | null> };
|
||||
|
||||
function useUpdate(): [number, () => void] {
|
||||
const [value, setValue] = useState(0);
|
||||
return [value, () => setValue(value + 1)] as const;
|
||||
}
|
||||
|
||||
function Toolbar({ editor, canvasRef }: Props): ReactElement {
|
||||
const [uid, update] = useUpdate();
|
||||
const [selectedTool, setSelectedTool] = useState("select");
|
||||
|
||||
useEffect(() => {
|
||||
const handle = editor.addUpdateAction(() => update());
|
||||
return () => editor.removeUpdateAction(handle);
|
||||
});
|
||||
useEffect(() =>
|
||||
editor.events.subscribe(["ShowSelectedTool"], (ev) => {
|
||||
setSelectedTool(ev.tool);
|
||||
}),
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="Toolbar">
|
||||
{editor.tools().map((tool, key) => (
|
||||
<button
|
||||
key={`${uid}${key}`}
|
||||
className={editor.selectedTool() === tool ? "active" : ""}
|
||||
key={`${key}`}
|
||||
className={selectedTool === tool ? "active" : ""}
|
||||
onClick={() => {
|
||||
editor.selectTool(tool);
|
||||
editor.events.send({ tag: "SelectTool", tool });
|
||||
canvasRef.current?.focus();
|
||||
}}
|
||||
>
|
||||
|
||||
@ -18,8 +18,8 @@ export type Tool = string;
|
||||
export class Cx {
|
||||
public offset = v2(0, 0);
|
||||
private renderNeeded = false;
|
||||
|
||||
private state = new states.Normal(this) as states.State;
|
||||
private updateActions: (() => void)[] = [];
|
||||
|
||||
public selectionBox: SelectionBox | null = null;
|
||||
private componentPlacer: ComponentPlacer | null = null;
|
||||
@ -31,8 +31,30 @@ export class Cx {
|
||||
|
||||
public keysPressed = new Set<string>();
|
||||
|
||||
public eventBus = new EventBus();
|
||||
public mouse = new Mouse(this.eventBus);
|
||||
public mouse: Mouse;
|
||||
|
||||
constructor(public events: EventBus) {
|
||||
this.mouse = new Mouse(this.events);
|
||||
|
||||
this.state.enter();
|
||||
|
||||
this.events.subscribe(
|
||||
["MouseDown", "MouseUp", "MouseMove", "KeyDown", "KeyUp", "SelectTool"],
|
||||
(ev) => {
|
||||
switch (ev.tag) {
|
||||
case "KeyDown":
|
||||
this.keysPressed.add(ev.key);
|
||||
break;
|
||||
case "KeyUp":
|
||||
this.keysPressed.delete(ev.key);
|
||||
break;
|
||||
case "SelectTool":
|
||||
this.onSelectTool(ev.tool);
|
||||
}
|
||||
this.renderNeeded = true;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
render(canvas: HTMLCanvasElement) {
|
||||
const r = new Renderer(canvas, this.offset);
|
||||
@ -52,29 +74,7 @@ export class Cx {
|
||||
}
|
||||
}
|
||||
|
||||
mouseDown(pos: V2) {
|
||||
this.state.onMouseDown?.(pos);
|
||||
this.renderNeeded = true;
|
||||
}
|
||||
mouseUp(pos: V2) {
|
||||
this.state.onMouseUp?.(pos);
|
||||
this.renderNeeded = true;
|
||||
}
|
||||
mouseMove(deltaPos: V2, pos: V2) {
|
||||
this.state.onMouseMove?.(deltaPos, pos);
|
||||
this.renderNeeded = true;
|
||||
}
|
||||
keyDown(key: string) {
|
||||
this.keysPressed.add(key);
|
||||
this.state.onKeyDown?.(key);
|
||||
this.renderNeeded = true;
|
||||
}
|
||||
keyUp(key: string) {
|
||||
this.keysPressed.delete(key);
|
||||
this.state.onKeyUp?.(key);
|
||||
this.renderNeeded = true;
|
||||
}
|
||||
selectTool(tool: Tool) {
|
||||
private onSelectTool(tool: Tool) {
|
||||
switch (tool) {
|
||||
case "pan":
|
||||
this.transitionTo(new states.Panning(this));
|
||||
@ -89,34 +89,14 @@ export class Cx {
|
||||
default:
|
||||
this.transitionTo(new states.Normal(this));
|
||||
}
|
||||
}
|
||||
selectedTool(): Tool {
|
||||
return this.state.selectedTool?.() || "select";
|
||||
}
|
||||
|
||||
addUpdateAction(action: () => void): object {
|
||||
this.updateActions.push(action);
|
||||
return action;
|
||||
}
|
||||
|
||||
removeUpdateAction(actionId: object) {
|
||||
this.updateActions = this.updateActions.filter(
|
||||
(action) => action !== actionId,
|
||||
);
|
||||
this.events.send({ tag: "ShowSelectedTool", tool });
|
||||
}
|
||||
|
||||
transitionTo(newState: states.State) {
|
||||
this.state.leaveState?.();
|
||||
this.state.leave();
|
||||
this.state = newState;
|
||||
// console.log(`Entering state ${newState.constructor.name}`);
|
||||
this.state.enterState?.();
|
||||
this.notifyListeners();
|
||||
}
|
||||
|
||||
notifyListeners() {
|
||||
for (const action of this.updateActions) {
|
||||
action();
|
||||
}
|
||||
console.log(`Entering state ${newState.constructor.name}`);
|
||||
this.state.enter();
|
||||
}
|
||||
|
||||
moveOffset(deltaPos: V2) {
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
import { Cx, type Tool } from "./Cx";
|
||||
import { EventBus } from "./events";
|
||||
import { V2 } from "./V2";
|
||||
|
||||
export class Editor {
|
||||
private cx = new Cx();
|
||||
public events = new EventBus();
|
||||
private cx = new Cx(this.events);
|
||||
|
||||
render(canvas: HTMLCanvasElement) {
|
||||
this.cx.render(canvas);
|
||||
@ -12,50 +14,7 @@ export class Editor {
|
||||
this.cx.renderIfNeeded(canvas);
|
||||
}
|
||||
|
||||
mouseDown(pos: V2) {
|
||||
this.cx.eventBus.send({ tag: "MouseDown", pos });
|
||||
this.cx.mouseDown(pos);
|
||||
}
|
||||
|
||||
mouseUp(pos: V2) {
|
||||
this.cx.eventBus.send({ tag: "MouseUp", pos });
|
||||
this.cx.mouseUp(pos);
|
||||
}
|
||||
|
||||
mouseMove(deltaPos: V2, pos: V2) {
|
||||
this.cx.eventBus.send({ tag: "MouseMove", pos, deltaPos });
|
||||
this.cx.mouseMove(deltaPos, pos);
|
||||
}
|
||||
|
||||
mouseLeave() {
|
||||
this.cx.eventBus.send({ tag: "MouseLeave" });
|
||||
}
|
||||
|
||||
keyDown(key: string) {
|
||||
this.cx.keyDown(key);
|
||||
}
|
||||
|
||||
keyUp(key: string) {
|
||||
this.cx.keyUp(key);
|
||||
}
|
||||
|
||||
selectTool(tool: Tool) {
|
||||
this.cx.selectTool(tool);
|
||||
}
|
||||
|
||||
selectedTool(): Tool | null {
|
||||
return this.cx.selectedTool();
|
||||
}
|
||||
|
||||
tools(): Tool[] {
|
||||
return ["select", "pan", "input", "output", "and", "or", "not"];
|
||||
}
|
||||
|
||||
addUpdateAction(action: () => void): object {
|
||||
return this.cx.addUpdateAction(action);
|
||||
}
|
||||
|
||||
removeUpdateAction(actionId: object) {
|
||||
this.cx.removeUpdateAction(actionId);
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,8 +24,6 @@ class Normal implements State {
|
||||
private unsubscribe: EventUnsub;
|
||||
|
||||
constructor(private cx: Mouse) {
|
||||
console.log("Mouse::Normal");
|
||||
|
||||
this.unsubscribe = cx.eventBus.subscribe(
|
||||
["MouseDown", "MouseUp", "MouseMove", "MouseLeave"],
|
||||
(ev) => {
|
||||
@ -61,8 +59,6 @@ class FirstPress implements State {
|
||||
private cx: Mouse,
|
||||
private pos: V2,
|
||||
) {
|
||||
console.log("Mouse::FirstPress");
|
||||
|
||||
this.unsubscribe = cx.eventBus.subscribe(
|
||||
["MouseDown", "MouseUp", "MouseMove", "MouseLeave"],
|
||||
(ev) => {
|
||||
@ -112,8 +108,6 @@ class FirstRelease implements State {
|
||||
private cx: Mouse,
|
||||
private pos: V2,
|
||||
) {
|
||||
console.log("Mouse::FirstRelease");
|
||||
|
||||
this.unsubscribe = cx.eventBus.subscribe(
|
||||
["MouseDown", "MouseUp", "MouseMove", "MouseLeave"],
|
||||
(ev) => {
|
||||
@ -121,14 +115,13 @@ class FirstRelease implements State {
|
||||
case "MouseDown":
|
||||
this.cx.transitionTo(new SecondPress(this.cx, this.pos));
|
||||
break;
|
||||
case "MouseUp":
|
||||
break;
|
||||
case "MouseMove":
|
||||
break;
|
||||
case "MouseLeave":
|
||||
this.cx.transitionTo(new Normal(this.cx));
|
||||
break;
|
||||
default:
|
||||
throw new Error("invalid state");
|
||||
throw new Error(`unexpected event ${ev.tag}`);
|
||||
}
|
||||
},
|
||||
);
|
||||
@ -153,8 +146,6 @@ class SecondPress implements State {
|
||||
private cx: Mouse,
|
||||
private pos: V2,
|
||||
) {
|
||||
console.log("Mouse::SecondPress");
|
||||
|
||||
this.unsubscribe = cx.eventBus.subscribe(
|
||||
["MouseDown", "MouseUp", "MouseMove", "MouseLeave"],
|
||||
(ev) => {
|
||||
@ -179,7 +170,7 @@ class SecondPress implements State {
|
||||
this.cx.transitionTo(new Normal(this.cx));
|
||||
break;
|
||||
default:
|
||||
throw new Error("invalid state");
|
||||
throw new Error(`unexpected event ${ev.tag}`);
|
||||
}
|
||||
},
|
||||
);
|
||||
@ -194,8 +185,6 @@ class Dragging implements State {
|
||||
private unsubscribe: EventUnsub;
|
||||
|
||||
constructor(private cx: Mouse) {
|
||||
console.log("Mouse::Dragging");
|
||||
|
||||
this.unsubscribe = cx.eventBus.subscribe(
|
||||
["MouseDown", "MouseUp", "MouseMove", "MouseLeave"],
|
||||
(ev) => {
|
||||
@ -217,7 +206,7 @@ class Dragging implements State {
|
||||
this.cx.transitionTo(new Normal(this.cx));
|
||||
break;
|
||||
default:
|
||||
throw new Error("invalid state");
|
||||
throw new Error(`unexpected event ${ev.tag}`);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
@ -24,6 +24,10 @@ export class V2 {
|
||||
abs(): V2 {
|
||||
return new V2(Math.abs(this.x), Math.abs(this.y));
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return `V2(${this.x}, ${this.y})`;
|
||||
}
|
||||
}
|
||||
|
||||
export const v2 = (x: number, y: number): V2 => new V2(x, y);
|
||||
|
||||
@ -14,7 +14,8 @@ export type Event =
|
||||
pos: V2;
|
||||
deltaPos: V2;
|
||||
}
|
||||
| { tag: "MouseDrag" };
|
||||
| { tag: "KeyDown" | "KeyUp"; key: string }
|
||||
| { tag: "SelectTool" | "ShowSelectedTool"; tool: string };
|
||||
|
||||
export type EventOf<Tag extends Event["tag"]> = Event & { tag: Tag };
|
||||
|
||||
|
||||
@ -1,30 +1,56 @@
|
||||
import { Component, Joint, type ComponentKind } from "./Board";
|
||||
import { ConnectingWire, Selection, type ConnectingWireKind } from "./Cx";
|
||||
import { SelectionBox, type Cx, type Tool } from "./Cx";
|
||||
import type { EventUnsub } from "./events";
|
||||
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;
|
||||
leave(): void;
|
||||
enter(): void;
|
||||
selectedTool?(): Tool | null;
|
||||
}
|
||||
|
||||
export class Normal implements State {
|
||||
private dragStart = v2(0, 0);
|
||||
private isMouseDown = false;
|
||||
private unsubscribe!: EventUnsub;
|
||||
|
||||
constructor(private cx: Cx) {}
|
||||
|
||||
enterState(): void {
|
||||
enter(): void {
|
||||
this.unsubscribe = this.cx.events.subscribe(
|
||||
["MouseDown", "MouseMove", "MouseDragBegin", "KeyDown"],
|
||||
(ev) => {
|
||||
switch (ev.tag) {
|
||||
case "MouseDown":
|
||||
this._onMouseDown(ev.pos);
|
||||
break;
|
||||
case "MouseMove":
|
||||
this.cx.board.updateMouseHover(ev.pos.sub(this.cx.offset));
|
||||
break;
|
||||
case "MouseDragBegin": {
|
||||
this.cx.selectionBox = new SelectionBox(ev.pos, ev.deltaPos);
|
||||
this.cx.transitionTo(new SelectingBox(this.cx));
|
||||
break;
|
||||
}
|
||||
case "KeyDown": {
|
||||
if (ev.key === "Shift") {
|
||||
this.cx.transitionTo(new Panning(this.cx));
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
this.cx.events.send({ tag: "ShowSelectedTool", tool: "select" });
|
||||
this.cx.runSimulation();
|
||||
}
|
||||
|
||||
onMouseDown(pos: V2): void {
|
||||
leave(): void {
|
||||
this.unsubscribe();
|
||||
}
|
||||
|
||||
private _onMouseDown(pos: V2): void {
|
||||
if (
|
||||
this.cx.board.handleMouseClick(pos.sub(this.cx.offset), {
|
||||
onInputPinClicked: (comp, i) => {
|
||||
@ -61,26 +87,6 @@ export class Normal implements State {
|
||||
},
|
||||
}) !== "handled"
|
||||
) {
|
||||
this.isMouseDown = true;
|
||||
this.dragStart = pos;
|
||||
}
|
||||
}
|
||||
|
||||
onMouseMove(_deltaPos: V2, pos: V2): void {
|
||||
if (this.isMouseDown && this.dragStart.sub(pos).len() > 5) {
|
||||
this.cx.selectionBox = new SelectionBox(
|
||||
this.dragStart,
|
||||
pos.sub(this.dragStart),
|
||||
);
|
||||
this.cx.transitionTo(new SelectingBox(this.cx));
|
||||
}
|
||||
this.cx.board.updateMouseHover(pos.sub(this.cx.offset));
|
||||
}
|
||||
|
||||
onKeyDown(key: string): void {
|
||||
if (key === "Shift") {
|
||||
this.cx.transitionTo(new Panning(this.cx));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@ -90,36 +96,42 @@ export class Normal implements State {
|
||||
}
|
||||
|
||||
export class Panning implements State {
|
||||
private dragging = false;
|
||||
private unsubscribe!: EventUnsub;
|
||||
|
||||
constructor(private cx: Cx) {}
|
||||
|
||||
onMouseDown(_pos: V2): void {
|
||||
this.dragging = true;
|
||||
enter(): void {
|
||||
this.unsubscribe = this.cx.events.subscribe(
|
||||
["MouseDragBegin", "MouseDrag", "KeyDown", "KeyUp"],
|
||||
(ev) => {
|
||||
switch (ev.tag) {
|
||||
case "MouseDragBegin":
|
||||
case "MouseDrag":
|
||||
this.cx.moveOffset(ev.deltaPos);
|
||||
break;
|
||||
case "KeyDown": {
|
||||
if (ev.key === "Escape") {
|
||||
this.cx.transitionTo(new Normal(this.cx));
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "KeyUp": {
|
||||
if (ev.key === "Shift") {
|
||||
this.cx.transitionTo(new Normal(this.cx));
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
this.cx.events.send({ tag: "ShowSelectedTool", tool: "pan" });
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
leave(): void {
|
||||
this.unsubscribe();
|
||||
}
|
||||
|
||||
selectedTool(): Tool | null {
|
||||
@ -128,6 +140,8 @@ export class Panning implements State {
|
||||
}
|
||||
|
||||
export class Placing implements State {
|
||||
private unsubscribe!: EventUnsub;
|
||||
|
||||
private compDef: ComponentKind;
|
||||
|
||||
constructor(
|
||||
@ -137,31 +151,39 @@ export class Placing implements State {
|
||||
this.compDef = this.cx.componentRepo.get(this.tool);
|
||||
}
|
||||
|
||||
enterState(): void {
|
||||
enter(): void {
|
||||
this.unsubscribe = this.cx.events.subscribe(
|
||||
["MouseDown", "MouseMove", "KeyDown"],
|
||||
(ev) => {
|
||||
switch (ev.tag) {
|
||||
case "MouseDown": {
|
||||
const boardPos = this.cx.canvasPosToBoard(ev.pos);
|
||||
if (this.cx.board.canPlaceComponent(this.compDef, boardPos)) {
|
||||
this.cx.board.placeComponent(this.compDef, boardPos);
|
||||
this.cx.transitionTo(new Normal(this.cx));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "MouseMove":
|
||||
this.cx.setComponentPlacerPos(ev.pos);
|
||||
break;
|
||||
case "KeyDown": {
|
||||
if (ev.key === "Escape") {
|
||||
this.cx.transitionTo(new Normal(this.cx));
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
this.cx.addComponentPlacer(v2(0, 0), this.compDef.size);
|
||||
}
|
||||
|
||||
leaveState(): void {
|
||||
leave(): 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;
|
||||
}
|
||||
this.unsubscribe();
|
||||
}
|
||||
|
||||
selectedTool(): Tool | null {
|
||||
@ -170,11 +192,51 @@ export class Placing implements State {
|
||||
}
|
||||
|
||||
export class Selecting implements State {
|
||||
private unsubscribe!: EventUnsub;
|
||||
|
||||
private isMouseDown = false;
|
||||
|
||||
constructor(private cx: Cx) {}
|
||||
|
||||
onMouseDown(pos: V2): void {
|
||||
enter(): void {
|
||||
this.unsubscribe = this.cx.events.subscribe(
|
||||
["MouseDown", "MouseUp", "MouseMove", "KeyDown"],
|
||||
(ev) => {
|
||||
switch (ev.tag) {
|
||||
case "MouseDown":
|
||||
this.onMouseDown(ev.pos);
|
||||
break;
|
||||
case "MouseUp":
|
||||
this.isMouseDown = false;
|
||||
break;
|
||||
case "MouseMove": {
|
||||
this.cx.board.updateMouseHover(ev.pos.sub(this.cx.offset));
|
||||
if (this.isMouseDown) {
|
||||
this.cx.transitionTo(new Moving(this.cx));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "KeyDown": {
|
||||
if (ev.key === "Delete") {
|
||||
if (!this.cx.selection) {
|
||||
throw new Error("expected selection");
|
||||
}
|
||||
this.cx.board.deleteSelection(this.cx.selection);
|
||||
this.cx.selection = null;
|
||||
this.cx.transitionTo(new Normal(this.cx));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
leave(): void {
|
||||
this.unsubscribe();
|
||||
}
|
||||
|
||||
private onMouseDown(pos: V2): void {
|
||||
if (
|
||||
this.cx.board.handleMouseClick(pos.sub(this.cx.offset), {
|
||||
onComponentClicked: (comp) => {
|
||||
@ -207,46 +269,60 @@ export class Selecting implements State {
|
||||
|
||||
this.isMouseDown = true;
|
||||
}
|
||||
|
||||
onMouseUp(_pos: V2): void {
|
||||
this.isMouseDown = false;
|
||||
}
|
||||
|
||||
onMouseMove(_deltaPos: V2, pos: V2): void {
|
||||
this.cx.board.updateMouseHover(pos.sub(this.cx.offset));
|
||||
if (this.isMouseDown) {
|
||||
this.cx.transitionTo(new Moving(this.cx));
|
||||
}
|
||||
}
|
||||
|
||||
onKeyDown(key: string): void {
|
||||
if (key === "Delete") {
|
||||
if (!this.cx.selection) {
|
||||
throw new Error("expected selection");
|
||||
}
|
||||
this.cx.board.deleteSelection(this.cx.selection);
|
||||
this.cx.selection = null;
|
||||
this.cx.transitionTo(new Normal(this.cx));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class Moving implements State {
|
||||
private unsubscribe!: EventUnsub;
|
||||
|
||||
constructor(private cx: Cx) {}
|
||||
|
||||
onMouseUp(_pos: V2): void {
|
||||
this.cx.transitionTo(new Selecting(this.cx));
|
||||
enter(): void {
|
||||
this.unsubscribe = this.cx.events.subscribe(
|
||||
["MouseUp", "MouseMove"],
|
||||
(ev) => {
|
||||
switch (ev.tag) {
|
||||
case "MouseUp":
|
||||
this.cx.transitionTo(new Selecting(this.cx));
|
||||
break;
|
||||
case "MouseMove":
|
||||
this.cx.selection?.move(ev.deltaPos);
|
||||
break;
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
onMouseMove(deltaPos: V2, _pos: V2): void {
|
||||
this.cx.selection?.move(deltaPos);
|
||||
leave(): void {
|
||||
this.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
export class SelectingBox implements State {
|
||||
private unsubscribe!: EventUnsub;
|
||||
|
||||
constructor(private cx: Cx) {}
|
||||
|
||||
onMouseUp(_pos: V2): void {
|
||||
enter(): void {
|
||||
this.unsubscribe = this.cx.events.subscribe(
|
||||
["MouseUp", "MouseMove"],
|
||||
(ev) => {
|
||||
switch (ev.tag) {
|
||||
case "MouseUp":
|
||||
this.onMouseUp(ev.pos);
|
||||
break;
|
||||
case "MouseMove":
|
||||
this.cx.selectionBox?.move(ev.deltaPos);
|
||||
break;
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
leave(): void {
|
||||
this.unsubscribe();
|
||||
}
|
||||
|
||||
private onMouseUp(_pos: V2): void {
|
||||
if (!this.cx.selectionBox) {
|
||||
throw new Error("expected selectionBox to active");
|
||||
}
|
||||
@ -278,19 +354,50 @@ export class SelectingBox implements State {
|
||||
}
|
||||
}
|
||||
|
||||
onMouseMove(deltaPos: V2): void {
|
||||
this.cx.selectionBox?.move(deltaPos);
|
||||
}
|
||||
|
||||
selectedTool(): Tool | null {
|
||||
return "select";
|
||||
}
|
||||
}
|
||||
|
||||
export class Wiring implements State {
|
||||
private unsubscribe!: EventUnsub;
|
||||
|
||||
constructor(private cx: Cx) {}
|
||||
|
||||
onMouseDown(pos: V2): void {
|
||||
enter(): void {
|
||||
this.unsubscribe = this.cx.events.subscribe(
|
||||
["MouseDown", "MouseMove", "KeyDown"],
|
||||
(ev) => {
|
||||
switch (ev.tag) {
|
||||
case "MouseDown":
|
||||
this.onMouseDown(ev.pos);
|
||||
break;
|
||||
case "MouseMove": {
|
||||
if (!this.cx.connectingWire) {
|
||||
throw new Error("expected connectingWire to be active");
|
||||
}
|
||||
this.cx.connectingWire.move(ev.pos.sub(this.cx.offset));
|
||||
this.cx.board.updateMouseHover(ev.pos.sub(this.cx.offset));
|
||||
break;
|
||||
}
|
||||
case "KeyDown": {
|
||||
if (ev.key === "Escape") {
|
||||
this.cx.transitionTo(new Normal(this.cx));
|
||||
this.cx.connectingWire = null;
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
leave(): void {
|
||||
this.unsubscribe();
|
||||
}
|
||||
|
||||
private onMouseDown(pos: V2): void {
|
||||
if (
|
||||
this.cx.board.handleMouseClick(pos.sub(this.cx.offset), {
|
||||
onInputPinClicked: (comp, i) => {
|
||||
@ -315,23 +422,10 @@ export class Wiring implements State {
|
||||
prev: this.cx.connectingWire!,
|
||||
pos: pos.sub(this.cx.offset),
|
||||
};
|
||||
this.cx.connectingWire = new ConnectingWire(kind, pos);
|
||||
}
|
||||
}
|
||||
|
||||
onMouseMove(_deltaPos: V2, pos: V2): void {
|
||||
if (!this.cx.connectingWire) {
|
||||
throw new Error("expected connectingWire to be active");
|
||||
}
|
||||
this.cx.connectingWire.move(pos.sub(this.cx.offset));
|
||||
this.cx.board.updateMouseHover(pos.sub(this.cx.offset));
|
||||
}
|
||||
|
||||
onKeyDown(key: string): void {
|
||||
if (key === "Escape") {
|
||||
this.cx.transitionTo(new Normal(this.cx));
|
||||
this.cx.connectingWire = null;
|
||||
return;
|
||||
this.cx.connectingWire = new ConnectingWire(
|
||||
kind,
|
||||
pos.sub(this.cx.offset),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user