Skip to content

Commit

Permalink
Implement fireEvent and createEvent (#13)
Browse files Browse the repository at this point in the history
* Implement events

* Add tests

* Ensure export

* Add wheel observable and more tests
  • Loading branch information
hmallen99 authored May 7, 2024
1 parent bf93516 commit a8b1bb2
Show file tree
Hide file tree
Showing 4 changed files with 258 additions and 0 deletions.
118 changes: 118 additions & 0 deletions src/event.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import {
Engine,
Mesh,
MeshBuilder,
NullEngine,
Scene,
Vector2,
} from '@babylonjs/core';
import { Event, createEvent, fireEvent } from './event';
import { EventMap, eventMap } from './eventMap';
import { AdvancedDynamicTexture, Button, Control, Grid } from '@babylonjs/gui';

describe('event', () => {
let scene: Scene,
engine: Engine,
texture: AdvancedDynamicTexture,
containerControl: Grid,
expectedControl: Control,
uiPlane: Mesh;

beforeAll(() => {
engine = new NullEngine();
});

beforeEach(() => {
scene = new Scene(engine);

uiPlane = MeshBuilder.CreatePlane('container');
texture = AdvancedDynamicTexture.CreateForMesh(uiPlane);

containerControl = new Grid('container');
containerControl.addColumnDefinition(1);
containerControl.addColumnDefinition(1);
texture.addControl(containerControl);

expectedControl = new Button('button');
containerControl.addControl(expectedControl, 0, 0);
});

afterEach(() => {
texture.dispose();
uiPlane.material?.dispose();
uiPlane.dispose();
scene.dispose();
});

afterAll(() => {
engine.dispose();
});

it.each(Object.keys(eventMap))(
'should create a default event for %s',
(key: keyof EventMap) => {
const eventFunc = createEvent[key];
expect(eventFunc).toBeDefined();

const event = eventMap[key];
expect(eventFunc(expectedControl)).toEqual({
key,
observableName: event.observableName,
eventData: event.defaultInit,
});
}
);

it.each(Object.keys(eventMap))(
'should fire the %s event',
(key: keyof EventMap) => {
const spy = jest.fn();
const { observableName } = eventMap[key];

expectedControl[observableName].add(spy as null);

const fireFunc = fireEvent[key];
expect(fireFunc).toBeDefined();

fireFunc(expectedControl);

expect(spy).toHaveBeenCalledTimes(1);
}
);

it('should throw when fireEvent is called with a bad observable name', () => {
expect(() => {
fireEvent(expectedControl, {
key: 'bogusEvent',
observableName: 'notAnObservable',
eventData: {},
} as unknown as Event<'pointerClick'>);
}).toThrow(
new Error(
`Unable to fire an event - event of type "bogusEvent" does not exist on "${expectedControl.getClassName()}"`
)
);
});

it('should allow passing custom data to fireEvent', () => {
const spy = jest.fn();
expectedControl.onWheelObservable.add((eventData) => {
spy(eventData);
});

fireEvent.wheel(expectedControl, new Vector2(0.5, 0.5));

expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledWith(new Vector2(0.5, 0.5));
});

it('should allow passing custom data to createEvent', () => {
const event = createEvent.wheel(expectedControl, new Vector2(0.5, 0.5));

expect(event).toEqual({
key: 'wheel',
observableName: 'onWheelObservable',
eventData: new Vector2(0.5, 0.5),
});
});
});
73 changes: 73 additions & 0 deletions src/event.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { Observable } from '@babylonjs/core';
import { Control } from '@babylonjs/gui';
import { EventMap, eventMap } from './eventMap';

export type Event<K extends keyof EventMap> = {
key: K;
observableName: EventMap[K]['observableName'];
eventData: EventMap[K]['defaultInit'];
};

function fireEventFn<K extends keyof EventMap>(
control: Control,
event: Event<K>
) {
const eventObservable = control[event.observableName];

if (!(eventObservable instanceof Observable)) {
throw new Error(
`Unable to fire an event - event of type "${
event.key
}" does not exist on "${control.getClassName()}"`
);
}

(eventObservable as Observable<EventMap[K]['defaultInit']>).notifyObservers(
event.eventData
);
}

function createEventFn<K extends keyof EventMap>(
eventName: K,
_control: Control,
init: EventMap[K]['defaultInit']
): Event<K> {
const event = eventMap[eventName];
return {
key: eventName,
observableName: event.observableName,
eventData: init,
};
}

Object.keys(eventMap).forEach((key: keyof EventMap) => {
const { defaultInit } = eventMap[key];
createEventFn[key] = (node: Control, init?: typeof defaultInit) =>
createEventFn(key, node, init ?? defaultInit);

fireEventFn[key] = (node: Control, init?: typeof defaultInit) => {
fireEventFn(node, createEventFn[key](node, init));
};
});

type CreateEventFn = (
k: keyof EventMap,
c: Control,
i: EventMap[keyof EventMap]['defaultInit']
) => Event<keyof EventMap>;

type CreateEventObject = {
[K in keyof EventMap]: (
c: Control,
i?: EventMap[K]['defaultInit']
) => Event<K>;
};

type fireEventFn = (c: Control, e: Event<keyof EventMap>) => void;

type fireEventObject = {
[K in keyof EventMap]: (c: Control, i?: EventMap[K]['defaultInit']) => void;
};

export const createEvent = createEventFn as CreateEventFn & CreateEventObject;
export const fireEvent = fireEventFn as fireEventFn & fireEventObject;
66 changes: 66 additions & 0 deletions src/eventMap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { Vector2 } from '@babylonjs/core';
import { Vector2WithInfo } from '@babylonjs/gui';

export type EventMap = {
pointerMove: {
observableName: 'onPointerUpObservable';
defaultInit: Vector2WithInfo;
};
pointerEnter: {
observableName: 'onPointerEnterObservable';
defaultInit: Vector2WithInfo;
};
pointerOut: {
observableName: 'onPointerOutObservable';
defaultInit: Vector2WithInfo;
};
pointerDown: {
observableName: 'onPointerDownObservable';
defaultInit: Vector2WithInfo;
};
pointerUp: {
observableName: 'onPointerUpObservable';
defaultInit: Vector2WithInfo;
};
pointerClick: {
observableName: 'onPointerClickObservable';
defaultInit: Vector2WithInfo;
};
wheel: {
observableName: 'onWheelObservable';
defaultInit: Vector2;
};
};

const DEFAULT_VECTOR2_WITH_INFO = new Vector2WithInfo(Vector2.Zero());

export const eventMap: EventMap = {
pointerMove: {
observableName: 'onPointerUpObservable',
defaultInit: DEFAULT_VECTOR2_WITH_INFO,
},
pointerEnter: {
observableName: 'onPointerEnterObservable',
defaultInit: DEFAULT_VECTOR2_WITH_INFO,
},
pointerOut: {
observableName: 'onPointerOutObservable',
defaultInit: DEFAULT_VECTOR2_WITH_INFO,
},
pointerDown: {
observableName: 'onPointerDownObservable',
defaultInit: DEFAULT_VECTOR2_WITH_INFO,
},
pointerUp: {
observableName: 'onPointerUpObservable',
defaultInit: DEFAULT_VECTOR2_WITH_INFO,
},
pointerClick: {
observableName: 'onPointerClickObservable',
defaultInit: DEFAULT_VECTOR2_WITH_INFO,
},
wheel: {
observableName: 'onWheelObservable',
defaultInit: Vector2.Zero(),
},
};
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './queries';
export * from './event';

0 comments on commit a8b1bb2

Please sign in to comment.