Skip to content

Commit 5d9e6ec

Browse files
authored
fix: controls event dispatcher types (#388)
1 parent 6b51b6d commit 5d9e6ec

11 files changed

+235
-18
lines changed

src/controls/ArcballControls.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,9 @@ import {
1818
OrthographicCamera,
1919
Mesh,
2020
Material,
21-
EventDispatcher,
2221
} from 'three'
22+
import { EventDispatcher } from './EventDispatcher'
23+
import { StandardControlsEventMap } from './StandardControlsEventMap'
2324

2425
type Camera = OrthographicCamera | PerspectiveCamera
2526
type Operation = 'PAN' | 'ROTATE' | 'ZOOM' | 'FOV'
@@ -82,7 +83,7 @@ const _endEvent = { type: 'end' }
8283
* @param {HTMLElement=null} domElement Renderer's dom element
8384
* @param {Scene=null} scene The scene to be rendered
8485
*/
85-
class ArcballControls extends EventDispatcher {
86+
class ArcballControls extends EventDispatcher<StandardControlsEventMap> {
8687
private camera: OrthographicCamera | PerspectiveCamera | null
8788
private domElement: HTMLElement | null | undefined
8889
private scene: Scene | null | undefined

src/controls/DeviceOrientationControls.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1-
import { Camera, Euler, EventDispatcher, MathUtils, Quaternion, Vector3 } from 'three'
1+
import { Camera, Euler, MathUtils, Quaternion, Vector3 } from 'three'
2+
import { EventDispatcher } from './EventDispatcher'
3+
import { StandardControlsEventMap } from './StandardControlsEventMap'
24

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

7-
class DeviceOrientationControls extends EventDispatcher {
9+
class DeviceOrientationControls extends EventDispatcher<StandardControlsEventMap> {
810
public object: Camera
911

1012
private changeEvent = { type: 'change' }

src/controls/DragControls.ts

+30-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,34 @@
1-
import { Camera, EventDispatcher, Intersection, Matrix4, Object3D, Plane, Raycaster, Vector2, Vector3 } from 'three'
1+
import { Camera, Intersection, Matrix4, Object3D, Plane, Raycaster, Vector2, Vector3 } from 'three'
2+
import { EventDispatcher } from './EventDispatcher'
3+
4+
export interface DragControlsEventMap {
5+
/**
6+
* Fires when the pointer is moved onto a 3D object, or onto one of its children.
7+
*/
8+
hoveron: { object: Object3D };
9+
10+
/**
11+
* Fires when the pointer is moved out of a 3D object.
12+
*/
13+
hoveroff: { object: Object3D };
14+
15+
/**
16+
* Fires when the user starts to drag a 3D object.
17+
*/
18+
dragstart: { object: Object3D };
19+
20+
/**
21+
* Fires when the user drags a 3D object.
22+
*/
23+
drag: { object: Object3D };
24+
25+
/**
26+
* Fires when the user has finished dragging a 3D object.
27+
*/
28+
dragend: { object: Object3D };
29+
}
230

3-
class DragControls extends EventDispatcher {
31+
class DragControls extends EventDispatcher<DragControlsEventMap> {
432
public enabled = true
533
public transformGroup = false
634

src/controls/EventDispatcher.ts

+139
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/*
2+
Due to @types/three r168 breaking change
3+
we have to manually copy the EventDispatcher class from three.js.
4+
So this files merges the declarations from https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/three/src/core/EventDispatcher.d.ts
5+
with the implementation from https://github.com/mrdoob/three.js/blob/dev/src/core/EventDispatcher.js
6+
More info in https://github.com/pmndrs/three-stdlib/issues/387
7+
*/
8+
9+
/**
10+
* The minimal basic Event that can be dispatched by a {@link EventDispatcher<>}.
11+
*/
12+
export interface BaseEvent<TEventType extends string = string> {
13+
readonly type: TEventType;
14+
// not defined in @types/three
15+
target: any;
16+
}
17+
18+
/**
19+
* The minimal expected contract of a fired Event that was dispatched by a {@link EventDispatcher<>}.
20+
*/
21+
export interface Event<TEventType extends string = string, TTarget = unknown> {
22+
readonly type: TEventType;
23+
readonly target: TTarget;
24+
}
25+
26+
export type EventListener<TEventData, TEventType extends string, TTarget> = (
27+
event: TEventData & Event<TEventType, TTarget>,
28+
) => void;
29+
30+
export class EventDispatcher<TEventMap extends {} = {}> {
31+
// not defined in @types/three
32+
private _listeners: any;
33+
34+
/**
35+
* Adds a listener to an event type.
36+
* @param type The type of event to listen to.
37+
* @param listener The function that gets called when the event is fired.
38+
*/
39+
addEventListener<T extends Extract<keyof TEventMap, string>>(
40+
type: T,
41+
listener: EventListener<TEventMap[T], T, this>,
42+
): void {
43+
44+
if ( this._listeners === undefined ) this._listeners = {};
45+
46+
const listeners = this._listeners;
47+
48+
if ( listeners[ type ] === undefined ) {
49+
50+
listeners[ type ] = [];
51+
52+
}
53+
54+
if ( listeners[ type ].indexOf( listener ) === - 1 ) {
55+
56+
listeners[ type ].push( listener );
57+
58+
}
59+
60+
}
61+
62+
/**
63+
* Checks if listener is added to an event type.
64+
* @param type The type of event to listen to.
65+
* @param listener The function that gets called when the event is fired.
66+
*/
67+
hasEventListener<T extends Extract<keyof TEventMap, string>>(
68+
type: T,
69+
listener: EventListener<TEventMap[T], T, this>,
70+
): boolean {
71+
72+
if ( this._listeners === undefined ) return false;
73+
74+
const listeners = this._listeners;
75+
76+
return listeners[ type ] !== undefined && listeners[ type ].indexOf( listener ) !== - 1;
77+
78+
}
79+
80+
/**
81+
* Removes a listener from an event type.
82+
* @param type The type of the listener that gets removed.
83+
* @param listener The listener function that gets removed.
84+
*/
85+
removeEventListener<T extends Extract<keyof TEventMap, string>>(
86+
type: T,
87+
listener: EventListener<TEventMap[T], T, this>,
88+
): void {
89+
90+
if ( this._listeners === undefined ) return;
91+
92+
const listeners = this._listeners;
93+
const listenerArray = listeners[ type ];
94+
95+
if ( listenerArray !== undefined ) {
96+
97+
const index = listenerArray.indexOf( listener );
98+
99+
if ( index !== - 1 ) {
100+
101+
listenerArray.splice( index, 1 );
102+
103+
}
104+
105+
}
106+
107+
}
108+
109+
/**
110+
* Fire an event type.
111+
* @param event The event that gets fired.
112+
*/
113+
dispatchEvent<T extends Extract<keyof TEventMap, string>>(event: BaseEvent<T> & TEventMap[T]): void {
114+
115+
if ( this._listeners === undefined ) return;
116+
117+
const listeners = this._listeners;
118+
const listenerArray = listeners[ event.type ];
119+
120+
if ( listenerArray !== undefined ) {
121+
122+
event.target = this;
123+
124+
// Make a copy, in case listeners are removed while iterating.
125+
const array = listenerArray.slice( 0 );
126+
127+
for ( let i = 0, l = array.length; i < l; i ++ ) {
128+
129+
array[ i ].call( this, event );
130+
131+
}
132+
133+
event.target = null;
134+
135+
}
136+
137+
}
138+
139+
}

src/controls/FirstPersonControls.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
import { MathUtils, Spherical, Vector3, EventDispatcher, Camera } from 'three'
1+
import { MathUtils, Spherical, Vector3, Camera } from 'three'
2+
import { EventDispatcher } from './EventDispatcher'
3+
import { StandardControlsEventMap } from './StandardControlsEventMap'
24

35
const targetPosition = new Vector3()
46

5-
export class FirstPersonControls extends EventDispatcher {
7+
export class FirstPersonControls extends EventDispatcher<{}> {
68
public object: Camera
79
public domElement?: HTMLElement | null
810

src/controls/FlyControls.ts

+10-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
1-
import { Camera, EventDispatcher, Quaternion, Vector3 } from 'three'
1+
import { Camera, Quaternion, Vector3 } from 'three'
2+
import { EventDispatcher } from './EventDispatcher'
23

34
function contextmenu(event: Event): void {
45
event.preventDefault()
56
}
67

7-
class FlyControls extends EventDispatcher {
8+
export interface FlyControlsEventMap {
9+
/**
10+
* Fires when the camera has been transformed by the controls.
11+
*/
12+
change: {};
13+
}
14+
15+
class FlyControls extends EventDispatcher<FlyControlsEventMap> {
816
public object: Camera
917
public domElement: HTMLElement | Document = null!
1018

src/controls/OrbitControls.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import {
2-
EventDispatcher,
32
Matrix4,
43
MOUSE,
54
OrthographicCamera,
@@ -12,6 +11,8 @@ import {
1211
Ray,
1312
Plane,
1413
} from 'three'
14+
import { EventDispatcher } from './EventDispatcher'
15+
import { StandardControlsEventMap } from './StandardControlsEventMap'
1516

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

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

29-
class OrbitControls extends EventDispatcher {
30+
class OrbitControls extends EventDispatcher<StandardControlsEventMap> {
3031
object: PerspectiveCamera | OrthographicCamera
3132
domElement: HTMLElement | undefined
3233
// Set to false to disable this control

src/controls/PointerLockControls.ts

+20-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { Euler, Camera, EventDispatcher, Vector3 } from 'three'
1+
import { Euler, Camera, Vector3 } from 'three'
2+
import { EventDispatcher } from './EventDispatcher'
23

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

10-
class PointerLockControls extends EventDispatcher {
11+
export interface PointerLockControlsEventMap {
12+
/**
13+
* Fires when the user moves the mouse.
14+
*/
15+
change: {};
16+
17+
/**
18+
* Fires when the pointer lock status is "locked" (in other words: the mouse is captured).
19+
*/
20+
lock: {};
21+
22+
/**
23+
* Fires when the pointer lock status is "unlocked" (in other words: the mouse is not captured anymore).
24+
*/
25+
unlock: {};
26+
}
27+
28+
class PointerLockControls extends EventDispatcher<PointerLockControlsEventMap> {
1129
public camera: Camera
1230
public domElement?: HTMLElement
1331
public isLocked: boolean
+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
export interface StandardControlsEventMap {
2+
/**
3+
* Fires when the camera has been transformed by the controls.
4+
*/
5+
change: {};
6+
7+
/**
8+
* Fires when an interaction was initiated.
9+
*/
10+
start: {};
11+
12+
/**
13+
* Fires when an interaction has finished.
14+
*/
15+
end: {};
16+
}

src/controls/TrackballControls.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
import { EventDispatcher, MOUSE, Quaternion, Vector2, Vector3, PerspectiveCamera, OrthographicCamera } from 'three'
1+
import { MOUSE, Quaternion, Vector2, Vector3, PerspectiveCamera, OrthographicCamera } from 'three'
2+
import { EventDispatcher } from './EventDispatcher'
3+
import { StandardControlsEventMap } from './StandardControlsEventMap'
24

3-
class TrackballControls extends EventDispatcher {
5+
class TrackballControls extends EventDispatcher<StandardControlsEventMap> {
46
public enabled = true
57

68
public screen = { left: 0, top: 0, width: 0, height: 0 }

src/controls/experimental/CameraControls.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import {
2-
EventDispatcher,
32
MOUSE,
43
Matrix4,
54
OrthographicCamera,
@@ -10,6 +9,7 @@ import {
109
Vector2,
1110
Vector3,
1211
} from 'three'
12+
import { EventDispatcher } from '../EventDispatcher'
1313

1414
export type CHANGE_EVENT = {
1515
type: 'change' | 'start' | 'end'
@@ -26,7 +26,7 @@ export const STATE = {
2626
TOUCH_DOLLY_ROTATE: 6,
2727
}
2828

29-
class CameraControls extends EventDispatcher {
29+
class CameraControls extends EventDispatcher<Record<string, {}>> {
3030
object: PerspectiveCamera | OrthographicCamera
3131
domElement: HTMLElement
3232

0 commit comments

Comments
 (0)