import Cartesian2 from '../Core/Cartesian2.js'; import defined from '../Core/defined.js'; import defineProperties from '../Core/defineProperties.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 {Canvas} [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); } } } } 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;