wiring
This commit is contained in:
parent
0ec608ee54
commit
6a5b051872
@ -1,12 +1,22 @@
|
|||||||
import type { Selection } from "./Cx";
|
import type { Selection } from "./Cx";
|
||||||
import type { Renderer } from "./Renderer";
|
import type { Renderer } from "./Renderer";
|
||||||
import { pointInsideRect, rectsCollide, v2, V2 } from "./V2";
|
import {
|
||||||
|
lineSegmentPointDistance,
|
||||||
|
pointInsideRect,
|
||||||
|
rectsCollide,
|
||||||
|
v2,
|
||||||
|
V2,
|
||||||
|
} from "./V2";
|
||||||
|
|
||||||
export class Board {
|
export class Board {
|
||||||
private components: PlacedComponent[] = [];
|
private components: Component[] = [];
|
||||||
|
private joints: Joint[] = [];
|
||||||
|
private wires: Wire[] = [];
|
||||||
|
|
||||||
private hoveredOverInput: [PlacedComponent, number] | null = null;
|
private hoveredOverInput: [Component, number] | null = null;
|
||||||
private hoveredOverOutput: [PlacedComponent, number] | null = null;
|
private hoveredOverOutput: [Component, number] | null = null;
|
||||||
|
private hoveredOverJoint: Joint | null = null;
|
||||||
|
private hoveredOverWire: Wire | null = null;
|
||||||
|
|
||||||
constructor() {}
|
constructor() {}
|
||||||
|
|
||||||
@ -17,7 +27,7 @@ export class Board {
|
|||||||
}
|
}
|
||||||
|
|
||||||
placeComponent(kind: ComponentKind, pos: V2) {
|
placeComponent(kind: ComponentKind, pos: V2) {
|
||||||
this.components.push({ kind: kind, pos });
|
this.components.push(new Component(kind, pos));
|
||||||
}
|
}
|
||||||
|
|
||||||
render(r: Renderer, selection: Selection | null) {
|
render(r: Renderer, selection: Selection | null) {
|
||||||
@ -29,7 +39,23 @@ export class Board {
|
|||||||
r.drawComponentBody(pos, kind);
|
r.drawComponentBody(pos, kind);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const { i, pinOffset } of kind.inputPinIter()) {
|
for (const wire of this.wires) {
|
||||||
|
if (this.hoveredOverWire == wire) {
|
||||||
|
r.drawWireHovered(wire.beginPos(), wire.endPos());
|
||||||
|
} else {
|
||||||
|
r.drawWire(wire.beginPos(), wire.endPos());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const joint of this.joints) {
|
||||||
|
r.drawJoint(joint.pos);
|
||||||
|
|
||||||
|
if (this.hoveredOverJoint === joint) {
|
||||||
|
r.drawJointHover(joint.pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [i, pinOffset] of kind.inputPinOffsets().entries()) {
|
||||||
if (kind.inputs[i] !== null) {
|
if (kind.inputs[i] !== null) {
|
||||||
throw new Error("pin text not implemented");
|
throw new Error("pin text not implemented");
|
||||||
}
|
}
|
||||||
@ -43,7 +69,7 @@ export class Board {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const { i, pinOffset } of kind.outputPinIter()) {
|
for (const [i, pinOffset] of kind.outputPinOffsets().entries()) {
|
||||||
if (kind.outputs[i] !== null) {
|
if (kind.outputs[i] !== null) {
|
||||||
throw new Error("pin text not implemented");
|
throw new Error("pin text not implemented");
|
||||||
}
|
}
|
||||||
@ -62,83 +88,93 @@ export class Board {
|
|||||||
updateMouseHover(pos: V2) {
|
updateMouseHover(pos: V2) {
|
||||||
this.hoveredOverInput = null;
|
this.hoveredOverInput = null;
|
||||||
this.hoveredOverOutput = null;
|
this.hoveredOverOutput = null;
|
||||||
|
this.hoveredOverJoint = null;
|
||||||
|
this.hoveredOverWire = null;
|
||||||
|
|
||||||
for (const comp of this.components) {
|
for (const comp of this.components) {
|
||||||
const {
|
const mouseOverResult = comp.mouseOver(pos);
|
||||||
pos: { x, y },
|
switch (mouseOverResult?.tag) {
|
||||||
kind: {
|
case "InputPin":
|
||||||
size: { x: w },
|
this.hoveredOverInput = [comp, mouseOverResult.i];
|
||||||
},
|
return;
|
||||||
} = comp;
|
case "OutputPin":
|
||||||
|
this.hoveredOverOutput = [comp, mouseOverResult.i];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
for (const joint of this.joints) {
|
||||||
!pointInsideRect(
|
if (joint.isMouseOver(pos)) {
|
||||||
pos,
|
this.hoveredOverJoint = joint;
|
||||||
comp.pos.sub(v2(5, 5)),
|
return;
|
||||||
comp.kind.size.add(v2(10, 10)),
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
for (const { i, pinOffset } of comp.kind.inputPinIter()) {
|
|
||||||
if (v2(x, y + pinOffset).distance(pos) < 6) {
|
|
||||||
this.hoveredOverInput = [comp, i];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const { i, pinOffset } of comp.kind.outputPinIter()) {
|
|
||||||
if (v2(x + w, y + pinOffset).distance(pos) < 6) {
|
for (const wire of this.wires) {
|
||||||
this.hoveredOverOutput = [comp, i];
|
if (wire.isMouseOver(pos)) {
|
||||||
}
|
this.hoveredOverWire = wire;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMouseClick(
|
handleMouseClick(
|
||||||
pos: V2,
|
pos: V2,
|
||||||
inputPinClicked: (comp: PlacedComponent, i: number) => void,
|
actions: {
|
||||||
outputPinClicked: (comp: PlacedComponent, i: number) => void,
|
onInputPinClicked?(comp: Component, i: number): void;
|
||||||
componentClicked: (comp: PlacedComponent) => void,
|
onOutputPinClicked?(comp: Component, i: number): void;
|
||||||
|
onComponentClicked?(comp: Component): void;
|
||||||
|
onJointClicked?(joint: Joint): void;
|
||||||
|
onWireClicked?(wire: Wire): void;
|
||||||
|
},
|
||||||
): "handled" | "not handled" {
|
): "handled" | "not handled" {
|
||||||
for (const comp of this.components) {
|
for (const comp of this.components) {
|
||||||
const {
|
const mouseOverResult = comp.mouseOver(pos);
|
||||||
pos: { x, y },
|
switch (mouseOverResult?.tag) {
|
||||||
kind: {
|
case "Component":
|
||||||
size: { x: w },
|
actions.onComponentClicked?.(comp);
|
||||||
},
|
return "handled";
|
||||||
} = comp;
|
case "InputPin":
|
||||||
|
actions.onInputPinClicked?.(comp, mouseOverResult.i);
|
||||||
|
return "handled";
|
||||||
|
case "OutputPin":
|
||||||
|
actions.onOutputPinClicked?.(comp, mouseOverResult.i);
|
||||||
|
return "handled";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
for (const joint of this.joints) {
|
||||||
!pointInsideRect(
|
if (joint.isMouseOver(pos)) {
|
||||||
pos,
|
actions.onJointClicked?.(joint);
|
||||||
comp.pos.sub(v2(5, 5)),
|
|
||||||
comp.kind.size.add(v2(10, 10)),
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
for (const { i, pinOffset } of comp.kind.inputPinIter()) {
|
|
||||||
if (v2(x, y + pinOffset).distance(pos) < 6) {
|
|
||||||
inputPinClicked(comp, i);
|
|
||||||
return "handled";
|
return "handled";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const { i, pinOffset } of comp.kind.outputPinIter()) {
|
|
||||||
if (v2(x + w, y + pinOffset).distance(pos) < 6) {
|
for (const wire of this.wires) {
|
||||||
outputPinClicked(comp, i);
|
if (wire.isMouseOver(pos)) {
|
||||||
|
actions.onWireClicked?.(wire);
|
||||||
return "handled";
|
return "handled";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
componentClicked(comp);
|
|
||||||
return "handled";
|
|
||||||
}
|
|
||||||
return "not handled";
|
return "not handled";
|
||||||
}
|
}
|
||||||
|
|
||||||
componentsInRect(pos: V2, size: V2): PlacedComponent[] {
|
componentsInRect(pos: V2, size: V2): Component[] {
|
||||||
return this.components.filter((comp) =>
|
return this.components.filter((comp) =>
|
||||||
rectsCollide(pos, size, comp.pos, comp.kind.size),
|
rectsCollide(pos, size, comp.pos, comp.kind.size),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addJoint(pos: V2): Joint {
|
||||||
|
const t = new Joint(pos);
|
||||||
|
this.joints.push(t);
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
addWire(begin: WireConnection, end: WireConnection): Wire {
|
||||||
|
const wire = new Wire(begin, end);
|
||||||
|
this.wires.push(wire);
|
||||||
|
return wire;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ComponentRepo {
|
export class ComponentRepo {
|
||||||
@ -167,10 +203,55 @@ export class ComponentRepo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PlacedComponent = {
|
export class Component {
|
||||||
kind: ComponentKind;
|
constructor(
|
||||||
pos: V2;
|
public kind: ComponentKind,
|
||||||
};
|
public pos: V2,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
mouseOver(pos: V2): ComponentMouseOverResult | null {
|
||||||
|
const {
|
||||||
|
pos: { x, y },
|
||||||
|
kind: {
|
||||||
|
size: { x: w },
|
||||||
|
},
|
||||||
|
} = this;
|
||||||
|
|
||||||
|
if (
|
||||||
|
!pointInsideRect(
|
||||||
|
pos,
|
||||||
|
this.pos.sub(v2(5, 5)),
|
||||||
|
this.kind.size.add(v2(10, 10)),
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [i, pinOffset] of this.kind.inputPinOffsets().entries()) {
|
||||||
|
if (v2(x, y + pinOffset).distance(pos) < 6) {
|
||||||
|
return { tag: "InputPin", i };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const [i, pinOffset] of this.kind.outputPinOffsets().entries()) {
|
||||||
|
if (v2(x + w, y + pinOffset).distance(pos) < 6) {
|
||||||
|
return { tag: "OutputPin", i };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { tag: "Component" };
|
||||||
|
}
|
||||||
|
|
||||||
|
inputPinPos(i: number): V2 {
|
||||||
|
return this.pos.add(v2(0, this.kind.inputPinOffsets()[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
outputPinPos(i: number): V2 {
|
||||||
|
return this.pos.add(v2(this.kind.size.x, this.kind.outputPinOffsets()[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ComponentMouseOverResult =
|
||||||
|
| { tag: "Component" }
|
||||||
|
| { tag: "InputPin" | "OutputPin"; i: number };
|
||||||
|
|
||||||
export class ComponentKind {
|
export class ComponentKind {
|
||||||
constructor(
|
constructor(
|
||||||
@ -180,20 +261,66 @@ export class ComponentKind {
|
|||||||
public outputs: (string | null)[],
|
public outputs: (string | null)[],
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
inputPinIter(): { i: number; pinOffset: number }[] {
|
inputPinOffsets(): number[] {
|
||||||
return this.inputs.map((_, i) => ({
|
return this.inputs.map(
|
||||||
i,
|
(_, i) => ((i + 1) * this.size.y) / (this.inputs.length + 1),
|
||||||
pinOffset: ((i + 1) * this.size.y) / (this.inputs.length + 1),
|
);
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
outputPinIter(): { i: number; pinOffset: number }[] {
|
outputPinOffsets(): number[] {
|
||||||
return this.outputs.map((_, i) => ({
|
return this.outputs.map(
|
||||||
i,
|
(_, i) => ((i + 1) * this.size.y) / (this.outputs.length + 1),
|
||||||
pinOffset: ((i + 1) * this.size.y) / (this.outputs.length + 1),
|
);
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class Joint {
|
||||||
|
constructor(public pos: V2) {}
|
||||||
|
|
||||||
|
isMouseOver(pos: V2): boolean {
|
||||||
|
return this.pos.distance(pos) < 6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Wire {
|
||||||
|
constructor(
|
||||||
|
private begin: WireConnection,
|
||||||
|
private end: WireConnection,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
isMouseOver(pos: V2): boolean {
|
||||||
|
const distance = lineSegmentPointDistance(
|
||||||
|
this.beginPos(),
|
||||||
|
this.endPos(),
|
||||||
|
pos,
|
||||||
|
);
|
||||||
|
return distance !== null && distance < 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
beginPos(): V2 {
|
||||||
|
return this.connectionPos(this.begin);
|
||||||
|
}
|
||||||
|
|
||||||
|
endPos(): V2 {
|
||||||
|
return this.connectionPos(this.end);
|
||||||
|
}
|
||||||
|
|
||||||
|
private connectionPos(connection: WireConnection): V2 {
|
||||||
|
switch (connection.tag) {
|
||||||
|
case "InputPin":
|
||||||
|
return connection.comp.inputPinPos(connection.i);
|
||||||
|
case "OutputPin":
|
||||||
|
return connection.comp.outputPinPos(connection.i);
|
||||||
|
case "Joint":
|
||||||
|
return connection.joint.pos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type WireConnection =
|
||||||
|
| { tag: "InputPin"; comp: Component; i: number }
|
||||||
|
| { tag: "OutputPin"; comp: Component; i: number }
|
||||||
|
| { tag: "Joint"; joint: Joint };
|
||||||
|
|
||||||
const defaultDefs = [
|
const defaultDefs = [
|
||||||
{
|
{
|
||||||
label: "input",
|
label: "input",
|
||||||
|
|||||||
@ -1,4 +1,10 @@
|
|||||||
import { Board, ComponentRepo, type PlacedComponent } from "./Board";
|
import {
|
||||||
|
Board,
|
||||||
|
ComponentRepo,
|
||||||
|
Joint,
|
||||||
|
type Component,
|
||||||
|
type WireConnection,
|
||||||
|
} from "./Board";
|
||||||
import { Renderer } from "./Renderer";
|
import { Renderer } from "./Renderer";
|
||||||
import * as states from "./states";
|
import * as states from "./states";
|
||||||
import { v2, V2 } from "./V2";
|
import { v2, V2 } from "./V2";
|
||||||
@ -133,9 +139,10 @@ export class Cx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class SelectionBox {
|
export class SelectionBox {
|
||||||
public size = v2(0, 0);
|
constructor(
|
||||||
|
public pos: V2,
|
||||||
constructor(public pos: V2) {}
|
public size = v2(0, 0),
|
||||||
|
) {}
|
||||||
|
|
||||||
render(r: Renderer) {
|
render(r: Renderer) {
|
||||||
r.drawSelectionBox(this.pos, this.size);
|
r.drawSelectionBox(this.pos, this.size);
|
||||||
@ -168,13 +175,13 @@ export class ComponentPlacer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class Selection {
|
export class Selection {
|
||||||
selectedComponents = new Set<PlacedComponent>();
|
selectedComponents = new Set<Component>();
|
||||||
|
|
||||||
addComponent(comp: PlacedComponent) {
|
addComponent(comp: Component) {
|
||||||
this.selectedComponents.add(comp);
|
this.selectedComponents.add(comp);
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleComponent(comp: PlacedComponent) {
|
toggleComponent(comp: Component) {
|
||||||
if (this.selectedComponents.has(comp)) {
|
if (this.selectedComponents.has(comp)) {
|
||||||
this.selectedComponents.delete(comp);
|
this.selectedComponents.delete(comp);
|
||||||
} else {
|
} else {
|
||||||
@ -182,7 +189,7 @@ export class Selection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isComponentSelected(comp: PlacedComponent) {
|
isComponentSelected(comp: Component) {
|
||||||
return this.selectedComponents.has(comp);
|
return this.selectedComponents.has(comp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -197,6 +204,11 @@ export class ConnectingWire {
|
|||||||
switch (this.kind.tag) {
|
switch (this.kind.tag) {
|
||||||
case "InputPin":
|
case "InputPin":
|
||||||
case "OutputPin":
|
case "OutputPin":
|
||||||
|
break;
|
||||||
|
case "Intermediary":
|
||||||
|
this.kind.prev.render(r);
|
||||||
|
r.drawConnectingWirePoint(this.beginPos());
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
r.drawConnectingWire(this.beginPos(), this.pos);
|
r.drawConnectingWire(this.beginPos(), this.pos);
|
||||||
}
|
}
|
||||||
@ -205,24 +217,62 @@ export class ConnectingWire {
|
|||||||
this.pos = pos;
|
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 });
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
private beginPos(): V2 {
|
||||||
switch (this.kind.tag) {
|
switch (this.kind.tag) {
|
||||||
case "InputPin":
|
case "InputPin":
|
||||||
return v2(
|
return v2(
|
||||||
this.kind.comp.pos.x,
|
this.kind.comp.pos.x,
|
||||||
this.kind.comp.pos.y +
|
this.kind.comp.pos.y +
|
||||||
this.kind.comp.kind.inputPinIter()[this.kind.i].pinOffset,
|
this.kind.comp.kind.inputPinOffsets()[this.kind.i],
|
||||||
);
|
);
|
||||||
case "OutputPin":
|
case "OutputPin":
|
||||||
return v2(
|
return v2(
|
||||||
this.kind.comp.pos.x + this.kind.comp.kind.size.x,
|
this.kind.comp.pos.x + this.kind.comp.kind.size.x,
|
||||||
this.kind.comp.pos.y +
|
this.kind.comp.pos.y +
|
||||||
this.kind.comp.kind.outputPinIter()[this.kind.i].pinOffset,
|
this.kind.comp.kind.outputPinOffsets()[this.kind.i],
|
||||||
);
|
);
|
||||||
|
case "Intermediary":
|
||||||
|
return this.kind.pos;
|
||||||
|
case "Joint":
|
||||||
|
return this.kind.joint.pos;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ConnectingWireKind =
|
export type ConnectingWireKind =
|
||||||
| { tag: "InputPin"; comp: PlacedComponent; i: number }
|
| { tag: "InputPin"; comp: Component; i: number }
|
||||||
| { tag: "OutputPin"; comp: PlacedComponent; i: number };
|
| { tag: "OutputPin"; comp: Component; i: number }
|
||||||
|
| { tag: "Intermediary"; prev: ConnectingWire; pos: V2 }
|
||||||
|
| { tag: "Joint"; joint: Joint };
|
||||||
|
|||||||
@ -146,6 +146,53 @@ export class Renderer {
|
|||||||
c.stroke();
|
c.stroke();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
drawWire(begin: V2, end: V2) {
|
||||||
|
const { c, offset } = this;
|
||||||
|
const { x: x0, y: y0 } = begin.add(offset);
|
||||||
|
const { x: x1, y: y1 } = end.add(offset);
|
||||||
|
|
||||||
|
c.strokeStyle = `#333333`;
|
||||||
|
c.lineWidth = 3;
|
||||||
|
c.beginPath();
|
||||||
|
c.moveTo(x0, y0);
|
||||||
|
c.lineTo(x1, y1);
|
||||||
|
c.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
drawWireHovered(begin: V2, end: V2) {
|
||||||
|
const { c, offset } = this;
|
||||||
|
const { x: x0, y: y0 } = begin.add(offset);
|
||||||
|
const { x: x1, y: y1 } = end.add(offset);
|
||||||
|
|
||||||
|
c.strokeStyle = `#444444`;
|
||||||
|
c.lineWidth = 3;
|
||||||
|
c.beginPath();
|
||||||
|
c.moveTo(x0, y0);
|
||||||
|
c.lineTo(x1, y1);
|
||||||
|
c.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
drawJoint(pos: V2) {
|
||||||
|
const { c, offset } = this;
|
||||||
|
const { x: x0, y: y0 } = pos.add(offset);
|
||||||
|
|
||||||
|
c.fillStyle = `#333333`;
|
||||||
|
c.beginPath();
|
||||||
|
c.arc(x0, y0, 3, 0, Math.PI * 2);
|
||||||
|
c.fill();
|
||||||
|
}
|
||||||
|
|
||||||
|
drawJointHover(pos: V2) {
|
||||||
|
const { c, offset } = this;
|
||||||
|
const { x, y } = pos.add(offset);
|
||||||
|
|
||||||
|
c.strokeStyle = `#eee`;
|
||||||
|
c.lineWidth = 2;
|
||||||
|
c.beginPath();
|
||||||
|
c.arc(x, y, 5, 0, Math.PI * 2);
|
||||||
|
c.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
drawConnectingWire(begin: V2, end: V2) {
|
drawConnectingWire(begin: V2, end: V2) {
|
||||||
const { c, offset } = this;
|
const { c, offset } = this;
|
||||||
const { x: x0, y: y0 } = begin.add(offset);
|
const { x: x0, y: y0 } = begin.add(offset);
|
||||||
@ -158,4 +205,14 @@ export class Renderer {
|
|||||||
c.lineTo(x1, y1);
|
c.lineTo(x1, y1);
|
||||||
c.stroke();
|
c.stroke();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
drawConnectingWirePoint(pos: V2) {
|
||||||
|
const { c, offset } = this;
|
||||||
|
const { x: x0, y: y0 } = pos.add(offset);
|
||||||
|
|
||||||
|
c.fillStyle = `#333333`;
|
||||||
|
c.beginPath();
|
||||||
|
c.arc(x0, y0, 3, 0, Math.PI * 2);
|
||||||
|
c.fill();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,14 +17,36 @@ export class V2 {
|
|||||||
len(): number {
|
len(): number {
|
||||||
return Math.sqrt(this.x ** 2 + this.y ** 2);
|
return Math.sqrt(this.x ** 2 + this.y ** 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
distance(other: V2) {
|
distance(other: V2) {
|
||||||
return this.rsub(other).len();
|
return this.rsub(other).len();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abs(): V2 {
|
||||||
|
return new V2(Math.abs(this.x), Math.abs(this.y));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const v2 = (x: number, y: number): V2 => new V2(x, y);
|
export const v2 = (x: number, y: number): V2 => new V2(x, y);
|
||||||
|
|
||||||
|
export function lineSegmentPointDistance(p1: V2, p2: V2, p: V2): number | null {
|
||||||
|
const len = p2.sub(p1).len();
|
||||||
|
const dist1 = p1.sub(p).len();
|
||||||
|
const dist2 = p2.sub(p).len();
|
||||||
|
|
||||||
|
return dist1 < len && dist2 < len ? linePointDistance(p1, p2, p) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function linePointDistance(p1: V2, p2: V2, p: V2): number {
|
||||||
|
const { x: x1, y: y1 } = p1;
|
||||||
|
const { x: x2, y: y2 } = p2;
|
||||||
|
const { x, y } = p;
|
||||||
|
|
||||||
|
return (
|
||||||
|
Math.abs((y2 - y1) * x - (x2 - x1) * y + x2 * y1 - y2 * x1) /
|
||||||
|
Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function rectsCollide(
|
export function rectsCollide(
|
||||||
{ x: ax, y: ay }: V2,
|
{ x: ax, y: ay }: V2,
|
||||||
{ x: aw, y: ah }: V2,
|
{ x: aw, y: ah }: V2,
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { type ComponentKind } from "./Board";
|
import { type ComponentKind } from "./Board";
|
||||||
import { ConnectingWire, Selection } from "./Cx";
|
import { ConnectingWire, Selection, type ConnectingWireKind } from "./Cx";
|
||||||
import { SelectionBox, type Cx, type Tool } from "./Cx";
|
import { SelectionBox, type Cx, type Tool } from "./Cx";
|
||||||
import { v2, type V2 } from "./V2";
|
import { v2, type V2 } from "./V2";
|
||||||
|
|
||||||
@ -15,50 +15,55 @@ export interface State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class Normal implements State {
|
export class Normal implements State {
|
||||||
|
private dragStart = v2(0, 0);
|
||||||
|
private isMouseDown = false;
|
||||||
|
|
||||||
constructor(private cx: Cx) {}
|
constructor(private cx: Cx) {}
|
||||||
|
|
||||||
onMouseDown(pos: V2): void {
|
onMouseDown(pos: V2): void {
|
||||||
if (
|
if (
|
||||||
this.cx.board.handleMouseClick(
|
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),
|
pos.sub(this.cx.offset),
|
||||||
(comp, i) => {
|
|
||||||
console.log({ comp, i });
|
|
||||||
this.cx.connectingWire = new ConnectingWire(
|
|
||||||
{
|
|
||||||
tag: "InputPin",
|
|
||||||
comp,
|
|
||||||
i,
|
|
||||||
},
|
|
||||||
pos,
|
|
||||||
);
|
);
|
||||||
this.cx.transitionTo(new Wiring(this.cx));
|
this.cx.transitionTo(new Wiring(this.cx));
|
||||||
},
|
},
|
||||||
(comp, i) => {
|
onOutputPinClicked: (comp, i) => {
|
||||||
this.cx.connectingWire = new ConnectingWire(
|
this.cx.connectingWire = new ConnectingWire(
|
||||||
{
|
{ tag: "OutputPin", comp, i },
|
||||||
tag: "OutputPin",
|
pos.sub(this.cx.offset),
|
||||||
comp,
|
|
||||||
i,
|
|
||||||
},
|
|
||||||
pos,
|
|
||||||
);
|
);
|
||||||
this.cx.transitionTo(new Wiring(this.cx));
|
this.cx.transitionTo(new Wiring(this.cx));
|
||||||
},
|
},
|
||||||
(comp) => {
|
onComponentClicked: (comp) => {
|
||||||
this.cx.selection = new Selection();
|
this.cx.selection = new Selection();
|
||||||
this.cx.selection.addComponent(comp);
|
this.cx.selection.addComponent(comp);
|
||||||
this.cx.transitionTo(new Selecting(this.cx));
|
this.cx.transitionTo(new Selecting(this.cx));
|
||||||
},
|
},
|
||||||
) === "handled"
|
onJointClicked: (joint) => {
|
||||||
|
this.cx.connectingWire = new ConnectingWire(
|
||||||
|
{ tag: "Joint", joint },
|
||||||
|
pos.sub(this.cx.offset),
|
||||||
|
);
|
||||||
|
this.cx.transitionTo(new Wiring(this.cx));
|
||||||
|
},
|
||||||
|
}) !== "handled"
|
||||||
) {
|
) {
|
||||||
return;
|
this.isMouseDown = true;
|
||||||
} else {
|
this.dragStart = pos;
|
||||||
this.cx.selectionBox = new SelectionBox(pos);
|
|
||||||
this.cx.transitionTo(new SelectingBox(this.cx));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMouseMove(_deltaPos: V2, pos: V2): void {
|
onMouseMove(_deltaPos: V2, pos: V2): void {
|
||||||
|
if (this.isMouseDown && this.dragStart.sub(pos).len() > 40) {
|
||||||
|
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));
|
this.cx.board.updateMouseHover(pos.sub(this.cx.offset));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,11 +164,8 @@ export class Selecting implements State {
|
|||||||
|
|
||||||
onMouseDown(pos: V2): void {
|
onMouseDown(pos: V2): void {
|
||||||
if (
|
if (
|
||||||
this.cx.board.handleMouseClick(
|
this.cx.board.handleMouseClick(pos.sub(this.cx.offset), {
|
||||||
pos.sub(this.cx.offset),
|
onComponentClicked: (comp) => {
|
||||||
(_comp, _i) => {},
|
|
||||||
(_comp, _i) => {},
|
|
||||||
(comp) => {
|
|
||||||
if (this.cx.keysPressed.has("Control")) {
|
if (this.cx.keysPressed.has("Control")) {
|
||||||
this.cx.selection?.toggleComponent(comp);
|
this.cx.selection?.toggleComponent(comp);
|
||||||
} else {
|
} else {
|
||||||
@ -171,10 +173,8 @@ export class Selecting implements State {
|
|||||||
this.cx.selection.addComponent(comp);
|
this.cx.selection.addComponent(comp);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
) === "handled"
|
}) !== "handled"
|
||||||
) {
|
) {
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
if (this.cx.keysPressed.has("Control")) {
|
if (this.cx.keysPressed.has("Control")) {
|
||||||
this.cx.selectionBox = new SelectionBox(pos);
|
this.cx.selectionBox = new SelectionBox(pos);
|
||||||
this.cx.transitionTo(new SelectingBox(this.cx));
|
this.cx.transitionTo(new SelectingBox(this.cx));
|
||||||
@ -225,11 +225,35 @@ export class SelectingBox implements State {
|
|||||||
export class Wiring implements State {
|
export class Wiring implements State {
|
||||||
constructor(private cx: Cx) {}
|
constructor(private cx: Cx) {}
|
||||||
|
|
||||||
|
onMouseDown(pos: V2): void {
|
||||||
|
if (
|
||||||
|
this.cx.board.handleMouseClick(pos.sub(this.cx.offset), {
|
||||||
|
onInputPinClicked: (comp, i) => {
|
||||||
|
this.cx.connectingWire!.connectToInput(this.cx.board, comp, i);
|
||||||
|
this.cx.connectingWire = null;
|
||||||
|
this.cx.transitionTo(new Normal(this.cx));
|
||||||
|
},
|
||||||
|
onOutputPinClicked: (comp, i) => {
|
||||||
|
this.cx.connectingWire!.connectToInput(this.cx.board, comp, i);
|
||||||
|
this.cx.connectingWire = null;
|
||||||
|
this.cx.transitionTo(new Normal(this.cx));
|
||||||
|
},
|
||||||
|
}) !== "handled"
|
||||||
|
) {
|
||||||
|
const kind: ConnectingWireKind = {
|
||||||
|
tag: "Intermediary",
|
||||||
|
prev: this.cx.connectingWire!,
|
||||||
|
pos: pos.sub(this.cx.offset),
|
||||||
|
};
|
||||||
|
this.cx.connectingWire = new ConnectingWire(kind, pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMouseMove(_deltaPos: V2, pos: V2): void {
|
onMouseMove(_deltaPos: V2, pos: V2): void {
|
||||||
if (!this.cx.connectingWire) {
|
if (!this.cx.connectingWire) {
|
||||||
throw new Error("expected connectingWire to be active");
|
throw new Error("expected connectingWire to be active");
|
||||||
}
|
}
|
||||||
this.cx.connectingWire.move(pos);
|
this.cx.connectingWire.move(pos.sub(this.cx.offset));
|
||||||
this.cx.board.updateMouseHover(pos.sub(this.cx.offset));
|
this.cx.board.updateMouseHover(pos.sub(this.cx.offset));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user