373 lines
8.2 KiB
JavaScript
373 lines
8.2 KiB
JavaScript
|
|
import {
|
||
|
|
forEach
|
||
|
|
} from 'min-dash';
|
||
|
|
|
||
|
|
import {
|
||
|
|
event as domEvent,
|
||
|
|
closest as domClosest
|
||
|
|
} from 'min-dom';
|
||
|
|
|
||
|
|
import Hammer from 'hammerjs';
|
||
|
|
|
||
|
|
import {
|
||
|
|
toPoint
|
||
|
|
} from '../../util/Event';
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @typedef {import('didi').Injector} Injector
|
||
|
|
*
|
||
|
|
* @typedef {import('../../core/Canvas').default} Canvas
|
||
|
|
* @typedef {import('../../core/ElementRegistry').default} ElementRegistry
|
||
|
|
* @typedef {import('../../core/EventBus').default} EventBus
|
||
|
|
* @typedef {import('../interaction-events/InteractionEvents').default} InteractionEvents
|
||
|
|
*/
|
||
|
|
|
||
|
|
var MIN_ZOOM = 0.2,
|
||
|
|
MAX_ZOOM = 4;
|
||
|
|
|
||
|
|
var mouseEvents = [
|
||
|
|
'mousedown',
|
||
|
|
'mouseup',
|
||
|
|
'mouseover',
|
||
|
|
'mouseout',
|
||
|
|
'click',
|
||
|
|
'dblclick'
|
||
|
|
];
|
||
|
|
|
||
|
|
function log() {
|
||
|
|
|
||
|
|
// console.log.apply(console, arguments);
|
||
|
|
}
|
||
|
|
|
||
|
|
function get(service, injector) {
|
||
|
|
return injector.get(service, false);
|
||
|
|
}
|
||
|
|
|
||
|
|
function stopEvent(event) {
|
||
|
|
|
||
|
|
event.preventDefault();
|
||
|
|
|
||
|
|
if (typeof event.stopPropagation === 'function') {
|
||
|
|
event.stopPropagation();
|
||
|
|
} else if (event.srcEvent && typeof event.srcEvent.stopPropagation === 'function') {
|
||
|
|
|
||
|
|
// iPhone & iPad
|
||
|
|
event.srcEvent.stopPropagation();
|
||
|
|
}
|
||
|
|
|
||
|
|
if (typeof event.stopImmediatePropagation === 'function') {
|
||
|
|
event.stopImmediatePropagation();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
function createTouchRecognizer(node) {
|
||
|
|
|
||
|
|
function stopMouse(event) {
|
||
|
|
|
||
|
|
forEach(mouseEvents, function(e) {
|
||
|
|
domEvent.bind(node, e, stopEvent, true);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function allowMouse(event) {
|
||
|
|
setTimeout(function() {
|
||
|
|
forEach(mouseEvents, function(e) {
|
||
|
|
domEvent.unbind(node, e, stopEvent, true);
|
||
|
|
});
|
||
|
|
}, 500);
|
||
|
|
}
|
||
|
|
|
||
|
|
domEvent.bind(node, 'touchstart', stopMouse, true);
|
||
|
|
domEvent.bind(node, 'touchend', allowMouse, true);
|
||
|
|
domEvent.bind(node, 'touchcancel', allowMouse, true);
|
||
|
|
|
||
|
|
// A touch event recognizer that handles
|
||
|
|
// touch events only (we know, we can already handle
|
||
|
|
// mouse events out of the box)
|
||
|
|
|
||
|
|
var recognizer = new Hammer.Manager(node, {
|
||
|
|
inputClass: Hammer.TouchInput,
|
||
|
|
recognizers: [],
|
||
|
|
domEvents: true
|
||
|
|
});
|
||
|
|
|
||
|
|
|
||
|
|
var tap = new Hammer.Tap();
|
||
|
|
var pan = new Hammer.Pan({ threshold: 10 });
|
||
|
|
var press = new Hammer.Press();
|
||
|
|
var pinch = new Hammer.Pinch();
|
||
|
|
|
||
|
|
var doubleTap = new Hammer.Tap({ event: 'doubletap', taps: 2 });
|
||
|
|
|
||
|
|
pinch.requireFailure(pan);
|
||
|
|
pinch.requireFailure(press);
|
||
|
|
|
||
|
|
recognizer.add([ pan, press, pinch, doubleTap, tap ]);
|
||
|
|
|
||
|
|
recognizer.reset = function(force) {
|
||
|
|
var recognizers = this.recognizers,
|
||
|
|
session = this.session;
|
||
|
|
|
||
|
|
if (session.stopped) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
log('recognizer', 'stop');
|
||
|
|
|
||
|
|
recognizer.stop(force);
|
||
|
|
|
||
|
|
setTimeout(function() {
|
||
|
|
var i, r;
|
||
|
|
|
||
|
|
log('recognizer', 'reset');
|
||
|
|
for (i = 0; (r = recognizers[i]); i++) {
|
||
|
|
r.reset();
|
||
|
|
r.state = 8; // FAILED STATE
|
||
|
|
}
|
||
|
|
|
||
|
|
session.curRecognizer = null;
|
||
|
|
}, 0);
|
||
|
|
};
|
||
|
|
|
||
|
|
recognizer.on('hammer.input', function(event) {
|
||
|
|
if (event.srcEvent.defaultPrevented) {
|
||
|
|
recognizer.reset(true);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
return recognizer;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* A plugin that provides touch events for elements.
|
||
|
|
*
|
||
|
|
* @param {Injector} injector
|
||
|
|
* @param {Canvas} canvas
|
||
|
|
* @param {EventBus} eventBus
|
||
|
|
* @param {ElementRegistry} elementRegistry
|
||
|
|
* @param {InteractionEvents} interactionEvents
|
||
|
|
*/
|
||
|
|
export default function TouchInteractionEvents(
|
||
|
|
injector, canvas, eventBus,
|
||
|
|
elementRegistry, interactionEvents) {
|
||
|
|
|
||
|
|
// optional integrations
|
||
|
|
var dragging = get('dragging', injector),
|
||
|
|
move = get('move', injector),
|
||
|
|
contextPad = get('contextPad', injector),
|
||
|
|
palette = get('palette', injector);
|
||
|
|
|
||
|
|
// the touch recognizer
|
||
|
|
var recognizer;
|
||
|
|
|
||
|
|
function handler(type, buttonType) {
|
||
|
|
|
||
|
|
return function(event) {
|
||
|
|
log('element', type, event);
|
||
|
|
|
||
|
|
var gfx = getGfx(event.target),
|
||
|
|
element = gfx && elementRegistry.get(gfx);
|
||
|
|
|
||
|
|
// translate into an actual mouse click event
|
||
|
|
if (buttonType) {
|
||
|
|
event.srcEvent.button = buttonType;
|
||
|
|
}
|
||
|
|
|
||
|
|
return interactionEvents.fire(type, event, element);
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
function getGfx(target) {
|
||
|
|
var node = domClosest(target, 'svg, .djs-element', true);
|
||
|
|
return node;
|
||
|
|
}
|
||
|
|
|
||
|
|
function initEvents(svg) {
|
||
|
|
|
||
|
|
// touch recognizer
|
||
|
|
recognizer = createTouchRecognizer(svg);
|
||
|
|
|
||
|
|
function startGrabCanvas(event) {
|
||
|
|
|
||
|
|
log('canvas', 'grab start');
|
||
|
|
|
||
|
|
var lx = 0, ly = 0;
|
||
|
|
|
||
|
|
function update(e) {
|
||
|
|
|
||
|
|
var dx = e.deltaX - lx,
|
||
|
|
dy = e.deltaY - ly;
|
||
|
|
|
||
|
|
canvas.scroll({ dx: dx, dy: dy });
|
||
|
|
|
||
|
|
lx = e.deltaX;
|
||
|
|
ly = e.deltaY;
|
||
|
|
}
|
||
|
|
|
||
|
|
function end(e) {
|
||
|
|
recognizer.off('panmove', update);
|
||
|
|
recognizer.off('panend', end);
|
||
|
|
recognizer.off('pancancel', end);
|
||
|
|
|
||
|
|
log('canvas', 'grab end');
|
||
|
|
}
|
||
|
|
|
||
|
|
recognizer.on('panmove', update);
|
||
|
|
recognizer.on('panend', end);
|
||
|
|
recognizer.on('pancancel', end);
|
||
|
|
}
|
||
|
|
|
||
|
|
function startGrab(event) {
|
||
|
|
|
||
|
|
var gfx = getGfx(event.target),
|
||
|
|
element = gfx && elementRegistry.get(gfx);
|
||
|
|
|
||
|
|
// recognizer
|
||
|
|
if (move && canvas.getRootElement() !== element) {
|
||
|
|
log('element', 'move start', element, event, true);
|
||
|
|
return move.start(event, element, true);
|
||
|
|
} else {
|
||
|
|
startGrabCanvas(event);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function startZoom(e) {
|
||
|
|
|
||
|
|
log('canvas', 'zoom start');
|
||
|
|
|
||
|
|
var zoom = canvas.zoom(),
|
||
|
|
mid = e.center;
|
||
|
|
|
||
|
|
function update(e) {
|
||
|
|
|
||
|
|
var ratio = 1 - (1 - e.scale) / 1.50,
|
||
|
|
newZoom = Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, ratio * zoom));
|
||
|
|
|
||
|
|
canvas.zoom(newZoom, mid);
|
||
|
|
|
||
|
|
stopEvent(e);
|
||
|
|
}
|
||
|
|
|
||
|
|
function end(e) {
|
||
|
|
recognizer.off('pinchmove', update);
|
||
|
|
recognizer.off('pinchend', end);
|
||
|
|
recognizer.off('pinchcancel', end);
|
||
|
|
|
||
|
|
recognizer.reset(true);
|
||
|
|
|
||
|
|
log('canvas', 'zoom end');
|
||
|
|
}
|
||
|
|
|
||
|
|
recognizer.on('pinchmove', update);
|
||
|
|
recognizer.on('pinchend', end);
|
||
|
|
recognizer.on('pinchcancel', end);
|
||
|
|
}
|
||
|
|
|
||
|
|
recognizer.on('tap', handler('element.click'));
|
||
|
|
recognizer.on('doubletap', handler('element.dblclick', 1));
|
||
|
|
|
||
|
|
recognizer.on('panstart', startGrab);
|
||
|
|
recognizer.on('press', startGrab);
|
||
|
|
|
||
|
|
recognizer.on('pinchstart', startZoom);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (dragging) {
|
||
|
|
|
||
|
|
// simulate hover during dragging
|
||
|
|
eventBus.on('drag.move', function(event) {
|
||
|
|
|
||
|
|
var originalEvent = event.originalEvent;
|
||
|
|
|
||
|
|
if (!originalEvent || originalEvent instanceof MouseEvent) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
var position = toPoint(originalEvent);
|
||
|
|
|
||
|
|
// this gets really expensive ...
|
||
|
|
var node = document.elementFromPoint(position.x, position.y),
|
||
|
|
gfx = getGfx(node),
|
||
|
|
element = gfx && elementRegistry.get(gfx);
|
||
|
|
|
||
|
|
if (element !== event.hover) {
|
||
|
|
if (event.hover) {
|
||
|
|
dragging.out(event);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (element) {
|
||
|
|
dragging.hover({ element: element, gfx: gfx });
|
||
|
|
|
||
|
|
event.hover = element;
|
||
|
|
event.hoverGfx = gfx;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
if (contextPad) {
|
||
|
|
|
||
|
|
eventBus.on('contextPad.create', function(event) {
|
||
|
|
var node = event.pad.html;
|
||
|
|
|
||
|
|
// touch recognizer
|
||
|
|
var padRecognizer = createTouchRecognizer(node);
|
||
|
|
|
||
|
|
padRecognizer.on('panstart', function(event) {
|
||
|
|
log('context-pad', 'panstart', event);
|
||
|
|
contextPad.trigger('dragstart', event, true);
|
||
|
|
});
|
||
|
|
|
||
|
|
padRecognizer.on('press', function(event) {
|
||
|
|
log('context-pad', 'press', event);
|
||
|
|
contextPad.trigger('dragstart', event, true);
|
||
|
|
});
|
||
|
|
|
||
|
|
padRecognizer.on('tap', function(event) {
|
||
|
|
log('context-pad', 'tap', event);
|
||
|
|
contextPad.trigger('click', event);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
if (palette) {
|
||
|
|
eventBus.on('palette.create', function(event) {
|
||
|
|
var node = event.container;
|
||
|
|
|
||
|
|
// touch recognizer
|
||
|
|
var padRecognizer = createTouchRecognizer(node);
|
||
|
|
|
||
|
|
padRecognizer.on('panstart', function(event) {
|
||
|
|
log('palette', 'panstart', event);
|
||
|
|
palette.trigger('dragstart', event, true);
|
||
|
|
});
|
||
|
|
|
||
|
|
padRecognizer.on('press', function(event) {
|
||
|
|
log('palette', 'press', event);
|
||
|
|
palette.trigger('dragstart', event, true);
|
||
|
|
});
|
||
|
|
|
||
|
|
padRecognizer.on('tap', function(event) {
|
||
|
|
log('palette', 'tap', event);
|
||
|
|
palette.trigger('click', event);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
eventBus.on('canvas.init', function(event) {
|
||
|
|
initEvents(event.svg);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
TouchInteractionEvents.$inject = [
|
||
|
|
'injector',
|
||
|
|
'canvas',
|
||
|
|
'eventBus',
|
||
|
|
'elementRegistry',
|
||
|
|
'interactionEvents',
|
||
|
|
'touchFix'
|
||
|
|
];
|