import Cartesian2 from "../Core/Cartesian2.js";
import defined from "../Core/defined.js";
import destroyObject from "../Core/destroyObject.js";
import DeveloperError from "../Core/DeveloperError.js";
import KeyboardEventModifier from "../Core/KeyboardEventModifier.js";
import CesiumMath from "../Core/Math.js";
import ScreenSpaceEventHandler from "../Core/ScreenSpaceEventHandler.js";
import ScreenSpaceEventType from "../Core/ScreenSpaceEventType.js";
import CameraEventType from "./CameraEventType.js";
function getKey(type, modifier) {
var key = type;
if (defined(modifier)) {
key += "+" + modifier;
}
return key;
}
function clonePinchMovement(pinchMovement, result) {
Cartesian2.clone(
pinchMovement.distance.startPosition,
result.distance.startPosition
);
Cartesian2.clone(
pinchMovement.distance.endPosition,
result.distance.endPosition
);
Cartesian2.clone(
pinchMovement.angleAndHeight.startPosition,
result.angleAndHeight.startPosition
);
Cartesian2.clone(
pinchMovement.angleAndHeight.endPosition,
result.angleAndHeight.endPosition
);
}
function listenToPinch(aggregator, modifier, canvas) {
var key = getKey(CameraEventType.PINCH, modifier);
var update = aggregator._update;
var isDown = aggregator._isDown;
var eventStartPosition = aggregator._eventStartPosition;
var pressTime = aggregator._pressTime;
var releaseTime = aggregator._releaseTime;
update[key] = true;
isDown[key] = false;
eventStartPosition[key] = new Cartesian2();
var movement = aggregator._movement[key];
if (!defined(movement)) {
movement = aggregator._movement[key] = {};
}
movement.distance = {
startPosition: new Cartesian2(),
endPosition: new Cartesian2(),
};
movement.angleAndHeight = {
startPosition: new Cartesian2(),
endPosition: new Cartesian2(),
};
movement.prevAngle = 0.0;
aggregator._eventHandler.setInputAction(
function (event) {
aggregator._buttonsDown++;
isDown[key] = true;
pressTime[key] = new Date();
// Compute center position and store as start point.
Cartesian2.lerp(
event.position1,
event.position2,
0.5,
eventStartPosition[key]
);
},
ScreenSpaceEventType.PINCH_START,
modifier
);
aggregator._eventHandler.setInputAction(
function () {
aggregator._buttonsDown = Math.max(aggregator._buttonsDown - 1, 0);
isDown[key] = false;
releaseTime[key] = new Date();
},
ScreenSpaceEventType.PINCH_END,
modifier
);
aggregator._eventHandler.setInputAction(
function (mouseMovement) {
if (isDown[key]) {
// Aggregate several input events into a single animation frame.
if (!update[key]) {
Cartesian2.clone(
mouseMovement.distance.endPosition,
movement.distance.endPosition
);
Cartesian2.clone(
mouseMovement.angleAndHeight.endPosition,
movement.angleAndHeight.endPosition
);
} else {
clonePinchMovement(mouseMovement, movement);
update[key] = false;
movement.prevAngle = movement.angleAndHeight.startPosition.x;
}
// Make sure our aggregation of angles does not "flip" over 360 degrees.
var angle = movement.angleAndHeight.endPosition.x;
var prevAngle = movement.prevAngle;
var TwoPI = Math.PI * 2;
while (angle >= prevAngle + Math.PI) {
angle -= TwoPI;
}
while (angle < prevAngle - Math.PI) {
angle += TwoPI;
}
movement.angleAndHeight.endPosition.x =
(-angle * canvas.clientWidth) / 12;
movement.angleAndHeight.startPosition.x =
(-prevAngle * canvas.clientWidth) / 12;
}
},
ScreenSpaceEventType.PINCH_MOVE,
modifier
);
}
function listenToWheel(aggregator, modifier) {
var key = getKey(CameraEventType.WHEEL, modifier);
var update = aggregator._update;
update[key] = true;
var movement = aggregator._movement[key];
if (!defined(movement)) {
movement = aggregator._movement[key] = {};
}
movement.startPosition = new Cartesian2();
movement.endPosition = new Cartesian2();
aggregator._eventHandler.setInputAction(
function (delta) {
// TODO: magic numbers
var arcLength = 15.0 * CesiumMath.toRadians(delta);
if (!update[key]) {
movement.endPosition.y = movement.endPosition.y + arcLength;
} else {
Cartesian2.clone(Cartesian2.ZERO, movement.startPosition);
movement.endPosition.x = 0.0;
movement.endPosition.y = arcLength;
update[key] = false;
}
},
ScreenSpaceEventType.WHEEL,
modifier
);
}
function listenMouseButtonDownUp(aggregator, modifier, type) {
var key = getKey(type, modifier);
var isDown = aggregator._isDown;
var eventStartPosition = aggregator._eventStartPosition;
var pressTime = aggregator._pressTime;
var releaseTime = aggregator._releaseTime;
isDown[key] = false;
eventStartPosition[key] = new Cartesian2();
var lastMovement = aggregator._lastMovement[key];
if (!defined(lastMovement)) {
lastMovement = aggregator._lastMovement[key] = {
startPosition: new Cartesian2(),
endPosition: new Cartesian2(),
valid: false,
};
}
var down;
var up;
if (type === CameraEventType.LEFT_DRAG) {
down = ScreenSpaceEventType.LEFT_DOWN;
up = ScreenSpaceEventType.LEFT_UP;
} else if (type === CameraEventType.RIGHT_DRAG) {
down = ScreenSpaceEventType.RIGHT_DOWN;
up = ScreenSpaceEventType.RIGHT_UP;
} else if (type === CameraEventType.MIDDLE_DRAG) {
down = ScreenSpaceEventType.MIDDLE_DOWN;
up = ScreenSpaceEventType.MIDDLE_UP;
}
aggregator._eventHandler.setInputAction(
function (event) {
aggregator._buttonsDown++;
lastMovement.valid = false;
isDown[key] = true;
pressTime[key] = new Date();
Cartesian2.clone(event.position, eventStartPosition[key]);
},
down,
modifier
);
aggregator._eventHandler.setInputAction(
function () {
aggregator._buttonsDown = Math.max(aggregator._buttonsDown - 1, 0);
isDown[key] = false;
releaseTime[key] = new Date();
},
up,
modifier
);
}
function cloneMouseMovement(mouseMovement, result) {
Cartesian2.clone(mouseMovement.startPosition, result.startPosition);
Cartesian2.clone(mouseMovement.endPosition, result.endPosition);
}
function listenMouseMove(aggregator, modifier) {
var update = aggregator._update;
var movement = aggregator._movement;
var lastMovement = aggregator._lastMovement;
var isDown = aggregator._isDown;
for (var typeName in CameraEventType) {
if (CameraEventType.hasOwnProperty(typeName)) {
var type = CameraEventType[typeName];
if (defined(type)) {
var key = getKey(type, modifier);
update[key] = true;
if (!defined(aggregator._lastMovement[key])) {
aggregator._lastMovement[key] = {
startPosition: new Cartesian2(),
endPosition: new Cartesian2(),
valid: false,
};
}
if (!defined(aggregator._movement[key])) {
aggregator._movement[key] = {
startPosition: new Cartesian2(),
endPosition: new Cartesian2(),
};
}
}
}
}
aggregator._eventHandler.setInputAction(
function (mouseMovement) {
for (var typeName in CameraEventType) {
if (CameraEventType.hasOwnProperty(typeName)) {
var type = CameraEventType[typeName];
if (defined(type)) {
var key = getKey(type, modifier);
if (isDown[key]) {
if (!update[key]) {
Cartesian2.clone(
mouseMovement.endPosition,
movement[key].endPosition
);
} else {
cloneMouseMovement(movement[key], lastMovement[key]);
lastMovement[key].valid = true;
cloneMouseMovement(mouseMovement, movement[key]);
update[key] = false;
}
}
}
}
}
Cartesian2.clone(
mouseMovement.endPosition,
aggregator._currentMousePosition
);
},
ScreenSpaceEventType.MOUSE_MOVE,
modifier
);
}
/**
* Aggregates input events. For example, suppose the following inputs are received between frames:
* left mouse button down, mouse move, mouse move, left mouse button up. These events will be aggregated into
* one event with a start and end position of the mouse.
*
* @alias CameraEventAggregator
* @constructor
*
* @param {HTMLCanvasElement} [canvas=document] The element to handle events for.
*
* @see ScreenSpaceEventHandler
*/
function CameraEventAggregator(canvas) {
//>>includeStart('debug', pragmas.debug);
if (!defined(canvas)) {
throw new DeveloperError("canvas is required.");
}
//>>includeEnd('debug');
this._eventHandler = new ScreenSpaceEventHandler(canvas);
this._update = {};
this._movement = {};
this._lastMovement = {};
this._isDown = {};
this._eventStartPosition = {};
this._pressTime = {};
this._releaseTime = {};
this._buttonsDown = 0;
this._currentMousePosition = new Cartesian2();
listenToWheel(this, undefined);
listenToPinch(this, undefined, canvas);
listenMouseButtonDownUp(this, undefined, CameraEventType.LEFT_DRAG);
listenMouseButtonDownUp(this, undefined, CameraEventType.RIGHT_DRAG);
listenMouseButtonDownUp(this, undefined, CameraEventType.MIDDLE_DRAG);
listenMouseMove(this, undefined);
for (var modifierName in KeyboardEventModifier) {
if (KeyboardEventModifier.hasOwnProperty(modifierName)) {
var modifier = KeyboardEventModifier[modifierName];
if (defined(modifier)) {
listenToWheel(this, modifier);
listenToPinch(this, modifier, canvas);
listenMouseButtonDownUp(this, modifier, CameraEventType.LEFT_DRAG);
listenMouseButtonDownUp(this, modifier, CameraEventType.RIGHT_DRAG);
listenMouseButtonDownUp(this, modifier, CameraEventType.MIDDLE_DRAG);
listenMouseMove(this, modifier);
}
}
}
}
Object.defineProperties(CameraEventAggregator.prototype, {
/**
* Gets the current mouse position.
* @memberof CameraEventAggregator.prototype
* @type {Cartesian2}
*/
currentMousePosition: {
get: function () {
return this._currentMousePosition;
},
},
/**
* Gets whether any mouse button is down, a touch has started, or the wheel has been moved.
* @memberof CameraEventAggregator.prototype
* @type {Boolean}
*/
anyButtonDown: {
get: function () {
var wheelMoved =
!this._update[getKey(CameraEventType.WHEEL)] ||
!this._update[
getKey(CameraEventType.WHEEL, KeyboardEventModifier.SHIFT)
] ||
!this._update[
getKey(CameraEventType.WHEEL, KeyboardEventModifier.CTRL)
] ||
!this._update[getKey(CameraEventType.WHEEL, KeyboardEventModifier.ALT)];
return this._buttonsDown > 0 || wheelMoved;
},
},
});
/**
* Gets if a mouse button down or touch has started and has been moved.
*
* @param {CameraEventType} type The camera event type.
* @param {KeyboardEventModifier} [modifier] The keyboard modifier.
* @returns {Boolean} Returns true if a mouse button down or touch has started and has been moved; otherwise, false
*/
CameraEventAggregator.prototype.isMoving = function (type, modifier) {
//>>includeStart('debug', pragmas.debug);
if (!defined(type)) {
throw new DeveloperError("type is required.");
}
//>>includeEnd('debug');
var key = getKey(type, modifier);
return !this._update[key];
};
/**
* Gets the aggregated start and end position of the current event.
*
* @param {CameraEventType} type The camera event type.
* @param {KeyboardEventModifier} [modifier] The keyboard modifier.
* @returns {Object} An object with two {@link Cartesian2} properties: startPosition and endPosition.
*/
CameraEventAggregator.prototype.getMovement = function (type, modifier) {
//>>includeStart('debug', pragmas.debug);
if (!defined(type)) {
throw new DeveloperError("type is required.");
}
//>>includeEnd('debug');
var key = getKey(type, modifier);
var movement = this._movement[key];
return movement;
};
/**
* Gets the start and end position of the last move event (not the aggregated event).
*
* @param {CameraEventType} type The camera event type.
* @param {KeyboardEventModifier} [modifier] The keyboard modifier.
* @returns {Object|undefined} An object with two {@link Cartesian2} properties: startPosition and endPosition or undefined.
*/
CameraEventAggregator.prototype.getLastMovement = function (type, modifier) {
//>>includeStart('debug', pragmas.debug);
if (!defined(type)) {
throw new DeveloperError("type is required.");
}
//>>includeEnd('debug');
var key = getKey(type, modifier);
var lastMovement = this._lastMovement[key];
if (lastMovement.valid) {
return lastMovement;
}
return undefined;
};
/**
* Gets whether the mouse button is down or a touch has started.
*
* @param {CameraEventType} type The camera event type.
* @param {KeyboardEventModifier} [modifier] The keyboard modifier.
* @returns {Boolean} Whether the mouse button is down or a touch has started.
*/
CameraEventAggregator.prototype.isButtonDown = function (type, modifier) {
//>>includeStart('debug', pragmas.debug);
if (!defined(type)) {
throw new DeveloperError("type is required.");
}
//>>includeEnd('debug');
var key = getKey(type, modifier);
return this._isDown[key];
};
/**
* Gets the mouse position that started the aggregation.
*
* @param {CameraEventType} type The camera event type.
* @param {KeyboardEventModifier} [modifier] The keyboard modifier.
* @returns {Cartesian2} The mouse position.
*/
CameraEventAggregator.prototype.getStartMousePosition = function (
type,
modifier
) {
//>>includeStart('debug', pragmas.debug);
if (!defined(type)) {
throw new DeveloperError("type is required.");
}
//>>includeEnd('debug');
if (type === CameraEventType.WHEEL) {
return this._currentMousePosition;
}
var key = getKey(type, modifier);
return this._eventStartPosition[key];
};
/**
* Gets the time the button was pressed or the touch was started.
*
* @param {CameraEventType} type The camera event type.
* @param {KeyboardEventModifier} [modifier] The keyboard modifier.
* @returns {Date} The time the button was pressed or the touch was started.
*/
CameraEventAggregator.prototype.getButtonPressTime = function (type, modifier) {
//>>includeStart('debug', pragmas.debug);
if (!defined(type)) {
throw new DeveloperError("type is required.");
}
//>>includeEnd('debug');
var key = getKey(type, modifier);
return this._pressTime[key];
};
/**
* Gets the time the button was released or the touch was ended.
*
* @param {CameraEventType} type The camera event type.
* @param {KeyboardEventModifier} [modifier] The keyboard modifier.
* @returns {Date} The time the button was released or the touch was ended.
*/
CameraEventAggregator.prototype.getButtonReleaseTime = function (
type,
modifier
) {
//>>includeStart('debug', pragmas.debug);
if (!defined(type)) {
throw new DeveloperError("type is required.");
}
//>>includeEnd('debug');
var key = getKey(type, modifier);
return this._releaseTime[key];
};
/**
* Signals that all of the events have been handled and the aggregator should be reset to handle new events.
*/
CameraEventAggregator.prototype.reset = function () {
for (var name in this._update) {
if (this._update.hasOwnProperty(name)) {
this._update[name] = true;
}
}
};
/**
* 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 CameraEventAggregator#destroy
*/
CameraEventAggregator.prototype.isDestroyed = function () {
return false;
};
/**
* Removes mouse 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 CameraEventAggregator#isDestroyed
*/
CameraEventAggregator.prototype.destroy = function () {
this._eventHandler = this._eventHandler && this._eventHandler.destroy();
return destroyObject(this);
};
export default CameraEventAggregator;