From 022e8b89f7d749ddcfb8d9a4347c5cbcf9de38f9 Mon Sep 17 00:00:00 2001 From: sfja Date: Wed, 10 Jun 2026 04:10:36 +0200 Subject: [PATCH] extract offset --- editor/src/editor/Cx.ts | 23 ++---- editor/src/editor/ViewPos.ts | 34 ++++++++ editor/src/editor/events.ts | 4 +- editor/src/editor/states.ts | 146 +++++++++++++++-------------------- 4 files changed, 106 insertions(+), 101 deletions(-) create mode 100644 editor/src/editor/ViewPos.ts diff --git a/editor/src/editor/Cx.ts b/editor/src/editor/Cx.ts index 92f3e19..e46489e 100644 --- a/editor/src/editor/Cx.ts +++ b/editor/src/editor/Cx.ts @@ -12,11 +12,12 @@ import * as ir from "./ir"; import { Sim } from "./sim"; import { EventBus } from "./events"; import { Mouse } from "./Mouse"; +import { ViewPos } from "./ViewPos"; export type Tool = string; export class Cx { - public offset = v2(0, 0); + public viewpos: ViewPos; private renderNeeded = false; private state = new states.Normal(this) as states.State; @@ -34,6 +35,7 @@ export class Cx { public mouse: Mouse; constructor(public events: EventBus) { + this.viewpos = new ViewPos(events); this.mouse = new Mouse(this.events); this.state.enter(); @@ -57,7 +59,7 @@ export class Cx { } render(canvas: HTMLCanvasElement) { - const r = new Renderer(canvas, this.offset); + const r = new Renderer(canvas, this.viewpos.offset); r.clear(); r.drawGrid(); @@ -95,15 +97,10 @@ export class Cx { transitionTo(newState: states.State) { this.state.leave(); this.state = newState; - console.log(`Entering state ${newState.constructor.name}`); + // console.log(`Entering state ${newState.constructor.name}`); this.state.enter(); } - moveOffset(deltaPos: V2) { - this.offset.x += deltaPos.x; - this.offset.y += deltaPos.y; - } - addComponentPlacer(pos: V2, size: V2) { this.componentPlacer = new ComponentPlacer(pos, size); } @@ -118,12 +115,6 @@ export class Cx { } } - canvasPosToBoard(pos: V2): V2 { - const absX = pos.x - this.offset.x; - const absY = pos.y - this.offset.y; - return v2(absX, absY); - } - runSimulation() { // const comp = this.board.toIr(); // console.log("Before optimizing"); @@ -150,14 +141,14 @@ export class SelectionBox { this.size = this.size.add(deltaPos); } - normalized(): { pos: V2; size: V2 } { + boardRect(viewpos: ViewPos): { pos: V2; size: V2 } { const normalizedAxis = (p: number, s: number): [number, number] => s >= 0 ? [p, s] : [p + s, -s]; const [x, w] = normalizedAxis(this.pos.x, this.size.x); const [y, h] = normalizedAxis(this.pos.y, this.size.y); - return { pos: v2(x, y), size: v2(w, h) }; + return { pos: v2(x, y).sub(viewpos.offset), size: v2(w, h) }; } } diff --git a/editor/src/editor/ViewPos.ts b/editor/src/editor/ViewPos.ts new file mode 100644 index 0000000..56deb85 --- /dev/null +++ b/editor/src/editor/ViewPos.ts @@ -0,0 +1,34 @@ +import type { EventBus } from "./events"; +import { v2, type V2 } from "./V2"; + +export class ViewPos { + public offset = v2(0, 0); + + constructor(private events: EventBus) { + this.events.subscribe(["MouseDown", "MouseMove"], (ev) => { + const absPos = ev.pos; + const pos = this.canvasToBoard(absPos); + switch (ev.tag) { + case "MouseDown": + this.events.send({ tag: "MouseDownOffset", pos, absPos }); + break; + case "MouseMove": + this.events.send({ + tag: "MouseMoveOffset", + pos, + deltaPos: ev.deltaPos, + }); + break; + } + }); + } + + canvasToBoard(pos: V2): V2 { + return pos.sub(this.offset); + } + + move(deltaPos: V2) { + this.offset.x += deltaPos.x; + this.offset.y += deltaPos.y; + } +} diff --git a/editor/src/editor/events.ts b/editor/src/editor/events.ts index b51f456..e4f1a46 100644 --- a/editor/src/editor/events.ts +++ b/editor/src/editor/events.ts @@ -15,7 +15,9 @@ export type Event = deltaPos: V2; } | { tag: "KeyDown" | "KeyUp"; key: string } - | { tag: "SelectTool" | "ShowSelectedTool"; tool: string }; + | { tag: "SelectTool" | "ShowSelectedTool"; tool: string } + | { tag: "MouseDownOffset"; pos: V2; absPos: V2 } + | { tag: "MouseMoveOffset"; pos: V2; deltaPos: V2 }; export type EventOf = Event & { tag: Tag }; diff --git a/editor/src/editor/states.ts b/editor/src/editor/states.ts index 9999a19..dbaf3a4 100644 --- a/editor/src/editor/states.ts +++ b/editor/src/editor/states.ts @@ -7,7 +7,6 @@ import { v2, type V2 } from "./V2"; export interface State { leave(): void; enter(): void; - selectedTool?(): Tool | null; } export class Normal implements State { @@ -17,14 +16,14 @@ export class Normal implements State { enter(): void { this.unsubscribe = this.cx.events.subscribe( - ["MouseDown", "MouseMove", "MouseDragBegin", "KeyDown"], + ["MouseDownOffset", "MouseMoveOffset", "MouseDragBegin", "KeyDown"], (ev) => { switch (ev.tag) { - case "MouseDown": - this._onMouseDown(ev.pos); + case "MouseDownOffset": + this.onMouseDown(ev.pos); break; - case "MouseMove": - this.cx.board.updateMouseHover(ev.pos.sub(this.cx.offset)); + case "MouseMoveOffset": + this.cx.board.updateMouseHover(ev.pos); break; case "MouseDragBegin": { this.cx.selectionBox = new SelectionBox(ev.pos, ev.deltaPos); @@ -50,48 +49,41 @@ export class Normal implements State { this.unsubscribe(); } - private _onMouseDown(pos: V2): void { - if ( - this.cx.board.handleMouseClick(pos.sub(this.cx.offset), { - onInputPinClicked: (comp, i) => { - this.cx.connectingWire = new ConnectingWire( - { tag: "InputPin", comp, i }, - pos.sub(this.cx.offset), - ); - this.cx.transitionTo(new Wiring(this.cx)); - }, - onOutputPinClicked: (comp, i) => { - this.cx.connectingWire = new ConnectingWire( - { tag: "OutputPin", comp, i }, - pos.sub(this.cx.offset), - ); - this.cx.transitionTo(new Wiring(this.cx)); - }, - onComponentClicked: (comp) => { + private onMouseDown(pos: V2): void { + this.cx.board.handleMouseClick(pos, { + onInputPinClicked: (comp, i) => { + this.cx.connectingWire = new ConnectingWire( + { tag: "InputPin", comp, i }, + pos, + ); + this.cx.transitionTo(new Wiring(this.cx)); + }, + onOutputPinClicked: (comp, i) => { + this.cx.connectingWire = new ConnectingWire( + { tag: "OutputPin", comp, i }, + pos, + ); + this.cx.transitionTo(new Wiring(this.cx)); + }, + onComponentClicked: (comp) => { + this.cx.selection = new Selection(); + this.cx.selection.addComponent(comp); + this.cx.transitionTo(new Selecting(this.cx)); + }, + onJointClicked: (joint) => { + if (this.cx.keysPressed.has("Control")) { this.cx.selection = new Selection(); - this.cx.selection.addComponent(comp); + this.cx.selection.addJoint(joint); this.cx.transitionTo(new Selecting(this.cx)); - }, - onJointClicked: (joint) => { - if (this.cx.keysPressed.has("Control")) { - this.cx.selection = new Selection(); - this.cx.selection.addJoint(joint); - this.cx.transitionTo(new Selecting(this.cx)); - } else { - this.cx.connectingWire = new ConnectingWire( - { tag: "Joint", joint }, - pos.sub(this.cx.offset), - ); - this.cx.transitionTo(new Wiring(this.cx)); - } - }, - }) !== "handled" - ) { - } - } - - selectedTool(): Tool | null { - return "select"; + } else { + this.cx.connectingWire = new ConnectingWire( + { tag: "Joint", joint }, + pos, + ); + this.cx.transitionTo(new Wiring(this.cx)); + } + }, + }); } } @@ -107,7 +99,7 @@ export class Panning implements State { switch (ev.tag) { case "MouseDragBegin": case "MouseDrag": - this.cx.moveOffset(ev.deltaPos); + this.cx.viewpos.move(ev.deltaPos); break; case "KeyDown": { if (ev.key === "Escape") { @@ -133,10 +125,6 @@ export class Panning implements State { leave(): void { this.unsubscribe(); } - - selectedTool(): Tool | null { - return "pan"; - } } export class Placing implements State { @@ -153,11 +141,11 @@ export class Placing implements State { enter(): void { this.unsubscribe = this.cx.events.subscribe( - ["MouseDown", "MouseMove", "KeyDown"], + ["MouseDownOffset", "MouseMove", "KeyDown"], (ev) => { switch (ev.tag) { - case "MouseDown": { - const boardPos = this.cx.canvasPosToBoard(ev.pos); + case "MouseDownOffset": { + const boardPos = ev.pos; if (this.cx.board.canPlaceComponent(this.compDef, boardPos)) { this.cx.board.placeComponent(this.compDef, boardPos); this.cx.transitionTo(new Normal(this.cx)); @@ -185,10 +173,6 @@ export class Placing implements State { this.cx.removeComponentPlacer(); this.unsubscribe(); } - - selectedTool(): Tool | null { - return this.tool; - } } export class Selecting implements State { @@ -200,17 +184,17 @@ export class Selecting implements State { enter(): void { this.unsubscribe = this.cx.events.subscribe( - ["MouseDown", "MouseUp", "MouseMove", "KeyDown"], + ["MouseDownOffset", "MouseUp", "MouseMoveOffset", "KeyDown"], (ev) => { switch (ev.tag) { - case "MouseDown": - this.onMouseDown(ev.pos); + case "MouseDownOffset": + this.onMouseDown(ev.pos, ev.absPos); break; case "MouseUp": this.isMouseDown = false; break; - case "MouseMove": { - this.cx.board.updateMouseHover(ev.pos.sub(this.cx.offset)); + case "MouseMoveOffset": { + this.cx.board.updateMouseHover(ev.pos); if (this.isMouseDown) { this.cx.transitionTo(new Moving(this.cx)); } @@ -236,9 +220,9 @@ export class Selecting implements State { this.unsubscribe(); } - private onMouseDown(pos: V2): void { + private onMouseDown(pos: V2, absPos: V2): void { if ( - this.cx.board.handleMouseClick(pos.sub(this.cx.offset), { + this.cx.board.handleMouseClick(pos, { onComponentClicked: (comp) => { if (this.cx.keysPressed.has("Control")) { this.cx.selection?.toggleComponent(comp); @@ -258,11 +242,11 @@ export class Selecting implements State { }) !== "handled" ) { if (this.cx.keysPressed.has("Control")) { - this.cx.selectionBox = new SelectionBox(pos); + this.cx.selectionBox = new SelectionBox(absPos); this.cx.transitionTo(new SelectingBox(this.cx)); } else { this.cx.selection = null; - this.cx.selectionBox = new SelectionBox(pos); + this.cx.selectionBox = new SelectionBox(absPos); this.cx.transitionTo(new SelectingBox(this.cx)); } } @@ -326,13 +310,10 @@ export class SelectingBox implements State { if (!this.cx.selectionBox) { throw new Error("expected selectionBox to active"); } - const { pos, size } = this.cx.selectionBox.normalized(); + const { pos, size } = this.cx.selectionBox.boardRect(this.cx.viewpos); - const components = this.cx.board.componentsInRect( - pos.sub(this.cx.offset), - size, - ); - const joints = this.cx.board.jointsInRect(pos.sub(this.cx.offset), size); + const components = this.cx.board.componentsInRect(pos, size); + const joints = this.cx.board.jointsInRect(pos, size); if (components.length > 0 || joints.length > 0) { this.cx.selection ??= new Selection(); @@ -366,18 +347,18 @@ export class Wiring implements State { enter(): void { this.unsubscribe = this.cx.events.subscribe( - ["MouseDown", "MouseMove", "KeyDown"], + ["MouseDownOffset", "MouseMoveOffset", "KeyDown"], (ev) => { switch (ev.tag) { - case "MouseDown": + case "MouseDownOffset": this.onMouseDown(ev.pos); break; - case "MouseMove": { + case "MouseMoveOffset": { 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)); + this.cx.connectingWire.move(ev.pos); + this.cx.board.updateMouseHover(ev.pos); break; } case "KeyDown": { @@ -399,7 +380,7 @@ export class Wiring implements State { private onMouseDown(pos: V2): void { if ( - this.cx.board.handleMouseClick(pos.sub(this.cx.offset), { + this.cx.board.handleMouseClick(pos, { onInputPinClicked: (comp, i) => { this.cx.connectingWire!.connectToInput(this.cx.board, comp, i); this.cx.connectingWire = null; @@ -420,12 +401,9 @@ export class Wiring implements State { const kind: ConnectingWireKind = { tag: "Intermediary", prev: this.cx.connectingWire!, - pos: pos.sub(this.cx.offset), + pos, }; - this.cx.connectingWire = new ConnectingWire( - kind, - pos.sub(this.cx.offset), - ); + this.cx.connectingWire = new ConnectingWire(kind, pos); } } }