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;