TouchInteractionEvents.js 7.8 KB

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