ScreenSpaceEventHandler.js 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140
  1. import AssociativeArray from "./AssociativeArray.js";
  2. import Cartesian2 from "./Cartesian2.js";
  3. import defaultValue from "./defaultValue.js";
  4. import defined from "./defined.js";
  5. import destroyObject from "./destroyObject.js";
  6. import DeveloperError from "./DeveloperError.js";
  7. import FeatureDetection from "./FeatureDetection.js";
  8. import getTimestamp from "./getTimestamp.js";
  9. import KeyboardEventModifier from "./KeyboardEventModifier.js";
  10. import ScreenSpaceEventType from "./ScreenSpaceEventType.js";
  11. function getPosition(screenSpaceEventHandler, event, result) {
  12. const element = screenSpaceEventHandler._element;
  13. if (element === document) {
  14. result.x = event.clientX;
  15. result.y = event.clientY;
  16. return result;
  17. }
  18. const rect = element.getBoundingClientRect();
  19. result.x = event.clientX - rect.left;
  20. result.y = event.clientY - rect.top;
  21. return result;
  22. }
  23. function getInputEventKey(type, modifier) {
  24. let key = type;
  25. if (defined(modifier)) {
  26. key += `+${modifier}`;
  27. }
  28. return key;
  29. }
  30. function getModifier(event) {
  31. if (event.shiftKey) {
  32. return KeyboardEventModifier.SHIFT;
  33. } else if (event.ctrlKey) {
  34. return KeyboardEventModifier.CTRL;
  35. } else if (event.altKey) {
  36. return KeyboardEventModifier.ALT;
  37. }
  38. return undefined;
  39. }
  40. const MouseButton = {
  41. LEFT: 0,
  42. MIDDLE: 1,
  43. RIGHT: 2,
  44. };
  45. function registerListener(screenSpaceEventHandler, domType, element, callback) {
  46. function listener(e) {
  47. callback(screenSpaceEventHandler, e);
  48. }
  49. if (FeatureDetection.isInternetExplorer()) {
  50. element.addEventListener(domType, listener, false);
  51. } else {
  52. element.addEventListener(domType, listener, {
  53. capture: false,
  54. passive: false,
  55. });
  56. }
  57. screenSpaceEventHandler._removalFunctions.push(function () {
  58. element.removeEventListener(domType, listener, false);
  59. });
  60. }
  61. function registerListeners(screenSpaceEventHandler) {
  62. const element = screenSpaceEventHandler._element;
  63. // some listeners may be registered on the document, so we still get events even after
  64. // leaving the bounds of element.
  65. // this is affected by the existence of an undocumented disableRootEvents property on element.
  66. const alternateElement = !defined(element.disableRootEvents)
  67. ? document
  68. : element;
  69. if (FeatureDetection.supportsPointerEvents()) {
  70. registerListener(
  71. screenSpaceEventHandler,
  72. "pointerdown",
  73. element,
  74. handlePointerDown
  75. );
  76. registerListener(
  77. screenSpaceEventHandler,
  78. "pointerup",
  79. element,
  80. handlePointerUp
  81. );
  82. registerListener(
  83. screenSpaceEventHandler,
  84. "pointermove",
  85. element,
  86. handlePointerMove
  87. );
  88. registerListener(
  89. screenSpaceEventHandler,
  90. "pointercancel",
  91. element,
  92. handlePointerUp
  93. );
  94. } else {
  95. registerListener(
  96. screenSpaceEventHandler,
  97. "mousedown",
  98. element,
  99. handleMouseDown
  100. );
  101. registerListener(
  102. screenSpaceEventHandler,
  103. "mouseup",
  104. alternateElement,
  105. handleMouseUp
  106. );
  107. registerListener(
  108. screenSpaceEventHandler,
  109. "mousemove",
  110. alternateElement,
  111. handleMouseMove
  112. );
  113. registerListener(
  114. screenSpaceEventHandler,
  115. "touchstart",
  116. element,
  117. handleTouchStart
  118. );
  119. registerListener(
  120. screenSpaceEventHandler,
  121. "touchend",
  122. alternateElement,
  123. handleTouchEnd
  124. );
  125. registerListener(
  126. screenSpaceEventHandler,
  127. "touchmove",
  128. alternateElement,
  129. handleTouchMove
  130. );
  131. registerListener(
  132. screenSpaceEventHandler,
  133. "touchcancel",
  134. alternateElement,
  135. handleTouchEnd
  136. );
  137. }
  138. registerListener(
  139. screenSpaceEventHandler,
  140. "dblclick",
  141. element,
  142. handleDblClick
  143. );
  144. // detect available wheel event
  145. let wheelEvent;
  146. if ("onwheel" in element) {
  147. // spec event type
  148. wheelEvent = "wheel";
  149. } else if (document.onmousewheel !== undefined) {
  150. // legacy event type
  151. wheelEvent = "mousewheel";
  152. } else {
  153. // older Firefox
  154. wheelEvent = "DOMMouseScroll";
  155. }
  156. registerListener(screenSpaceEventHandler, wheelEvent, element, handleWheel);
  157. }
  158. function unregisterListeners(screenSpaceEventHandler) {
  159. const removalFunctions = screenSpaceEventHandler._removalFunctions;
  160. for (let i = 0; i < removalFunctions.length; ++i) {
  161. removalFunctions[i]();
  162. }
  163. }
  164. const mouseDownEvent = {
  165. position: new Cartesian2(),
  166. };
  167. function gotTouchEvent(screenSpaceEventHandler) {
  168. screenSpaceEventHandler._lastSeenTouchEvent = getTimestamp();
  169. }
  170. function canProcessMouseEvent(screenSpaceEventHandler) {
  171. return (
  172. getTimestamp() - screenSpaceEventHandler._lastSeenTouchEvent >
  173. ScreenSpaceEventHandler.mouseEmulationIgnoreMilliseconds
  174. );
  175. }
  176. function checkPixelTolerance(startPosition, endPosition, pixelTolerance) {
  177. const xDiff = startPosition.x - endPosition.x;
  178. const yDiff = startPosition.y - endPosition.y;
  179. const totalPixels = Math.sqrt(xDiff * xDiff + yDiff * yDiff);
  180. return totalPixels < pixelTolerance;
  181. }
  182. function handleMouseDown(screenSpaceEventHandler, event) {
  183. if (!canProcessMouseEvent(screenSpaceEventHandler)) {
  184. return;
  185. }
  186. const button = event.button;
  187. screenSpaceEventHandler._buttonDown[button] = true;
  188. let screenSpaceEventType;
  189. if (button === MouseButton.LEFT) {
  190. screenSpaceEventType = ScreenSpaceEventType.LEFT_DOWN;
  191. } else if (button === MouseButton.MIDDLE) {
  192. screenSpaceEventType = ScreenSpaceEventType.MIDDLE_DOWN;
  193. } else if (button === MouseButton.RIGHT) {
  194. screenSpaceEventType = ScreenSpaceEventType.RIGHT_DOWN;
  195. } else {
  196. return;
  197. }
  198. const position = getPosition(
  199. screenSpaceEventHandler,
  200. event,
  201. screenSpaceEventHandler._primaryPosition
  202. );
  203. Cartesian2.clone(position, screenSpaceEventHandler._primaryStartPosition);
  204. Cartesian2.clone(position, screenSpaceEventHandler._primaryPreviousPosition);
  205. const modifier = getModifier(event);
  206. const action = screenSpaceEventHandler.getInputAction(
  207. screenSpaceEventType,
  208. modifier
  209. );
  210. if (defined(action)) {
  211. Cartesian2.clone(position, mouseDownEvent.position);
  212. action(mouseDownEvent);
  213. event.preventDefault();
  214. }
  215. }
  216. const mouseUpEvent = {
  217. position: new Cartesian2(),
  218. };
  219. const mouseClickEvent = {
  220. position: new Cartesian2(),
  221. };
  222. function cancelMouseEvent(
  223. screenSpaceEventHandler,
  224. screenSpaceEventType,
  225. clickScreenSpaceEventType,
  226. event
  227. ) {
  228. const modifier = getModifier(event);
  229. const action = screenSpaceEventHandler.getInputAction(
  230. screenSpaceEventType,
  231. modifier
  232. );
  233. const clickAction = screenSpaceEventHandler.getInputAction(
  234. clickScreenSpaceEventType,
  235. modifier
  236. );
  237. if (defined(action) || defined(clickAction)) {
  238. const position = getPosition(
  239. screenSpaceEventHandler,
  240. event,
  241. screenSpaceEventHandler._primaryPosition
  242. );
  243. if (defined(action)) {
  244. Cartesian2.clone(position, mouseUpEvent.position);
  245. action(mouseUpEvent);
  246. }
  247. if (defined(clickAction)) {
  248. const startPosition = screenSpaceEventHandler._primaryStartPosition;
  249. if (
  250. checkPixelTolerance(
  251. startPosition,
  252. position,
  253. screenSpaceEventHandler._clickPixelTolerance
  254. )
  255. ) {
  256. Cartesian2.clone(position, mouseClickEvent.position);
  257. clickAction(mouseClickEvent);
  258. }
  259. }
  260. }
  261. }
  262. function handleMouseUp(screenSpaceEventHandler, event) {
  263. if (!canProcessMouseEvent(screenSpaceEventHandler)) {
  264. return;
  265. }
  266. const button = event.button;
  267. if (
  268. button !== MouseButton.LEFT &&
  269. button !== MouseButton.MIDDLE &&
  270. button !== MouseButton.RIGHT
  271. ) {
  272. return;
  273. }
  274. if (screenSpaceEventHandler._buttonDown[MouseButton.LEFT]) {
  275. cancelMouseEvent(
  276. screenSpaceEventHandler,
  277. ScreenSpaceEventType.LEFT_UP,
  278. ScreenSpaceEventType.LEFT_CLICK,
  279. event
  280. );
  281. screenSpaceEventHandler._buttonDown[MouseButton.LEFT] = false;
  282. }
  283. if (screenSpaceEventHandler._buttonDown[MouseButton.MIDDLE]) {
  284. cancelMouseEvent(
  285. screenSpaceEventHandler,
  286. ScreenSpaceEventType.MIDDLE_UP,
  287. ScreenSpaceEventType.MIDDLE_CLICK,
  288. event
  289. );
  290. screenSpaceEventHandler._buttonDown[MouseButton.MIDDLE] = false;
  291. }
  292. if (screenSpaceEventHandler._buttonDown[MouseButton.RIGHT]) {
  293. cancelMouseEvent(
  294. screenSpaceEventHandler,
  295. ScreenSpaceEventType.RIGHT_UP,
  296. ScreenSpaceEventType.RIGHT_CLICK,
  297. event
  298. );
  299. screenSpaceEventHandler._buttonDown[MouseButton.RIGHT] = false;
  300. }
  301. }
  302. const mouseMoveEvent = {
  303. startPosition: new Cartesian2(),
  304. endPosition: new Cartesian2(),
  305. };
  306. function handleMouseMove(screenSpaceEventHandler, event) {
  307. if (!canProcessMouseEvent(screenSpaceEventHandler)) {
  308. return;
  309. }
  310. const modifier = getModifier(event);
  311. const position = getPosition(
  312. screenSpaceEventHandler,
  313. event,
  314. screenSpaceEventHandler._primaryPosition
  315. );
  316. const previousPosition = screenSpaceEventHandler._primaryPreviousPosition;
  317. const action = screenSpaceEventHandler.getInputAction(
  318. ScreenSpaceEventType.MOUSE_MOVE,
  319. modifier
  320. );
  321. if (defined(action)) {
  322. Cartesian2.clone(previousPosition, mouseMoveEvent.startPosition);
  323. Cartesian2.clone(position, mouseMoveEvent.endPosition);
  324. action(mouseMoveEvent);
  325. }
  326. Cartesian2.clone(position, previousPosition);
  327. if (
  328. screenSpaceEventHandler._buttonDown[MouseButton.LEFT] ||
  329. screenSpaceEventHandler._buttonDown[MouseButton.MIDDLE] ||
  330. screenSpaceEventHandler._buttonDown[MouseButton.RIGHT]
  331. ) {
  332. event.preventDefault();
  333. }
  334. }
  335. const mouseDblClickEvent = {
  336. position: new Cartesian2(),
  337. };
  338. function handleDblClick(screenSpaceEventHandler, event) {
  339. const button = event.button;
  340. let screenSpaceEventType;
  341. if (button === MouseButton.LEFT) {
  342. screenSpaceEventType = ScreenSpaceEventType.LEFT_DOUBLE_CLICK;
  343. } else {
  344. return;
  345. }
  346. const modifier = getModifier(event);
  347. const action = screenSpaceEventHandler.getInputAction(
  348. screenSpaceEventType,
  349. modifier
  350. );
  351. if (defined(action)) {
  352. getPosition(screenSpaceEventHandler, event, mouseDblClickEvent.position);
  353. action(mouseDblClickEvent);
  354. }
  355. }
  356. function handleWheel(screenSpaceEventHandler, event) {
  357. // currently this event exposes the delta value in terms of
  358. // the obsolete mousewheel event type. so, for now, we adapt the other
  359. // values to that scheme.
  360. let delta;
  361. // standard wheel event uses deltaY. sign is opposite wheelDelta.
  362. // deltaMode indicates what unit it is in.
  363. if (defined(event.deltaY)) {
  364. const deltaMode = event.deltaMode;
  365. if (deltaMode === event.DOM_DELTA_PIXEL) {
  366. delta = -event.deltaY;
  367. } else if (deltaMode === event.DOM_DELTA_LINE) {
  368. delta = -event.deltaY * 40;
  369. } else {
  370. // DOM_DELTA_PAGE
  371. delta = -event.deltaY * 120;
  372. }
  373. } else if (event.detail > 0) {
  374. // old Firefox versions use event.detail to count the number of clicks. The sign
  375. // of the integer is the direction the wheel is scrolled.
  376. delta = event.detail * -120;
  377. } else {
  378. delta = event.wheelDelta;
  379. }
  380. if (!defined(delta)) {
  381. return;
  382. }
  383. const modifier = getModifier(event);
  384. const action = screenSpaceEventHandler.getInputAction(
  385. ScreenSpaceEventType.WHEEL,
  386. modifier
  387. );
  388. if (defined(action)) {
  389. action(delta);
  390. event.preventDefault();
  391. }
  392. }
  393. function handleTouchStart(screenSpaceEventHandler, event) {
  394. gotTouchEvent(screenSpaceEventHandler);
  395. const changedTouches = event.changedTouches;
  396. let i;
  397. const length = changedTouches.length;
  398. let touch;
  399. let identifier;
  400. const positions = screenSpaceEventHandler._positions;
  401. for (i = 0; i < length; ++i) {
  402. touch = changedTouches[i];
  403. identifier = touch.identifier;
  404. positions.set(
  405. identifier,
  406. getPosition(screenSpaceEventHandler, touch, new Cartesian2())
  407. );
  408. }
  409. fireTouchEvents(screenSpaceEventHandler, event);
  410. const previousPositions = screenSpaceEventHandler._previousPositions;
  411. for (i = 0; i < length; ++i) {
  412. touch = changedTouches[i];
  413. identifier = touch.identifier;
  414. previousPositions.set(
  415. identifier,
  416. Cartesian2.clone(positions.get(identifier))
  417. );
  418. }
  419. }
  420. function handleTouchEnd(screenSpaceEventHandler, event) {
  421. gotTouchEvent(screenSpaceEventHandler);
  422. const changedTouches = event.changedTouches;
  423. let i;
  424. const length = changedTouches.length;
  425. let touch;
  426. let identifier;
  427. const positions = screenSpaceEventHandler._positions;
  428. for (i = 0; i < length; ++i) {
  429. touch = changedTouches[i];
  430. identifier = touch.identifier;
  431. positions.remove(identifier);
  432. }
  433. fireTouchEvents(screenSpaceEventHandler, event);
  434. const previousPositions = screenSpaceEventHandler._previousPositions;
  435. for (i = 0; i < length; ++i) {
  436. touch = changedTouches[i];
  437. identifier = touch.identifier;
  438. previousPositions.remove(identifier);
  439. }
  440. }
  441. const touchStartEvent = {
  442. position: new Cartesian2(),
  443. };
  444. const touch2StartEvent = {
  445. position1: new Cartesian2(),
  446. position2: new Cartesian2(),
  447. };
  448. const touchEndEvent = {
  449. position: new Cartesian2(),
  450. };
  451. const touchClickEvent = {
  452. position: new Cartesian2(),
  453. };
  454. const touchHoldEvent = {
  455. position: new Cartesian2(),
  456. };
  457. function fireTouchEvents(screenSpaceEventHandler, event) {
  458. const modifier = getModifier(event);
  459. const positions = screenSpaceEventHandler._positions;
  460. const numberOfTouches = positions.length;
  461. let action;
  462. let clickAction;
  463. const pinching = screenSpaceEventHandler._isPinching;
  464. if (
  465. numberOfTouches !== 1 &&
  466. screenSpaceEventHandler._buttonDown[MouseButton.LEFT]
  467. ) {
  468. // transitioning from single touch, trigger UP and might trigger CLICK
  469. screenSpaceEventHandler._buttonDown[MouseButton.LEFT] = false;
  470. if (defined(screenSpaceEventHandler._touchHoldTimer)) {
  471. clearTimeout(screenSpaceEventHandler._touchHoldTimer);
  472. screenSpaceEventHandler._touchHoldTimer = undefined;
  473. }
  474. action = screenSpaceEventHandler.getInputAction(
  475. ScreenSpaceEventType.LEFT_UP,
  476. modifier
  477. );
  478. if (defined(action)) {
  479. Cartesian2.clone(
  480. screenSpaceEventHandler._primaryPosition,
  481. touchEndEvent.position
  482. );
  483. action(touchEndEvent);
  484. }
  485. if (numberOfTouches === 0 && !screenSpaceEventHandler._isTouchHolding) {
  486. // releasing single touch, check for CLICK
  487. clickAction = screenSpaceEventHandler.getInputAction(
  488. ScreenSpaceEventType.LEFT_CLICK,
  489. modifier
  490. );
  491. if (defined(clickAction)) {
  492. const startPosition = screenSpaceEventHandler._primaryStartPosition;
  493. const endPosition =
  494. screenSpaceEventHandler._previousPositions.values[0];
  495. if (
  496. checkPixelTolerance(
  497. startPosition,
  498. endPosition,
  499. screenSpaceEventHandler._clickPixelTolerance
  500. )
  501. ) {
  502. Cartesian2.clone(
  503. screenSpaceEventHandler._primaryPosition,
  504. touchClickEvent.position
  505. );
  506. clickAction(touchClickEvent);
  507. }
  508. }
  509. }
  510. screenSpaceEventHandler._isTouchHolding = false;
  511. // Otherwise don't trigger CLICK, because we are adding more touches.
  512. }
  513. if (numberOfTouches === 0 && pinching) {
  514. // transitioning from pinch, trigger PINCH_END
  515. screenSpaceEventHandler._isPinching = false;
  516. action = screenSpaceEventHandler.getInputAction(
  517. ScreenSpaceEventType.PINCH_END,
  518. modifier
  519. );
  520. if (defined(action)) {
  521. action();
  522. }
  523. }
  524. if (numberOfTouches === 1 && !pinching) {
  525. // transitioning to single touch, trigger DOWN
  526. const position = positions.values[0];
  527. Cartesian2.clone(position, screenSpaceEventHandler._primaryPosition);
  528. Cartesian2.clone(position, screenSpaceEventHandler._primaryStartPosition);
  529. Cartesian2.clone(
  530. position,
  531. screenSpaceEventHandler._primaryPreviousPosition
  532. );
  533. screenSpaceEventHandler._buttonDown[MouseButton.LEFT] = true;
  534. action = screenSpaceEventHandler.getInputAction(
  535. ScreenSpaceEventType.LEFT_DOWN,
  536. modifier
  537. );
  538. if (defined(action)) {
  539. Cartesian2.clone(position, touchStartEvent.position);
  540. action(touchStartEvent);
  541. }
  542. screenSpaceEventHandler._touchHoldTimer = setTimeout(function () {
  543. if (!screenSpaceEventHandler.isDestroyed()) {
  544. screenSpaceEventHandler._touchHoldTimer = undefined;
  545. screenSpaceEventHandler._isTouchHolding = true;
  546. clickAction = screenSpaceEventHandler.getInputAction(
  547. ScreenSpaceEventType.RIGHT_CLICK,
  548. modifier
  549. );
  550. if (defined(clickAction)) {
  551. const startPosition = screenSpaceEventHandler._primaryStartPosition;
  552. const endPosition =
  553. screenSpaceEventHandler._previousPositions.values[0];
  554. if (
  555. checkPixelTolerance(
  556. startPosition,
  557. endPosition,
  558. screenSpaceEventHandler._holdPixelTolerance
  559. )
  560. ) {
  561. Cartesian2.clone(
  562. screenSpaceEventHandler._primaryPosition,
  563. touchHoldEvent.position
  564. );
  565. clickAction(touchHoldEvent);
  566. }
  567. }
  568. }
  569. }, ScreenSpaceEventHandler.touchHoldDelayMilliseconds);
  570. event.preventDefault();
  571. }
  572. if (numberOfTouches === 2 && !pinching) {
  573. // transitioning to pinch, trigger PINCH_START
  574. screenSpaceEventHandler._isPinching = true;
  575. action = screenSpaceEventHandler.getInputAction(
  576. ScreenSpaceEventType.PINCH_START,
  577. modifier
  578. );
  579. if (defined(action)) {
  580. Cartesian2.clone(positions.values[0], touch2StartEvent.position1);
  581. Cartesian2.clone(positions.values[1], touch2StartEvent.position2);
  582. action(touch2StartEvent);
  583. // Touch-enabled devices, in particular iOS can have many default behaviours for
  584. // "pinch" events, which can still be executed unless we prevent them here.
  585. event.preventDefault();
  586. }
  587. }
  588. }
  589. function handleTouchMove(screenSpaceEventHandler, event) {
  590. gotTouchEvent(screenSpaceEventHandler);
  591. const changedTouches = event.changedTouches;
  592. let i;
  593. const length = changedTouches.length;
  594. let touch;
  595. let identifier;
  596. const positions = screenSpaceEventHandler._positions;
  597. for (i = 0; i < length; ++i) {
  598. touch = changedTouches[i];
  599. identifier = touch.identifier;
  600. const position = positions.get(identifier);
  601. if (defined(position)) {
  602. getPosition(screenSpaceEventHandler, touch, position);
  603. }
  604. }
  605. fireTouchMoveEvents(screenSpaceEventHandler, event);
  606. const previousPositions = screenSpaceEventHandler._previousPositions;
  607. for (i = 0; i < length; ++i) {
  608. touch = changedTouches[i];
  609. identifier = touch.identifier;
  610. Cartesian2.clone(
  611. positions.get(identifier),
  612. previousPositions.get(identifier)
  613. );
  614. }
  615. }
  616. const touchMoveEvent = {
  617. startPosition: new Cartesian2(),
  618. endPosition: new Cartesian2(),
  619. };
  620. const touchPinchMovementEvent = {
  621. distance: {
  622. startPosition: new Cartesian2(),
  623. endPosition: new Cartesian2(),
  624. },
  625. angleAndHeight: {
  626. startPosition: new Cartesian2(),
  627. endPosition: new Cartesian2(),
  628. },
  629. };
  630. function fireTouchMoveEvents(screenSpaceEventHandler, event) {
  631. const modifier = getModifier(event);
  632. const positions = screenSpaceEventHandler._positions;
  633. const previousPositions = screenSpaceEventHandler._previousPositions;
  634. const numberOfTouches = positions.length;
  635. let action;
  636. if (
  637. numberOfTouches === 1 &&
  638. screenSpaceEventHandler._buttonDown[MouseButton.LEFT]
  639. ) {
  640. // moving single touch
  641. const position = positions.values[0];
  642. Cartesian2.clone(position, screenSpaceEventHandler._primaryPosition);
  643. const previousPosition = screenSpaceEventHandler._primaryPreviousPosition;
  644. action = screenSpaceEventHandler.getInputAction(
  645. ScreenSpaceEventType.MOUSE_MOVE,
  646. modifier
  647. );
  648. if (defined(action)) {
  649. Cartesian2.clone(previousPosition, touchMoveEvent.startPosition);
  650. Cartesian2.clone(position, touchMoveEvent.endPosition);
  651. action(touchMoveEvent);
  652. }
  653. Cartesian2.clone(position, previousPosition);
  654. event.preventDefault();
  655. } else if (numberOfTouches === 2 && screenSpaceEventHandler._isPinching) {
  656. // moving pinch
  657. action = screenSpaceEventHandler.getInputAction(
  658. ScreenSpaceEventType.PINCH_MOVE,
  659. modifier
  660. );
  661. if (defined(action)) {
  662. const position1 = positions.values[0];
  663. const position2 = positions.values[1];
  664. const previousPosition1 = previousPositions.values[0];
  665. const previousPosition2 = previousPositions.values[1];
  666. const dX = position2.x - position1.x;
  667. const dY = position2.y - position1.y;
  668. const dist = Math.sqrt(dX * dX + dY * dY) * 0.25;
  669. const prevDX = previousPosition2.x - previousPosition1.x;
  670. const prevDY = previousPosition2.y - previousPosition1.y;
  671. const prevDist = Math.sqrt(prevDX * prevDX + prevDY * prevDY) * 0.25;
  672. const cY = (position2.y + position1.y) * 0.125;
  673. const prevCY = (previousPosition2.y + previousPosition1.y) * 0.125;
  674. const angle = Math.atan2(dY, dX);
  675. const prevAngle = Math.atan2(prevDY, prevDX);
  676. Cartesian2.fromElements(
  677. 0.0,
  678. prevDist,
  679. touchPinchMovementEvent.distance.startPosition
  680. );
  681. Cartesian2.fromElements(
  682. 0.0,
  683. dist,
  684. touchPinchMovementEvent.distance.endPosition
  685. );
  686. Cartesian2.fromElements(
  687. prevAngle,
  688. prevCY,
  689. touchPinchMovementEvent.angleAndHeight.startPosition
  690. );
  691. Cartesian2.fromElements(
  692. angle,
  693. cY,
  694. touchPinchMovementEvent.angleAndHeight.endPosition
  695. );
  696. action(touchPinchMovementEvent);
  697. }
  698. }
  699. }
  700. function handlePointerDown(screenSpaceEventHandler, event) {
  701. event.target.setPointerCapture(event.pointerId);
  702. if (event.pointerType === "touch") {
  703. const positions = screenSpaceEventHandler._positions;
  704. const identifier = event.pointerId;
  705. positions.set(
  706. identifier,
  707. getPosition(screenSpaceEventHandler, event, new Cartesian2())
  708. );
  709. fireTouchEvents(screenSpaceEventHandler, event);
  710. const previousPositions = screenSpaceEventHandler._previousPositions;
  711. previousPositions.set(
  712. identifier,
  713. Cartesian2.clone(positions.get(identifier))
  714. );
  715. } else {
  716. handleMouseDown(screenSpaceEventHandler, event);
  717. }
  718. }
  719. function handlePointerUp(screenSpaceEventHandler, event) {
  720. if (event.pointerType === "touch") {
  721. const positions = screenSpaceEventHandler._positions;
  722. const identifier = event.pointerId;
  723. positions.remove(identifier);
  724. fireTouchEvents(screenSpaceEventHandler, event);
  725. const previousPositions = screenSpaceEventHandler._previousPositions;
  726. previousPositions.remove(identifier);
  727. } else {
  728. handleMouseUp(screenSpaceEventHandler, event);
  729. }
  730. }
  731. function handlePointerMove(screenSpaceEventHandler, event) {
  732. if (event.pointerType === "touch") {
  733. const positions = screenSpaceEventHandler._positions;
  734. const identifier = event.pointerId;
  735. const position = positions.get(identifier);
  736. if (!defined(position)) {
  737. return;
  738. }
  739. getPosition(screenSpaceEventHandler, event, position);
  740. fireTouchMoveEvents(screenSpaceEventHandler, event);
  741. const previousPositions = screenSpaceEventHandler._previousPositions;
  742. Cartesian2.clone(
  743. positions.get(identifier),
  744. previousPositions.get(identifier)
  745. );
  746. } else {
  747. handleMouseMove(screenSpaceEventHandler, event);
  748. }
  749. }
  750. /**
  751. * @typedef {object} ScreenSpaceEventHandler.PositionedEvent
  752. *
  753. * An Event that occurs at a single position on screen.
  754. *
  755. * @property {Cartesian2} position
  756. */
  757. /**
  758. * @callback ScreenSpaceEventHandler.PositionedEventCallback
  759. *
  760. * The callback invoked when a positioned event triggers an event listener.
  761. *
  762. * @param {ScreenSpaceEventHandler.PositionedEvent} event The event which triggered the listener
  763. */
  764. /**
  765. * @typedef {object} ScreenSpaceEventHandler.MotionEvent
  766. *
  767. * An Event that starts at one position and ends at another.
  768. *
  769. * @property {Cartesian2} startPosition
  770. * @property {Cartesian2} endPosition
  771. */
  772. /**
  773. * @callback ScreenSpaceEventHandler.MotionEventCallback
  774. *
  775. * The callback invoked when a motion event triggers an event listener.
  776. *
  777. * @param {ScreenSpaceEventHandler.MotionEvent} event The event which triggered the listener
  778. */
  779. /**
  780. * @typedef {object} ScreenSpaceEventHandler.TwoPointEvent
  781. *
  782. * An Event that occurs at a two positions on screen.
  783. *
  784. * @property {Cartesian2} position1
  785. * @property {Cartesian2} position2
  786. */
  787. /**
  788. * @callback ScreenSpaceEventHandler.TwoPointEventCallback
  789. *
  790. * The callback invoked when a two-point event triggers an event listener.
  791. *
  792. * @param {ScreenSpaceEventHandler.TwoPointEvent} event The event which triggered the listener
  793. */
  794. /**
  795. * @typedef {object} ScreenSpaceEventHandler.TwoPointMotionEvent
  796. *
  797. * An Event that starts at a two positions on screen and moves to two other positions.
  798. *
  799. * @property {Cartesian2} position1
  800. * @property {Cartesian2} position2
  801. * @property {Cartesian2} previousPosition1
  802. * @property {Cartesian2} previousPosition2
  803. */
  804. /**
  805. * @callback ScreenSpaceEventHandler.TwoPointMotionEventCallback
  806. *
  807. * The callback invoked when a two-point motion event triggers an event listener.
  808. *
  809. * @param {ScreenSpaceEventHandler.TwoPointMotionEvent} event The event which triggered the listener
  810. */
  811. /**
  812. * @callback ScreenSpaceEventHandler.WheelEventCallback
  813. *
  814. * The callback invoked when a mouse-wheel event triggers an event listener.
  815. *
  816. * @param {number} delta The amount that the mouse wheel moved
  817. */
  818. /**
  819. * Handles user input events. Custom functions can be added to be executed on
  820. * when the user enters input.
  821. *
  822. * @alias ScreenSpaceEventHandler
  823. *
  824. * @param {HTMLCanvasElement} [element=document] The element to add events to.
  825. *
  826. * @constructor
  827. */
  828. function ScreenSpaceEventHandler(element) {
  829. this._inputEvents = {};
  830. this._buttonDown = {
  831. LEFT: false,
  832. MIDDLE: false,
  833. RIGHT: false,
  834. };
  835. this._isPinching = false;
  836. this._isTouchHolding = false;
  837. this._lastSeenTouchEvent = -ScreenSpaceEventHandler.mouseEmulationIgnoreMilliseconds;
  838. this._primaryStartPosition = new Cartesian2();
  839. this._primaryPosition = new Cartesian2();
  840. this._primaryPreviousPosition = new Cartesian2();
  841. this._positions = new AssociativeArray();
  842. this._previousPositions = new AssociativeArray();
  843. this._removalFunctions = [];
  844. this._touchHoldTimer = undefined;
  845. // TODO: Revisit when doing mobile development. May need to be configurable
  846. // or determined based on the platform?
  847. this._clickPixelTolerance = 5;
  848. this._holdPixelTolerance = 25;
  849. this._element = defaultValue(element, document);
  850. registerListeners(this);
  851. }
  852. /**
  853. * Set a function to be executed on an input event.
  854. *
  855. * @param {ScreenSpaceEventHandler.PositionedEventCallback|ScreenSpaceEventHandler.MotionEventCallback|ScreenSpaceEventHandler.WheelEventCallback|ScreenSpaceEventHandler.TwoPointEventCallback|ScreenSpaceEventHandler.TwoPointMotionEventCallback} action Function to be executed when the input event occurs.
  856. * @param {ScreenSpaceEventType} type The ScreenSpaceEventType of input event.
  857. * @param {KeyboardEventModifier} [modifier] A KeyboardEventModifier key that is held when a <code>type</code>
  858. * event occurs.
  859. *
  860. * @see ScreenSpaceEventHandler#getInputAction
  861. * @see ScreenSpaceEventHandler#removeInputAction
  862. */
  863. ScreenSpaceEventHandler.prototype.setInputAction = function (
  864. action,
  865. type,
  866. modifier
  867. ) {
  868. //>>includeStart('debug', pragmas.debug);
  869. if (!defined(action)) {
  870. throw new DeveloperError("action is required.");
  871. }
  872. if (!defined(type)) {
  873. throw new DeveloperError("type is required.");
  874. }
  875. //>>includeEnd('debug');
  876. const key = getInputEventKey(type, modifier);
  877. this._inputEvents[key] = action;
  878. };
  879. /**
  880. * Returns the function to be executed on an input event.
  881. *
  882. * @param {ScreenSpaceEventType} type The ScreenSpaceEventType of input event.
  883. * @param {KeyboardEventModifier} [modifier] A KeyboardEventModifier key that is held when a <code>type</code>
  884. * event occurs.
  885. *
  886. * @returns {ScreenSpaceEventHandler.PositionedEventCallback|ScreenSpaceEventHandler.MotionEventCallback|ScreenSpaceEventHandler.WheelEventCallback|ScreenSpaceEventHandler.TwoPointEventCallback|ScreenSpaceEventHandler.TwoPointMotionEventCallback} The function to be executed on an input event.
  887. *
  888. * @see ScreenSpaceEventHandler#setInputAction
  889. * @see ScreenSpaceEventHandler#removeInputAction
  890. */
  891. ScreenSpaceEventHandler.prototype.getInputAction = function (type, modifier) {
  892. //>>includeStart('debug', pragmas.debug);
  893. if (!defined(type)) {
  894. throw new DeveloperError("type is required.");
  895. }
  896. //>>includeEnd('debug');
  897. const key = getInputEventKey(type, modifier);
  898. return this._inputEvents[key];
  899. };
  900. /**
  901. * Removes the function to be executed on an input event.
  902. *
  903. * @param {ScreenSpaceEventType} type The ScreenSpaceEventType of input event.
  904. * @param {KeyboardEventModifier} [modifier] A KeyboardEventModifier key that is held when a <code>type</code>
  905. * event occurs.
  906. *
  907. * @see ScreenSpaceEventHandler#getInputAction
  908. * @see ScreenSpaceEventHandler#setInputAction
  909. */
  910. ScreenSpaceEventHandler.prototype.removeInputAction = function (
  911. type,
  912. modifier
  913. ) {
  914. //>>includeStart('debug', pragmas.debug);
  915. if (!defined(type)) {
  916. throw new DeveloperError("type is required.");
  917. }
  918. //>>includeEnd('debug');
  919. const key = getInputEventKey(type, modifier);
  920. delete this._inputEvents[key];
  921. };
  922. /**
  923. * Returns true if this object was destroyed; otherwise, false.
  924. * <br /><br />
  925. * If this object was destroyed, it should not be used; calling any function other than
  926. * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
  927. *
  928. * @returns {boolean} <code>true</code> if this object was destroyed; otherwise, <code>false</code>.
  929. *
  930. * @see ScreenSpaceEventHandler#destroy
  931. */
  932. ScreenSpaceEventHandler.prototype.isDestroyed = function () {
  933. return false;
  934. };
  935. /**
  936. * Removes listeners held by this object.
  937. * <br /><br />
  938. * Once an object is destroyed, it should not be used; calling any function other than
  939. * <code>isDestroyed</code> will result in a {@link DeveloperError} exception. Therefore,
  940. * assign the return value (<code>undefined</code>) to the object as done in the example.
  941. *
  942. * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
  943. *
  944. *
  945. * @example
  946. * handler = handler && handler.destroy();
  947. *
  948. * @see ScreenSpaceEventHandler#isDestroyed
  949. */
  950. ScreenSpaceEventHandler.prototype.destroy = function () {
  951. unregisterListeners(this);
  952. return destroyObject(this);
  953. };
  954. /**
  955. * The amount of time, in milliseconds, that mouse events will be disabled after
  956. * receiving any touch events, such that any emulated mouse events will be ignored.
  957. * @type {number}
  958. * @default 800
  959. */
  960. ScreenSpaceEventHandler.mouseEmulationIgnoreMilliseconds = 800;
  961. /**
  962. * The amount of time, in milliseconds, before a touch on the screen becomes a
  963. * touch and hold.
  964. * @type {number}
  965. * @default 1500
  966. */
  967. ScreenSpaceEventHandler.touchHoldDelayMilliseconds = 1500;
  968. export default ScreenSpaceEventHandler;