Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: controls event dispatcher types #388

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/controls/ArcballControls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ import {
OrthographicCamera,
Mesh,
Material,
EventDispatcher,
} from 'three'
import { EventDispatcher } from './EventDispatcher'
import { StandardControlsEventMap } from './StandardControlsEventMap'

type Camera = OrthographicCamera | PerspectiveCamera
type Operation = 'PAN' | 'ROTATE' | 'ZOOM' | 'FOV'
Expand Down Expand Up @@ -82,7 +83,7 @@ const _endEvent = { type: 'end' }
* @param {HTMLElement=null} domElement Renderer's dom element
* @param {Scene=null} scene The scene to be rendered
*/
class ArcballControls extends EventDispatcher {
class ArcballControls extends EventDispatcher<StandardControlsEventMap> {
private camera: OrthographicCamera | PerspectiveCamera | null
private domElement: HTMLElement | null | undefined
private scene: Scene | null | undefined
Expand Down
6 changes: 4 additions & 2 deletions src/controls/DeviceOrientationControls.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { Camera, Euler, EventDispatcher, MathUtils, Quaternion, Vector3 } from 'three'
import { Camera, Euler, MathUtils, Quaternion, Vector3 } from 'three'
import { EventDispatcher } from './EventDispatcher'
import { StandardControlsEventMap } from './StandardControlsEventMap'

/**
* W3C Device Orientation control (http://w3c.github.io/deviceorientation/spec-source-orientation.html)
*/

class DeviceOrientationControls extends EventDispatcher {
class DeviceOrientationControls extends EventDispatcher<StandardControlsEventMap> {
public object: Camera

private changeEvent = { type: 'change' }
Expand Down
32 changes: 30 additions & 2 deletions src/controls/DragControls.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,34 @@
import { Camera, EventDispatcher, Intersection, Matrix4, Object3D, Plane, Raycaster, Vector2, Vector3 } from 'three'
import { Camera, Intersection, Matrix4, Object3D, Plane, Raycaster, Vector2, Vector3 } from 'three'
import { EventDispatcher } from './EventDispatcher'

export interface DragControlsEventMap {
/**
* Fires when the pointer is moved onto a 3D object, or onto one of its children.
*/
hoveron: { object: Object3D };

/**
* Fires when the pointer is moved out of a 3D object.
*/
hoveroff: { object: Object3D };

/**
* Fires when the user starts to drag a 3D object.
*/
dragstart: { object: Object3D };

/**
* Fires when the user drags a 3D object.
*/
drag: { object: Object3D };

/**
* Fires when the user has finished dragging a 3D object.
*/
dragend: { object: Object3D };
}

class DragControls extends EventDispatcher {
class DragControls extends EventDispatcher<DragControlsEventMap> {
public enabled = true
public transformGroup = false

Expand Down
139 changes: 139 additions & 0 deletions src/controls/EventDispatcher.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/*
Due to @types/three r168 breaking change
we have to manually copy the EventDispatcher class from three.js.
So this files merges the declarations from https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/three/src/core/EventDispatcher.d.ts
with the implementation from https://github.com/mrdoob/three.js/blob/dev/src/core/EventDispatcher.js
More info in https://github.com/pmndrs/three-stdlib/issues/387
*/

/**
* The minimal basic Event that can be dispatched by a {@link EventDispatcher<>}.
*/
export interface BaseEvent<TEventType extends string = string> {
readonly type: TEventType;
// not defined in @types/three
target: any;
}

/**
* The minimal expected contract of a fired Event that was dispatched by a {@link EventDispatcher<>}.
*/
export interface Event<TEventType extends string = string, TTarget = unknown> {
readonly type: TEventType;
readonly target: TTarget;
}

export type EventListener<TEventData, TEventType extends string, TTarget> = (
event: TEventData & Event<TEventType, TTarget>,
) => void;

export class EventDispatcher<TEventMap extends {} = {}> {
// not defined in @types/three
private _listeners: any;

/**
* Adds a listener to an event type.
* @param type The type of event to listen to.
* @param listener The function that gets called when the event is fired.
*/
addEventListener<T extends Extract<keyof TEventMap, string>>(
type: T,
listener: EventListener<TEventMap[T], T, this>,
): void {

if ( this._listeners === undefined ) this._listeners = {};

const listeners = this._listeners;

if ( listeners[ type ] === undefined ) {

listeners[ type ] = [];

}

if ( listeners[ type ].indexOf( listener ) === - 1 ) {

listeners[ type ].push( listener );

}

}

/**
* Checks if listener is added to an event type.
* @param type The type of event to listen to.
* @param listener The function that gets called when the event is fired.
*/
hasEventListener<T extends Extract<keyof TEventMap, string>>(
type: T,
listener: EventListener<TEventMap[T], T, this>,
): boolean {

if ( this._listeners === undefined ) return false;

const listeners = this._listeners;

return listeners[ type ] !== undefined && listeners[ type ].indexOf( listener ) !== - 1;

}

/**
* Removes a listener from an event type.
* @param type The type of the listener that gets removed.
* @param listener The listener function that gets removed.
*/
removeEventListener<T extends Extract<keyof TEventMap, string>>(
type: T,
listener: EventListener<TEventMap[T], T, this>,
): void {

if ( this._listeners === undefined ) return;

const listeners = this._listeners;
const listenerArray = listeners[ type ];

if ( listenerArray !== undefined ) {

const index = listenerArray.indexOf( listener );

if ( index !== - 1 ) {

listenerArray.splice( index, 1 );

}

}

}

/**
* Fire an event type.
* @param event The event that gets fired.
*/
dispatchEvent<T extends Extract<keyof TEventMap, string>>(event: BaseEvent<T> & TEventMap[T]): void {

if ( this._listeners === undefined ) return;

const listeners = this._listeners;
const listenerArray = listeners[ event.type ];

if ( listenerArray !== undefined ) {

event.target = this;

// Make a copy, in case listeners are removed while iterating.
const array = listenerArray.slice( 0 );

for ( let i = 0, l = array.length; i < l; i ++ ) {

array[ i ].call( this, event );

}

event.target = null;

}

}

}
6 changes: 4 additions & 2 deletions src/controls/FirstPersonControls.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { MathUtils, Spherical, Vector3, EventDispatcher, Camera } from 'three'
import { MathUtils, Spherical, Vector3, Camera } from 'three'
import { EventDispatcher } from './EventDispatcher'
import { StandardControlsEventMap } from './StandardControlsEventMap'

const targetPosition = new Vector3()

export class FirstPersonControls extends EventDispatcher {
export class FirstPersonControls extends EventDispatcher<{}> {
public object: Camera
public domElement?: HTMLElement | null

Expand Down
12 changes: 10 additions & 2 deletions src/controls/FlyControls.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
import { Camera, EventDispatcher, Quaternion, Vector3 } from 'three'
import { Camera, Quaternion, Vector3 } from 'three'
import { EventDispatcher } from './EventDispatcher'

function contextmenu(event: Event): void {
event.preventDefault()
}

class FlyControls extends EventDispatcher {
export interface FlyControlsEventMap {
/**
* Fires when the camera has been transformed by the controls.
*/
change: {};
}

class FlyControls extends EventDispatcher<FlyControlsEventMap> {
public object: Camera
public domElement: HTMLElement | Document = null!

Expand Down
5 changes: 3 additions & 2 deletions src/controls/OrbitControls.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {
EventDispatcher,
Matrix4,
MOUSE,
OrthographicCamera,
Expand All @@ -12,6 +11,8 @@ import {
Ray,
Plane,
} from 'three'
import { EventDispatcher } from './EventDispatcher'
import { StandardControlsEventMap } from './StandardControlsEventMap'

const _ray = new Ray()
const _plane = new Plane()
Expand All @@ -26,7 +27,7 @@ const TILT_LIMIT = Math.cos(70 * (Math.PI / 180))

const moduloWrapAround = (offset: number, capacity: number) => ((offset % capacity) + capacity) % capacity

class OrbitControls extends EventDispatcher {
class OrbitControls extends EventDispatcher<StandardControlsEventMap> {
object: PerspectiveCamera | OrthographicCamera
domElement: HTMLElement | undefined
// Set to false to disable this control
Expand Down
22 changes: 20 additions & 2 deletions src/controls/PointerLockControls.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Euler, Camera, EventDispatcher, Vector3 } from 'three'
import { Euler, Camera, Vector3 } from 'three'
import { EventDispatcher } from './EventDispatcher'

const _euler = new Euler(0, 0, 0, 'YXZ')
const _vector = new Vector3()
Expand All @@ -7,7 +8,24 @@ const _lockEvent = { type: 'lock' }
const _unlockEvent = { type: 'unlock' }
const _PI_2 = Math.PI / 2

class PointerLockControls extends EventDispatcher {
export interface PointerLockControlsEventMap {
/**
* Fires when the user moves the mouse.
*/
change: {};

/**
* Fires when the pointer lock status is "locked" (in other words: the mouse is captured).
*/
lock: {};

/**
* Fires when the pointer lock status is "unlocked" (in other words: the mouse is not captured anymore).
*/
unlock: {};
}

class PointerLockControls extends EventDispatcher<PointerLockControlsEventMap> {
public camera: Camera
public domElement?: HTMLElement
public isLocked: boolean
Expand Down
16 changes: 16 additions & 0 deletions src/controls/StandardControlsEventMap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export interface StandardControlsEventMap {
/**
* Fires when the camera has been transformed by the controls.
*/
change: {};

/**
* Fires when an interaction was initiated.
*/
start: {};

/**
* Fires when an interaction has finished.
*/
end: {};
}
6 changes: 4 additions & 2 deletions src/controls/TrackballControls.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { EventDispatcher, MOUSE, Quaternion, Vector2, Vector3, PerspectiveCamera, OrthographicCamera } from 'three'
import { MOUSE, Quaternion, Vector2, Vector3, PerspectiveCamera, OrthographicCamera } from 'three'
import { EventDispatcher } from './EventDispatcher'
import { StandardControlsEventMap } from './StandardControlsEventMap'

class TrackballControls extends EventDispatcher {
class TrackballControls extends EventDispatcher<StandardControlsEventMap> {
public enabled = true

public screen = { left: 0, top: 0, width: 0, height: 0 }
Expand Down
4 changes: 2 additions & 2 deletions src/controls/experimental/CameraControls.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {
EventDispatcher,
MOUSE,
Matrix4,
OrthographicCamera,
Expand All @@ -10,6 +9,7 @@ import {
Vector2,
Vector3,
} from 'three'
import { EventDispatcher } from '../EventDispatcher'

export type CHANGE_EVENT = {
type: 'change' | 'start' | 'end'
Expand All @@ -26,7 +26,7 @@ export const STATE = {
TOUCH_DOLLY_ROTATE: 6,
}

class CameraControls extends EventDispatcher {
class CameraControls extends EventDispatcher<Record<string, {}>> {
object: PerspectiveCamera | OrthographicCamera
domElement: HTMLElement

Expand Down