add eventbus
This commit is contained in:
parent
90bbe2794f
commit
cc1ef05b95
716
editor/package-lock.json
generated
716
editor/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -41,6 +41,10 @@ function Canvas({ editor, canvasRef, width, height }: Props): ReactElement {
|
||||
editor.mouseMove(deltaPos, pos);
|
||||
editor.renderIfNeeded(ev.target as HTMLCanvasElement);
|
||||
}}
|
||||
onMouseLeave={(ev) => {
|
||||
editor.mouseLeave();
|
||||
editor.renderIfNeeded(ev.target as HTMLCanvasElement);
|
||||
}}
|
||||
onKeyDown={(ev) => {
|
||||
editor.keyDown(ev.key);
|
||||
editor.renderIfNeeded(ev.target as HTMLCanvasElement);
|
||||
|
||||
@ -9,6 +9,9 @@ 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";
|
||||
|
||||
export type Tool = string;
|
||||
|
||||
@ -28,6 +31,9 @@ export class Cx {
|
||||
|
||||
public keysPressed = new Set<string>();
|
||||
|
||||
public eventBus = new EventBus();
|
||||
public mouse = new Mouse(this.eventBus);
|
||||
|
||||
render(canvas: HTMLCanvasElement) {
|
||||
const r = new Renderer(canvas, this.offset);
|
||||
|
||||
@ -102,7 +108,7 @@ export class Cx {
|
||||
transitionTo(newState: states.State) {
|
||||
this.state.leaveState?.();
|
||||
this.state = newState;
|
||||
console.log(`Entering state ${newState.constructor.name}`);
|
||||
// console.log(`Entering state ${newState.constructor.name}`);
|
||||
this.state.enterState?.();
|
||||
this.notifyListeners();
|
||||
}
|
||||
@ -139,14 +145,14 @@ export class Cx {
|
||||
}
|
||||
|
||||
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 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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -13,17 +13,24 @@ export class Editor {
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
229
editor/src/editor/Mouse.ts
Normal file
229
editor/src/editor/Mouse.ts
Normal file
@ -0,0 +1,229 @@
|
||||
import type { EventBus, EventUnsub } from "./events";
|
||||
import { v2, type V2 } from "./V2";
|
||||
|
||||
const doubleClickDelay = 200;
|
||||
|
||||
export class Mouse {
|
||||
private state: State;
|
||||
|
||||
constructor(public eventBus: EventBus) {
|
||||
this.state = new Normal(this);
|
||||
}
|
||||
|
||||
transitionTo(state: State) {
|
||||
this.state.leave();
|
||||
this.state = state;
|
||||
}
|
||||
}
|
||||
|
||||
interface State {
|
||||
leave(): void;
|
||||
}
|
||||
|
||||
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) => {
|
||||
switch (ev.tag) {
|
||||
case "MouseDown":
|
||||
this.cx.transitionTo(new FirstPress(this.cx, ev.pos));
|
||||
break;
|
||||
case "MouseUp":
|
||||
break;
|
||||
case "MouseMove":
|
||||
break;
|
||||
case "MouseLeave":
|
||||
break;
|
||||
default:
|
||||
throw new Error("invalid state");
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
leave(): void {
|
||||
this.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
class FirstPress implements State {
|
||||
private unsubscribe: EventUnsub;
|
||||
|
||||
private time = Date.now();
|
||||
private totalDelta = v2(0, 0);
|
||||
|
||||
constructor(
|
||||
private cx: Mouse,
|
||||
private pos: V2,
|
||||
) {
|
||||
console.log("Mouse::FirstPress");
|
||||
|
||||
this.unsubscribe = cx.eventBus.subscribe(
|
||||
["MouseDown", "MouseUp", "MouseMove", "MouseLeave"],
|
||||
(ev) => {
|
||||
switch (ev.tag) {
|
||||
case "MouseUp": {
|
||||
if (Date.now() - this.time < doubleClickDelay) {
|
||||
this.cx.transitionTo(new FirstRelease(this.cx, this.pos));
|
||||
break;
|
||||
}
|
||||
this.cx.eventBus.send({ tag: "MouseClick", pos: ev.pos });
|
||||
this.cx.transitionTo(new Normal(this.cx));
|
||||
break;
|
||||
}
|
||||
case "MouseMove": {
|
||||
this.totalDelta = this.totalDelta.add(ev.deltaPos);
|
||||
if (this.totalDelta.len() > 5) {
|
||||
this.cx.eventBus.send({
|
||||
tag: "MouseDragBegin",
|
||||
pos: this.pos,
|
||||
deltaPos: this.totalDelta,
|
||||
});
|
||||
this.cx.transitionTo(new Dragging(this.cx));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "MouseLeave":
|
||||
this.cx.transitionTo(new Normal(this.cx));
|
||||
break;
|
||||
default:
|
||||
throw new Error(`invalid state, unexpected ${ev.tag}`);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
leave(): void {
|
||||
this.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
class FirstRelease implements State {
|
||||
private unsubscribe: EventUnsub;
|
||||
|
||||
private timeout: ReturnType<typeof setTimeout>;
|
||||
|
||||
constructor(
|
||||
private cx: Mouse,
|
||||
private pos: V2,
|
||||
) {
|
||||
console.log("Mouse::FirstRelease");
|
||||
|
||||
this.unsubscribe = cx.eventBus.subscribe(
|
||||
["MouseDown", "MouseUp", "MouseMove", "MouseLeave"],
|
||||
(ev) => {
|
||||
switch (ev.tag) {
|
||||
case "MouseDown":
|
||||
this.cx.transitionTo(new SecondPress(this.cx, this.pos));
|
||||
break;
|
||||
case "MouseUp":
|
||||
break;
|
||||
case "MouseMove":
|
||||
break;
|
||||
case "MouseLeave":
|
||||
break;
|
||||
default:
|
||||
throw new Error("invalid state");
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
this.timeout = setTimeout(() => {
|
||||
this.cx.transitionTo(new Normal(this.cx));
|
||||
}, doubleClickDelay);
|
||||
}
|
||||
|
||||
leave(): void {
|
||||
this.unsubscribe();
|
||||
clearTimeout(this.timeout);
|
||||
}
|
||||
}
|
||||
|
||||
class SecondPress implements State {
|
||||
private unsubscribe: EventUnsub;
|
||||
|
||||
private totalDelta = v2(0, 0);
|
||||
|
||||
constructor(
|
||||
private cx: Mouse,
|
||||
private pos: V2,
|
||||
) {
|
||||
console.log("Mouse::SecondPress");
|
||||
|
||||
this.unsubscribe = cx.eventBus.subscribe(
|
||||
["MouseDown", "MouseUp", "MouseMove", "MouseLeave"],
|
||||
(ev) => {
|
||||
switch (ev.tag) {
|
||||
case "MouseUp":
|
||||
this.cx.eventBus.send({ tag: "MouseDoubleClick", pos: this.pos });
|
||||
this.cx.transitionTo(new Normal(this.cx));
|
||||
break;
|
||||
case "MouseMove":
|
||||
this.totalDelta = this.totalDelta.add(ev.deltaPos);
|
||||
if (this.totalDelta.len() > 5) {
|
||||
this.cx.eventBus.send({
|
||||
tag: "MouseDragBegin",
|
||||
pos: this.pos,
|
||||
deltaPos: this.totalDelta,
|
||||
});
|
||||
this.cx.transitionTo(new Dragging(this.cx));
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case "MouseLeave":
|
||||
this.cx.transitionTo(new Normal(this.cx));
|
||||
break;
|
||||
default:
|
||||
throw new Error("invalid state");
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
leave(): void {
|
||||
this.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
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) => {
|
||||
switch (ev.tag) {
|
||||
case "MouseDown":
|
||||
this.cx.transitionTo(new FirstPress(this.cx, ev.pos));
|
||||
break;
|
||||
case "MouseUp":
|
||||
this.cx.transitionTo(new Normal(this.cx));
|
||||
break;
|
||||
case "MouseMove":
|
||||
this.cx.eventBus.send({
|
||||
tag: "MouseDrag",
|
||||
pos: ev.pos,
|
||||
deltaPos: ev.deltaPos,
|
||||
});
|
||||
break;
|
||||
case "MouseLeave":
|
||||
this.cx.transitionTo(new Normal(this.cx));
|
||||
break;
|
||||
default:
|
||||
throw new Error("invalid state");
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
leave(): void {
|
||||
this.unsubscribe();
|
||||
}
|
||||
}
|
||||
79
editor/src/editor/events.ts
Normal file
79
editor/src/editor/events.ts
Normal file
@ -0,0 +1,79 @@
|
||||
import type { V2 } from "./V2";
|
||||
|
||||
export type Event =
|
||||
| { tag: "MouseDown" | "MouseUp"; pos: V2 }
|
||||
| { tag: "MouseMove"; pos: V2; deltaPos: V2 }
|
||||
| { tag: "MouseLeave" }
|
||||
| { tag: "MouseClick" | "MouseDoubleClick"; pos: V2 }
|
||||
| {
|
||||
tag:
|
||||
| "MouseDragBegin"
|
||||
| "MouseDrag"
|
||||
| "MouseDoubleDragBegin"
|
||||
| "MouseDoubleDrag";
|
||||
pos: V2;
|
||||
deltaPos: V2;
|
||||
}
|
||||
| { tag: "MouseDrag" };
|
||||
|
||||
export type EventOf<Tag extends Event["tag"]> = Event & { tag: Tag };
|
||||
|
||||
export type EventUnsub = () => void;
|
||||
export type EventAction = (event: Event) => void;
|
||||
|
||||
export class EventBus {
|
||||
private eventActionsMap = new Map<Event["tag"], Set<EventAction>>();
|
||||
private actionEventsMap = new Map<EventAction, Event["tag"][]>();
|
||||
|
||||
subscribe<Tags extends Event["tag"]>(
|
||||
tags: Tags[],
|
||||
action: (event: EventOf<Tags>) => void,
|
||||
): EventUnsub {
|
||||
this.addSubscriber<Tags>(tags, action as EventAction);
|
||||
return () => {
|
||||
this.removeSubscriber(action as EventAction);
|
||||
};
|
||||
}
|
||||
|
||||
private addSubscriber<Tags extends Event["tag"]>(
|
||||
tags: Tags[],
|
||||
action: EventAction,
|
||||
) {
|
||||
if (this.actionEventsMap.has(action)) {
|
||||
throw new Error("action was added twice without cleanup");
|
||||
}
|
||||
this.actionEventsMap.set(action, tags);
|
||||
|
||||
for (const tag of tags) {
|
||||
if (!this.eventActionsMap.has(tag)) {
|
||||
this.eventActionsMap.set(tag, new Set());
|
||||
}
|
||||
this.eventActionsMap.get(tag)!.add(action);
|
||||
}
|
||||
}
|
||||
|
||||
private removeSubscriber(action: EventAction) {
|
||||
if (!this.actionEventsMap.has(action)) {
|
||||
throw new Error("action was already cleaned up");
|
||||
}
|
||||
for (const tag of this.actionEventsMap.get(action)!) {
|
||||
this.eventActionsMap.get(tag)?.delete(action);
|
||||
}
|
||||
this.actionEventsMap.delete(action);
|
||||
}
|
||||
|
||||
send(event: Event) {
|
||||
const actionSet = this.eventActionsMap.get(event.tag);
|
||||
if (!actionSet) {
|
||||
return;
|
||||
}
|
||||
const actions = [...actionSet];
|
||||
for (const action of actions) {
|
||||
if (!this.actionEventsMap.has(action)) {
|
||||
// has been unsubscribed by prior action
|
||||
continue;
|
||||
}
|
||||
action(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user