Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit cd37dfe

Browse files
committed
Better events, with event bootstrapping
1 parent bb1f8bb commit cd37dfe

File tree

17 files changed

+146
-79
lines changed

17 files changed

+146
-79
lines changed

.yarnrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
--ignore-engines true

demos/demo-standard/index.story.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ storiesOf("Simple Usage", module).add("Full example", () => {
1313
//setup canvas engine
1414
let engine = new CanvasEngine();
1515
engine.installDefaults();
16-
engine.installHistoryBank();
1716

1817
let model = new CanvasModel();
1918
model.setOffset(100, 100);

src/CanvasEngine.ts

Lines changed: 78 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,24 +20,36 @@ import { KeyInput } from "./state-machine/input/KeyInput";
2020
import { ModelElementInput } from "./state-machine/input/ModelElementInput";
2121
import { DefaultState } from "./state-machine/states/DefaultState";
2222
import { Toolkit } from "./Toolkit";
23+
import { CanvasLayerModel } from "./models-canvas/CanvasLayerModel";
24+
import { SelectionElementModel } from "./primitives/selection/SelectionElementModel";
25+
import { ModelEvent } from "./event-bus/events/ModelEvent";
26+
import { BaseEvent, BaseObject } from "./models/BaseObject";
2327

2428
export class CanvasEngineError extends Error {}
2529

26-
export class CanvasEngine<T extends CanvasModel = CanvasModel> {
30+
export interface CanvasEngineListener<T> {
31+
modelChanged?: (event: BaseEvent & { model: T; oldModel: T }) => any;
32+
}
33+
34+
export class CanvasEngine<T extends CanvasModel = CanvasModel> extends BaseObject<CanvasEngineListener<T>> {
2735
protected elementFactories: { [type: string]: AbstractElementFactory };
2836
protected model: T;
2937
protected stateMachine: StateMachine;
3038
protected canvasWidget;
3139
protected historyBank: HistoryBank;
3240
protected eventBus: EventBus;
3341

42+
private modelListener: string;
43+
3444
constructor() {
45+
super();
3546
this.elementFactories = {};
3647
this.model = null;
3748
this.canvasWidget = null;
3849
this.stateMachine = new StateMachine();
3950
this.historyBank = new HistoryBank();
4051
this.eventBus = new EventBus();
52+
this.modelListener = null;
4153

4254
if (Toolkit.TESTING) {
4355
Toolkit.TESTING_UID = 0;
@@ -57,7 +69,28 @@ export class CanvasEngine<T extends CanvasModel = CanvasModel> {
5769
}
5870

5971
setModel(model: T) {
72+
// uninstall the old model
73+
if (this.modelListener) {
74+
this.model.removeListener(this.modelListener);
75+
}
76+
let oldModel = this.model;
6077
this.model = model;
78+
this.iterateListeners((listener, event) => {
79+
if (listener.modelChanged) {
80+
listener.modelChanged({ ...event, model: model, oldModel: oldModel });
81+
}
82+
});
83+
84+
// install the new model
85+
if (model) {
86+
this.modelListener = model.addListener({
87+
delegateEvent: event => {
88+
this.eventBus.fireEvent(new ModelEvent(event));
89+
}
90+
});
91+
} else {
92+
this.modelListener = null;
93+
}
6194
}
6295

6396
getModel(): T {
@@ -76,7 +109,9 @@ export class CanvasEngine<T extends CanvasModel = CanvasModel> {
76109
installHistoryBank() {
77110
this.stateMachine.addListener({
78111
stateChanged: event => {
79-
this.historyBank.pushState(this.model.serialize());
112+
if (this.model) {
113+
this.historyBank.pushState(this.model.serialize());
114+
}
80115
}
81116
});
82117
this.historyBank.addListener({
@@ -89,6 +124,42 @@ export class CanvasEngine<T extends CanvasModel = CanvasModel> {
89124
});
90125
}
91126

127+
installDefaultInteractivity() {
128+
// selection layer
129+
let selectionLayer = new CanvasLayerModel();
130+
selectionLayer.setSVG(false);
131+
selectionLayer.setTransformable(false);
132+
133+
// listen for a new model
134+
this.addListener({
135+
modelChanged: event => {
136+
if (event.oldModel) {
137+
event.oldModel.removeLayer(selectionLayer);
138+
}
139+
if (event.model) {
140+
event.model.addLayer(selectionLayer);
141+
}
142+
}
143+
});
144+
145+
this.eventBus.addListener({
146+
eventWillFire: event => {
147+
if (event instanceof ModelEvent) {
148+
let selected = _.filter(this.model.getElements(), element => {
149+
return element.isSelected();
150+
});
151+
if (selected.length > 0) {
152+
selectionLayer.clearEntities();
153+
let model = new SelectionElementModel();
154+
model.setModels(selected);
155+
selectionLayer.addEntity(model);
156+
this.canvasWidget.forceUpdate();
157+
}
158+
}
159+
}
160+
});
161+
}
162+
92163
registerElementFactory(factory: AbstractElementFactory) {
93164
this.elementFactories[factory.type] = factory;
94165
factory.setEngine(this);
@@ -117,6 +188,11 @@ export class CanvasEngine<T extends CanvasModel = CanvasModel> {
117188
this.stateMachine.addState(new TranslateCanvasState(this));
118189
this.stateMachine.addState(new DefaultState(this));
119190

191+
// default wiring
192+
this.installHistoryBank();
193+
this.installDefaultInteractivity();
194+
195+
// process to set the initial state
120196
this.stateMachine.process();
121197
}
122198

src/event-bus/EventBus.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import { BaseEvent, BaseListener, BaseObject } from "../models/BaseObject";
44
import * as _ from "lodash";
55

66
export interface EventBusListener extends BaseListener {
7-
eventWillFire: (event: BaseEvent & { event: Event }) => any;
8-
eventDidFire: (event: BaseEvent & { event: Event }) => any;
7+
eventWillFire?: (event: BaseEvent & { event: Event }) => any;
8+
eventDidFire?: (event: BaseEvent & { event: Event }) => any;
99
}
1010

1111
export class EventBus extends BaseObject<EventBusListener> {

src/event-bus/events/ModelEvent.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { Event } from "../Event";
2+
import { BaseEvent } from "../../models/BaseObject";
3+
4+
export class ModelEvent extends Event {
5+
modelEvent: BaseEvent;
6+
7+
constructor(modelEvent: BaseEvent) {
8+
super("model-event", modelEvent.source);
9+
this.modelEvent = modelEvent;
10+
}
11+
}

src/main.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export * from "./CanvasLayerFactory";
66
export * from "./widgets/BaseWidget";
77
export * from "./widgets/CanvasLayerWidget";
88
export * from "./widgets/AnchorWidget";
9-
export * from "./widgets/SelectionGroupWidget";
9+
export * from "./primitives/selection/SelectionGroupWidget";
1010

1111
export * from "./geometry/Point";
1212
export * from "./geometry/Polygon";

src/models-canvas/CanvasModel.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,20 @@ import { CanvasLayerModel } from "./CanvasLayerModel";
22
import * as _ from "lodash";
33
import { GraphModel } from "../models/GraphModel";
44
import { CanvasElementModel } from "./CanvasElementModel";
5-
import { BaseModel } from "../models/BaseModel";
5+
import { BaseModel, BaseModelListener } from "../models/BaseModel";
66
import { CanvasEngine } from "../CanvasEngine";
77
import { BaseEvent, BaseListener } from "../models/BaseObject";
8+
import { GraphModelOrdered } from "../models/GraphModelOrdered";
89

9-
export interface CanvasModelListener<T extends CanvasModel = any> extends BaseListener<T> {
10+
export interface CanvasModelListener<T extends CanvasModel = any> extends BaseModelListener<T> {
1011
offsetUpdated?(event: BaseEvent<T> & { offsetX: number; offsetY: number }): void;
1112

1213
zoomUpdated?(event: BaseEvent<T> & { zoom: number }): void;
1314
}
1415

1516
export class CanvasModel<T extends CanvasModelListener = CanvasModelListener> extends BaseModel<null, T> {
1617
selectedLayer: CanvasLayerModel;
17-
layers: GraphModel<CanvasLayerModel, CanvasModel>;
18+
layers: GraphModelOrdered<CanvasLayerModel, CanvasModel>;
1819

1920
//control variables
2021
offsetX: number;
@@ -24,8 +25,8 @@ export class CanvasModel<T extends CanvasModelListener = CanvasModelListener> ex
2425
constructor() {
2526
super("canvas");
2627
this.selectedLayer = null;
27-
this.layers = new GraphModel("layers");
28-
this.layers.setParent(this);
28+
this.layers = new GraphModelOrdered("layers");
29+
this.layers.setParentDelegate(this);
2930
this.offsetX = 0;
3031
this.offsetY = 0;
3132
this.zoom = 1;
@@ -84,6 +85,10 @@ export class CanvasModel<T extends CanvasModelListener = CanvasModelListener> ex
8485
});
8586
}
8687

88+
removeLayer(layer: CanvasLayerModel) {
89+
this.layers.removeEntity(layer);
90+
}
91+
8792
addLayer(layer: CanvasLayerModel) {
8893
this.layers.addEntity(layer);
8994
this.selectedLayer = layer;

src/models/BaseModel.ts

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@ export interface Serializable {
77
id: string;
88
}
99

10-
export interface BaseModelListener extends BaseListener {
11-
lockChanged(event: BaseEvent & { locked: boolean });
10+
export interface BaseModelListener<T extends BaseModel = BaseModel> extends BaseListener<T> {
11+
lockChanged?(event: BaseEvent & { locked: boolean });
12+
delegateEvent?(event: BaseEvent);
1213
}
1314

14-
export class BaseModel<PARENT extends BaseModel = any, LISTENER extends BaseListener = BaseListener> extends BaseObject<
15-
LISTENER
16-
> {
15+
export class BaseModel<
16+
PARENT extends BaseModel<any, BaseModelListener> = any,
17+
LISTENER extends BaseModelListener = BaseListener
18+
> extends BaseObject<LISTENER> {
1719
protected parent: PARENT;
1820
protected id: string;
1921
protected type: string;
@@ -45,6 +47,18 @@ export class BaseModel<PARENT extends BaseModel = any, LISTENER extends BaseList
4547
this.listeners = {};
4648
}
4749

50+
iterateListeners(cb: (t: LISTENER, event: BaseEvent) => any) {
51+
// optionally delegate the event up the stack so the event bus can grab it
52+
if (this.parent) {
53+
this.parent.iterateListeners((listener, event) => {
54+
if (listener.delegateEvent) {
55+
listener.delegateEvent(event);
56+
}
57+
});
58+
}
59+
return super.iterateListeners(cb);
60+
}
61+
4862
public deSerialize(data: { [s: string]: any }, engine: CanvasEngine, cache: { [id: string]: BaseModel }) {
4963
this.id = data.id;
5064
this.locked = !!data.locked;

src/models/GraphModel.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import { BaseModel, Serializable } from "./BaseModel";
1+
import { BaseModel, BaseModelListener, Serializable } from "./BaseModel";
22
import * as _ from "lodash";
3-
import { BaseEvent, BaseListener } from "./BaseObject";
3+
import { BaseEvent } from "./BaseObject";
44
import { CanvasEngine } from "../CanvasEngine";
55

6-
export interface GraphModelListener<CHILD = BaseModel> extends BaseListener {
6+
export interface GraphModelListener<CHILD = BaseModel> extends BaseModelListener {
77
modelsAdded: (event: BaseEvent & { models: CHILD[] }) => any;
88

99
modelsRemoved: (event: BaseEvent & { models: CHILD[] }) => any;

src/primitives/selection/SelectionElementFactory.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { CanvasEngine } from "../../CanvasEngine";
44
import { SelectionElementWidget } from "./SelectionElementWidget";
55
import * as React from "react";
66
import { ResizeDimensionsState } from "../../state-machine/states/ResizeDimensionsState";
7-
import {ResizeOriginDimensionsState} from "../../state-machine/states/ResizeOriginDimensionState";
7+
import { ResizeOriginDimensionsState } from "../../state-machine/states/ResizeOriginDimensionState";
88

99
export class SelectionElementFactory extends AbstractElementFactory<SelectionElementModel> {
1010
constructor() {
@@ -16,10 +16,7 @@ export class SelectionElementFactory extends AbstractElementFactory<SelectionEle
1616
}
1717

1818
getCanvasStates() {
19-
return [
20-
new ResizeOriginDimensionsState(this.engine),
21-
new ResizeDimensionsState(this.engine)
22-
];
19+
return [new ResizeOriginDimensionsState(this.engine), new ResizeDimensionsState(this.engine)];
2320
}
2421

2522
generateWidget(engine: CanvasEngine, model: SelectionElementModel): JSX.Element {

src/primitives/selection/SelectionElementWidget.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as React from "react";
22
import { BaseWidget, BaseWidgetProps } from "../../widgets/BaseWidget";
3-
import { SelectionGroupWidget } from "../../widgets/SelectionGroupWidget";
3+
import { SelectionGroupWidget } from "./SelectionGroupWidget";
44
import { CanvasEngine } from "../../CanvasEngine";
55
import { SelectionElementModel } from "./SelectionElementModel";
66

src/widgets/SelectionGroupWidget.tsx renamed to src/primitives/selection/SelectionGroupWidget.tsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import * as React from "react";
2-
import { BaseWidget, BaseWidgetProps } from "./BaseWidget";
3-
import { AnchorWidget } from "./AnchorWidget";
4-
import { CanvasEngine } from "../CanvasEngine";
5-
import { SelectionElementModel } from "../primitives/selection/SelectionElementModel";
6-
import { ModelAnchorInputPosition } from "../state-machine/input/ModelAnchorInput";
2+
import { BaseWidget, BaseWidgetProps } from "../../widgets/BaseWidget";
3+
import { AnchorWidget } from "../../widgets/AnchorWidget";
4+
import { CanvasEngine } from "../../CanvasEngine";
5+
import { SelectionElementModel } from "./SelectionElementModel";
6+
import { ModelAnchorInputPosition } from "../../state-machine/input/ModelAnchorInput";
77

88
export interface SelectionGroupWidgetProps extends BaseWidgetProps {
99
model: SelectionElementModel;
@@ -18,6 +18,10 @@ export class SelectionGroupWidget extends BaseWidget<SelectionGroupWidgetProps,
1818
this.state = {};
1919
}
2020

21+
componentWillUnmount() {
22+
console.log("unmount");
23+
}
24+
2125
render() {
2226
let dimension = this.props.model.getDimensions().toRealDimensions(this.props.engine.getModel());
2327
return (

src/state-machine/AbstractState.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,12 @@ export abstract class AbstractState {
3838
}
3939

4040
activated(machine: StateMachine) {
41-
console.log("activated state: " + this.name);
4241
_.forEach(this.actions, action => {
4342
this.engine.getEventBus().registerAction(action);
4443
});
4544
}
4645

4746
deactivated(machine: StateMachine) {
48-
console.log("deactivated state: " + this.name);
4947
_.forEach(this.actions, action => {
5048
this.engine.getEventBus().unRegisterAction(action);
5149
});

src/state-machine/StateMachine.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,6 @@ export class StateMachine extends BaseObject<StateMachineListener> {
8585
}
8686

8787
process() {
88-
console.log(_.keys(this.inputs));
8988
// check for possible reactions to current inputs
9089
let possibleReactions = _.map(
9190
_.filter(this.states, state => {

src/state-machine/states/ResizeOriginDimensionState.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { Point } from "../../geometry/Point";
66
import { AbstractDisplacementState } from "../AbstractDisplacementState";
77
import { Matrix } from "mathjs";
88
import { ModelAnchorInput, ModelAnchorInputPosition } from "../input/ModelAnchorInput";
9-
import {KeyCode, KeyInput} from "../input/KeyInput";
9+
import { KeyCode, KeyInput } from "../input/KeyInput";
1010

1111
export class ResizeOriginDimensionsState extends AbstractDisplacementState {
1212
anchorInput: ModelAnchorInput;
@@ -40,10 +40,10 @@ export class ResizeOriginDimensionsState extends AbstractDisplacementState {
4040
const distanceY = displacementY / zoom;
4141

4242
// work out the scaling factors for both positive and negative cases
43-
const scaleX = (this.initialDimension.getWidth() + distanceX) *2 / this.initialDimension.getWidth();
44-
const scaleY = (this.initialDimension.getHeight() + distanceY)*2 / this.initialDimension.getHeight();
45-
const scaleX2 = (this.initialDimension.getWidth() - distanceX)*2 / this.initialDimension.getWidth();
46-
const scaleY2 = (this.initialDimension.getHeight() - distanceY)*2 / this.initialDimension.getHeight();
43+
const scaleX = (this.initialDimension.getWidth() + distanceX) * 2 / this.initialDimension.getWidth();
44+
const scaleY = (this.initialDimension.getHeight() + distanceY) * 2 / this.initialDimension.getHeight();
45+
const scaleX2 = (this.initialDimension.getWidth() - distanceX) * 2 / this.initialDimension.getWidth();
46+
const scaleY2 = (this.initialDimension.getHeight() - distanceY) * 2 / this.initialDimension.getHeight();
4747

4848
// construct the correct transform matrix
4949
let transform: Matrix = null;

src/widgets/AnchorWidget.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ export class AnchorWidget extends BaseWidget<AnchorWidgetProps> {
1515
super("src-anchor", props);
1616
}
1717

18+
componentWillUnmount() {
19+
this.props.engine.getStateMachine().removeInput(ModelAnchorInput.NAME);
20+
}
21+
1822
render() {
1923
return (
2024
<div

0 commit comments

Comments
 (0)