upsert cx
This commit is contained in:
parent
a0f0486590
commit
9a1894c6b5
@ -1,4 +1,4 @@
|
|||||||
import type { Selection } from "./Cx";
|
import type { Selection } from "./Selection";
|
||||||
import type { Renderer } from "./Renderer";
|
import type { Renderer } from "./Renderer";
|
||||||
import {
|
import {
|
||||||
lineSegmentPointDistance,
|
lineSegmentPointDistance,
|
||||||
|
|||||||
13
editor/src/editor/ComponentPlacer.ts
Normal file
13
editor/src/editor/ComponentPlacer.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import type { Renderer } from "./Renderer";
|
||||||
|
import type { V2 } from "./V2";
|
||||||
|
|
||||||
|
export class ComponentPlacer {
|
||||||
|
constructor(
|
||||||
|
public pos: V2,
|
||||||
|
public size: V2,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
render(r: Renderer) {
|
||||||
|
r.drawComponentPlacer(this.pos, this.size);
|
||||||
|
}
|
||||||
|
}
|
||||||
90
editor/src/editor/ConnectingWire.ts
Normal file
90
editor/src/editor/ConnectingWire.ts
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
import type { Board, Component, Joint, WireConnection } from "./Board";
|
||||||
|
import type { Renderer } from "./Renderer";
|
||||||
|
import { V2, v2 } from "./V2";
|
||||||
|
|
||||||
|
export class ConnectingWire {
|
||||||
|
constructor(
|
||||||
|
public kind: ConnectingWireKind,
|
||||||
|
public pos: V2,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
render(r: Renderer) {
|
||||||
|
switch (this.kind.tag) {
|
||||||
|
case "InputPin":
|
||||||
|
case "OutputPin":
|
||||||
|
break;
|
||||||
|
case "Intermediary":
|
||||||
|
this.kind.prev.render(r);
|
||||||
|
r.drawConnectingWirePoint(this.beginPos());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
r.drawConnectingWire(this.beginPos(), this.pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
move(pos: V2) {
|
||||||
|
this.pos = pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectToInput(board: Board, comp: Component, i: number) {
|
||||||
|
this.pushWire(board, { tag: "InputPin", comp, i });
|
||||||
|
}
|
||||||
|
|
||||||
|
connectToOutput(board: Board, comp: Component, i: number) {
|
||||||
|
this.pushWire(board, { tag: "OutputPin", comp, i });
|
||||||
|
}
|
||||||
|
|
||||||
|
connectToJoint(board: Board, joint: Joint) {
|
||||||
|
this.pushWire(board, { tag: "Joint", joint });
|
||||||
|
}
|
||||||
|
|
||||||
|
private pushWire(board: Board, end: WireConnection) {
|
||||||
|
switch (this.kind.tag) {
|
||||||
|
case "InputPin":
|
||||||
|
case "OutputPin": {
|
||||||
|
const { tag, comp, i } = this.kind;
|
||||||
|
board.addWire({ tag, comp, i }, end);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "Intermediary": {
|
||||||
|
const joint = board.addJoint(this.kind.pos);
|
||||||
|
board.addWire({ tag: "Joint", joint }, end);
|
||||||
|
this.kind.prev.pushWire(board, { tag: "Joint", joint });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "Joint": {
|
||||||
|
const joint = this.kind.joint;
|
||||||
|
board.addWire({ tag: "Joint", joint }, end);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
this.kind satisfies never;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private beginPos(): V2 {
|
||||||
|
switch (this.kind.tag) {
|
||||||
|
case "InputPin":
|
||||||
|
return v2(
|
||||||
|
this.kind.comp.pos.x,
|
||||||
|
this.kind.comp.pos.y +
|
||||||
|
this.kind.comp.kind.inputPinOffsets()[this.kind.i],
|
||||||
|
);
|
||||||
|
case "OutputPin":
|
||||||
|
return v2(
|
||||||
|
this.kind.comp.pos.x + this.kind.comp.kind.size.x,
|
||||||
|
this.kind.comp.pos.y +
|
||||||
|
this.kind.comp.kind.outputPinOffsets()[this.kind.i],
|
||||||
|
);
|
||||||
|
case "Intermediary":
|
||||||
|
return this.kind.pos;
|
||||||
|
case "Joint":
|
||||||
|
return this.kind.joint.pos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ConnectingWireKind =
|
||||||
|
| { tag: "InputPin"; comp: Component; i: number }
|
||||||
|
| { tag: "OutputPin"; comp: Component; i: number }
|
||||||
|
| { tag: "Intermediary"; prev: ConnectingWire; pos: V2 }
|
||||||
|
| { tag: "Joint"; joint: Joint };
|
||||||
@ -1,294 +0,0 @@
|
|||||||
import {
|
|
||||||
Board,
|
|
||||||
ComponentRepo,
|
|
||||||
Joint,
|
|
||||||
type Component,
|
|
||||||
type WireConnection,
|
|
||||||
} from "./Board";
|
|
||||||
import { Renderer } from "./Renderer";
|
|
||||||
import * as states from "./states";
|
|
||||||
import { v2, V2 } from "./V2";
|
|
||||||
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 viewpos: ViewPos;
|
|
||||||
private renderNeeded = false;
|
|
||||||
|
|
||||||
private state = new states.Normal(this) as states.State;
|
|
||||||
|
|
||||||
public selectionBox: SelectionBox | null = null;
|
|
||||||
private componentPlacer: ComponentPlacer | null = null;
|
|
||||||
public selection: Selection | null = null;
|
|
||||||
public connectingWire: ConnectingWire | null = null;
|
|
||||||
|
|
||||||
public board = new Board();
|
|
||||||
public componentRepo = ComponentRepo.withDefaults();
|
|
||||||
|
|
||||||
public keysPressed = new Set<string>();
|
|
||||||
|
|
||||||
public mouse: Mouse;
|
|
||||||
|
|
||||||
constructor(public events: EventBus) {
|
|
||||||
this.viewpos = new ViewPos(events);
|
|
||||||
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.viewpos.offset);
|
|
||||||
|
|
||||||
r.clear();
|
|
||||||
r.drawGrid();
|
|
||||||
this.board.render(r, this.selection);
|
|
||||||
this.selectionBox?.render(r);
|
|
||||||
this.componentPlacer?.render(r);
|
|
||||||
this.connectingWire?.render(r);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderIfNeeded(canvas: HTMLCanvasElement) {
|
|
||||||
if (this.renderNeeded) {
|
|
||||||
this.render(canvas);
|
|
||||||
this.renderNeeded = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private onSelectTool(tool: Tool) {
|
|
||||||
switch (tool) {
|
|
||||||
case "pan":
|
|
||||||
this.transitionTo(new states.Panning(this));
|
|
||||||
break;
|
|
||||||
case "input":
|
|
||||||
case "output":
|
|
||||||
case "and":
|
|
||||||
case "or":
|
|
||||||
case "not":
|
|
||||||
this.transitionTo(new states.Placing(this, tool));
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
this.transitionTo(new states.Normal(this));
|
|
||||||
}
|
|
||||||
this.events.send({ tag: "ShowSelectedTool", tool });
|
|
||||||
}
|
|
||||||
|
|
||||||
transitionTo(newState: states.State) {
|
|
||||||
this.state.leave();
|
|
||||||
this.state = newState;
|
|
||||||
// console.log(`Entering state ${newState.constructor.name}`);
|
|
||||||
this.state.enter();
|
|
||||||
}
|
|
||||||
|
|
||||||
addComponentPlacer(pos: V2, size: V2) {
|
|
||||||
this.componentPlacer = new ComponentPlacer(pos, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
removeComponentPlacer() {
|
|
||||||
this.componentPlacer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
setComponentPlacerPos(pos: V2) {
|
|
||||||
if (this.componentPlacer) {
|
|
||||||
this.componentPlacer.pos = pos;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
runSimulation() {
|
|
||||||
// const comp = this.board.toIr();
|
|
||||||
// console.log("Before optimizing");
|
|
||||||
// console.log(...new ir.ComponentPrinter().stringifyToConsole(comp));
|
|
||||||
// new ir.ComponentOptimizer(comp).optimize();
|
|
||||||
// console.log("After optimizing");
|
|
||||||
// console.log(...new ir.ComponentPrinter().stringifyToConsole(comp));
|
|
||||||
// const sim = new Sim(comp, [], []);
|
|
||||||
// sim.simulate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class SelectionBox {
|
|
||||||
constructor(
|
|
||||||
public pos: V2,
|
|
||||||
public size = v2(0, 0),
|
|
||||||
) {}
|
|
||||||
|
|
||||||
render(r: Renderer) {
|
|
||||||
r.drawSelectionBox(this.pos, this.size);
|
|
||||||
}
|
|
||||||
|
|
||||||
move(deltaPos: V2) {
|
|
||||||
this.size = this.size.add(deltaPos);
|
|
||||||
}
|
|
||||||
|
|
||||||
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).sub(viewpos.offset), size: v2(w, h) };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ComponentPlacer {
|
|
||||||
constructor(
|
|
||||||
public pos: V2,
|
|
||||||
public size: V2,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
render(r: Renderer) {
|
|
||||||
r.drawComponentPlacer(this.pos, this.size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Selection {
|
|
||||||
private selectedComponents = new Set<Component>();
|
|
||||||
private selectedJoints = new Set<Joint>();
|
|
||||||
|
|
||||||
addComponent(comp: Component) {
|
|
||||||
this.selectedComponents.add(comp);
|
|
||||||
}
|
|
||||||
addJoint(joint: Joint) {
|
|
||||||
this.selectedJoints.add(joint);
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleComponent(comp: Component) {
|
|
||||||
if (this.selectedComponents.has(comp)) {
|
|
||||||
this.selectedComponents.delete(comp);
|
|
||||||
} else {
|
|
||||||
this.selectedComponents.add(comp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
toggleJoint(joint: Joint) {
|
|
||||||
if (this.selectedJoints.has(joint)) {
|
|
||||||
this.selectedJoints.delete(joint);
|
|
||||||
} else {
|
|
||||||
this.selectedJoints.add(joint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
isComponentSelected(comp: Component) {
|
|
||||||
return this.selectedComponents.has(comp);
|
|
||||||
}
|
|
||||||
isJointSelected(joint: Joint) {
|
|
||||||
return this.selectedJoints.has(joint);
|
|
||||||
}
|
|
||||||
|
|
||||||
move(deltaPos: V2) {
|
|
||||||
for (const comp of this.selectedComponents) {
|
|
||||||
comp.pos = comp.pos.add(deltaPos);
|
|
||||||
}
|
|
||||||
for (const joint of this.selectedJoints) {
|
|
||||||
joint.pos = joint.pos.add(deltaPos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ConnectingWire {
|
|
||||||
constructor(
|
|
||||||
public kind: ConnectingWireKind,
|
|
||||||
public pos: V2,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
render(r: Renderer) {
|
|
||||||
switch (this.kind.tag) {
|
|
||||||
case "InputPin":
|
|
||||||
case "OutputPin":
|
|
||||||
break;
|
|
||||||
case "Intermediary":
|
|
||||||
this.kind.prev.render(r);
|
|
||||||
r.drawConnectingWirePoint(this.beginPos());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
r.drawConnectingWire(this.beginPos(), this.pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
move(pos: V2) {
|
|
||||||
this.pos = pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
connectToInput(board: Board, comp: Component, i: number) {
|
|
||||||
this.pushWire(board, { tag: "InputPin", comp, i });
|
|
||||||
}
|
|
||||||
|
|
||||||
connectToOutput(board: Board, comp: Component, i: number) {
|
|
||||||
this.pushWire(board, { tag: "OutputPin", comp, i });
|
|
||||||
}
|
|
||||||
|
|
||||||
connectToJoint(board: Board, joint: Joint) {
|
|
||||||
this.pushWire(board, { tag: "Joint", joint });
|
|
||||||
}
|
|
||||||
|
|
||||||
private pushWire(board: Board, end: WireConnection) {
|
|
||||||
switch (this.kind.tag) {
|
|
||||||
case "InputPin":
|
|
||||||
case "OutputPin": {
|
|
||||||
const { tag, comp, i } = this.kind;
|
|
||||||
board.addWire({ tag, comp, i }, end);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "Intermediary": {
|
|
||||||
const joint = board.addJoint(this.kind.pos);
|
|
||||||
board.addWire({ tag: "Joint", joint }, end);
|
|
||||||
this.kind.prev.pushWire(board, { tag: "Joint", joint });
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "Joint": {
|
|
||||||
const joint = this.kind.joint;
|
|
||||||
board.addWire({ tag: "Joint", joint }, end);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
this.kind satisfies never;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private beginPos(): V2 {
|
|
||||||
switch (this.kind.tag) {
|
|
||||||
case "InputPin":
|
|
||||||
return v2(
|
|
||||||
this.kind.comp.pos.x,
|
|
||||||
this.kind.comp.pos.y +
|
|
||||||
this.kind.comp.kind.inputPinOffsets()[this.kind.i],
|
|
||||||
);
|
|
||||||
case "OutputPin":
|
|
||||||
return v2(
|
|
||||||
this.kind.comp.pos.x + this.kind.comp.kind.size.x,
|
|
||||||
this.kind.comp.pos.y +
|
|
||||||
this.kind.comp.kind.outputPinOffsets()[this.kind.i],
|
|
||||||
);
|
|
||||||
case "Intermediary":
|
|
||||||
return this.kind.pos;
|
|
||||||
case "Joint":
|
|
||||||
return this.kind.joint.pos;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ConnectingWireKind =
|
|
||||||
| { tag: "InputPin"; comp: Component; i: number }
|
|
||||||
| { tag: "OutputPin"; comp: Component; i: number }
|
|
||||||
| { tag: "Intermediary"; prev: ConnectingWire; pos: V2 }
|
|
||||||
| { tag: "Joint"; joint: Joint };
|
|
||||||
@ -1,19 +1,124 @@
|
|||||||
import { Cx, type Tool } from "./Cx";
|
import { Board, ComponentRepo } from "./Board";
|
||||||
|
import { SelectionBox } from "./SelectionBox";
|
||||||
|
import { ComponentPlacer } from "./ComponentPlacer";
|
||||||
|
import { Selection } from "./Selection";
|
||||||
|
import { ConnectingWire } from "./ConnectingWire";
|
||||||
import { EventBus } from "./events";
|
import { EventBus } from "./events";
|
||||||
|
import { Mouse } from "./Mouse";
|
||||||
|
import { Renderer } from "./Renderer";
|
||||||
|
import * as states from "./states";
|
||||||
|
import type { V2 } from "./V2";
|
||||||
|
import { ViewPos } from "./ViewPos";
|
||||||
|
|
||||||
export class Editor {
|
export class Editor {
|
||||||
public events = new EventBus();
|
public events = new EventBus();
|
||||||
private cx = new Cx(this.events);
|
public viewpos = new ViewPos(this.events);
|
||||||
|
private renderNeeded = false;
|
||||||
|
|
||||||
|
private state = new states.Normal(this) as states.State;
|
||||||
|
|
||||||
|
public selectionBox: SelectionBox | null = null;
|
||||||
|
private componentPlacer: ComponentPlacer | null = null;
|
||||||
|
public selection: Selection | null = null;
|
||||||
|
public connectingWire: ConnectingWire | null = null;
|
||||||
|
|
||||||
|
public board = new Board();
|
||||||
|
public componentRepo = ComponentRepo.withDefaults();
|
||||||
|
|
||||||
|
public keysPressed = new Set<string>();
|
||||||
|
|
||||||
|
public mouse = new Mouse(this.events);
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
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) {
|
render(canvas: HTMLCanvasElement) {
|
||||||
this.cx.render(canvas);
|
const r = new Renderer(canvas, this.viewpos.offset);
|
||||||
|
|
||||||
|
r.clear();
|
||||||
|
r.drawGrid();
|
||||||
|
this.board.render(r, this.selection);
|
||||||
|
this.selectionBox?.render(r);
|
||||||
|
this.componentPlacer?.render(r);
|
||||||
|
this.connectingWire?.render(r);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderIfNeeded(canvas: HTMLCanvasElement) {
|
renderIfNeeded(canvas: HTMLCanvasElement) {
|
||||||
this.cx.renderIfNeeded(canvas);
|
if (this.renderNeeded) {
|
||||||
|
this.render(canvas);
|
||||||
|
this.renderNeeded = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tools(): Tool[] {
|
private onSelectTool(tool: string) {
|
||||||
|
switch (tool) {
|
||||||
|
case "pan":
|
||||||
|
this.transitionTo(new states.Panning(this));
|
||||||
|
break;
|
||||||
|
case "input":
|
||||||
|
case "output":
|
||||||
|
case "and":
|
||||||
|
case "or":
|
||||||
|
case "not":
|
||||||
|
this.transitionTo(new states.Placing(this, tool));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this.transitionTo(new states.Normal(this));
|
||||||
|
}
|
||||||
|
this.events.send({ tag: "ShowSelectedTool", tool });
|
||||||
|
}
|
||||||
|
|
||||||
|
transitionTo(newState: states.State) {
|
||||||
|
this.state.leave();
|
||||||
|
this.state = newState;
|
||||||
|
// console.log(`Entering state ${newState.constructor.name}`);
|
||||||
|
this.state.enter();
|
||||||
|
}
|
||||||
|
|
||||||
|
addComponentPlacer(pos: V2, size: V2) {
|
||||||
|
this.componentPlacer = new ComponentPlacer(pos, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeComponentPlacer() {
|
||||||
|
this.componentPlacer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
setComponentPlacerPos(pos: V2) {
|
||||||
|
if (this.componentPlacer) {
|
||||||
|
this.componentPlacer.pos = pos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
runSimulation() {
|
||||||
|
// const comp = this.board.toIr();
|
||||||
|
// console.log("Before optimizing");
|
||||||
|
// console.log(...new ir.ComponentPrinter().stringifyToConsole(comp));
|
||||||
|
// new ir.ComponentOptimizer(comp).optimize();
|
||||||
|
// console.log("After optimizing");
|
||||||
|
// console.log(...new ir.ComponentPrinter().stringifyToConsole(comp));
|
||||||
|
// const sim = new Sim(comp, [], []);
|
||||||
|
// sim.simulate();
|
||||||
|
}
|
||||||
|
|
||||||
|
tools(): string[] {
|
||||||
return ["select", "pan", "input", "output", "and", "or", "not"];
|
return ["select", "pan", "input", "output", "and", "or", "not"];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
45
editor/src/editor/Selection.ts
Normal file
45
editor/src/editor/Selection.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import type { Component, Joint } from "./Board";
|
||||||
|
import type { V2 } from "./V2";
|
||||||
|
|
||||||
|
export class Selection {
|
||||||
|
private selectedComponents = new Set<Component>();
|
||||||
|
private selectedJoints = new Set<Joint>();
|
||||||
|
|
||||||
|
addComponent(comp: Component) {
|
||||||
|
this.selectedComponents.add(comp);
|
||||||
|
}
|
||||||
|
addJoint(joint: Joint) {
|
||||||
|
this.selectedJoints.add(joint);
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleComponent(comp: Component) {
|
||||||
|
if (this.selectedComponents.has(comp)) {
|
||||||
|
this.selectedComponents.delete(comp);
|
||||||
|
} else {
|
||||||
|
this.selectedComponents.add(comp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
toggleJoint(joint: Joint) {
|
||||||
|
if (this.selectedJoints.has(joint)) {
|
||||||
|
this.selectedJoints.delete(joint);
|
||||||
|
} else {
|
||||||
|
this.selectedJoints.add(joint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isComponentSelected(comp: Component) {
|
||||||
|
return this.selectedComponents.has(comp);
|
||||||
|
}
|
||||||
|
isJointSelected(joint: Joint) {
|
||||||
|
return this.selectedJoints.has(joint);
|
||||||
|
}
|
||||||
|
|
||||||
|
move(deltaPos: V2) {
|
||||||
|
for (const comp of this.selectedComponents) {
|
||||||
|
comp.pos = comp.pos.add(deltaPos);
|
||||||
|
}
|
||||||
|
for (const joint of this.selectedJoints) {
|
||||||
|
joint.pos = joint.pos.add(deltaPos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
28
editor/src/editor/SelectionBox.ts
Normal file
28
editor/src/editor/SelectionBox.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import type { Renderer } from "./Renderer";
|
||||||
|
import { V2, v2 } from "./V2";
|
||||||
|
import type { ViewPos } from "./ViewPos";
|
||||||
|
|
||||||
|
export class SelectionBox {
|
||||||
|
constructor(
|
||||||
|
public pos: V2,
|
||||||
|
public size = v2(0, 0),
|
||||||
|
) {}
|
||||||
|
|
||||||
|
render(r: Renderer) {
|
||||||
|
r.drawSelectionBox(this.pos, this.size);
|
||||||
|
}
|
||||||
|
|
||||||
|
move(deltaPos: V2) {
|
||||||
|
this.size = this.size.add(deltaPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
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).sub(viewpos.offset), size: v2(w, h) };
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,6 +1,8 @@
|
|||||||
import { Component, Joint, type ComponentKind } from "./Board";
|
import { Component, Joint, type ComponentKind } from "./Board";
|
||||||
import { ConnectingWire, Selection, type ConnectingWireKind } from "./Cx";
|
import { Selection } from "./Selection";
|
||||||
import { SelectionBox, type Cx, type Tool } from "./Cx";
|
import { ConnectingWire, type ConnectingWireKind } from "./ConnectingWire";
|
||||||
|
import { SelectionBox } from "./SelectionBox";
|
||||||
|
import type { Editor } from "./Editor";
|
||||||
import type { EventUnsub } from "./events";
|
import type { EventUnsub } from "./events";
|
||||||
import { v2, type V2 } from "./V2";
|
import { v2, type V2 } from "./V2";
|
||||||
|
|
||||||
@ -12,7 +14,7 @@ export interface State {
|
|||||||
export class Normal implements State {
|
export class Normal implements State {
|
||||||
private unsubscribe!: EventUnsub;
|
private unsubscribe!: EventUnsub;
|
||||||
|
|
||||||
constructor(private cx: Cx) {}
|
constructor(private cx: Editor) {}
|
||||||
|
|
||||||
enter(): void {
|
enter(): void {
|
||||||
this.unsubscribe = this.cx.events.subscribe(
|
this.unsubscribe = this.cx.events.subscribe(
|
||||||
@ -90,7 +92,7 @@ export class Normal implements State {
|
|||||||
export class Panning implements State {
|
export class Panning implements State {
|
||||||
private unsubscribe!: EventUnsub;
|
private unsubscribe!: EventUnsub;
|
||||||
|
|
||||||
constructor(private cx: Cx) {}
|
constructor(private cx: Editor) {}
|
||||||
|
|
||||||
enter(): void {
|
enter(): void {
|
||||||
this.unsubscribe = this.cx.events.subscribe(
|
this.unsubscribe = this.cx.events.subscribe(
|
||||||
@ -133,8 +135,8 @@ export class Placing implements State {
|
|||||||
private compDef: ComponentKind;
|
private compDef: ComponentKind;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private cx: Cx,
|
private cx: Editor,
|
||||||
private tool: Tool,
|
private tool: string,
|
||||||
) {
|
) {
|
||||||
this.compDef = this.cx.componentRepo.get(this.tool);
|
this.compDef = this.cx.componentRepo.get(this.tool);
|
||||||
}
|
}
|
||||||
@ -180,7 +182,7 @@ export class Selecting implements State {
|
|||||||
|
|
||||||
private isMouseDown = false;
|
private isMouseDown = false;
|
||||||
|
|
||||||
constructor(private cx: Cx) {}
|
constructor(private cx: Editor) {}
|
||||||
|
|
||||||
enter(): void {
|
enter(): void {
|
||||||
this.unsubscribe = this.cx.events.subscribe(
|
this.unsubscribe = this.cx.events.subscribe(
|
||||||
@ -258,7 +260,7 @@ export class Selecting implements State {
|
|||||||
export class Moving implements State {
|
export class Moving implements State {
|
||||||
private unsubscribe!: EventUnsub;
|
private unsubscribe!: EventUnsub;
|
||||||
|
|
||||||
constructor(private cx: Cx) {}
|
constructor(private cx: Editor) {}
|
||||||
|
|
||||||
enter(): void {
|
enter(): void {
|
||||||
this.unsubscribe = this.cx.events.subscribe(
|
this.unsubscribe = this.cx.events.subscribe(
|
||||||
@ -284,7 +286,7 @@ export class Moving implements State {
|
|||||||
export class SelectingBox implements State {
|
export class SelectingBox implements State {
|
||||||
private unsubscribe!: EventUnsub;
|
private unsubscribe!: EventUnsub;
|
||||||
|
|
||||||
constructor(private cx: Cx) {}
|
constructor(private cx: Editor) {}
|
||||||
|
|
||||||
enter(): void {
|
enter(): void {
|
||||||
this.unsubscribe = this.cx.events.subscribe(
|
this.unsubscribe = this.cx.events.subscribe(
|
||||||
@ -334,16 +336,12 @@ export class SelectingBox implements State {
|
|||||||
this.cx.transitionTo(new Normal(this.cx));
|
this.cx.transitionTo(new Normal(this.cx));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
selectedTool(): Tool | null {
|
|
||||||
return "select";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Wiring implements State {
|
export class Wiring implements State {
|
||||||
private unsubscribe!: EventUnsub;
|
private unsubscribe!: EventUnsub;
|
||||||
|
|
||||||
constructor(private cx: Cx) {}
|
constructor(private cx: Editor) {}
|
||||||
|
|
||||||
enter(): void {
|
enter(): void {
|
||||||
this.unsubscribe = this.cx.events.subscribe(
|
this.unsubscribe = this.cx.events.subscribe(
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user