import AssociativeArray from "./AssociativeArray.js";
import Cartesian2 from "./Cartesian2.js";
import defaultValue from "./defaultValue.js";
import defined from "./defined.js";
import destroyObject from "./destroyObject.js";
import DeveloperError from "./DeveloperError.js";
import FeatureDetection from "./FeatureDetection.js";
import getTimestamp from "./getTimestamp.js";
import KeyboardEventModifier from "./KeyboardEventModifier.js";
import ScreenSpaceEventType from "./ScreenSpaceEventType.js";
function getPosition(screenSpaceEventHandler, event, result) {
const element = screenSpaceEventHandler._element;
if (element === document) {
result.x = event.clientX;
result.y = event.clientY;
return result;
}
const rect = element.getBoundingClientRect();
result.x = event.clientX - rect.left;
result.y = event.clientY - rect.top;
return result;
}
function getInputEventKey(type, modifier) {
let key = type;
if (defined(modifier)) {
key += `+${modifier}`;
}
return key;
}
function getModifier(event) {
if (event.shiftKey) {
return KeyboardEventModifier.SHIFT;
} else if (event.ctrlKey) {
return KeyboardEventModifier.CTRL;
} else if (event.altKey) {
return KeyboardEventModifier.ALT;
}
return undefined;
}
const MouseButton = {
LEFT: 0,
MIDDLE: 1,
RIGHT: 2,
};
function registerListener(screenSpaceEventHandler, domType, element, callback) {
function listener(e) {
callback(screenSpaceEventHandler, e);
}
if (FeatureDetection.isInternetExplorer()) {
element.addEventListener(domType, listener, false);
} else {
element.addEventListener(domType, listener, {
capture: false,
passive: false,
});
}
screenSpaceEventHandler._removalFunctions.push(function () {
element.removeEventListener(domType, listener, false);
});
}
function registerListeners(screenSpaceEventHandler) {
const element = screenSpaceEventHandler._element;
// some listeners may be registered on the document, so we still get events even after
// leaving the bounds of element.
// this is affected by the existence of an undocumented disableRootEvents property on element.
const alternateElement = !defined(element.disableRootEvents)
? document
: element;
if (FeatureDetection.supportsPointerEvents()) {
registerListener(
screenSpaceEventHandler,
"pointerdown",
element,
handlePointerDown
);
registerListener(
screenSpaceEventHandler,
"pointerup",
element,
handlePointerUp
);
registerListener(
screenSpaceEventHandler,
"pointermove",
element,
handlePointerMove
);
registerListener(
screenSpaceEventHandler,
"pointercancel",
element,
handlePointerUp
);
} else {
registerListener(
screenSpaceEventHandler,
"mousedown",
element,
handleMouseDown
);
registerListener(
screenSpaceEventHandler,
"mouseup",
alternateElement,
handleMouseUp
);
registerListener(
screenSpaceEventHandler,
"mousemove",
alternateElement,
handleMouseMove
);
registerListener(
screenSpaceEventHandler,
"touchstart",
element,
handleTouchStart
);
registerListener(
screenSpaceEventHandler,
"touchend",
alternateElement,
handleTouchEnd
);
registerListener(
screenSpaceEventHandler,
"touchmove",
alternateElement,
handleTouchMove
);
registerListener(
screenSpaceEventHandler,
"touchcancel",
alternateElement,
handleTouchEnd
);
}
registerListener(
screenSpaceEventHandler,
"dblclick",
element,
handleDblClick
);
// detect available wheel event
let wheelEvent;
if ("onwheel" in element) {
// spec event type
wheelEvent = "wheel";
} else if (document.onmousewheel !== undefined) {
// legacy event type
wheelEvent = "mousewheel";
} else {
// older Firefox
wheelEvent = "DOMMouseScroll";
}
registerListener(screenSpaceEventHandler, wheelEvent, element, handleWheel);
}
function unregisterListeners(screenSpaceEventHandler) {
const removalFunctions = screenSpaceEventHandler._removalFunctions;
for (let i = 0; i < removalFunctions.length; ++i) {
removalFunctions[i]();
}
}
const mouseDownEvent = {
position: new Cartesian2(),
};
function gotTouchEvent(screenSpaceEventHandler) {
screenSpaceEventHandler._lastSeenTouchEvent = getTimestamp();
}
function canProcessMouseEvent(screenSpaceEventHandler) {
return (
getTimestamp() - screenSpaceEventHandler._lastSeenTouchEvent >
ScreenSpaceEventHandler.mouseEmulationIgnoreMilliseconds
);
}
function checkPixelTolerance(startPosition, endPosition, pixelTolerance) {
const xDiff = startPosition.x - endPosition.x;
const yDiff = startPosition.y - endPosition.y;
const totalPixels = Math.sqrt(xDiff * xDiff + yDiff * yDiff);
return totalPixels < pixelTolerance;
}
function handleMouseDown(screenSpaceEventHandler, event) {
if (!canProcessMouseEvent(screenSpaceEventHandler)) {
return;
}
const button = event.button;
screenSpaceEventHandler._buttonDown[button] = true;
let screenSpaceEventType;
if (button === MouseButton.LEFT) {
screenSpaceEventType = ScreenSpaceEventType.LEFT_DOWN;
} else if (button === MouseButton.MIDDLE) {
screenSpaceEventType = ScreenSpaceEventType.MIDDLE_DOWN;
} else if (button === MouseButton.RIGHT) {
screenSpaceEventType = ScreenSpaceEventType.RIGHT_DOWN;
} else {
return;
}
const position = getPosition(
screenSpaceEventHandler,
event,
screenSpaceEventHandler._primaryPosition
);
Cartesian2.clone(position, screenSpaceEventHandler._primaryStartPosition);
Cartesian2.clone(position, screenSpaceEventHandler._primaryPreviousPosition);
const modifier = getModifier(event);
const action = screenSpaceEventHandler.getInputAction(
screenSpaceEventType,
modifier
);
if (defined(action)) {
Cartesian2.clone(position, mouseDownEvent.position);
action(mouseDownEvent);
event.preventDefault();
}
}
const mouseUpEvent = {
position: new Cartesian2(),
};
const mouseClickEvent = {
position: new Cartesian2(),
};
function cancelMouseEvent(
screenSpaceEventHandler,
screenSpaceEventType,
clickScreenSpaceEventType,
event
) {
const modifier = getModifier(event);
const action = screenSpaceEventHandler.getInputAction(
screenSpaceEventType,
modifier
);
const clickAction = screenSpaceEventHandler.getInputAction(
clickScreenSpaceEventType,
modifier
);
if (defined(action) || defined(clickAction)) {
const position = getPosition(
screenSpaceEventHandler,
event,
screenSpaceEventHandler._primaryPosition
);
if (defined(action)) {
Cartesian2.clone(position, mouseUpEvent.position);
action(mouseUpEvent);
}
if (defined(clickAction)) {
const startPosition = screenSpaceEventHandler._primaryStartPosition;
if (
checkPixelTolerance(
startPosition,
position,
screenSpaceEventHandler._clickPixelTolerance
)
) {
Cartesian2.clone(position, mouseClickEvent.position);
clickAction(mouseClickEvent);
}
}
}
}
function handleMouseUp(screenSpaceEventHandler, event) {
if (!canProcessMouseEvent(screenSpaceEventHandler)) {
return;
}
const button = event.button;
if (
button !== MouseButton.LEFT &&
button !== MouseButton.MIDDLE &&
button !== MouseButton.RIGHT
) {
return;
}
if (screenSpaceEventHandler._buttonDown[MouseButton.LEFT]) {
cancelMouseEvent(
screenSpaceEventHandler,
ScreenSpaceEventType.LEFT_UP,
ScreenSpaceEventType.LEFT_CLICK,
event
);
screenSpaceEventHandler._buttonDown[MouseButton.LEFT] = false;
}
if (screenSpaceEventHandler._buttonDown[MouseButton.MIDDLE]) {
cancelMouseEvent(
screenSpaceEventHandler,
ScreenSpaceEventType.MIDDLE_UP,
ScreenSpaceEventType.MIDDLE_CLICK,
event
);
screenSpaceEventHandler._buttonDown[MouseButton.MIDDLE] = false;
}
if (screenSpaceEventHandler._buttonDown[MouseButton.RIGHT]) {
cancelMouseEvent(
screenSpaceEventHandler,
ScreenSpaceEventType.RIGHT_UP,
ScreenSpaceEventType.RIGHT_CLICK,
event
);
screenSpaceEventHandler._buttonDown[MouseButton.RIGHT] = false;
}
}
const mouseMoveEvent = {
startPosition: new Cartesian2(),
endPosition: new Cartesian2(),
};
function handleMouseMove(screenSpaceEventHandler, event) {
if (!canProcessMouseEvent(screenSpaceEventHandler)) {
return;
}
const modifier = getModifier(event);
const position = getPosition(
screenSpaceEventHandler,
event,
screenSpaceEventHandler._primaryPosition
);
const previousPosition = screenSpaceEventHandler._primaryPreviousPosition;
const action = screenSpaceEventHandler.getInputAction(
ScreenSpaceEventType.MOUSE_MOVE,
modifier
);
if (defined(action)) {
Cartesian2.clone(previousPosition, mouseMoveEvent.startPosition);
Cartesian2.clone(position, mouseMoveEvent.endPosition);
action(mouseMoveEvent);
}
Cartesian2.clone(position, previousPosition);
if (
screenSpaceEventHandler._buttonDown[MouseButton.LEFT] ||
screenSpaceEventHandler._buttonDown[MouseButton.MIDDLE] ||
screenSpaceEventHandler._buttonDown[MouseButton.RIGHT]
) {
event.preventDefault();
}
}
const mouseDblClickEvent = {
position: new Cartesian2(),
};
function handleDblClick(screenSpaceEventHandler, event) {
const button = event.button;
let screenSpaceEventType;
if (button === MouseButton.LEFT) {
screenSpaceEventType = ScreenSpaceEventType.LEFT_DOUBLE_CLICK;
} else {
return;
}
const modifier = getModifier(event);
const action = screenSpaceEventHandler.getInputAction(
screenSpaceEventType,
modifier
);
if (defined(action)) {
getPosition(screenSpaceEventHandler, event, mouseDblClickEvent.position);
action(mouseDblClickEvent);
}
}
function handleWheel(screenSpaceEventHandler, event) {
// currently this event exposes the delta value in terms of
// the obsolete mousewheel event type. so, for now, we adapt the other
// values to that scheme.
let delta;
// standard wheel event uses deltaY. sign is opposite wheelDelta.
// deltaMode indicates what unit it is in.
if (defined(event.deltaY)) {
const deltaMode = event.deltaMode;
if (deltaMode === event.DOM_DELTA_PIXEL) {
delta = -event.deltaY;
} else if (deltaMode === event.DOM_DELTA_LINE) {
delta = -event.deltaY * 40;
} else {
// DOM_DELTA_PAGE
delta = -event.deltaY * 120;
}
} else if (event.detail > 0) {
// old Firefox versions use event.detail to count the number of clicks. The sign
// of the integer is the direction the wheel is scrolled.
delta = event.detail * -120;
} else {
delta = event.wheelDelta;
}
if (!defined(delta)) {
return;
}
const modifier = getModifier(event);
const action = screenSpaceEventHandler.getInputAction(
ScreenSpaceEventType.WHEEL,
modifier
);
if (defined(action)) {
action(delta);
event.preventDefault();
}
}
function handleTouchStart(screenSpaceEventHandler, event) {
gotTouchEvent(screenSpaceEventHandler);
const changedTouches = event.changedTouches;
let i;
const length = changedTouches.length;
let touch;
let identifier;
const positions = screenSpaceEventHandler._positions;
for (i = 0; i < length; ++i) {
touch = changedTouches[i];
identifier = touch.identifier;
positions.set(
identifier,
getPosition(screenSpaceEventHandler, touch, new Cartesian2())
);
}
fireTouchEvents(screenSpaceEventHandler, event);
const previousPositions = screenSpaceEventHandler._previousPositions;
for (i = 0; i < length; ++i) {
touch = changedTouches[i];
identifier = touch.identifier;
previousPositions.set(
identifier,
Cartesian2.clone(positions.get(identifier))
);
}
}
function handleTouchEnd(screenSpaceEventHandler, event) {
gotTouchEvent(screenSpaceEventHandler);
const changedTouches = event.changedTouches;
let i;
const length = changedTouches.length;
let touch;
let identifier;
const positions = screenSpaceEventHandler._positions;
for (i = 0; i < length; ++i) {
touch = changedTouches[i];
identifier = touch.identifier;
positions.remove(identifier);
}
fireTouchEvents(screenSpaceEventHandler, event);
const previousPositions = screenSpaceEventHandler._previousPositions;
for (i = 0; i < length; ++i) {
touch = changedTouches[i];
identifier = touch.identifier;
previousPositions.remove(identifier);
}
}
const touchStartEvent = {
position: new Cartesian2(),
};
const touch2StartEvent = {
position1: new Cartesian2(),
position2: new Cartesian2(),
};
const touchEndEvent = {
position: new Cartesian2(),
};
const touchClickEvent = {
position: new Cartesian2(),
};
const touchHoldEvent = {
position: new Cartesian2(),
};
function fireTouchEvents(screenSpaceEventHandler, event) {
const modifier = getModifier(event);
const positions = screenSpaceEventHandler._positions;
const numberOfTouches = positions.length;
let action;
let clickAction;
const pinching = screenSpaceEventHandler._isPinching;
if (
numberOfTouches !== 1 &&
screenSpaceEventHandler._buttonDown[MouseButton.LEFT]
) {
// transitioning from single touch, trigger UP and might trigger CLICK
screenSpaceEventHandler._buttonDown[MouseButton.LEFT] = false;
if (defined(screenSpaceEventHandler._touchHoldTimer)) {
clearTimeout(screenSpaceEventHandler._touchHoldTimer);
screenSpaceEventHandler._touchHoldTimer = undefined;
}
action = screenSpaceEventHandler.getInputAction(
ScreenSpaceEventType.LEFT_UP,
modifier
);
if (defined(action)) {
Cartesian2.clone(
screenSpaceEventHandler._primaryPosition,
touchEndEvent.position
);
action(touchEndEvent);
}
if (numberOfTouches === 0 && !screenSpaceEventHandler._isTouchHolding) {
// releasing single touch, check for CLICK
clickAction = screenSpaceEventHandler.getInputAction(
ScreenSpaceEventType.LEFT_CLICK,
modifier
);
if (defined(clickAction)) {
const startPosition = screenSpaceEventHandler._primaryStartPosition;
const endPosition =
screenSpaceEventHandler._previousPositions.values[0];
if (
checkPixelTolerance(
startPosition,
endPosition,
screenSpaceEventHandler._clickPixelTolerance
)
) {
Cartesian2.clone(
screenSpaceEventHandler._primaryPosition,
touchClickEvent.position
);
clickAction(touchClickEvent);
}
}
}
screenSpaceEventHandler._isTouchHolding = false;
// Otherwise don't trigger CLICK, because we are adding more touches.
}
if (numberOfTouches === 0 && pinching) {
// transitioning from pinch, trigger PINCH_END
screenSpaceEventHandler._isPinching = false;
action = screenSpaceEventHandler.getInputAction(
ScreenSpaceEventType.PINCH_END,
modifier
);
if (defined(action)) {
action();
}
}
if (numberOfTouches === 1 && !pinching) {
// transitioning to single touch, trigger DOWN
const position = positions.values[0];
Cartesian2.clone(position, screenSpaceEventHandler._primaryPosition);
Cartesian2.clone(position, screenSpaceEventHandler._primaryStartPosition);
Cartesian2.clone(
position,
screenSpaceEventHandler._primaryPreviousPosition
);
screenSpaceEventHandler._buttonDown[MouseButton.LEFT] = true;
action = screenSpaceEventHandler.getInputAction(
ScreenSpaceEventType.LEFT_DOWN,
modifier
);
if (defined(action)) {
Cartesian2.clone(position, touchStartEvent.position);
action(touchStartEvent);
}
screenSpaceEventHandler._touchHoldTimer = setTimeout(function () {
if (!screenSpaceEventHandler.isDestroyed()) {
screenSpaceEventHandler._touchHoldTimer = undefined;
screenSpaceEventHandler._isTouchHolding = true;
clickAction = screenSpaceEventHandler.getInputAction(
ScreenSpaceEventType.RIGHT_CLICK,
modifier
);
if (defined(clickAction)) {
const startPosition = screenSpaceEventHandler._primaryStartPosition;
const endPosition =
screenSpaceEventHandler._previousPositions.values[0];
if (
checkPixelTolerance(
startPosition,
endPosition,
screenSpaceEventHandler._holdPixelTolerance
)
) {
Cartesian2.clone(
screenSpaceEventHandler._primaryPosition,
touchHoldEvent.position
);
clickAction(touchHoldEvent);
}
}
}
}, ScreenSpaceEventHandler.touchHoldDelayMilliseconds);
event.preventDefault();
}
if (numberOfTouches === 2 && !pinching) {
// transitioning to pinch, trigger PINCH_START
screenSpaceEventHandler._isPinching = true;
action = screenSpaceEventHandler.getInputAction(
ScreenSpaceEventType.PINCH_START,
modifier
);
if (defined(action)) {
Cartesian2.clone(positions.values[0], touch2StartEvent.position1);
Cartesian2.clone(positions.values[1], touch2StartEvent.position2);
action(touch2StartEvent);
// Touch-enabled devices, in particular iOS can have many default behaviours for
// "pinch" events, which can still be executed unless we prevent them here.
event.preventDefault();
}
}
}
function handleTouchMove(screenSpaceEventHandler, event) {
gotTouchEvent(screenSpaceEventHandler);
const changedTouches = event.changedTouches;
let i;
const length = changedTouches.length;
let touch;
let identifier;
const positions = screenSpaceEventHandler._positions;
for (i = 0; i < length; ++i) {
touch = changedTouches[i];
identifier = touch.identifier;
const position = positions.get(identifier);
if (defined(position)) {
getPosition(screenSpaceEventHandler, touch, position);
}
}
fireTouchMoveEvents(screenSpaceEventHandler, event);
const previousPositions = screenSpaceEventHandler._previousPositions;
for (i = 0; i < length; ++i) {
touch = changedTouches[i];
identifier = touch.identifier;
Cartesian2.clone(
positions.get(identifier),
previousPositions.get(identifier)
);
}
}
const touchMoveEvent = {
startPosition: new Cartesian2(),
endPosition: new Cartesian2(),
};
const touchPinchMovementEvent = {
distance: {
startPosition: new Cartesian2(),
endPosition: new Cartesian2(),
},
angleAndHeight: {
startPosition: new Cartesian2(),
endPosition: new Cartesian2(),
},
};
function fireTouchMoveEvents(screenSpaceEventHandler, event) {
const modifier = getModifier(event);
const positions = screenSpaceEventHandler._positions;
const previousPositions = screenSpaceEventHandler._previousPositions;
const numberOfTouches = positions.length;
let action;
if (
numberOfTouches === 1 &&
screenSpaceEventHandler._buttonDown[MouseButton.LEFT]
) {
// moving single touch
const position = positions.values[0];
Cartesian2.clone(position, screenSpaceEventHandler._primaryPosition);
const previousPosition = screenSpaceEventHandler._primaryPreviousPosition;
action = screenSpaceEventHandler.getInputAction(
ScreenSpaceEventType.MOUSE_MOVE,
modifier
);
if (defined(action)) {
Cartesian2.clone(previousPosition, touchMoveEvent.startPosition);
Cartesian2.clone(position, touchMoveEvent.endPosition);
action(touchMoveEvent);
}
Cartesian2.clone(position, previousPosition);
event.preventDefault();
} else if (numberOfTouches === 2 && screenSpaceEventHandler._isPinching) {
// moving pinch
action = screenSpaceEventHandler.getInputAction(
ScreenSpaceEventType.PINCH_MOVE,
modifier
);
if (defined(action)) {
const position1 = positions.values[0];
const position2 = positions.values[1];
const previousPosition1 = previousPositions.values[0];
const previousPosition2 = previousPositions.values[1];
const dX = position2.x - position1.x;
const dY = position2.y - position1.y;
const dist = Math.sqrt(dX * dX + dY * dY) * 0.25;
const prevDX = previousPosition2.x - previousPosition1.x;
const prevDY = previousPosition2.y - previousPosition1.y;
const prevDist = Math.sqrt(prevDX * prevDX + prevDY * prevDY) * 0.25;
const cY = (position2.y + position1.y) * 0.125;
const prevCY = (previousPosition2.y + previousPosition1.y) * 0.125;
const angle = Math.atan2(dY, dX);
const prevAngle = Math.atan2(prevDY, prevDX);
Cartesian2.fromElements(
0.0,
prevDist,
touchPinchMovementEvent.distance.startPosition
);
Cartesian2.fromElements(
0.0,
dist,
touchPinchMovementEvent.distance.endPosition
);
Cartesian2.fromElements(
prevAngle,
prevCY,
touchPinchMovementEvent.angleAndHeight.startPosition
);
Cartesian2.fromElements(
angle,
cY,
touchPinchMovementEvent.angleAndHeight.endPosition
);
action(touchPinchMovementEvent);
}
}
}
function handlePointerDown(screenSpaceEventHandler, event) {
event.target.setPointerCapture(event.pointerId);
if (event.pointerType === "touch") {
const positions = screenSpaceEventHandler._positions;
const identifier = event.pointerId;
positions.set(
identifier,
getPosition(screenSpaceEventHandler, event, new Cartesian2())
);
fireTouchEvents(screenSpaceEventHandler, event);
const previousPositions = screenSpaceEventHandler._previousPositions;
previousPositions.set(
identifier,
Cartesian2.clone(positions.get(identifier))
);
} else {
handleMouseDown(screenSpaceEventHandler, event);
}
}
function handlePointerUp(screenSpaceEventHandler, event) {
if (event.pointerType === "touch") {
const positions = screenSpaceEventHandler._positions;
const identifier = event.pointerId;
positions.remove(identifier);
fireTouchEvents(screenSpaceEventHandler, event);
const previousPositions = screenSpaceEventHandler._previousPositions;
previousPositions.remove(identifier);
} else {
handleMouseUp(screenSpaceEventHandler, event);
}
}
function handlePointerMove(screenSpaceEventHandler, event) {
if (event.pointerType === "touch") {
const positions = screenSpaceEventHandler._positions;
const identifier = event.pointerId;
const position = positions.get(identifier);
if (!defined(position)) {
return;
}
getPosition(screenSpaceEventHandler, event, position);
fireTouchMoveEvents(screenSpaceEventHandler, event);
const previousPositions = screenSpaceEventHandler._previousPositions;
Cartesian2.clone(
positions.get(identifier),
previousPositions.get(identifier)
);
} else {
handleMouseMove(screenSpaceEventHandler, event);
}
}
/**
* @typedef {object} ScreenSpaceEventHandler.PositionedEvent
*
* An Event that occurs at a single position on screen.
*
* @property {Cartesian2} position
*/
/**
* @callback ScreenSpaceEventHandler.PositionedEventCallback
*
* The callback invoked when a positioned event triggers an event listener.
*
* @param {ScreenSpaceEventHandler.PositionedEvent} event The event which triggered the listener
*/
/**
* @typedef {object} ScreenSpaceEventHandler.MotionEvent
*
* An Event that starts at one position and ends at another.
*
* @property {Cartesian2} startPosition
* @property {Cartesian2} endPosition
*/
/**
* @callback ScreenSpaceEventHandler.MotionEventCallback
*
* The callback invoked when a motion event triggers an event listener.
*
* @param {ScreenSpaceEventHandler.MotionEvent} event The event which triggered the listener
*/
/**
* @typedef {object} ScreenSpaceEventHandler.TwoPointEvent
*
* An Event that occurs at a two positions on screen.
*
* @property {Cartesian2} position1
* @property {Cartesian2} position2
*/
/**
* @callback ScreenSpaceEventHandler.TwoPointEventCallback
*
* The callback invoked when a two-point event triggers an event listener.
*
* @param {ScreenSpaceEventHandler.TwoPointEvent} event The event which triggered the listener
*/
/**
* @typedef {object} ScreenSpaceEventHandler.TwoPointMotionEvent
*
* An Event that starts at a two positions on screen and moves to two other positions.
*
* @property {Cartesian2} position1
* @property {Cartesian2} position2
* @property {Cartesian2} previousPosition1
* @property {Cartesian2} previousPosition2
*/
/**
* @callback ScreenSpaceEventHandler.TwoPointMotionEventCallback
*
* The callback invoked when a two-point motion event triggers an event listener.
*
* @param {ScreenSpaceEventHandler.TwoPointMotionEvent} event The event which triggered the listener
*/
/**
* @callback ScreenSpaceEventHandler.WheelEventCallback
*
* The callback invoked when a mouse-wheel event triggers an event listener.
*
* @param {number} delta The amount that the mouse wheel moved
*/
/**
* Handles user input events. Custom functions can be added to be executed on
* when the user enters input.
*
* @alias ScreenSpaceEventHandler
*
* @param {HTMLCanvasElement} [element=document] The element to add events to.
*
* @constructor
*/
function ScreenSpaceEventHandler(element) {
this._inputEvents = {};
this._buttonDown = {
LEFT: false,
MIDDLE: false,
RIGHT: false,
};
this._isPinching = false;
this._isTouchHolding = false;
this._lastSeenTouchEvent = -ScreenSpaceEventHandler.mouseEmulationIgnoreMilliseconds;
this._primaryStartPosition = new Cartesian2();
this._primaryPosition = new Cartesian2();
this._primaryPreviousPosition = new Cartesian2();
this._positions = new AssociativeArray();
this._previousPositions = new AssociativeArray();
this._removalFunctions = [];
this._touchHoldTimer = undefined;
// TODO: Revisit when doing mobile development. May need to be configurable
// or determined based on the platform?
this._clickPixelTolerance = 5;
this._holdPixelTolerance = 25;
this._element = defaultValue(element, document);
registerListeners(this);
}
/**
* Set a function to be executed on an input event.
*
* @param {ScreenSpaceEventHandler.PositionedEventCallback|ScreenSpaceEventHandler.MotionEventCallback|ScreenSpaceEventHandler.WheelEventCallback|ScreenSpaceEventHandler.TwoPointEventCallback|ScreenSpaceEventHandler.TwoPointMotionEventCallback} action Function to be executed when the input event occurs.
* @param {ScreenSpaceEventType} type The ScreenSpaceEventType of input event.
* @param {KeyboardEventModifier} [modifier] A KeyboardEventModifier key that is held when a type
* event occurs.
*
* @see ScreenSpaceEventHandler#getInputAction
* @see ScreenSpaceEventHandler#removeInputAction
*/
ScreenSpaceEventHandler.prototype.setInputAction = function (
action,
type,
modifier
) {
//>>includeStart('debug', pragmas.debug);
if (!defined(action)) {
throw new DeveloperError("action is required.");
}
if (!defined(type)) {
throw new DeveloperError("type is required.");
}
//>>includeEnd('debug');
const key = getInputEventKey(type, modifier);
this._inputEvents[key] = action;
};
/**
* Returns the function to be executed on an input event.
*
* @param {ScreenSpaceEventType} type The ScreenSpaceEventType of input event.
* @param {KeyboardEventModifier} [modifier] A KeyboardEventModifier key that is held when a type
* event occurs.
*
* @returns {ScreenSpaceEventHandler.PositionedEventCallback|ScreenSpaceEventHandler.MotionEventCallback|ScreenSpaceEventHandler.WheelEventCallback|ScreenSpaceEventHandler.TwoPointEventCallback|ScreenSpaceEventHandler.TwoPointMotionEventCallback} The function to be executed on an input event.
*
* @see ScreenSpaceEventHandler#setInputAction
* @see ScreenSpaceEventHandler#removeInputAction
*/
ScreenSpaceEventHandler.prototype.getInputAction = function (type, modifier) {
//>>includeStart('debug', pragmas.debug);
if (!defined(type)) {
throw new DeveloperError("type is required.");
}
//>>includeEnd('debug');
const key = getInputEventKey(type, modifier);
return this._inputEvents[key];
};
/**
* Removes the function to be executed on an input event.
*
* @param {ScreenSpaceEventType} type The ScreenSpaceEventType of input event.
* @param {KeyboardEventModifier} [modifier] A KeyboardEventModifier key that is held when a type
* event occurs.
*
* @see ScreenSpaceEventHandler#getInputAction
* @see ScreenSpaceEventHandler#setInputAction
*/
ScreenSpaceEventHandler.prototype.removeInputAction = function (
type,
modifier
) {
//>>includeStart('debug', pragmas.debug);
if (!defined(type)) {
throw new DeveloperError("type is required.");
}
//>>includeEnd('debug');
const key = getInputEventKey(type, modifier);
delete this._inputEvents[key];
};
/**
* Returns true if this object was destroyed; otherwise, false.
*
* If this object was destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception.
*
* @returns {boolean} true
if this object was destroyed; otherwise, false
.
*
* @see ScreenSpaceEventHandler#destroy
*/
ScreenSpaceEventHandler.prototype.isDestroyed = function () {
return false;
};
/**
* Removes listeners held by this object.
*
* Once an object is destroyed, it should not be used; calling any function other than
* isDestroyed
will result in a {@link DeveloperError} exception. Therefore,
* assign the return value (undefined
) to the object as done in the example.
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
*
* @example
* handler = handler && handler.destroy();
*
* @see ScreenSpaceEventHandler#isDestroyed
*/
ScreenSpaceEventHandler.prototype.destroy = function () {
unregisterListeners(this);
return destroyObject(this);
};
/**
* The amount of time, in milliseconds, that mouse events will be disabled after
* receiving any touch events, such that any emulated mouse events will be ignored.
* @type {number}
* @default 800
*/
ScreenSpaceEventHandler.mouseEmulationIgnoreMilliseconds = 800;
/**
* The amount of time, in milliseconds, before a touch on the screen becomes a
* touch and hold.
* @type {number}
* @default 1500
*/
ScreenSpaceEventHandler.touchHoldDelayMilliseconds = 1500;
export default ScreenSpaceEventHandler;