TouchInteractionEvents.js 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. import {
  2. forEach
  3. } from 'min-dash';
  4. import {
  5. event as domEvent,
  6. closest as domClosest
  7. } from 'min-dom';
  8. import Hammer from 'hammerjs';
  9. import {
  10. toPoint
  11. } from '../../util/Event';
  12. var MIN_ZOOM = 0.2,
  13. MAX_ZOOM = 4;
  14. var mouseEvents = [
  15. 'mousedown',
  16. 'mouseup',
  17. 'mouseover',
  18. 'mouseout',
  19. 'click',
  20. 'dblclick'
  21. ];
  22. function log() {
  23. // console.log.apply(console, arguments);
  24. }
  25. function get(service, injector) {
  26. return injector.get(service, false);
  27. }
  28. function stopEvent(event) {
  29. event.preventDefault();
  30. event.stopPropagation();
  31. if (typeof event.stopImmediatePropagation === 'function') {
  32. event.stopImmediatePropagation();
  33. }
  34. }
  35. function createTouchRecognizer(node) {
  36. function stopMouse(event) {
  37. forEach(mouseEvents, function(e) {
  38. domEvent.bind(node, e, stopEvent, true);
  39. });
  40. }
  41. function allowMouse(event) {
  42. setTimeout(function() {
  43. forEach(mouseEvents, function(e) {
  44. domEvent.unbind(node, e, stopEvent, true);
  45. });
  46. }, 500);
  47. }
  48. domEvent.bind(node, 'touchstart', stopMouse, true);
  49. domEvent.bind(node, 'touchend', allowMouse, true);
  50. domEvent.bind(node, 'touchcancel', allowMouse, true);
  51. // A touch event recognizer that handles
  52. // touch events only (we know, we can already handle
  53. // mouse events out of the box)
  54. var recognizer = new Hammer.Manager(node, {
  55. inputClass: Hammer.TouchInput,
  56. recognizers: []
  57. });
  58. var tap = new Hammer.Tap();
  59. var pan = new Hammer.Pan({ threshold: 10 });
  60. var press = new Hammer.Press();
  61. var pinch = new Hammer.Pinch();
  62. var doubleTap = new Hammer.Tap({ event: 'doubletap', taps: 2 });
  63. pinch.requireFailure(pan);
  64. pinch.requireFailure(press);
  65. recognizer.add([ pan, press, pinch, doubleTap, tap ]);
  66. recognizer.reset = function(force) {
  67. var recognizers = this.recognizers,
  68. session = this.session;
  69. if (session.stopped) {
  70. return;
  71. }
  72. log('recognizer', 'stop');
  73. recognizer.stop(force);
  74. setTimeout(function() {
  75. var i, r;
  76. log('recognizer', 'reset');
  77. for (i = 0; (r = recognizers[i]); i++) {
  78. r.reset();
  79. r.state = 8; // FAILED STATE
  80. }
  81. session.curRecognizer = null;
  82. }, 0);
  83. };
  84. recognizer.on('hammer.input', function(event) {
  85. if (event.srcEvent.defaultPrevented) {
  86. recognizer.reset(true);
  87. }
  88. });
  89. return recognizer;
  90. }
  91. /**
  92. * A plugin that provides touch events for elements.
  93. *
  94. * @param {EventBus} eventBus
  95. * @param {InteractionEvents} interactionEvents
  96. */
  97. export default function TouchInteractionEvents(
  98. injector, canvas, eventBus,
  99. elementRegistry, interactionEvents) {
  100. // optional integrations
  101. var dragging = get('dragging', injector),
  102. move = get('move', injector),
  103. contextPad = get('contextPad', injector),
  104. palette = get('palette', injector);
  105. // the touch recognizer
  106. var recognizer;
  107. function handler(type) {
  108. return function(event) {
  109. log('element', type, event);
  110. interactionEvents.fire(type, event);
  111. };
  112. }
  113. function getGfx(target) {
  114. var node = domClosest(target, 'svg, .djs-element', true);
  115. return node;
  116. }
  117. function initEvents(svg) {
  118. // touch recognizer
  119. recognizer = createTouchRecognizer(svg);
  120. recognizer.on('doubletap', handler('element.dblclick'));
  121. recognizer.on('tap', handler('element.click'));
  122. function startGrabCanvas(event) {
  123. log('canvas', 'grab start');
  124. var lx = 0, ly = 0;
  125. function update(e) {
  126. var dx = e.deltaX - lx,
  127. dy = e.deltaY - ly;
  128. canvas.scroll({ dx: dx, dy: dy });
  129. lx = e.deltaX;
  130. ly = e.deltaY;
  131. }
  132. function end(e) {
  133. recognizer.off('panmove', update);
  134. recognizer.off('panend', end);
  135. recognizer.off('pancancel', end);
  136. log('canvas', 'grab end');
  137. }
  138. recognizer.on('panmove', update);
  139. recognizer.on('panend', end);
  140. recognizer.on('pancancel', end);
  141. }
  142. function startGrab(event) {
  143. var gfx = getGfx(event.target),
  144. element = gfx && elementRegistry.get(gfx);
  145. // recognizer
  146. if (move && canvas.getRootElement() !== element) {
  147. log('element', 'move start', element, event, true);
  148. return move.start(event, element, true);
  149. } else {
  150. startGrabCanvas(event);
  151. }
  152. }
  153. function startZoom(e) {
  154. log('canvas', 'zoom start');
  155. var zoom = canvas.zoom(),
  156. mid = e.center;
  157. function update(e) {
  158. var ratio = 1 - (1 - e.scale) / 1.50,
  159. newZoom = Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, ratio * zoom));
  160. canvas.zoom(newZoom, mid);
  161. stopEvent(e);
  162. }
  163. function end(e) {
  164. recognizer.off('pinchmove', update);
  165. recognizer.off('pinchend', end);
  166. recognizer.off('pinchcancel', end);
  167. recognizer.reset(true);
  168. log('canvas', 'zoom end');
  169. }
  170. recognizer.on('pinchmove', update);
  171. recognizer.on('pinchend', end);
  172. recognizer.on('pinchcancel', end);
  173. }
  174. recognizer.on('panstart', startGrab);
  175. recognizer.on('press', startGrab);
  176. recognizer.on('pinchstart', startZoom);
  177. }
  178. if (dragging) {
  179. // simulate hover during dragging
  180. eventBus.on('drag.move', function(event) {
  181. var originalEvent = event.originalEvent;
  182. if (!originalEvent || originalEvent instanceof MouseEvent) {
  183. return;
  184. }
  185. var position = toPoint(originalEvent);
  186. // this gets really expensive ...
  187. var node = document.elementFromPoint(position.x, position.y),
  188. gfx = getGfx(node),
  189. element = gfx && elementRegistry.get(gfx);
  190. if (element !== event.hover) {
  191. if (event.hover) {
  192. dragging.out(event);
  193. }
  194. if (element) {
  195. dragging.hover({ element: element, gfx: gfx });
  196. event.hover = element;
  197. event.hoverGfx = gfx;
  198. }
  199. }
  200. });
  201. }
  202. if (contextPad) {
  203. eventBus.on('contextPad.create', function(event) {
  204. var node = event.pad.html;
  205. // touch recognizer
  206. var padRecognizer = createTouchRecognizer(node);
  207. padRecognizer.on('panstart', function(event) {
  208. log('context-pad', 'panstart', event);
  209. contextPad.trigger('dragstart', event, true);
  210. });
  211. padRecognizer.on('press', function(event) {
  212. log('context-pad', 'press', event);
  213. contextPad.trigger('dragstart', event, true);
  214. });
  215. padRecognizer.on('tap', function(event) {
  216. log('context-pad', 'tap', event);
  217. contextPad.trigger('click', event);
  218. });
  219. });
  220. }
  221. if (palette) {
  222. eventBus.on('palette.create', function(event) {
  223. var node = event.container;
  224. // touch recognizer
  225. var padRecognizer = createTouchRecognizer(node);
  226. padRecognizer.on('panstart', function(event) {
  227. log('palette', 'panstart', event);
  228. palette.trigger('dragstart', event, true);
  229. });
  230. padRecognizer.on('press', function(event) {
  231. log('palette', 'press', event);
  232. palette.trigger('dragstart', event, true);
  233. });
  234. padRecognizer.on('tap', function(event) {
  235. log('palette', 'tap', event);
  236. palette.trigger('click', event);
  237. });
  238. });
  239. }
  240. eventBus.on('canvas.init', function(event) {
  241. initEvents(event.svg);
  242. });
  243. }
  244. TouchInteractionEvents.$inject = [
  245. 'injector',
  246. 'canvas',
  247. 'eventBus',
  248. 'elementRegistry',
  249. 'interactionEvents',
  250. 'touchFix'
  251. ];