extract offset

This commit is contained in:
sfja 2026-06-10 04:10:36 +02:00
parent 9def12cad9
commit 022e8b89f7
4 changed files with 106 additions and 101 deletions

View File

@ -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) };
}
}

View File

@ -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;
}
}

View File

@ -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<Tag extends Event["tag"]> = Event & { tag: Tag };

View File

@ -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);
}
}
}