CameraEventAggregator.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496
  1. import Cartesian2 from '../Core/Cartesian2.js';
  2. import defined from '../Core/defined.js';
  3. import defineProperties from '../Core/defineProperties.js';
  4. import destroyObject from '../Core/destroyObject.js';
  5. import DeveloperError from '../Core/DeveloperError.js';
  6. import KeyboardEventModifier from '../Core/KeyboardEventModifier.js';
  7. import CesiumMath from '../Core/Math.js';
  8. import ScreenSpaceEventHandler from '../Core/ScreenSpaceEventHandler.js';
  9. import ScreenSpaceEventType from '../Core/ScreenSpaceEventType.js';
  10. import CameraEventType from './CameraEventType.js';
  11. function getKey(type, modifier) {
  12. var key = type;
  13. if (defined(modifier)) {
  14. key += '+' + modifier;
  15. }
  16. return key;
  17. }
  18. function clonePinchMovement(pinchMovement, result) {
  19. Cartesian2.clone(pinchMovement.distance.startPosition, result.distance.startPosition);
  20. Cartesian2.clone(pinchMovement.distance.endPosition, result.distance.endPosition);
  21. Cartesian2.clone(pinchMovement.angleAndHeight.startPosition, result.angleAndHeight.startPosition);
  22. Cartesian2.clone(pinchMovement.angleAndHeight.endPosition, result.angleAndHeight.endPosition);
  23. }
  24. function listenToPinch(aggregator, modifier, canvas) {
  25. var key = getKey(CameraEventType.PINCH, modifier);
  26. var update = aggregator._update;
  27. var isDown = aggregator._isDown;
  28. var eventStartPosition = aggregator._eventStartPosition;
  29. var pressTime = aggregator._pressTime;
  30. var releaseTime = aggregator._releaseTime;
  31. update[key] = true;
  32. isDown[key] = false;
  33. eventStartPosition[key] = new Cartesian2();
  34. var movement = aggregator._movement[key];
  35. if (!defined(movement)) {
  36. movement = aggregator._movement[key] = {};
  37. }
  38. movement.distance = {
  39. startPosition : new Cartesian2(),
  40. endPosition : new Cartesian2()
  41. };
  42. movement.angleAndHeight = {
  43. startPosition : new Cartesian2(),
  44. endPosition : new Cartesian2()
  45. };
  46. movement.prevAngle = 0.0;
  47. aggregator._eventHandler.setInputAction(function(event) {
  48. aggregator._buttonsDown++;
  49. isDown[key] = true;
  50. pressTime[key] = new Date();
  51. // Compute center position and store as start point.
  52. Cartesian2.lerp(event.position1, event.position2, 0.5, eventStartPosition[key]);
  53. }, ScreenSpaceEventType.PINCH_START, modifier);
  54. aggregator._eventHandler.setInputAction(function() {
  55. aggregator._buttonsDown = Math.max(aggregator._buttonsDown - 1, 0);
  56. isDown[key] = false;
  57. releaseTime[key] = new Date();
  58. }, ScreenSpaceEventType.PINCH_END, modifier);
  59. aggregator._eventHandler.setInputAction(function(mouseMovement) {
  60. if (isDown[key]) {
  61. // Aggregate several input events into a single animation frame.
  62. if (!update[key]) {
  63. Cartesian2.clone(mouseMovement.distance.endPosition, movement.distance.endPosition);
  64. Cartesian2.clone(mouseMovement.angleAndHeight.endPosition, movement.angleAndHeight.endPosition);
  65. } else {
  66. clonePinchMovement(mouseMovement, movement);
  67. update[key] = false;
  68. movement.prevAngle = movement.angleAndHeight.startPosition.x;
  69. }
  70. // Make sure our aggregation of angles does not "flip" over 360 degrees.
  71. var angle = movement.angleAndHeight.endPosition.x;
  72. var prevAngle = movement.prevAngle;
  73. var TwoPI = Math.PI * 2;
  74. while (angle >= (prevAngle + Math.PI)) {
  75. angle -= TwoPI;
  76. }
  77. while (angle < (prevAngle - Math.PI)) {
  78. angle += TwoPI;
  79. }
  80. movement.angleAndHeight.endPosition.x = -angle * canvas.clientWidth / 12;
  81. movement.angleAndHeight.startPosition.x = -prevAngle * canvas.clientWidth / 12;
  82. }
  83. }, ScreenSpaceEventType.PINCH_MOVE, modifier);
  84. }
  85. function listenToWheel(aggregator, modifier) {
  86. var key = getKey(CameraEventType.WHEEL, modifier);
  87. var update = aggregator._update;
  88. update[key] = true;
  89. var movement = aggregator._movement[key];
  90. if (!defined(movement)) {
  91. movement = aggregator._movement[key] = {};
  92. }
  93. movement.startPosition = new Cartesian2();
  94. movement.endPosition = new Cartesian2();
  95. aggregator._eventHandler.setInputAction(function(delta) {
  96. // TODO: magic numbers
  97. var arcLength = 15.0 * CesiumMath.toRadians(delta);
  98. if (!update[key]) {
  99. movement.endPosition.y = movement.endPosition.y + arcLength;
  100. } else {
  101. Cartesian2.clone(Cartesian2.ZERO, movement.startPosition);
  102. movement.endPosition.x = 0.0;
  103. movement.endPosition.y = arcLength;
  104. update[key] = false;
  105. }
  106. }, ScreenSpaceEventType.WHEEL, modifier);
  107. }
  108. function listenMouseButtonDownUp(aggregator, modifier, type) {
  109. var key = getKey(type, modifier);
  110. var isDown = aggregator._isDown;
  111. var eventStartPosition = aggregator._eventStartPosition;
  112. var pressTime = aggregator._pressTime;
  113. var releaseTime = aggregator._releaseTime;
  114. isDown[key] = false;
  115. eventStartPosition[key] = new Cartesian2();
  116. var lastMovement = aggregator._lastMovement[key];
  117. if (!defined(lastMovement)) {
  118. lastMovement = aggregator._lastMovement[key] = {
  119. startPosition : new Cartesian2(),
  120. endPosition : new Cartesian2(),
  121. valid : false
  122. };
  123. }
  124. var down;
  125. var up;
  126. if (type === CameraEventType.LEFT_DRAG) {
  127. down = ScreenSpaceEventType.LEFT_DOWN;
  128. up = ScreenSpaceEventType.LEFT_UP;
  129. } else if (type === CameraEventType.RIGHT_DRAG) {
  130. down = ScreenSpaceEventType.RIGHT_DOWN;
  131. up = ScreenSpaceEventType.RIGHT_UP;
  132. } else if (type === CameraEventType.MIDDLE_DRAG) {
  133. down = ScreenSpaceEventType.MIDDLE_DOWN;
  134. up = ScreenSpaceEventType.MIDDLE_UP;
  135. }
  136. aggregator._eventHandler.setInputAction(function(event) {
  137. aggregator._buttonsDown++;
  138. lastMovement.valid = false;
  139. isDown[key] = true;
  140. pressTime[key] = new Date();
  141. Cartesian2.clone(event.position, eventStartPosition[key]);
  142. }, down, modifier);
  143. aggregator._eventHandler.setInputAction(function() {
  144. aggregator._buttonsDown = Math.max(aggregator._buttonsDown - 1, 0);
  145. isDown[key] = false;
  146. releaseTime[key] = new Date();
  147. }, up, modifier);
  148. }
  149. function cloneMouseMovement(mouseMovement, result) {
  150. Cartesian2.clone(mouseMovement.startPosition, result.startPosition);
  151. Cartesian2.clone(mouseMovement.endPosition, result.endPosition);
  152. }
  153. function listenMouseMove(aggregator, modifier) {
  154. var update = aggregator._update;
  155. var movement = aggregator._movement;
  156. var lastMovement = aggregator._lastMovement;
  157. var isDown = aggregator._isDown;
  158. for ( var typeName in CameraEventType) {
  159. if (CameraEventType.hasOwnProperty(typeName)) {
  160. var type = CameraEventType[typeName];
  161. if (defined(type)) {
  162. var key = getKey(type, modifier);
  163. update[key] = true;
  164. if (!defined(aggregator._lastMovement[key])) {
  165. aggregator._lastMovement[key] = {
  166. startPosition : new Cartesian2(),
  167. endPosition : new Cartesian2(),
  168. valid : false
  169. };
  170. }
  171. if (!defined(aggregator._movement[key])) {
  172. aggregator._movement[key] = {
  173. startPosition : new Cartesian2(),
  174. endPosition : new Cartesian2()
  175. };
  176. }
  177. }
  178. }
  179. }
  180. aggregator._eventHandler.setInputAction(function(mouseMovement) {
  181. for ( var typeName in CameraEventType) {
  182. if (CameraEventType.hasOwnProperty(typeName)) {
  183. var type = CameraEventType[typeName];
  184. if (defined(type)) {
  185. var key = getKey(type, modifier);
  186. if (isDown[key]) {
  187. if (!update[key]) {
  188. Cartesian2.clone(mouseMovement.endPosition, movement[key].endPosition);
  189. } else {
  190. cloneMouseMovement(movement[key], lastMovement[key]);
  191. lastMovement[key].valid = true;
  192. cloneMouseMovement(mouseMovement, movement[key]);
  193. update[key] = false;
  194. }
  195. }
  196. }
  197. }
  198. }
  199. Cartesian2.clone(mouseMovement.endPosition, aggregator._currentMousePosition);
  200. }, ScreenSpaceEventType.MOUSE_MOVE, modifier);
  201. }
  202. /**
  203. * Aggregates input events. For example, suppose the following inputs are received between frames:
  204. * left mouse button down, mouse move, mouse move, left mouse button up. These events will be aggregated into
  205. * one event with a start and end position of the mouse.
  206. *
  207. * @alias CameraEventAggregator
  208. * @constructor
  209. *
  210. * @param {Canvas} [canvas=document] The element to handle events for.
  211. *
  212. * @see ScreenSpaceEventHandler
  213. */
  214. function CameraEventAggregator(canvas) {
  215. //>>includeStart('debug', pragmas.debug);
  216. if (!defined(canvas)) {
  217. throw new DeveloperError('canvas is required.');
  218. }
  219. //>>includeEnd('debug');
  220. this._eventHandler = new ScreenSpaceEventHandler(canvas);
  221. this._update = {};
  222. this._movement = {};
  223. this._lastMovement = {};
  224. this._isDown = {};
  225. this._eventStartPosition = {};
  226. this._pressTime = {};
  227. this._releaseTime = {};
  228. this._buttonsDown = 0;
  229. this._currentMousePosition = new Cartesian2();
  230. listenToWheel(this, undefined);
  231. listenToPinch(this, undefined, canvas);
  232. listenMouseButtonDownUp(this, undefined, CameraEventType.LEFT_DRAG);
  233. listenMouseButtonDownUp(this, undefined, CameraEventType.RIGHT_DRAG);
  234. listenMouseButtonDownUp(this, undefined, CameraEventType.MIDDLE_DRAG);
  235. listenMouseMove(this, undefined);
  236. for ( var modifierName in KeyboardEventModifier) {
  237. if (KeyboardEventModifier.hasOwnProperty(modifierName)) {
  238. var modifier = KeyboardEventModifier[modifierName];
  239. if (defined(modifier)) {
  240. listenToWheel(this, modifier);
  241. listenToPinch(this, modifier, canvas);
  242. listenMouseButtonDownUp(this, modifier, CameraEventType.LEFT_DRAG);
  243. listenMouseButtonDownUp(this, modifier, CameraEventType.RIGHT_DRAG);
  244. listenMouseButtonDownUp(this, modifier, CameraEventType.MIDDLE_DRAG);
  245. listenMouseMove(this, modifier);
  246. }
  247. }
  248. }
  249. }
  250. defineProperties(CameraEventAggregator.prototype, {
  251. /**
  252. * Gets the current mouse position.
  253. * @memberof CameraEventAggregator.prototype
  254. * @type {Cartesian2}
  255. */
  256. currentMousePosition : {
  257. get : function() {
  258. return this._currentMousePosition;
  259. }
  260. },
  261. /**
  262. * Gets whether any mouse button is down, a touch has started, or the wheel has been moved.
  263. * @memberof CameraEventAggregator.prototype
  264. * @type {Boolean}
  265. */
  266. anyButtonDown : {
  267. get : function() {
  268. var wheelMoved = !this._update[getKey(CameraEventType.WHEEL)] ||
  269. !this._update[getKey(CameraEventType.WHEEL, KeyboardEventModifier.SHIFT)] ||
  270. !this._update[getKey(CameraEventType.WHEEL, KeyboardEventModifier.CTRL)] ||
  271. !this._update[getKey(CameraEventType.WHEEL, KeyboardEventModifier.ALT)];
  272. return this._buttonsDown > 0 || wheelMoved;
  273. }
  274. }
  275. });
  276. /**
  277. * Gets if a mouse button down or touch has started and has been moved.
  278. *
  279. * @param {CameraEventType} type The camera event type.
  280. * @param {KeyboardEventModifier} [modifier] The keyboard modifier.
  281. * @returns {Boolean} Returns <code>true</code> if a mouse button down or touch has started and has been moved; otherwise, <code>false</code>
  282. */
  283. CameraEventAggregator.prototype.isMoving = function(type, modifier) {
  284. //>>includeStart('debug', pragmas.debug);
  285. if (!defined(type)) {
  286. throw new DeveloperError('type is required.');
  287. }
  288. //>>includeEnd('debug');
  289. var key = getKey(type, modifier);
  290. return !this._update[key];
  291. };
  292. /**
  293. * Gets the aggregated start and end position of the current event.
  294. *
  295. * @param {CameraEventType} type The camera event type.
  296. * @param {KeyboardEventModifier} [modifier] The keyboard modifier.
  297. * @returns {Object} An object with two {@link Cartesian2} properties: <code>startPosition</code> and <code>endPosition</code>.
  298. */
  299. CameraEventAggregator.prototype.getMovement = function(type, modifier) {
  300. //>>includeStart('debug', pragmas.debug);
  301. if (!defined(type)) {
  302. throw new DeveloperError('type is required.');
  303. }
  304. //>>includeEnd('debug');
  305. var key = getKey(type, modifier);
  306. var movement = this._movement[key];
  307. return movement;
  308. };
  309. /**
  310. * Gets the start and end position of the last move event (not the aggregated event).
  311. *
  312. * @param {CameraEventType} type The camera event type.
  313. * @param {KeyboardEventModifier} [modifier] The keyboard modifier.
  314. * @returns {Object|undefined} An object with two {@link Cartesian2} properties: <code>startPosition</code> and <code>endPosition</code> or <code>undefined</code>.
  315. */
  316. CameraEventAggregator.prototype.getLastMovement = function(type, modifier) {
  317. //>>includeStart('debug', pragmas.debug);
  318. if (!defined(type)) {
  319. throw new DeveloperError('type is required.');
  320. }
  321. //>>includeEnd('debug');
  322. var key = getKey(type, modifier);
  323. var lastMovement = this._lastMovement[key];
  324. if (lastMovement.valid) {
  325. return lastMovement;
  326. }
  327. return undefined;
  328. };
  329. /**
  330. * Gets whether the mouse button is down or a touch has started.
  331. *
  332. * @param {CameraEventType} type The camera event type.
  333. * @param {KeyboardEventModifier} [modifier] The keyboard modifier.
  334. * @returns {Boolean} Whether the mouse button is down or a touch has started.
  335. */
  336. CameraEventAggregator.prototype.isButtonDown = function(type, modifier) {
  337. //>>includeStart('debug', pragmas.debug);
  338. if (!defined(type)) {
  339. throw new DeveloperError('type is required.');
  340. }
  341. //>>includeEnd('debug');
  342. var key = getKey(type, modifier);
  343. return this._isDown[key];
  344. };
  345. /**
  346. * Gets the mouse position that started the aggregation.
  347. *
  348. * @param {CameraEventType} type The camera event type.
  349. * @param {KeyboardEventModifier} [modifier] The keyboard modifier.
  350. * @returns {Cartesian2} The mouse position.
  351. */
  352. CameraEventAggregator.prototype.getStartMousePosition = function(type, modifier) {
  353. //>>includeStart('debug', pragmas.debug);
  354. if (!defined(type)) {
  355. throw new DeveloperError('type is required.');
  356. }
  357. //>>includeEnd('debug');
  358. if (type === CameraEventType.WHEEL) {
  359. return this._currentMousePosition;
  360. }
  361. var key = getKey(type, modifier);
  362. return this._eventStartPosition[key];
  363. };
  364. /**
  365. * Gets the time the button was pressed or the touch was started.
  366. *
  367. * @param {CameraEventType} type The camera event type.
  368. * @param {KeyboardEventModifier} [modifier] The keyboard modifier.
  369. * @returns {Date} The time the button was pressed or the touch was started.
  370. */
  371. CameraEventAggregator.prototype.getButtonPressTime = function(type, modifier) {
  372. //>>includeStart('debug', pragmas.debug);
  373. if (!defined(type)) {
  374. throw new DeveloperError('type is required.');
  375. }
  376. //>>includeEnd('debug');
  377. var key = getKey(type, modifier);
  378. return this._pressTime[key];
  379. };
  380. /**
  381. * Gets the time the button was released or the touch was ended.
  382. *
  383. * @param {CameraEventType} type The camera event type.
  384. * @param {KeyboardEventModifier} [modifier] The keyboard modifier.
  385. * @returns {Date} The time the button was released or the touch was ended.
  386. */
  387. CameraEventAggregator.prototype.getButtonReleaseTime = function(type, modifier) {
  388. //>>includeStart('debug', pragmas.debug);
  389. if (!defined(type)) {
  390. throw new DeveloperError('type is required.');
  391. }
  392. //>>includeEnd('debug');
  393. var key = getKey(type, modifier);
  394. return this._releaseTime[key];
  395. };
  396. /**
  397. * Signals that all of the events have been handled and the aggregator should be reset to handle new events.
  398. */
  399. CameraEventAggregator.prototype.reset = function() {
  400. for ( var name in this._update) {
  401. if (this._update.hasOwnProperty(name)) {
  402. this._update[name] = true;
  403. }
  404. }
  405. };
  406. /**
  407. * Returns true if this object was destroyed; otherwise, false.
  408. * <br /><br />
  409. * If this object was destroyed, it should not be used; calling any function other than
  410. * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
  411. *
  412. * @returns {Boolean} <code>true</code> if this object was destroyed; otherwise, <code>false</code>.
  413. *
  414. * @see CameraEventAggregator#destroy
  415. */
  416. CameraEventAggregator.prototype.isDestroyed = function() {
  417. return false;
  418. };
  419. /**
  420. * Removes mouse listeners held by this object.
  421. * <br /><br />
  422. * Once an object is destroyed, it should not be used; calling any function other than
  423. * <code>isDestroyed</code> will result in a {@link DeveloperError} exception. Therefore,
  424. * assign the return value (<code>undefined</code>) to the object as done in the example.
  425. *
  426. * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
  427. *
  428. *
  429. * @example
  430. * handler = handler && handler.destroy();
  431. *
  432. * @see CameraEventAggregator#isDestroyed
  433. */
  434. CameraEventAggregator.prototype.destroy = function() {
  435. this._eventHandler = this._eventHandler && this._eventHandler.destroy();
  436. return destroyObject(this);
  437. };
  438. export default CameraEventAggregator;