diff --git a/build/contact.js b/build/contact.js index 938b4cc..e05294f 100644 --- a/build/contact.js +++ b/build/contact.js @@ -35,9 +35,19 @@ const GESTURE_STATE_BLOCKED = "blocked"; */ class Contact { - constructor (pointerdownEvent) { + constructor (pointerdownEvent, options) { - this.DEBUG = false; + options = options || {}; + + this.options = { + "DEBUG" : false + }; + + for (let key in options){ + this.options[key] = options[key]; + } + + this.DEBUG = this.options.DEBUG; this.id = new Date().getTime(); @@ -86,8 +96,12 @@ class Contact { addPointer (pointerdownEvent) { this.currentPointerEvent = pointerdownEvent; + + var pointerInputOptions = { + "DEBUG" : this.DEBUG + }; - var pointerInput = new PointerInput(pointerdownEvent); + var pointerInput = new PointerInput(pointerdownEvent, pointerInputOptions); this.pointerInputs[pointerdownEvent.pointerId] = pointerInput; this.activePointerInputs[pointerdownEvent.pointerId] = pointerInput; } @@ -435,15 +449,23 @@ class PointerInput { constructor (pointerdownEvent, options) { - this.DEBUG = false; - options = options || {}; + + this.options = { + "DEBUG" : false + }; + + for (let key in options){ + this.options[key] = options[key]; + } + + this.DEBUG = this.options.DEBUG; var now = new Date().getTime(); this.pointerId = pointerdownEvent.pointerId; - var hasVectorTimespan = Object.prototype.hasOwnProperty.call(options, "vectorTimespan"); - this.vectorTimespan = hasVectorTimespan == true ? options.vectorTimespan : 100; // milliseconds + var hasVectorTimespan = Object.prototype.hasOwnProperty.call(this.options, "vectorTimespan"); + this.vectorTimespan = hasVectorTimespan == true ? this.options.vectorTimespan : 100; // milliseconds // events used for vector calculation this.initialPointerEvent = pointerdownEvent; @@ -865,7 +887,7 @@ class Gesture { if (minValue != null && value != null && value < minValue){ if (this.DEBUG == true){ - console.log("dismissing min" + this.constructor.name + ": required " + parameterName + ": " + minValue + ", current value: " + value); + console.log("dismissing min" + this.eventBaseName + ": required " + parameterName + ": " + minValue + ", current value: " + value); } return false; @@ -874,7 +896,7 @@ class Gesture { if (maxValue != null && value != null && value > maxValue){ if (this.DEBUG == true){ - console.log("dismissing max" + this.constructor.name + ": required " + parameterName + ": " + maxValue + ", current value: " + value); + console.log("dismissing max" + this.eventBaseName + ": required " + parameterName + ": " + maxValue + ", current value: " + value); } return false; @@ -897,7 +919,7 @@ class Gesture { } if (this.DEBUG == true){ - console.log("[Gestures] dismissing " + this.constructor.name + ": " + parameterName + " required: " + requiredValue + ", actual value: " + value); + console.log("[Gestures] dismissing " + this.eventBaseName + ": " + parameterName + " required: " + requiredValue + ", actual value: " + value); } return false; @@ -947,7 +969,7 @@ class Gesture { var primaryPointerInput = contact.getPrimaryPointerInput(); if (this.DEBUG == true){ - console.log("[Gestures] running recognition for " + this.constructor.name); + console.log("[Gestures] running recognition for " + this.eventBaseName); } @@ -988,7 +1010,7 @@ class Gesture { if (this.options.supportedDirections.indexOf(primaryPointerInput.liveParameters.vector.direction) == -1){ if (this.DEBUG == true){ - console.log("[Gestures] dismissing " + this.constructor.name + ": supported directions: " + this.options.supportedDirections + ", current direction: " + primaryPointerInput.liveParameters.vector.direction); + console.log("[Gestures] dismissing " + this.eventBaseName + ": supported directions: " + this.options.supportedDirections + ", current direction: " + primaryPointerInput.liveParameters.vector.direction); } return false; @@ -1036,7 +1058,7 @@ class Gesture { let gesture = this.options.blocks[g]; if (gesture.isActive == false) { if (this.DEBUG == false){ - console.log("[Gesture] blocking " + gesture.constructor.name); + console.log("[Gesture] blocking " + gesture.eventBaseName); } gesture.state = GESTURE_STATE_BLOCKED; } @@ -1069,7 +1091,7 @@ class Gesture { emit (contact, eventName) { // fire general event like "pan" , "pinch", "rotate" - eventName = eventName || this.constructor.name.toLowerCase(); + eventName = eventName || this.eventBaseName; if (this.DEBUG === true){ console.log("[Gestures] detected and firing event " + eventName); @@ -1134,7 +1156,7 @@ class Gesture { this.initialPointerEvent = contact.currentPointerEvent; - var eventName = "" + this.constructor.name.toLowerCase() + "start"; + var eventName = "" + this.eventBaseName + "start"; if (this.DEBUG === true) { console.log("[Gestures] firing event: " + eventName); @@ -1156,7 +1178,7 @@ class Gesture { this.isActive = false; - var eventName = "" + this.constructor.name.toLowerCase() + "end"; + var eventName = "" + this.eventBaseName + "end"; if (this.DEBUG === true) { console.log("[Gestures] firing event: " + eventName); @@ -1265,6 +1287,8 @@ class Pan extends SinglePointerGesture { options = options || {}; super(domElement, options); + + this.eventBaseName = "pan"; this.initialMinMaxParameters["pointerCount"] = [1,1]; // 1: no pan recognized at the pointerup event. 0: pan recognized at pointerup this.initialMinMaxParameters["duration"] = [0, null]; @@ -1352,6 +1376,8 @@ class Tap extends SinglePointerGesture { options = options || {}; super(domElement, options); + + this.eventBaseName = "tap"; this.initialMinMaxParameters["pointerCount"] = [0,0]; // count of fingers touching the surface. a tap is fired AFTER the user removed his finger this.initialMinMaxParameters["duration"] = [0, 200]; // milliseconds. after a certain touch duration, it is not a TAP anymore @@ -1390,6 +1416,8 @@ class Press extends SinglePointerGesture { options = options || {}; super(domElement, options); + + this.eventBaseName = "press"; this.initialMinMaxParameters["pointerCount"] = [1, 1]; // count of fingers touching the surface. a press is fired during an active contact this.initialMinMaxParameters["duration"] = [600, null]; // milliseconds. after a certain touch duration, it is not a TAP anymore @@ -1592,6 +1620,8 @@ class Pinch extends TwoPointerGesture { options = options || {}; super(domElement, options); + + this.eventBaseName = "pinch"; this.initialMinMaxParameters["centerMovement"] = [0, 50]; //px this.initialMinMaxParameters["distanceChange"] = [5, null]; // distance between 2 fingers @@ -1617,6 +1647,8 @@ class Rotate extends TwoPointerGesture { options = options || {}; super(domElement, options); + + this.eventBaseName = "rotate"; this.initialMinMaxParameters["centerMovement"] = [0, 50]; this.initialMinMaxParameters["distanceChange"] = [null, 50]; @@ -1637,6 +1669,8 @@ class TwoFingerPan extends TwoPointerGesture { options = options || {}; super(domElement, options); + + this.eventBaseName = "twofingerpan"; this.initialMinMaxParameters["centerMovement"] = [3, null]; this.initialMinMaxParameters["distanceChange"] = [null, 50]; @@ -1665,8 +1699,6 @@ var ALL_GESTURE_CLASSES = [Tap, Press, Pan, Pinch, Rotate, TwoFingerPan]; class PointerListener { constructor (domElement, options){ - - this.DEBUG = false; // registry for events like "pan", "rotate", which have to be removed on this.destroy(); this.eventHandlers = {}; @@ -1681,7 +1713,10 @@ class PointerListener { this.options = { "bubbles" : true, - "handleTouchEvents" : false + "handleTouchEvents" : true, + "DEBUG" : false, + "DEBUG_GESTURES" : false, + "DEBUG_CONTACT" : false }; // add user-defined options to this.options @@ -1693,6 +1728,8 @@ class PointerListener { this.options[key] = options[key]; } + this.DEBUG = this.options.DEBUG; + // add instantiatedGestures to options.supportedGestures var supportedGestures = ALL_GESTURE_CLASSES; var instantiatedGestures = []; @@ -1708,7 +1745,8 @@ class PointerListener { let gesture; let GestureClass = supportedGestures[i]; let gestureOptions = { - bubbles : this.options.bubbles + "bubbles" : this.options.bubbles, + "DEBUG" : this.options.DEBUG_GESTURES }; if (typeof GestureClass == "function"){ @@ -1751,13 +1789,20 @@ class PointerListener { // javascript fires the events "pointerdown", "pointermove", "pointerup" and "pointercancel" // on each of these events, the contact instance is updated and GestureRecognizers of this.supported_events are run var onPointerDown = function (event) { + + if (self.DEBUG == true){ + console.log("[PointerListener] pointerdown event detected"); + } // re-target all pointerevents to the current element // see https://developer.mozilla.org/en-US/docs/Web/API/Element/setPointerCapture domElement.setPointerCapture(event.pointerId); if (self.contact == null || self.contact.isActive == false) { - self.contact = new Contact(event); + let contactOptions = { + "DEBUG" : self.options.DEBUG_CONTACT + }; + self.contact = new Contact(event, contactOptions); } else { // use existing contact instance if a second pointer becomes present @@ -1801,6 +1846,10 @@ class PointerListener { } var onPointerUp = function (event) { + + if (self.DEBUG == true){ + console.log("[PointerListener] pointerup event detected"); + } domElement.releasePointerCapture(event.pointerId); @@ -1829,6 +1878,10 @@ class PointerListener { * MDN: Pointer capture allows events for a particular pointer event (PointerEvent) to be re-targeted to a particular element instead of the normal (or hit test) target at a pointer's location. This can be used to ensure that an element continues to receive pointer events even if the pointer device's contact moves off the element (such as by scrolling or panning). */ var onPointerLeave = function (event) { + + if (self.DEBUG == true){ + console.log("[PointerListener] pointerleave detected"); + } if (self.contact != null && self.contact.isActive == true){ self.contact.onPointerLeave(event); @@ -1842,7 +1895,7 @@ class PointerListener { domElement.releasePointerCapture(event.pointerId); - if (this.DEBUG == true){ + if (self.DEBUG == true){ console.log("[PointerListener] pointercancel detected"); } @@ -1935,10 +1988,6 @@ class PointerListener { // to recognize Press, recognition has to be run if the user does nothing while having contact with the surfave (no pointermove, no pointerup, no pointercancel) onIdle () { - - if (this.DEBUG == true){ - console.log("[PointerListener] onIdle"); - } if (this.contact == null || this.contact.isActive == false){ this.clearIdleRecognitionInterval(); @@ -1957,7 +2006,7 @@ class PointerListener { this.contact.onIdle(); if (this.DEBUG == true){ - console.log("[PointerListener] run idle recognition"); + console.log("[PointerListener] onIdle - running idle recognition"); } this.recognizeGestures(); diff --git a/build/contact.min.js b/build/contact.min.js index 44e6312..939c28c 100644 --- a/build/contact.min.js +++ b/build/contact.min.js @@ -1 +1 @@ -"use strict";const DIRECTION_NONE="0",DIRECTION_LEFT="left",DIRECTION_RIGHT="right",DIRECTION_UP="up",DIRECTION_DOWN="down",DIRECTION_CLOCKWISE=1,DIRECTION_COUNTER_CLOCKWISE=-1,DIRECTION_HORIZONTAL=[DIRECTION_LEFT,DIRECTION_RIGHT],DIRECTION_VERTICAL=[DIRECTION_UP,DIRECTION_DOWN],DIRECTION_ALL=[DIRECTION_LEFT,DIRECTION_RIGHT,DIRECTION_UP,DIRECTION_DOWN],GESTURE_STATE_POSSIBLE="possible",GESTURE_STATE_BLOCKED="blocked";class Contact{constructor(pointerdownEvent){this.DEBUG=!1,this.id=(new Date).getTime(),this.pointerInputs={},this.activePointerInputs={},this.primaryPointerId=pointerdownEvent.pointerId,this.initialPointerEvent=pointerdownEvent,this.currentPointerEvent=pointerdownEvent,this.addPointer(pointerdownEvent),this.isActive=!0,this.startTimestamp=pointerdownEvent.timeStamp,this.currentTimestamp=this.startTimestamp,this.endTimestamp=null,this.multipointer={liveParameters:{centerMovement:null,centerMovementVector:null,distanceChange:null,relativeDistanceChange:null,rotationAngle:null,vectorAngle:null},globalParameters:{centerMovement:null,centerMovementVector:null,distanceChange:null,relativeDistanceChange:null,rotationAngle:null,vectorAngle:null}}}addPointer(pointerdownEvent){this.currentPointerEvent=pointerdownEvent;var pointerInput=new PointerInput(pointerdownEvent);this.pointerInputs[pointerdownEvent.pointerId]=pointerInput,this.activePointerInputs[pointerdownEvent.pointerId]=pointerInput}removePointer(pointerId){delete this.activePointerInputs[pointerId]}getPointerInput(pointerId){if(Object.prototype.hasOwnProperty.call(this.pointers,pointerId)){return this.pointers[pointerId]}throw new Error("invalid pointerId: "+pointerId+". Pointer not found in Contact.pointers")}getPrimaryPointerInput(){return this.pointerInputs[this.primaryPointerId]}getMultiPointerInputs(){var pointerId_1=Object.keys(this.activePointerInputs)[0],pointerInput_1=this.activePointerInputs[pointerId_1],pointerId_2=Object.keys(this.activePointerInputs)[1];return[pointerInput_1,this.activePointerInputs[pointerId_2]]}onPointerMove(pointermoveEvent){this.currentPointerEvent=pointermoveEvent,this.currentTimestamp=pointermoveEvent.timeStamp,this.pointerInputs[pointermoveEvent.pointerId].onMove(pointermoveEvent),!0===this.DEBUG&&console.log(this.pointerInputs),this.updateState()}onPointerUp(pointerupEvent){var pointerId=pointerupEvent.pointerId;this.currentPointerEvent=pointerupEvent,this.currentTimestamp=pointerupEvent.timeStamp,this.pointerInputs[pointerId].onUp(pointerupEvent),this.removePointer(pointerId),this.updateState()}onPointerCancel(pointercancelEvent){this.onPointerUp(pointercancelEvent),1==this.DEBUG&&console.log("[Contact] pointercancel detected")}onPointerLeave(pointerleaveEvent){this.onPointerUp(pointerleaveEvent),1==this.DEBUG&&console.log("[Contact] pointerleave detected")}onIdle(){for(let pointerInputId in this.activePointerInputs){this.activePointerInputs[pointerInputId].onIdle()}}updateState(){var isActive=!1;Object.keys(this.activePointerInputs).length>0&&(isActive=!0),this.isActive=isActive,0==this.isActive?this.endTimestamp=this.currentTimestamp:Object.keys(this.activePointerInputs).length>=2&&this.updateMultipointerParameters()}updateMultipointerParameters(){var multiPointerInputs=this.getMultiPointerInputs(),pointerInput_1=multiPointerInputs[0],pointerInput_2=multiPointerInputs[1],vector_1=pointerInput_1.liveParameters.vector,vector_2=pointerInput_2.liveParameters.vector;if(null!=vector_1&&null!=vector_2){var currentCenter=getCenter(vector_1.startPoint,vector_2.startPoint);this.multipointer.liveParameters.center=currentCenter;var centerMovementVector=this.calculateCenterMovement(vector_1,vector_2);this.multipointer.liveParameters.centerMovementVector=centerMovementVector,this.multipointer.liveParameters.centerMovement=centerMovementVector.vectorLength;var liveDistanceChange=this.calculateDistanceChange(vector_1,vector_2);this.multipointer.liveParameters.distanceChange=liveDistanceChange.absolute,this.multipointer.liveParameters.relativeDistanceChange=liveDistanceChange.relative;var liveRotationAngle=this.calculateRotationAngle(vector_1,vector_2);this.multipointer.liveParameters.rotationAngle=liveRotationAngle;var liveVectorAngle=this.calculateVectorAngle(vector_1,vector_2);this.multipointer.liveParameters.vectorAngle=liveVectorAngle}var globalVector_1=pointerInput_1.globalParameters.vector,globalVector_2=pointerInput_2.globalParameters.vector;if(null!=globalVector_1&&null!=globalVector_2){var globalCenter=getCenter(globalVector_1.startPoint,globalVector_2.startPoint);this.multipointer.globalParameters.center=globalCenter;var globalCenterMovementVector=this.calculateCenterMovement(globalVector_1,globalVector_2);this.multipointer.globalParameters.centerMovementVector=globalCenterMovementVector,this.multipointer.globalParameters.centerMovement=globalCenterMovementVector.vectorLength;var globalDistanceChange=this.calculateDistanceChange(globalVector_1,globalVector_2);this.multipointer.globalParameters.distanceChange=globalDistanceChange.absolute,this.multipointer.globalParameters.relativeDistanceChange=globalDistanceChange.relative;var globalRotationAngle=this.calculateRotationAngle(globalVector_1,globalVector_2);this.multipointer.globalParameters.rotationAngle=globalRotationAngle;var globalVectorAngle=this.calculateVectorAngle(globalVector_1,globalVector_2);this.multipointer.liveParameters.vectorAngle=globalVectorAngle}!0===this.DEBUG&&(console.log("[Contact] 2 fingers: centerMovement between pointer #"+pointerInput_1.pointerId+" and pointer #"+pointerInput_2.pointerId+" : "+this.multipointer.liveParameters.centerMovement+"px"),console.log("[Contact] 2 fingers: distanceChange: between pointer #"+pointerInput_1.pointerId+" and pointer #"+pointerInput_2.pointerId+" : "+this.multipointer.liveParameters.distanceChange+"px"),console.log("[Contact] 2 fingers live angle: "+this.multipointer.liveParameters.rotationAngle+"deg"),console.log("[Contact] 2 fingers global angle: "+this.multipointer.globalParameters.rotationAngle+"deg"))}calculateCenterMovement(vector_1,vector_2){var startPoint=getCenter(vector_1.startPoint,vector_2.startPoint),endPoint=getCenter(vector_1.endPoint,vector_2.endPoint);return new Vector(startPoint,endPoint)}calculateDistanceChange(vector_1,vector_2){var vectorBetweenStartPoints=new Vector(vector_1.startPoint,vector_2.startPoint),vectorBetweenEndPoints=new Vector(vector_1.endPoint,vector_2.endPoint);return{absolute:vectorBetweenEndPoints.vectorLength-vectorBetweenStartPoints.vectorLength,relative:vectorBetweenEndPoints.vectorLength/vectorBetweenStartPoints.vectorLength}}calculateRotationAngle(vector_1,vector_2){var angleVector_1=new Vector(vector_1.startPoint,vector_2.startPoint),angleVector_2=new Vector(vector_1.endPoint,vector_2.endPoint),origin=new Point(0,0),translationVector_1=new Vector(angleVector_1.startPoint,origin),translatedEndPoint_1=translatePoint(angleVector_1.endPoint,translationVector_1),translationVector_2=new Vector(angleVector_2.startPoint,origin),translatedEndPoint_2=translatePoint(angleVector_2.endPoint,translationVector_2),rotationAngle=-1*calcAngleRad(translatedEndPoint_1),x_2_rotated=translatedEndPoint_2.x*Math.cos(rotationAngle)-translatedEndPoint_2.y*Math.sin(rotationAngle),y_2_rotated=Math.round(translatedEndPoint_2.x*Math.sin(rotationAngle)+translatedEndPoint_2.y*Math.cos(rotationAngle));return 180*Math.atan2(y_2_rotated,x_2_rotated)/Math.PI}calculateVectorAngle(vector_1,vector_2){var angleDeg=null;if(vector_1.vectorLength>0&&vector_2.vectorLength>0){var cos=(vector_1.x*vector_2.x+vector_1.y*vector_2.y)/(vector_1.vectorLength*vector_2.vectorLength);angleDeg=rad2deg(Math.acos(cos))}return angleDeg}}class PointerInput{constructor(pointerdownEvent,options){this.DEBUG=!1,options=options||{};var now=(new Date).getTime();this.pointerId=pointerdownEvent.pointerId;var hasVectorTimespan=Object.prototype.hasOwnProperty.call(options,"vectorTimespan");this.vectorTimespan=1==hasVectorTimespan?options.vectorTimespan:100,this.initialPointerEvent=pointerdownEvent,this.currentPointerEvent=pointerdownEvent,this.recognizedEvents=[pointerdownEvent],this.canceled=!1,this.isActive=!0;var nullVector=this.getVector(pointerdownEvent,pointerdownEvent);this.liveParameters={vector:nullVector,speed:0,isMoving:!1},this.globalParameters={startX:this.initialPointerEvent.clientX,startY:this.initialPointerEvent.clientY,vector:nullVector,deltaX:0,deltaY:0,startTimestampUTC:now,startTimestamp:this.initialPointerEvent.timeStamp,currentTimestamp:this.initialPointerEvent.timeStamp,endTimestamp:null,maximumSpeed:0,averageSpeed:0,finalSpeed:null,traveledDistance:0,hasBeenMoved:!1,duration:0}}onIdle(){let duration=(new Date).getTime()-this.globalParameters.startTimestampUTC;this.globalParameters.duration=duration}onMove(pointermoveEvent){this.globalParameters.hasBeenMoved=!0,this.liveParameters.isMoving=!0,this.update(pointermoveEvent,!0)}onUp(pointerupEvent){this.globalParameters.finalSpeed=this.liveParameters.speed,this.liveParameters.currentSpeed=0,this.liveParameters.isMoving=!1,this.isActive=!1,this.globalParameters.endTimestamp=pointerupEvent.timeStamp,this.update(pointerupEvent),!0===this.DEBUG&&console.log("[Contact] pointerdown ended. pointerdown duration: "+this.globalParameters.duration+"ms")}onCancel(pointercancelEvent){this.update(pointercancelEvent),this.liveParameters.speed=0,this.canceled=!0,this.liveParameters.isMoving=!1,this.isActive=!1,this.globalParameters.endTimestamp=pointercancelEvent.timeStamp,!0===this.DEBUG&&console.log("[Contact] canceled, pointerdown duration:"+this.duration)}update(pointerEvent){this.currentPointerEvent=pointerEvent,this.recognizedEvents.push(pointerEvent);var timedPointerEvents=this.getTimedPointerEvents(),liveVector=this.getVector(timedPointerEvents[0],timedPointerEvents[1]);if(this.liveParameters.vector=liveVector,null!=liveVector){this.liveParameters.speed=this.getSpeed(liveVector,timedPointerEvents[0].timeStamp,timedPointerEvents[1].timeStamp),this.liveParameters.speed>this.globalParameters.maximumSpeed&&(this.globalParameters.maximumSpeed=this.liveParameters.speed),this.globalParameters.currentTimestamp=pointerEvent.timeStamp,this.globalParameters.duration=pointerEvent.timeStamp-this.globalParameters.startTimestamp,this.globalParameters.deltaX=liveVector.endPoint.x-this.globalParameters.startX,this.globalParameters.deltaY=liveVector.endPoint.y-this.globalParameters.startY;var globalVector=this.getVector(this.initialPointerEvent,this.currentPointerEvent);this.globalParameters.vector=globalVector,!0===this.DEBUG&&(console.log("[Contact] current speed: "+this.liveParameters.speed+"px/s"),console.log("[Contact] pointerdown duration: "+this.globalParameters.duration+"ms"),console.log("[Contact] live vector length within vectorTimespan: "+this.liveParameters.vector.vectorLength+"px"))}}getTimedPointerEvents(){for(var startPointerEvent=this.initialPointerEvent,endPointerEvent=this.recognizedEvents[this.recognizedEvents.length-1],startIndex=this.recognizedEvents.length-1,elapsedTime=0,endTimeStamp=endPointerEvent.timeStamp;elapsedTimeMath.abs(this.deltaY)?this.startPoint.xmaxValue)||(1==this.DEBUG&&console.log("dismissing max"+this.constructor.name+": required "+parameterName+": "+maxValue+", current value: "+value),!1)}validateBool(parameterName,value){var requiredValue=this.boolParameters[parameterName];return null!=requiredValue&&null!=value&&requiredValue===value||(null==requiredValue||(1==this.DEBUG&&console.log("[Gestures] dismissing "+this.constructor.name+": "+parameterName+" required: "+requiredValue+", actual value: "+value),!1))}getMinMaxParameters(contact){var primaryPointerInput=contact.getPrimaryPointerInput();return{pointerCount:Object.keys(contact.activePointerInputs).length,duration:primaryPointerInput.globalParameters.duration,currentSpeed:primaryPointerInput.liveParameters.speed,averageSpeed:primaryPointerInput.globalParameters.averageSpeed,finalSpeed:primaryPointerInput.globalParameters.finalSpeed,distance:primaryPointerInput.liveParameters.vector.vectorLength}}getBoolParameters(contact){var primaryPointerInput=contact.getPrimaryPointerInput();return{requiresPointerUp:!1===primaryPointerInput.isActive,requiresActivePointer:!0===primaryPointerInput.isActive,requiresPointerMove:!0===primaryPointerInput.globalParameters.hasBeenMoved}}validate(contact){if(this.state==GESTURE_STATE_BLOCKED)return!1;var primaryPointerInput=contact.getPrimaryPointerInput();1==this.DEBUG&&console.log("[Gestures] running recognition for "+this.constructor.name);var contactBoolParameters=this.getBoolParameters(contact);for(let boolParameterName in this.boolParameters){let boolValue=contactBoolParameters[boolParameterName];if(0==this.validateBool(boolParameterName,boolValue))return!1}var minMaxParameters,contactMinMaxParameters=this.getMinMaxParameters(contact);minMaxParameters=1==this.isActive?this.activeStateMinMaxParameters:this.initialMinMaxParameters;for(let minMaxParameterName in minMaxParameters){let value=contactMinMaxParameters[minMaxParameterName];if(0==this.validateMinMax(minMaxParameters,minMaxParameterName,value))return!1}return!(1==Object.prototype.hasOwnProperty.call(this.options,"supportedDirections")&&this.options.supportedDirections.length>0&&-1==this.options.supportedDirections.indexOf(primaryPointerInput.liveParameters.vector.direction))||(1==this.DEBUG&&console.log("[Gestures] dismissing "+this.constructor.name+": supported directions: "+this.options.supportedDirections+", current direction: "+primaryPointerInput.liveParameters.vector.direction),!1)}recognize(contact){var isValid=this.validate(contact);1==isValid&&0==this.isActive&&this.state==GESTURE_STATE_POSSIBLE&&this.onStart(contact),1==isValid&&1==this.isActive&&this.state==GESTURE_STATE_POSSIBLE?this.emit(contact):1==this.isActive&&0==isValid&&this.onEnd(contact)}block(gesture){-1==this.options.blocks.indexOf(gesture)&&this.options.blocks.push(gesture)}unblock(gesture){-1!=this.options.blocks.indexOf(gesture)&&this.options.blocks.splice(this.options.blocks.indexOf(gesture),1)}blockGestures(){for(let g=0;gthis.initialMinMaxParameters.distance[1]&&(this.hasBeenInvalidatedForContactId=contact.id),1==isValid&&0==this.hasBeenEmitted&&null==this.hasBeenInvalidatedForContactId)this.initialPointerEvent=contact.currentPointerEvent,this.emit(contact),this.hasBeenEmitted=!0;else{let duration=primaryPointerInput.globalParameters.duration;1==this.hasBeenEmitted&&duration<=this.initialMinMaxParameters.duration[0]&&(this.hasBeenEmitted=!1)}}}class MultiPointerGesture extends Gesture{constructor(domElement,options){super(domElement,options=options||{}),this.boolParameters={requiresPointerMove:null,requiresActivePointer:null},this.initialMinMaxParameters={pointerCount:[2,null]},this.activeStateMinMaxParameters={pointerCount:[2,null]},this.options=options||{}}}class TwoPointerGesture extends MultiPointerGesture{constructor(domElement,options){super(domElement,options=options||{}),this.boolParameters.requiresPointerMove=!0,this.boolParameters.requiresActivePointer=!0,this.initialMinMaxParameters.pointerCount=[2,2],this.initialMinMaxParameters.centerMovement=[null,null],this.initialMinMaxParameters.distanceChange=[null,null],this.initialMinMaxParameters.rotationAngle=[null,null],this.initialMinMaxParameters.vectorAngle=[null,null],this.activeStateMinMaxParameters.pointerCount=[2,2],this.activeStateMinMaxParameters.centerMovement=[null,null],this.activeStateMinMaxParameters.distanceChange=[null,null],this.activeStateMinMaxParameters.rotationAngle=[null,null],this.activeStateMinMaxParameters.vectorAngle=[null,null]}getMinMaxParameters(contact){var minMaxParameters=super.getMinMaxParameters(contact);return minMaxParameters.centerMovement=contact.multipointer.liveParameters.centerMovement,minMaxParameters.distanceChange=Math.abs(contact.multipointer.liveParameters.distanceChange),minMaxParameters.rotationAngle=Math.abs(contact.multipointer.liveParameters.rotationAngle),minMaxParameters.vectorAngle=contact.multipointer.liveParameters.vectorAngle,minMaxParameters}getEventData(contact){var eventData=super.getEventData(contact),globalDuration=contact.currentPointerEvent.timeStamp-this.initialPointerEvent.timeStamp,globalParameters=contact.multipointer.globalParameters,liveParameters=contact.multipointer.liveParameters;return eventData.global={deltaX:globalParameters.centerMovementVector.x,deltaY:globalParameters.centerMovementVector.y,distance:globalParameters.centerMovement,speedX:globalParameters.centerMovementVector.x/globalDuration,speedY:globalParameters.centerMovementVector.y/globalDuration,speed:globalParameters.centerMovementVector.vectorLength/globalDuration,direction:globalParameters.centerMovementVector.direction,scale:globalParameters.relativeDistanceChange,rotation:globalParameters.rotationAngle,srcEvent:contact.currentPointerEvent},eventData.live={deltaX:liveParameters.centerMovementVector.x,deltaY:liveParameters.centerMovementVector.y,distance:liveParameters.centerMovement,speedX:liveParameters.centerMovementVector.x/globalDuration,speedY:liveParameters.centerMovementVector.y/globalDuration,speed:liveParameters.centerMovementVector.vectorLength/globalDuration,direction:liveParameters.centerMovementVector.direction,scale:liveParameters.relativeDistanceChange,rotation:liveParameters.rotationAngle,center:{x:liveParameters.centerMovementVector.startPoint.x,y:liveParameters.centerMovementVector.startPoint.y},srcEvent:contact.currentPointerEvent},eventData}}class Pinch extends TwoPointerGesture{constructor(domElement,options){super(domElement,options=options||{}),this.initialMinMaxParameters.centerMovement=[0,50],this.initialMinMaxParameters.distanceChange=[5,null],this.initialMinMaxParameters.rotationAngle=[null,20],this.initialMinMaxParameters.vectorAngle=[10,null]}}class Rotate extends TwoPointerGesture{constructor(domElement,options){super(domElement,options=options||{}),this.initialMinMaxParameters.centerMovement=[0,50],this.initialMinMaxParameters.distanceChange=[null,50],this.initialMinMaxParameters.rotationAngle=[5,null]}}class TwoFingerPan extends TwoPointerGesture{constructor(domElement,options){super(domElement,options=options||{}),this.initialMinMaxParameters.centerMovement=[3,null],this.initialMinMaxParameters.distanceChange=[null,50],this.initialMinMaxParameters.rotationAngle=[null,null],this.initialMinMaxParameters.vectorAngle=[null,150]}}var ALL_GESTURE_CLASSES=[Tap,Press,Pan,Pinch,Rotate,TwoFingerPan];class PointerListener{constructor(domElement,options){this.DEBUG=!1,this.eventHandlers={},this.lastRecognitionTimestamp=null,this.idleRecognitionIntervalId=null,this.pointerEventHandlers={},this.touchEventHandlers={},options=options||{},this.options={bubbles:!0,handleTouchEvents:!1};for(let key in options)"supportedGestures"!=key&&(this.options[key]=options[key]);var supportedGestures=ALL_GESTURE_CLASSES,instantiatedGestures=[];1==Object.prototype.hasOwnProperty.call(options,"supportedGestures")&&(supportedGestures=options.supportedGestures);for(let i=0;i100)&&(this.contact.onIdle(),1==this.DEBUG&&console.log("[PointerListener] run idle recognition"),this.recognizeGestures())}}clearIdleRecognitionInterval(){null!=this.idleRecognitionIntervalId&&(clearInterval(this.idleRecognitionIntervalId),this.idleRecognitionIntervalId=null)}recognizeGestures(){this.lastRecognitionTimestamp=(new Date).getTime();for(let g=0;g=0&&(handlerReferences.splice(index,1),this.eventHandlers[eventType]=handlerReferences),this.domElement.removeEventListener(eventType,handlerReference,!1)}}}destroy(){for(let event in this.eventHandlers){let handlerList=this.eventHandlers[event];for(let h=0;h0&&(isActive=!0),this.isActive=isActive,0==this.isActive?this.endTimestamp=this.currentTimestamp:Object.keys(this.activePointerInputs).length>=2&&this.updateMultipointerParameters()}updateMultipointerParameters(){var multiPointerInputs=this.getMultiPointerInputs(),pointerInput_1=multiPointerInputs[0],pointerInput_2=multiPointerInputs[1],vector_1=pointerInput_1.liveParameters.vector,vector_2=pointerInput_2.liveParameters.vector;if(null!=vector_1&&null!=vector_2){var currentCenter=getCenter(vector_1.startPoint,vector_2.startPoint);this.multipointer.liveParameters.center=currentCenter;var centerMovementVector=this.calculateCenterMovement(vector_1,vector_2);this.multipointer.liveParameters.centerMovementVector=centerMovementVector,this.multipointer.liveParameters.centerMovement=centerMovementVector.vectorLength;var liveDistanceChange=this.calculateDistanceChange(vector_1,vector_2);this.multipointer.liveParameters.distanceChange=liveDistanceChange.absolute,this.multipointer.liveParameters.relativeDistanceChange=liveDistanceChange.relative;var liveRotationAngle=this.calculateRotationAngle(vector_1,vector_2);this.multipointer.liveParameters.rotationAngle=liveRotationAngle;var liveVectorAngle=this.calculateVectorAngle(vector_1,vector_2);this.multipointer.liveParameters.vectorAngle=liveVectorAngle}var globalVector_1=pointerInput_1.globalParameters.vector,globalVector_2=pointerInput_2.globalParameters.vector;if(null!=globalVector_1&&null!=globalVector_2){var globalCenter=getCenter(globalVector_1.startPoint,globalVector_2.startPoint);this.multipointer.globalParameters.center=globalCenter;var globalCenterMovementVector=this.calculateCenterMovement(globalVector_1,globalVector_2);this.multipointer.globalParameters.centerMovementVector=globalCenterMovementVector,this.multipointer.globalParameters.centerMovement=globalCenterMovementVector.vectorLength;var globalDistanceChange=this.calculateDistanceChange(globalVector_1,globalVector_2);this.multipointer.globalParameters.distanceChange=globalDistanceChange.absolute,this.multipointer.globalParameters.relativeDistanceChange=globalDistanceChange.relative;var globalRotationAngle=this.calculateRotationAngle(globalVector_1,globalVector_2);this.multipointer.globalParameters.rotationAngle=globalRotationAngle;var globalVectorAngle=this.calculateVectorAngle(globalVector_1,globalVector_2);this.multipointer.liveParameters.vectorAngle=globalVectorAngle}!0===this.DEBUG&&(console.log("[Contact] 2 fingers: centerMovement between pointer #"+pointerInput_1.pointerId+" and pointer #"+pointerInput_2.pointerId+" : "+this.multipointer.liveParameters.centerMovement+"px"),console.log("[Contact] 2 fingers: distanceChange: between pointer #"+pointerInput_1.pointerId+" and pointer #"+pointerInput_2.pointerId+" : "+this.multipointer.liveParameters.distanceChange+"px"),console.log("[Contact] 2 fingers live angle: "+this.multipointer.liveParameters.rotationAngle+"deg"),console.log("[Contact] 2 fingers global angle: "+this.multipointer.globalParameters.rotationAngle+"deg"))}calculateCenterMovement(vector_1,vector_2){var startPoint=getCenter(vector_1.startPoint,vector_2.startPoint),endPoint=getCenter(vector_1.endPoint,vector_2.endPoint);return new Vector(startPoint,endPoint)}calculateDistanceChange(vector_1,vector_2){var vectorBetweenStartPoints=new Vector(vector_1.startPoint,vector_2.startPoint),vectorBetweenEndPoints=new Vector(vector_1.endPoint,vector_2.endPoint);return{absolute:vectorBetweenEndPoints.vectorLength-vectorBetweenStartPoints.vectorLength,relative:vectorBetweenEndPoints.vectorLength/vectorBetweenStartPoints.vectorLength}}calculateRotationAngle(vector_1,vector_2){var angleVector_1=new Vector(vector_1.startPoint,vector_2.startPoint),angleVector_2=new Vector(vector_1.endPoint,vector_2.endPoint),origin=new Point(0,0),translationVector_1=new Vector(angleVector_1.startPoint,origin),translatedEndPoint_1=translatePoint(angleVector_1.endPoint,translationVector_1),translationVector_2=new Vector(angleVector_2.startPoint,origin),translatedEndPoint_2=translatePoint(angleVector_2.endPoint,translationVector_2),rotationAngle=-1*calcAngleRad(translatedEndPoint_1),x_2_rotated=translatedEndPoint_2.x*Math.cos(rotationAngle)-translatedEndPoint_2.y*Math.sin(rotationAngle),y_2_rotated=Math.round(translatedEndPoint_2.x*Math.sin(rotationAngle)+translatedEndPoint_2.y*Math.cos(rotationAngle));return 180*Math.atan2(y_2_rotated,x_2_rotated)/Math.PI}calculateVectorAngle(vector_1,vector_2){var angleDeg=null;if(vector_1.vectorLength>0&&vector_2.vectorLength>0){var cos=(vector_1.x*vector_2.x+vector_1.y*vector_2.y)/(vector_1.vectorLength*vector_2.vectorLength);angleDeg=rad2deg(Math.acos(cos))}return angleDeg}}class PointerInput{constructor(pointerdownEvent,options){options=options||{},this.options={DEBUG:!1};for(let key in options)this.options[key]=options[key];this.DEBUG=this.options.DEBUG;var now=(new Date).getTime();this.pointerId=pointerdownEvent.pointerId;var hasVectorTimespan=Object.prototype.hasOwnProperty.call(this.options,"vectorTimespan");this.vectorTimespan=1==hasVectorTimespan?this.options.vectorTimespan:100,this.initialPointerEvent=pointerdownEvent,this.currentPointerEvent=pointerdownEvent,this.recognizedEvents=[pointerdownEvent],this.canceled=!1,this.isActive=!0;var nullVector=this.getVector(pointerdownEvent,pointerdownEvent);this.liveParameters={vector:nullVector,speed:0,isMoving:!1},this.globalParameters={startX:this.initialPointerEvent.clientX,startY:this.initialPointerEvent.clientY,vector:nullVector,deltaX:0,deltaY:0,startTimestampUTC:now,startTimestamp:this.initialPointerEvent.timeStamp,currentTimestamp:this.initialPointerEvent.timeStamp,endTimestamp:null,maximumSpeed:0,averageSpeed:0,finalSpeed:null,traveledDistance:0,hasBeenMoved:!1,duration:0}}onIdle(){let duration=(new Date).getTime()-this.globalParameters.startTimestampUTC;this.globalParameters.duration=duration}onMove(pointermoveEvent){this.globalParameters.hasBeenMoved=!0,this.liveParameters.isMoving=!0,this.update(pointermoveEvent,!0)}onUp(pointerupEvent){this.globalParameters.finalSpeed=this.liveParameters.speed,this.liveParameters.currentSpeed=0,this.liveParameters.isMoving=!1,this.isActive=!1,this.globalParameters.endTimestamp=pointerupEvent.timeStamp,this.update(pointerupEvent),!0===this.DEBUG&&console.log("[Contact] pointerdown ended. pointerdown duration: "+this.globalParameters.duration+"ms")}onCancel(pointercancelEvent){this.update(pointercancelEvent),this.liveParameters.speed=0,this.canceled=!0,this.liveParameters.isMoving=!1,this.isActive=!1,this.globalParameters.endTimestamp=pointercancelEvent.timeStamp,!0===this.DEBUG&&console.log("[Contact] canceled, pointerdown duration:"+this.duration)}update(pointerEvent){this.currentPointerEvent=pointerEvent,this.recognizedEvents.push(pointerEvent);var timedPointerEvents=this.getTimedPointerEvents(),liveVector=this.getVector(timedPointerEvents[0],timedPointerEvents[1]);if(this.liveParameters.vector=liveVector,null!=liveVector){this.liveParameters.speed=this.getSpeed(liveVector,timedPointerEvents[0].timeStamp,timedPointerEvents[1].timeStamp),this.liveParameters.speed>this.globalParameters.maximumSpeed&&(this.globalParameters.maximumSpeed=this.liveParameters.speed),this.globalParameters.currentTimestamp=pointerEvent.timeStamp,this.globalParameters.duration=pointerEvent.timeStamp-this.globalParameters.startTimestamp,this.globalParameters.deltaX=liveVector.endPoint.x-this.globalParameters.startX,this.globalParameters.deltaY=liveVector.endPoint.y-this.globalParameters.startY;var globalVector=this.getVector(this.initialPointerEvent,this.currentPointerEvent);this.globalParameters.vector=globalVector,!0===this.DEBUG&&(console.log("[Contact] current speed: "+this.liveParameters.speed+"px/s"),console.log("[Contact] pointerdown duration: "+this.globalParameters.duration+"ms"),console.log("[Contact] live vector length within vectorTimespan: "+this.liveParameters.vector.vectorLength+"px"))}}getTimedPointerEvents(){for(var startPointerEvent=this.initialPointerEvent,endPointerEvent=this.recognizedEvents[this.recognizedEvents.length-1],startIndex=this.recognizedEvents.length-1,elapsedTime=0,endTimeStamp=endPointerEvent.timeStamp;elapsedTimeMath.abs(this.deltaY)?this.startPoint.xmaxValue)||(1==this.DEBUG&&console.log("dismissing max"+this.eventBaseName+": required "+parameterName+": "+maxValue+", current value: "+value),!1)}validateBool(parameterName,value){var requiredValue=this.boolParameters[parameterName];return null!=requiredValue&&null!=value&&requiredValue===value||(null==requiredValue||(1==this.DEBUG&&console.log("[Gestures] dismissing "+this.eventBaseName+": "+parameterName+" required: "+requiredValue+", actual value: "+value),!1))}getMinMaxParameters(contact){var primaryPointerInput=contact.getPrimaryPointerInput();return{pointerCount:Object.keys(contact.activePointerInputs).length,duration:primaryPointerInput.globalParameters.duration,currentSpeed:primaryPointerInput.liveParameters.speed,averageSpeed:primaryPointerInput.globalParameters.averageSpeed,finalSpeed:primaryPointerInput.globalParameters.finalSpeed,distance:primaryPointerInput.liveParameters.vector.vectorLength}}getBoolParameters(contact){var primaryPointerInput=contact.getPrimaryPointerInput();return{requiresPointerUp:!1===primaryPointerInput.isActive,requiresActivePointer:!0===primaryPointerInput.isActive,requiresPointerMove:!0===primaryPointerInput.globalParameters.hasBeenMoved}}validate(contact){if(this.state==GESTURE_STATE_BLOCKED)return!1;var primaryPointerInput=contact.getPrimaryPointerInput();1==this.DEBUG&&console.log("[Gestures] running recognition for "+this.eventBaseName);var contactBoolParameters=this.getBoolParameters(contact);for(let boolParameterName in this.boolParameters){let boolValue=contactBoolParameters[boolParameterName];if(0==this.validateBool(boolParameterName,boolValue))return!1}var minMaxParameters,contactMinMaxParameters=this.getMinMaxParameters(contact);minMaxParameters=1==this.isActive?this.activeStateMinMaxParameters:this.initialMinMaxParameters;for(let minMaxParameterName in minMaxParameters){let value=contactMinMaxParameters[minMaxParameterName];if(0==this.validateMinMax(minMaxParameters,minMaxParameterName,value))return!1}return!(1==Object.prototype.hasOwnProperty.call(this.options,"supportedDirections")&&this.options.supportedDirections.length>0&&-1==this.options.supportedDirections.indexOf(primaryPointerInput.liveParameters.vector.direction))||(1==this.DEBUG&&console.log("[Gestures] dismissing "+this.eventBaseName+": supported directions: "+this.options.supportedDirections+", current direction: "+primaryPointerInput.liveParameters.vector.direction),!1)}recognize(contact){var isValid=this.validate(contact);1==isValid&&0==this.isActive&&this.state==GESTURE_STATE_POSSIBLE&&this.onStart(contact),1==isValid&&1==this.isActive&&this.state==GESTURE_STATE_POSSIBLE?this.emit(contact):1==this.isActive&&0==isValid&&this.onEnd(contact)}block(gesture){-1==this.options.blocks.indexOf(gesture)&&this.options.blocks.push(gesture)}unblock(gesture){-1!=this.options.blocks.indexOf(gesture)&&this.options.blocks.splice(this.options.blocks.indexOf(gesture),1)}blockGestures(){for(let g=0;gthis.initialMinMaxParameters.distance[1]&&(this.hasBeenInvalidatedForContactId=contact.id),1==isValid&&0==this.hasBeenEmitted&&null==this.hasBeenInvalidatedForContactId)this.initialPointerEvent=contact.currentPointerEvent,this.emit(contact),this.hasBeenEmitted=!0;else{let duration=primaryPointerInput.globalParameters.duration;1==this.hasBeenEmitted&&duration<=this.initialMinMaxParameters.duration[0]&&(this.hasBeenEmitted=!1)}}}class MultiPointerGesture extends Gesture{constructor(domElement,options){super(domElement,options=options||{}),this.boolParameters={requiresPointerMove:null,requiresActivePointer:null},this.initialMinMaxParameters={pointerCount:[2,null]},this.activeStateMinMaxParameters={pointerCount:[2,null]},this.options=options||{}}}class TwoPointerGesture extends MultiPointerGesture{constructor(domElement,options){super(domElement,options=options||{}),this.boolParameters.requiresPointerMove=!0,this.boolParameters.requiresActivePointer=!0,this.initialMinMaxParameters.pointerCount=[2,2],this.initialMinMaxParameters.centerMovement=[null,null],this.initialMinMaxParameters.distanceChange=[null,null],this.initialMinMaxParameters.rotationAngle=[null,null],this.initialMinMaxParameters.vectorAngle=[null,null],this.activeStateMinMaxParameters.pointerCount=[2,2],this.activeStateMinMaxParameters.centerMovement=[null,null],this.activeStateMinMaxParameters.distanceChange=[null,null],this.activeStateMinMaxParameters.rotationAngle=[null,null],this.activeStateMinMaxParameters.vectorAngle=[null,null]}getMinMaxParameters(contact){var minMaxParameters=super.getMinMaxParameters(contact);return minMaxParameters.centerMovement=contact.multipointer.liveParameters.centerMovement,minMaxParameters.distanceChange=Math.abs(contact.multipointer.liveParameters.distanceChange),minMaxParameters.rotationAngle=Math.abs(contact.multipointer.liveParameters.rotationAngle),minMaxParameters.vectorAngle=contact.multipointer.liveParameters.vectorAngle,minMaxParameters}getEventData(contact){var eventData=super.getEventData(contact),globalDuration=contact.currentPointerEvent.timeStamp-this.initialPointerEvent.timeStamp,globalParameters=contact.multipointer.globalParameters,liveParameters=contact.multipointer.liveParameters;return eventData.global={deltaX:globalParameters.centerMovementVector.x,deltaY:globalParameters.centerMovementVector.y,distance:globalParameters.centerMovement,speedX:globalParameters.centerMovementVector.x/globalDuration,speedY:globalParameters.centerMovementVector.y/globalDuration,speed:globalParameters.centerMovementVector.vectorLength/globalDuration,direction:globalParameters.centerMovementVector.direction,scale:globalParameters.relativeDistanceChange,rotation:globalParameters.rotationAngle,srcEvent:contact.currentPointerEvent},eventData.live={deltaX:liveParameters.centerMovementVector.x,deltaY:liveParameters.centerMovementVector.y,distance:liveParameters.centerMovement,speedX:liveParameters.centerMovementVector.x/globalDuration,speedY:liveParameters.centerMovementVector.y/globalDuration,speed:liveParameters.centerMovementVector.vectorLength/globalDuration,direction:liveParameters.centerMovementVector.direction,scale:liveParameters.relativeDistanceChange,rotation:liveParameters.rotationAngle,center:{x:liveParameters.centerMovementVector.startPoint.x,y:liveParameters.centerMovementVector.startPoint.y},srcEvent:contact.currentPointerEvent},eventData}}class Pinch extends TwoPointerGesture{constructor(domElement,options){super(domElement,options=options||{}),this.eventBaseName="pinch",this.initialMinMaxParameters.centerMovement=[0,50],this.initialMinMaxParameters.distanceChange=[5,null],this.initialMinMaxParameters.rotationAngle=[null,20],this.initialMinMaxParameters.vectorAngle=[10,null]}}class Rotate extends TwoPointerGesture{constructor(domElement,options){super(domElement,options=options||{}),this.eventBaseName="rotate",this.initialMinMaxParameters.centerMovement=[0,50],this.initialMinMaxParameters.distanceChange=[null,50],this.initialMinMaxParameters.rotationAngle=[5,null]}}class TwoFingerPan extends TwoPointerGesture{constructor(domElement,options){super(domElement,options=options||{}),this.eventBaseName="twofingerpan",this.initialMinMaxParameters.centerMovement=[3,null],this.initialMinMaxParameters.distanceChange=[null,50],this.initialMinMaxParameters.rotationAngle=[null,null],this.initialMinMaxParameters.vectorAngle=[null,150]}}var ALL_GESTURE_CLASSES=[Tap,Press,Pan,Pinch,Rotate,TwoFingerPan];class PointerListener{constructor(domElement,options){this.eventHandlers={},this.lastRecognitionTimestamp=null,this.idleRecognitionIntervalId=null,this.pointerEventHandlers={},this.touchEventHandlers={},options=options||{},this.options={bubbles:!0,handleTouchEvents:!0,DEBUG:!1,DEBUG_GESTURES:!1,DEBUG_CONTACT:!1};for(let key in options)"supportedGestures"!=key&&(this.options[key]=options[key]);this.DEBUG=this.options.DEBUG;var supportedGestures=ALL_GESTURE_CLASSES,instantiatedGestures=[];1==Object.prototype.hasOwnProperty.call(options,"supportedGestures")&&(supportedGestures=options.supportedGestures);for(let i=0;i100)&&(this.contact.onIdle(),1==this.DEBUG&&console.log("[PointerListener] onIdle - running idle recognition"),this.recognizeGestures())}}clearIdleRecognitionInterval(){null!=this.idleRecognitionIntervalId&&(clearInterval(this.idleRecognitionIntervalId),this.idleRecognitionIntervalId=null)}recognizeGestures(){this.lastRecognitionTimestamp=(new Date).getTime();for(let g=0;g=0&&(handlerReferences.splice(index,1),this.eventHandlers[eventType]=handlerReferences),this.domElement.removeEventListener(eventType,handlerReference,!1)}}}destroy(){for(let event in this.eventHandlers){let handlerList=this.eventHandlers[event];for(let h=0;h maxValue){ if (this.DEBUG == true){ - console.log("dismissing max" + this.constructor.name + ": required " + parameterName + ": " + maxValue + ", current value: " + value); + console.log("dismissing max" + this.eventBaseName + ": required " + parameterName + ": " + maxValue + ", current value: " + value); } return false; @@ -897,7 +919,7 @@ class Gesture { } if (this.DEBUG == true){ - console.log("[Gestures] dismissing " + this.constructor.name + ": " + parameterName + " required: " + requiredValue + ", actual value: " + value); + console.log("[Gestures] dismissing " + this.eventBaseName + ": " + parameterName + " required: " + requiredValue + ", actual value: " + value); } return false; @@ -947,7 +969,7 @@ class Gesture { var primaryPointerInput = contact.getPrimaryPointerInput(); if (this.DEBUG == true){ - console.log("[Gestures] running recognition for " + this.constructor.name); + console.log("[Gestures] running recognition for " + this.eventBaseName); } @@ -988,7 +1010,7 @@ class Gesture { if (this.options.supportedDirections.indexOf(primaryPointerInput.liveParameters.vector.direction) == -1){ if (this.DEBUG == true){ - console.log("[Gestures] dismissing " + this.constructor.name + ": supported directions: " + this.options.supportedDirections + ", current direction: " + primaryPointerInput.liveParameters.vector.direction); + console.log("[Gestures] dismissing " + this.eventBaseName + ": supported directions: " + this.options.supportedDirections + ", current direction: " + primaryPointerInput.liveParameters.vector.direction); } return false; @@ -1036,7 +1058,7 @@ class Gesture { let gesture = this.options.blocks[g]; if (gesture.isActive == false) { if (this.DEBUG == false){ - console.log("[Gesture] blocking " + gesture.constructor.name); + console.log("[Gesture] blocking " + gesture.eventBaseName); } gesture.state = GESTURE_STATE_BLOCKED; } @@ -1069,7 +1091,7 @@ class Gesture { emit (contact, eventName) { // fire general event like "pan" , "pinch", "rotate" - eventName = eventName || this.constructor.name.toLowerCase(); + eventName = eventName || this.eventBaseName; if (this.DEBUG === true){ console.log("[Gestures] detected and firing event " + eventName); @@ -1134,7 +1156,7 @@ class Gesture { this.initialPointerEvent = contact.currentPointerEvent; - var eventName = "" + this.constructor.name.toLowerCase() + "start"; + var eventName = "" + this.eventBaseName + "start"; if (this.DEBUG === true) { console.log("[Gestures] firing event: " + eventName); @@ -1156,7 +1178,7 @@ class Gesture { this.isActive = false; - var eventName = "" + this.constructor.name.toLowerCase() + "end"; + var eventName = "" + this.eventBaseName + "end"; if (this.DEBUG === true) { console.log("[Gestures] firing event: " + eventName); @@ -1265,6 +1287,8 @@ class Pan extends SinglePointerGesture { options = options || {}; super(domElement, options); + + this.eventBaseName = "pan"; this.initialMinMaxParameters["pointerCount"] = [1,1]; // 1: no pan recognized at the pointerup event. 0: pan recognized at pointerup this.initialMinMaxParameters["duration"] = [0, null]; @@ -1352,6 +1376,8 @@ class Tap extends SinglePointerGesture { options = options || {}; super(domElement, options); + + this.eventBaseName = "tap"; this.initialMinMaxParameters["pointerCount"] = [0,0]; // count of fingers touching the surface. a tap is fired AFTER the user removed his finger this.initialMinMaxParameters["duration"] = [0, 200]; // milliseconds. after a certain touch duration, it is not a TAP anymore @@ -1390,6 +1416,8 @@ class Press extends SinglePointerGesture { options = options || {}; super(domElement, options); + + this.eventBaseName = "press"; this.initialMinMaxParameters["pointerCount"] = [1, 1]; // count of fingers touching the surface. a press is fired during an active contact this.initialMinMaxParameters["duration"] = [600, null]; // milliseconds. after a certain touch duration, it is not a TAP anymore @@ -1592,6 +1620,8 @@ class Pinch extends TwoPointerGesture { options = options || {}; super(domElement, options); + + this.eventBaseName = "pinch"; this.initialMinMaxParameters["centerMovement"] = [0, 50]; //px this.initialMinMaxParameters["distanceChange"] = [5, null]; // distance between 2 fingers @@ -1617,6 +1647,8 @@ class Rotate extends TwoPointerGesture { options = options || {}; super(domElement, options); + + this.eventBaseName = "rotate"; this.initialMinMaxParameters["centerMovement"] = [0, 50]; this.initialMinMaxParameters["distanceChange"] = [null, 50]; @@ -1637,6 +1669,8 @@ class TwoFingerPan extends TwoPointerGesture { options = options || {}; super(domElement, options); + + this.eventBaseName = "twofingerpan"; this.initialMinMaxParameters["centerMovement"] = [3, null]; this.initialMinMaxParameters["distanceChange"] = [null, 50]; @@ -1665,8 +1699,6 @@ var ALL_GESTURE_CLASSES = [Tap, Press, Pan, Pinch, Rotate, TwoFingerPan]; class PointerListener { constructor (domElement, options){ - - this.DEBUG = false; // registry for events like "pan", "rotate", which have to be removed on this.destroy(); this.eventHandlers = {}; @@ -1681,7 +1713,10 @@ class PointerListener { this.options = { "bubbles" : true, - "handleTouchEvents" : false + "handleTouchEvents" : true, + "DEBUG" : false, + "DEBUG_GESTURES" : false, + "DEBUG_CONTACT" : false }; // add user-defined options to this.options @@ -1693,6 +1728,8 @@ class PointerListener { this.options[key] = options[key]; } + this.DEBUG = this.options.DEBUG; + // add instantiatedGestures to options.supportedGestures var supportedGestures = ALL_GESTURE_CLASSES; var instantiatedGestures = []; @@ -1708,7 +1745,8 @@ class PointerListener { let gesture; let GestureClass = supportedGestures[i]; let gestureOptions = { - bubbles : this.options.bubbles + "bubbles" : this.options.bubbles, + "DEBUG" : this.options.DEBUG_GESTURES }; if (typeof GestureClass == "function"){ @@ -1751,13 +1789,20 @@ class PointerListener { // javascript fires the events "pointerdown", "pointermove", "pointerup" and "pointercancel" // on each of these events, the contact instance is updated and GestureRecognizers of this.supported_events are run var onPointerDown = function (event) { + + if (self.DEBUG == true){ + console.log("[PointerListener] pointerdown event detected"); + } // re-target all pointerevents to the current element // see https://developer.mozilla.org/en-US/docs/Web/API/Element/setPointerCapture domElement.setPointerCapture(event.pointerId); if (self.contact == null || self.contact.isActive == false) { - self.contact = new Contact(event); + let contactOptions = { + "DEBUG" : self.options.DEBUG_CONTACT + }; + self.contact = new Contact(event, contactOptions); } else { // use existing contact instance if a second pointer becomes present @@ -1801,6 +1846,10 @@ class PointerListener { } var onPointerUp = function (event) { + + if (self.DEBUG == true){ + console.log("[PointerListener] pointerup event detected"); + } domElement.releasePointerCapture(event.pointerId); @@ -1829,6 +1878,10 @@ class PointerListener { * MDN: Pointer capture allows events for a particular pointer event (PointerEvent) to be re-targeted to a particular element instead of the normal (or hit test) target at a pointer's location. This can be used to ensure that an element continues to receive pointer events even if the pointer device's contact moves off the element (such as by scrolling or panning). */ var onPointerLeave = function (event) { + + if (self.DEBUG == true){ + console.log("[PointerListener] pointerleave detected"); + } if (self.contact != null && self.contact.isActive == true){ self.contact.onPointerLeave(event); @@ -1842,7 +1895,7 @@ class PointerListener { domElement.releasePointerCapture(event.pointerId); - if (this.DEBUG == true){ + if (self.DEBUG == true){ console.log("[PointerListener] pointercancel detected"); } @@ -1935,10 +1988,6 @@ class PointerListener { // to recognize Press, recognition has to be run if the user does nothing while having contact with the surfave (no pointermove, no pointerup, no pointercancel) onIdle () { - - if (this.DEBUG == true){ - console.log("[PointerListener] onIdle"); - } if (this.contact == null || this.contact.isActive == false){ this.clearIdleRecognitionInterval(); @@ -1957,7 +2006,7 @@ class PointerListener { this.contact.onIdle(); if (this.DEBUG == true){ - console.log("[PointerListener] run idle recognition"); + console.log("[PointerListener] onIdle - running idle recognition"); } this.recognizeGestures(); diff --git a/contactjs-1.3.1.tgz b/contactjs-1.3.1.tgz deleted file mode 100644 index 04346e7..0000000 Binary files a/contactjs-1.3.1.tgz and /dev/null differ diff --git a/contactjs-1.4.0.tgz b/contactjs-1.4.0.tgz new file mode 100644 index 0000000..9cb38da Binary files /dev/null and b/contactjs-1.4.0.tgz differ diff --git a/dist/contact.js b/dist/contact.js index a5b9d15..e4af025 100644 --- a/dist/contact.js +++ b/dist/contact.js @@ -1,6 +1,6 @@ "use strict"; -// contact.js - v1.3.1 +// contactjs - v1.4.0 const DIRECTION_NONE = "0"; const DIRECTION_LEFT = "left"; @@ -37,9 +37,21 @@ const GESTURE_STATE_BLOCKED = "blocked"; */ class Contact { - constructor (pointerdownEvent) { + constructor (pointerdownEvent, options) { - this.DEBUG = false; + options = options || {}; + + this.options = { + "DEBUG" : false + }; + + for (let key in options){ + this.options[key] = options[key]; + } + + this.DEBUG = this.options.DEBUG; + + this.id = new Date().getTime(); // a map of all active PointerInput instances this.pointerInputs = {}; @@ -67,14 +79,16 @@ class Contact { centerMovementVector : null, distanceChange : null, // px relativeDistanceChange : null, // % - rotationAngle : null //deg ccw[0,360], cw[0,-360] + rotationAngle : null, //deg ccw[0,360], cw[0,-360] + vectorAngle : null // angle between the 2 vectors performed by the pointer. This differs from rotationAngle }, globalParameters : { centerMovement : null, centerMovementVector : null, distanceChange : null, relativeDistanceChange: null, - rotationAngle : null + rotationAngle : null, + vectorAngle : null } }; @@ -84,8 +98,12 @@ class Contact { addPointer (pointerdownEvent) { this.currentPointerEvent = pointerdownEvent; + + var pointerInputOptions = { + "DEBUG" : this.DEBUG + }; - var pointerInput = new PointerInput(pointerdownEvent); + var pointerInput = new PointerInput(pointerdownEvent, pointerInputOptions); this.pointerInputs[pointerdownEvent.pointerId] = pointerInput; this.activePointerInputs[pointerdownEvent.pointerId] = pointerInput; } @@ -119,6 +137,21 @@ class Contact { return this.pointerInputs[this.primaryPointerId]; } + // currently, on 2 Inputs are supported + getMultiPointerInputs () { + + var pointerId_1 = Object.keys(this.activePointerInputs)[0]; + var pointerInput_1 = this.activePointerInputs[pointerId_1]; + + + var pointerId_2 = Object.keys(this.activePointerInputs)[1]; + var pointerInput_2 = this.activePointerInputs[pointerId_2]; + + var multiPointerInputs = [pointerInput_1, pointerInput_2]; + + return multiPointerInputs; + + } // pointermove contains only one single pointer, not multiple like on touch events (touches, changedTouches,...) onPointerMove (pointermoveEvent) { @@ -211,12 +244,10 @@ class Contact { // functions for multi pointer gestures, currently only 2 pointers are supported updateMultipointerParameters () { - var pointerId_1 = Object.keys(this.activePointerInputs)[0]; - var pointerInput_1 = this.activePointerInputs[pointerId_1]; - - - var pointerId_2 = Object.keys(this.activePointerInputs)[1]; - var pointerInput_2 = this.activePointerInputs[pointerId_2]; + var multiPointerInputs = this.getMultiPointerInputs() + + var pointerInput_1 = multiPointerInputs[0]; + var pointerInput_2 = multiPointerInputs[1]; var vector_1 = pointerInput_1.liveParameters.vector; var vector_2 = pointerInput_2.liveParameters.vector; @@ -236,8 +267,13 @@ class Contact { // calculate rotation angle. imagine the user turning a wheel with 2 fingers - var liveAngle = this.calculateAngle(vector_1, vector_2); - this.multipointer.liveParameters.rotationAngle = liveAngle; + var liveRotationAngle = this.calculateRotationAngle(vector_1, vector_2); + this.multipointer.liveParameters.rotationAngle = liveRotationAngle; + + // calculate the simple vectorAngle for determining if the fingers moved into the same direction + var liveVectorAngle = this.calculateVectorAngle(vector_1, vector_2) + this.multipointer.liveParameters.vectorAngle = liveVectorAngle; + } @@ -259,14 +295,17 @@ class Contact { this.multipointer.globalParameters.relativeDistanceChange = globalDistanceChange.relative; - var globalAngle = this.calculateAngle(globalVector_1, globalVector_2); - this.multipointer.globalParameters.rotationAngle = globalAngle; + var globalRotationAngle = this.calculateRotationAngle(globalVector_1, globalVector_2); + this.multipointer.globalParameters.rotationAngle = globalRotationAngle; + + var globalVectorAngle = this.calculateVectorAngle(globalVector_1, globalVector_2) + this.multipointer.liveParameters.vectorAngle = globalVectorAngle; } if (this.DEBUG === true){ - console.log("[Contact] 2 fingers: centerMovement between pointer #" + pointerId_1 + " and pointer #" + pointerId_2 + " : " + this.multipointer.liveParameters.centerMovement + "px"); - console.log("[Contact] 2 fingers: distanceChange: between pointer #" + pointerId_1 + " and pointer #" + pointerId_2 + " : " + this.multipointer.liveParameters.distanceChange + "px"); + console.log("[Contact] 2 fingers: centerMovement between pointer #" + pointerInput_1.pointerId + " and pointer #" + pointerInput_2.pointerId + " : " + this.multipointer.liveParameters.centerMovement + "px"); + console.log("[Contact] 2 fingers: distanceChange: between pointer #" + pointerInput_1.pointerId + " and pointer #" + pointerInput_2.pointerId + " : " + this.multipointer.liveParameters.distanceChange + "px"); console.log("[Contact] 2 fingers live angle: " + this.multipointer.liveParameters.rotationAngle + "deg"); console.log("[Contact] 2 fingers global angle: " + this.multipointer.globalParameters.rotationAngle + "deg"); } @@ -315,7 +354,7 @@ class Contact { * - if the wheel has been turned cw, its state has a positive angle * - possible values for the angle: [-360,360] */ - calculateAngle (vector_1, vector_2) { + calculateRotationAngle (vector_1, vector_2) { // vector_ are vectors between 2 points in time, same finger // angleAector_ are vectors between 2 fingers @@ -375,6 +414,22 @@ class Contact { return angleDeg; } + + calculateVectorAngle (vector_1, vector_2) { + + var angleDeg = null; + + if (vector_1.vectorLength > 0 && vector_2.vectorLength > 0){ + + var cos = ( (vector_1.x * vector_2.x) + (vector_1.y * vector_2.y) ) / (vector_1.vectorLength * vector_2.vectorLength); + + var angleRad = Math.acos(cos); + angleDeg = rad2deg(angleRad); + + } + + return angleDeg; + } } @@ -396,15 +451,23 @@ class PointerInput { constructor (pointerdownEvent, options) { - this.DEBUG = false; - options = options || {}; + + this.options = { + "DEBUG" : false + }; + + for (let key in options){ + this.options[key] = options[key]; + } + + this.DEBUG = this.options.DEBUG; var now = new Date().getTime(); this.pointerId = pointerdownEvent.pointerId; - var hasVectorTimespan = Object.prototype.hasOwnProperty.call(options, "vectorTimespan"); - this.vectorTimespan = hasVectorTimespan == true ? options.vectorTimespan : 100; // milliseconds + var hasVectorTimespan = Object.prototype.hasOwnProperty.call(this.options, "vectorTimespan"); + this.vectorTimespan = hasVectorTimespan == true ? this.options.vectorTimespan : 100; // milliseconds // events used for vector calculation this.initialPointerEvent = pointerdownEvent; @@ -662,7 +725,7 @@ class Vector { this.y = this.deltaY; // determine length - this.vectorLength = Math.sqrt( Math.pow(this.deltaX,2) + Math.pow(this.deltaY, 2) ); + this.vectorLength = Math.sqrt( Math.pow(this.deltaX, 2) + Math.pow(this.deltaY, 2) ); // determine direction if (Math.abs(this.deltaX) > Math.abs(this.deltaY)){ @@ -758,8 +821,6 @@ function calcAngleRad (point) { class Gesture { constructor (domElement, options){ - - this.DEBUG = false; this.domElement = domElement; @@ -798,7 +859,9 @@ class Gesture { } let defaultOptions = { - "bubbles" : true + "bubbles" : true, + "blocks" : [], + "DEBUG" : false }; this.options = options || {}; @@ -808,6 +871,8 @@ class Gesture { this.options[key] = defaultOptions[key]; } } + + this.DEBUG = this.options.DEBUG; } @@ -824,7 +889,7 @@ class Gesture { if (minValue != null && value != null && value < minValue){ if (this.DEBUG == true){ - console.log("dismissing min" + this.constructor.name + ": required " + parameterName + ": " + minValue + ", current value: " + value); + console.log("dismissing min" + this.eventBaseName + ": required " + parameterName + ": " + minValue + ", current value: " + value); } return false; @@ -833,7 +898,7 @@ class Gesture { if (maxValue != null && value != null && value > maxValue){ if (this.DEBUG == true){ - console.log("dismissing max" + this.constructor.name + ": required " + parameterName + ": " + maxValue + ", current value: " + value); + console.log("dismissing max" + this.eventBaseName + ": required " + parameterName + ": " + maxValue + ", current value: " + value); } return false; @@ -856,7 +921,7 @@ class Gesture { } if (this.DEBUG == true){ - console.log("[Gestures] dismissing " + this.constructor.name + ": " + parameterName + " required: " + requiredValue + ", actual value: " + value); + console.log("[Gestures] dismissing " + this.eventBaseName + ": " + parameterName + " required: " + requiredValue + ", actual value: " + value); } return false; @@ -898,11 +963,15 @@ class Gesture { validate (contact){ var isValid = false; + + if (this.state == GESTURE_STATE_BLOCKED) { + return false; + } var primaryPointerInput = contact.getPrimaryPointerInput(); if (this.DEBUG == true){ - console.log("[Gestures] running recognition for " + this.constructor.name); + console.log("[Gestures] running recognition for " + this.eventBaseName); } @@ -943,7 +1012,7 @@ class Gesture { if (this.options.supportedDirections.indexOf(primaryPointerInput.liveParameters.vector.direction) == -1){ if (this.DEBUG == true){ - console.log("[Gestures] dismissing " + this.constructor.name + ": supported directions: " + this.options.supportedDirections + ", current direction: " + primaryPointerInput.liveParameters.vector.direction); + console.log("[Gestures] dismissing " + this.eventBaseName + ": supported directions: " + this.options.supportedDirections + ", current direction: " + primaryPointerInput.liveParameters.vector.direction); } return false; @@ -959,19 +1028,51 @@ class Gesture { var isValid = this.validate(contact); - if (isValid == true && this.isActive == false){ + if (isValid == true && this.isActive == false && this.state == GESTURE_STATE_POSSIBLE){ this.onStart(contact); } - if (isValid == true && this.state == GESTURE_STATE_POSSIBLE){ + if (isValid == true && this.isActive == true && this.state == GESTURE_STATE_POSSIBLE){ this.emit(contact); } else if (this.isActive == true && isValid == false){ this.onEnd(contact); + } } + + block (gesture) { + if (this.options.blocks.indexOf(gesture) == -1){ + this.options.blocks.push(gesture); + } + } + + unblock (gesture) { + if (this.options.blocks.indexOf(gesture) != -1){ + this.options.blocks.splice(this.options.blocks.indexOf(gesture), 1); + } + } + + blockGestures () { + for (let g=0; g this.initialMinMaxParameters["distance"][1]){ + this.hasBeenInvalidatedForContactId = contact.id; + } + } + + if (isValid == true && this.hasBeenEmitted == false && this.hasBeenInvalidatedForContactId == null){ this.initialPointerEvent = contact.currentPointerEvent; @@ -1345,7 +1477,6 @@ class Press extends SinglePointerGesture { } else { - var primaryPointerInput = contact.getPrimaryPointerInput(); let duration = primaryPointerInput.globalParameters.duration; if (this.hasBeenEmitted == true && duration <= this.initialMinMaxParameters["duration"][0]){ @@ -1403,11 +1534,13 @@ class TwoPointerGesture extends MultiPointerGesture { this.initialMinMaxParameters["centerMovement"] = [null,null]; //px this.initialMinMaxParameters["distanceChange"] = [null, null]; //px - distance between 2 fingers this.initialMinMaxParameters["rotationAngle"] = [null, null]; // degrees: positive = clockwise, negative = counter-clockwise (js convention, not mathematical convention) + this.initialMinMaxParameters["vectorAngle"] = [null, null]; this.activeStateMinMaxParameters["pointerCount"] = [2, 2]; this.activeStateMinMaxParameters["centerMovement"] = [null,null]; this.activeStateMinMaxParameters["distanceChange"] = [null, null]; this.activeStateMinMaxParameters["rotationAngle"] = [null, null]; + this.activeStateMinMaxParameters["vectorAngle"] = [null, null]; } @@ -1421,6 +1554,8 @@ class TwoPointerGesture extends MultiPointerGesture { minMaxParameters.rotationAngle = Math.abs(contact.multipointer.liveParameters.rotationAngle); + minMaxParameters.vectorAngle = contact.multipointer.liveParameters.vectorAngle; + return minMaxParameters; } @@ -1487,10 +1622,14 @@ class Pinch extends TwoPointerGesture { options = options || {}; super(domElement, options); + + this.eventBaseName = "pinch"; this.initialMinMaxParameters["centerMovement"] = [0, 50]; //px this.initialMinMaxParameters["distanceChange"] = [5, null]; // distance between 2 fingers this.initialMinMaxParameters["rotationAngle"] = [null, 20]; // distance between 2 fingers + this.initialMinMaxParameters["vectorAngle"] = [10, null]; + } @@ -1510,6 +1649,8 @@ class Rotate extends TwoPointerGesture { options = options || {}; super(domElement, options); + + this.eventBaseName = "rotate"; this.initialMinMaxParameters["centerMovement"] = [0, 50]; this.initialMinMaxParameters["distanceChange"] = [null, 50]; @@ -1520,6 +1661,9 @@ class Rotate extends TwoPointerGesture { } +/* +* 2 fingers are moved across the surface, in the same direction +*/ class TwoFingerPan extends TwoPointerGesture { constructor (domElement, options) { @@ -1527,10 +1671,13 @@ class TwoFingerPan extends TwoPointerGesture { options = options || {}; super(domElement, options); + + this.eventBaseName = "twofingerpan"; - this.initialMinMaxParameters["centerMovement"] = [5, null]; - this.initialMinMaxParameters["distanceChange"] = [null, 500]; + this.initialMinMaxParameters["centerMovement"] = [3, null]; + this.initialMinMaxParameters["distanceChange"] = [null, 50]; this.initialMinMaxParameters["rotationAngle"] = [null, null]; + this.initialMinMaxParameters["vectorAngle"] = [null, 150]; } @@ -1550,21 +1697,28 @@ class TwoFingerPan extends TwoPointerGesture { var ALL_GESTURE_CLASSES = [Tap, Press, Pan, Pinch, Rotate, TwoFingerPan]; + class PointerListener { constructor (domElement, options){ - - this.DEBUG = false; - - var self = this; + + // registry for events like "pan", "rotate", which have to be removed on this.destroy(); + this.eventHandlers = {}; this.lastRecognitionTimestamp = null; this.idleRecognitionIntervalId = null; + this.pointerEventHandlers = {}; + this.touchEventHandlers = {}; + options = options || {}; this.options = { - "bubbles" : true + "bubbles" : true, + "handleTouchEvents" : true, + "DEBUG" : false, + "DEBUG_GESTURES" : false, + "DEBUG_CONTACT" : false }; // add user-defined options to this.options @@ -1576,6 +1730,8 @@ class PointerListener { this.options[key] = options[key]; } + this.DEBUG = this.options.DEBUG; + // add instantiatedGestures to options.supportedGestures var supportedGestures = ALL_GESTURE_CLASSES; var instantiatedGestures = []; @@ -1591,7 +1747,8 @@ class PointerListener { let gesture; let GestureClass = supportedGestures[i]; let gestureOptions = { - bubbles : this.options.bubbles + "bubbles" : this.options.bubbles, + "DEBUG" : this.options.DEBUG_GESTURES }; if (typeof GestureClass == "function"){ @@ -1619,16 +1776,35 @@ class PointerListener { return false; });*/ - // javascript fires the events "pointerdown", "pointermove", "pointerup" and "pointercancel" - // on each of these events, the contact instance is updated and GestureRecognizers of this.supported_events are run - domElement.addEventListener("pointerdown", function(event){ + this.addPointerListeners(); + + this.addTouchListeners(); + } + + + addPointerListeners () { + + var self = this; + + var domElement = this.domElement; + // javascript fires the events "pointerdown", "pointermove", "pointerup" and "pointercancel" + // on each of these events, the contact instance is updated and GestureRecognizers of this.supported_events are run + var onPointerDown = function (event) { + + if (self.DEBUG == true){ + console.log("[PointerListener] pointerdown event detected"); + } + // re-target all pointerevents to the current element // see https://developer.mozilla.org/en-US/docs/Web/API/Element/setPointerCapture domElement.setPointerCapture(event.pointerId); if (self.contact == null || self.contact.isActive == false) { - self.contact = new Contact(event); + let contactOptions = { + "DEBUG" : self.options.DEBUG_CONTACT + }; + self.contact = new Contact(event, contactOptions); } else { // use existing contact instance if a second pointer becomes present @@ -1649,9 +1825,9 @@ class PointerListener { self.onIdle(); }, 100); - }, { "passive": true }); + } - domElement.addEventListener("pointermove", function(event){ + var onPointerMove = function (event) { // pointermove is also firing if the mouse button is not pressed @@ -1668,10 +1844,14 @@ class PointerListener { self.options.pointermove(event, self); } } - - }, { "passive": true }); - domElement.addEventListener("pointerup", function(event){ + } + + var onPointerUp = function (event) { + + if (self.DEBUG == true){ + console.log("[PointerListener] pointerup event detected"); + } domElement.releasePointerCapture(event.pointerId); @@ -1690,8 +1870,8 @@ class PointerListener { } self.clearIdleRecognitionInterval(); - - }); + + } /* * case: user presses mouse button and moves element. while moving, the cursor leaves the element (fires pointerout) @@ -1699,24 +1879,25 @@ class PointerListener { * during pan, pan should not end if the pointer leaves the element. * MDN: Pointer capture allows events for a particular pointer event (PointerEvent) to be re-targeted to a particular element instead of the normal (or hit test) target at a pointer's location. This can be used to ensure that an element continues to receive pointer events even if the pointer device's contact moves off the element (such as by scrolling or panning). */ + var onPointerLeave = function (event) { + + if (self.DEBUG == true){ + console.log("[PointerListener] pointerleave detected"); + } - domElement.addEventListener("pointerleave", function(event){ - if (self.contact != null && self.contact.isActive == true){ self.contact.onPointerLeave(event); self.recognizeGestures(); } self.clearIdleRecognitionInterval() - - }); - + } - domElement.addEventListener("pointercancel", function(event){ + var onPointerCancel = function (event) { domElement.releasePointerCapture(event.pointerId); - if (this.DEBUG == true){ + if (self.DEBUG == true){ console.log("[PointerListener] pointercancel detected"); } @@ -1731,12 +1912,32 @@ class PointerListener { if (hasPointerCancelHook == true){ self.options.pointercancel(event, self); } - - - }, { "passive": true }); + } - this.addTouchListeners(); + domElement.addEventListener("pointerdown", onPointerDown, { "passive": true }); + domElement.addEventListener("pointermove", onPointerMove, { "passive": true }); + domElement.addEventListener("pointerup", onPointerUp, { "passive": true }); + domElement.addEventListener("pointerleave", onPointerLeave, {"passive": true}); + domElement.addEventListener("pointercancel", onPointerCancel, { "passive": true }); + + this.pointerEventHandlers = { + "pointerdown" : onPointerDown, + "pointermove" : onPointerMove, + "pointerup" : onPointerUp, + "pointerleave" : onPointerLeave, + "pointercancel" : onPointerCancel + }; + + } + + removePointerListeners () { + + for (let event in this.pointerEventHandlers){ + let handler = this.pointerEventHandlers[event]; + this.domElement.removeEventListener(event, handler); + } + } // provide the ability to interact/prevent touch events @@ -1748,12 +1949,8 @@ class PointerListener { if (self.options.handleTouchEvents == true){ - /*this.domElement.addEventListener("touchstart", function(event){ - - });*/ - - this.domElement.addEventListener("touchmove", function(event){ - + + var onTouchMove = function (event) { // fire onTouchMove for all gestures for (let g=0; g= 0) { + handlerReferences.splice(index, 1); + + this.eventHandlers[eventType] = handlerReferences; + } + + this.domElement.removeEventListener(eventType, handlerReference, false); + + } + + } + } + + destroy () { + + // remove all EventListeners from self.domElement + for (let event in this.eventHandlers){ + let handlerList = this.eventHandlers[event]; + for (let h=0; h0&&(t=!0),this.isActive=t,0==this.isActive?this.endTimestamp=this.currentTimestamp:Object.keys(this.activePointerInputs).length>=2&&this.updateMultipointerParameters()}updateMultipointerParameters(){var t=Object.keys(this.activePointerInputs)[0],e=this.activePointerInputs[t],i=Object.keys(this.activePointerInputs)[1],n=this.activePointerInputs[i],r=e.liveParameters.vector,s=n.liveParameters.vector;if(null!=r&&null!=s){var a=getCenter(r.startPoint,s.startPoint);this.multipointer.liveParameters.center=a;var o=this.calculateCenterMovement(r,s);this.multipointer.liveParameters.centerMovementVector=o,this.multipointer.liveParameters.centerMovement=o.vectorLength;var l=this.calculateDistanceChange(r,s);this.multipointer.liveParameters.distanceChange=l.absolute,this.multipointer.liveParameters.relativeDistanceChange=l.relative;var c=this.calculateAngle(r,s);this.multipointer.liveParameters.rotationAngle=c}var u=e.globalParameters.vector,h=n.globalParameters.vector;if(null!=u&&null!=h){var p=getCenter(u.startPoint,h.startPoint);this.multipointer.globalParameters.center=p;var v=this.calculateCenterMovement(u,h);this.multipointer.globalParameters.centerMovementVector=v,this.multipointer.globalParameters.centerMovement=v.vectorLength;var m=this.calculateDistanceChange(u,h);this.multipointer.globalParameters.distanceChange=m.absolute,this.multipointer.globalParameters.relativeDistanceChange=m.relative;var d=this.calculateAngle(u,h);this.multipointer.globalParameters.rotationAngle=d}!0===this.DEBUG&&(console.log("[Contact] 2 fingers: centerMovement between pointer #"+t+" and pointer #"+i+" : "+this.multipointer.liveParameters.centerMovement+"px"),console.log("[Contact] 2 fingers: distanceChange: between pointer #"+t+" and pointer #"+i+" : "+this.multipointer.liveParameters.distanceChange+"px"),console.log("[Contact] 2 fingers live angle: "+this.multipointer.liveParameters.rotationAngle+"deg"),console.log("[Contact] 2 fingers global angle: "+this.multipointer.globalParameters.rotationAngle+"deg"))}calculateCenterMovement(t,e){var i=getCenter(t.startPoint,e.startPoint),n=getCenter(t.endPoint,e.endPoint);return new Vector(i,n)}calculateDistanceChange(t,e){var i=new Vector(t.startPoint,e.startPoint),n=new Vector(t.endPoint,e.endPoint);return{absolute:n.vectorLength-i.vectorLength,relative:n.vectorLength/i.vectorLength}}calculateAngle(t,e){var i=new Vector(t.startPoint,e.startPoint),n=new Vector(t.endPoint,e.endPoint),r=new Point(0,0),s=new Vector(i.startPoint,r),a=translatePoint(i.endPoint,s),o=new Vector(n.startPoint,r),l=translatePoint(n.endPoint,o),c=-1*calcAngleRad(a),u=l.x*Math.cos(c)-l.y*Math.sin(c),h=Math.round(l.x*Math.sin(c)+l.y*Math.cos(c));return 180*Math.atan2(h,u)/Math.PI}}class PointerInput{constructor(t,e){this.DEBUG=!1,e=e||{};var i=(new Date).getTime();this.pointerId=t.pointerId;var n=Object.prototype.hasOwnProperty.call(e,"vectorTimespan");this.vectorTimespan=1==n?e.vectorTimespan:100,this.initialPointerEvent=t,this.currentPointerEvent=t,this.recognizedEvents=[t],this.canceled=!1,this.isActive=!0;var r=this.getVector(t,t);this.liveParameters={vector:r,speed:0,isMoving:!1},this.globalParameters={startX:this.initialPointerEvent.clientX,startY:this.initialPointerEvent.clientY,vector:r,deltaX:0,deltaY:0,startTimestampUTC:i,startTimestamp:this.initialPointerEvent.timeStamp,currentTimestamp:this.initialPointerEvent.timeStamp,endTimestamp:null,maximumSpeed:0,averageSpeed:0,finalSpeed:null,traveledDistance:0,hasBeenMoved:!1,duration:0}}onIdle(){let t=(new Date).getTime()-this.globalParameters.startTimestampUTC;this.globalParameters.duration=t}onMove(t){this.globalParameters.hasBeenMoved=!0,this.liveParameters.isMoving=!0,this.update(t,!0)}onUp(t){this.globalParameters.finalSpeed=this.liveParameters.speed,this.liveParameters.currentSpeed=0,this.liveParameters.isMoving=!1,this.isActive=!1,this.globalParameters.endTimestamp=t.timeStamp,this.update(t),!0===this.DEBUG&&console.log("[Contact] pointerdown ended. pointerdown duration: "+this.globalParameters.duration+"ms")}onCancel(t){this.update(t),this.liveParameters.speed=0,this.canceled=!0,this.liveParameters.isMoving=!1,this.isActive=!1,this.globalParameters.endTimestamp=t.timeStamp,!0===this.DEBUG&&console.log("[Contact] canceled, pointerdown duration:"+this.duration)}update(t){this.currentPointerEvent=t,this.recognizedEvents.push(t);var e=this.getTimedPointerEvents(),i=this.getVector(e[0],e[1]);if(this.liveParameters.vector=i,null!=i){this.liveParameters.speed=this.getSpeed(i,e[0].timeStamp,e[1].timeStamp),this.liveParameters.speed>this.globalParameters.maximumSpeed&&(this.globalParameters.maximumSpeed=this.liveParameters.speed),this.globalParameters.currentTimestamp=t.timeStamp,this.globalParameters.duration=t.timeStamp-this.globalParameters.startTimestamp,this.globalParameters.deltaX=i.endPoint.x-this.globalParameters.startX,this.globalParameters.deltaY=i.endPoint.y-this.globalParameters.startY;var n=this.getVector(this.initialPointerEvent,this.currentPointerEvent);this.globalParameters.vector=n,!0===this.DEBUG&&(console.log("[Contact] current speed: "+this.liveParameters.speed+"px/s"),console.log("[Contact] pointerdown duration: "+this.globalParameters.duration+"ms"),console.log("[Contact] live vector length within vectorTimespan: "+this.liveParameters.vector.vectorLength+"px"))}}getTimedPointerEvents(){for(var t=this.initialPointerEvent,e=this.recognizedEvents[this.recognizedEvents.length-1],i=this.recognizedEvents.length-1,n=0,r=e.timeStamp;nMath.abs(this.deltaY)?this.startPoint.xr)||(1==this.DEBUG&&console.log("dismissing max"+this.constructor.name+": required "+e+": "+r+", current value: "+i),!1)}validateBool(t,e){var i=this.boolParameters[t];return null!=i&&null!=e&&i===e||(null==i||(1==this.DEBUG&&console.log("[Gestures] dismissing "+this.constructor.name+": "+t+" required: "+i+", actual value: "+e),!1))}getMinMaxParameters(t){var e=t.getPrimaryPointerInput();return{pointerCount:Object.keys(t.activePointerInputs).length,duration:e.globalParameters.duration,currentSpeed:e.liveParameters.speed,averageSpeed:e.globalParameters.averageSpeed,finalSpeed:e.globalParameters.finalSpeed,distance:e.liveParameters.vector.vectorLength}}getBoolParameters(t){var e=t.getPrimaryPointerInput();return{requiresPointerUp:!1===e.isActive,requiresActivePointer:!0===e.isActive,requiresPointerMove:!0===e.globalParameters.hasBeenMoved}}validate(t){var e=t.getPrimaryPointerInput();1==this.DEBUG&&console.log("[Gestures] running recognition for "+this.constructor.name);var i=this.getBoolParameters(t);for(let t in this.boolParameters){let e=i[t];if(0==this.validateBool(t,e))return!1}var n,r=this.getMinMaxParameters(t);n=1==this.isActive?this.activeStateMinMaxParameters:this.initialMinMaxParameters;for(let t in n){let e=r[t];if(0==this.validateMinMax(n,t,e))return!1}return!(1==Object.prototype.hasOwnProperty.call(this.options,"supportedDirections")&&this.options.supportedDirections.length>0&&-1==this.options.supportedDirections.indexOf(e.liveParameters.vector.direction))||(1==this.DEBUG&&console.log("[Gestures] dismissing "+this.constructor.name+": supported directions: "+this.options.supportedDirections+", current direction: "+e.liveParameters.vector.direction),!1)}recognize(t){var e=this.validate(t);1==e&&0==this.isActive&&this.onStart(t),1==e&&this.state==GESTURE_STATE_POSSIBLE?this.emit(t):1==this.isActive&&0==e&&this.onEnd(t)}getEventData(t){return{contact:t,recognizer:this}}emit(t,e){e=e||this.constructor.name.toLowerCase(),!0===this.DEBUG&&console.log("[Gestures] detected and firing event "+e);var i=this.getEventData(t),n={detail:i,bubbles:this.options.bubbles},r=new CustomEvent(e,n),s=t.initialPointerEvent.target;1==n.bubbles?s.dispatchEvent(r):this.domElement.dispatchEvent(r);var a=i.live.direction;if(1==Object.prototype.hasOwnProperty.call(this.options,"supportedDirections"))for(let t=0;t100)&&(this.contact.onIdle(),1==this.DEBUG&&console.log("[PointerListener] run idle recognition"),this.recognizeGestures())}}clearIdleRecognitionInterval(){null!=this.idleRecognitionIntervalId&&(clearInterval(this.idleRecognitionIntervalId),this.idleRecognitionIntervalId=null)}recognizeGestures(){this.lastRecognitionTimestamp=(new Date).getTime();for(let t=0;t0&&(isActive=!0),this.isActive=isActive,0==this.isActive?this.endTimestamp=this.currentTimestamp:Object.keys(this.activePointerInputs).length>=2&&this.updateMultipointerParameters()}updateMultipointerParameters(){var multiPointerInputs=this.getMultiPointerInputs(),pointerInput_1=multiPointerInputs[0],pointerInput_2=multiPointerInputs[1],vector_1=pointerInput_1.liveParameters.vector,vector_2=pointerInput_2.liveParameters.vector;if(null!=vector_1&&null!=vector_2){var currentCenter=getCenter(vector_1.startPoint,vector_2.startPoint);this.multipointer.liveParameters.center=currentCenter;var centerMovementVector=this.calculateCenterMovement(vector_1,vector_2);this.multipointer.liveParameters.centerMovementVector=centerMovementVector,this.multipointer.liveParameters.centerMovement=centerMovementVector.vectorLength;var liveDistanceChange=this.calculateDistanceChange(vector_1,vector_2);this.multipointer.liveParameters.distanceChange=liveDistanceChange.absolute,this.multipointer.liveParameters.relativeDistanceChange=liveDistanceChange.relative;var liveRotationAngle=this.calculateRotationAngle(vector_1,vector_2);this.multipointer.liveParameters.rotationAngle=liveRotationAngle;var liveVectorAngle=this.calculateVectorAngle(vector_1,vector_2);this.multipointer.liveParameters.vectorAngle=liveVectorAngle}var globalVector_1=pointerInput_1.globalParameters.vector,globalVector_2=pointerInput_2.globalParameters.vector;if(null!=globalVector_1&&null!=globalVector_2){var globalCenter=getCenter(globalVector_1.startPoint,globalVector_2.startPoint);this.multipointer.globalParameters.center=globalCenter;var globalCenterMovementVector=this.calculateCenterMovement(globalVector_1,globalVector_2);this.multipointer.globalParameters.centerMovementVector=globalCenterMovementVector,this.multipointer.globalParameters.centerMovement=globalCenterMovementVector.vectorLength;var globalDistanceChange=this.calculateDistanceChange(globalVector_1,globalVector_2);this.multipointer.globalParameters.distanceChange=globalDistanceChange.absolute,this.multipointer.globalParameters.relativeDistanceChange=globalDistanceChange.relative;var globalRotationAngle=this.calculateRotationAngle(globalVector_1,globalVector_2);this.multipointer.globalParameters.rotationAngle=globalRotationAngle;var globalVectorAngle=this.calculateVectorAngle(globalVector_1,globalVector_2);this.multipointer.liveParameters.vectorAngle=globalVectorAngle}!0===this.DEBUG&&(console.log("[Contact] 2 fingers: centerMovement between pointer #"+pointerInput_1.pointerId+" and pointer #"+pointerInput_2.pointerId+" : "+this.multipointer.liveParameters.centerMovement+"px"),console.log("[Contact] 2 fingers: distanceChange: between pointer #"+pointerInput_1.pointerId+" and pointer #"+pointerInput_2.pointerId+" : "+this.multipointer.liveParameters.distanceChange+"px"),console.log("[Contact] 2 fingers live angle: "+this.multipointer.liveParameters.rotationAngle+"deg"),console.log("[Contact] 2 fingers global angle: "+this.multipointer.globalParameters.rotationAngle+"deg"))}calculateCenterMovement(vector_1,vector_2){var startPoint=getCenter(vector_1.startPoint,vector_2.startPoint),endPoint=getCenter(vector_1.endPoint,vector_2.endPoint);return new Vector(startPoint,endPoint)}calculateDistanceChange(vector_1,vector_2){var vectorBetweenStartPoints=new Vector(vector_1.startPoint,vector_2.startPoint),vectorBetweenEndPoints=new Vector(vector_1.endPoint,vector_2.endPoint);return{absolute:vectorBetweenEndPoints.vectorLength-vectorBetweenStartPoints.vectorLength,relative:vectorBetweenEndPoints.vectorLength/vectorBetweenStartPoints.vectorLength}}calculateRotationAngle(vector_1,vector_2){var angleVector_1=new Vector(vector_1.startPoint,vector_2.startPoint),angleVector_2=new Vector(vector_1.endPoint,vector_2.endPoint),origin=new Point(0,0),translationVector_1=new Vector(angleVector_1.startPoint,origin),translatedEndPoint_1=translatePoint(angleVector_1.endPoint,translationVector_1),translationVector_2=new Vector(angleVector_2.startPoint,origin),translatedEndPoint_2=translatePoint(angleVector_2.endPoint,translationVector_2),rotationAngle=-1*calcAngleRad(translatedEndPoint_1),x_2_rotated=translatedEndPoint_2.x*Math.cos(rotationAngle)-translatedEndPoint_2.y*Math.sin(rotationAngle),y_2_rotated=Math.round(translatedEndPoint_2.x*Math.sin(rotationAngle)+translatedEndPoint_2.y*Math.cos(rotationAngle));return 180*Math.atan2(y_2_rotated,x_2_rotated)/Math.PI}calculateVectorAngle(vector_1,vector_2){var angleDeg=null;if(vector_1.vectorLength>0&&vector_2.vectorLength>0){var cos=(vector_1.x*vector_2.x+vector_1.y*vector_2.y)/(vector_1.vectorLength*vector_2.vectorLength);angleDeg=rad2deg(Math.acos(cos))}return angleDeg}}class PointerInput{constructor(pointerdownEvent,options){options=options||{},this.options={DEBUG:!1};for(let key in options)this.options[key]=options[key];this.DEBUG=this.options.DEBUG;var now=(new Date).getTime();this.pointerId=pointerdownEvent.pointerId;var hasVectorTimespan=Object.prototype.hasOwnProperty.call(this.options,"vectorTimespan");this.vectorTimespan=1==hasVectorTimespan?this.options.vectorTimespan:100,this.initialPointerEvent=pointerdownEvent,this.currentPointerEvent=pointerdownEvent,this.recognizedEvents=[pointerdownEvent],this.canceled=!1,this.isActive=!0;var nullVector=this.getVector(pointerdownEvent,pointerdownEvent);this.liveParameters={vector:nullVector,speed:0,isMoving:!1},this.globalParameters={startX:this.initialPointerEvent.clientX,startY:this.initialPointerEvent.clientY,vector:nullVector,deltaX:0,deltaY:0,startTimestampUTC:now,startTimestamp:this.initialPointerEvent.timeStamp,currentTimestamp:this.initialPointerEvent.timeStamp,endTimestamp:null,maximumSpeed:0,averageSpeed:0,finalSpeed:null,traveledDistance:0,hasBeenMoved:!1,duration:0}}onIdle(){let duration=(new Date).getTime()-this.globalParameters.startTimestampUTC;this.globalParameters.duration=duration}onMove(pointermoveEvent){this.globalParameters.hasBeenMoved=!0,this.liveParameters.isMoving=!0,this.update(pointermoveEvent,!0)}onUp(pointerupEvent){this.globalParameters.finalSpeed=this.liveParameters.speed,this.liveParameters.currentSpeed=0,this.liveParameters.isMoving=!1,this.isActive=!1,this.globalParameters.endTimestamp=pointerupEvent.timeStamp,this.update(pointerupEvent),!0===this.DEBUG&&console.log("[Contact] pointerdown ended. pointerdown duration: "+this.globalParameters.duration+"ms")}onCancel(pointercancelEvent){this.update(pointercancelEvent),this.liveParameters.speed=0,this.canceled=!0,this.liveParameters.isMoving=!1,this.isActive=!1,this.globalParameters.endTimestamp=pointercancelEvent.timeStamp,!0===this.DEBUG&&console.log("[Contact] canceled, pointerdown duration:"+this.duration)}update(pointerEvent){this.currentPointerEvent=pointerEvent,this.recognizedEvents.push(pointerEvent);var timedPointerEvents=this.getTimedPointerEvents(),liveVector=this.getVector(timedPointerEvents[0],timedPointerEvents[1]);if(this.liveParameters.vector=liveVector,null!=liveVector){this.liveParameters.speed=this.getSpeed(liveVector,timedPointerEvents[0].timeStamp,timedPointerEvents[1].timeStamp),this.liveParameters.speed>this.globalParameters.maximumSpeed&&(this.globalParameters.maximumSpeed=this.liveParameters.speed),this.globalParameters.currentTimestamp=pointerEvent.timeStamp,this.globalParameters.duration=pointerEvent.timeStamp-this.globalParameters.startTimestamp,this.globalParameters.deltaX=liveVector.endPoint.x-this.globalParameters.startX,this.globalParameters.deltaY=liveVector.endPoint.y-this.globalParameters.startY;var globalVector=this.getVector(this.initialPointerEvent,this.currentPointerEvent);this.globalParameters.vector=globalVector,!0===this.DEBUG&&(console.log("[Contact] current speed: "+this.liveParameters.speed+"px/s"),console.log("[Contact] pointerdown duration: "+this.globalParameters.duration+"ms"),console.log("[Contact] live vector length within vectorTimespan: "+this.liveParameters.vector.vectorLength+"px"))}}getTimedPointerEvents(){for(var startPointerEvent=this.initialPointerEvent,endPointerEvent=this.recognizedEvents[this.recognizedEvents.length-1],startIndex=this.recognizedEvents.length-1,elapsedTime=0,endTimeStamp=endPointerEvent.timeStamp;elapsedTimeMath.abs(this.deltaY)?this.startPoint.xmaxValue)||(1==this.DEBUG&&console.log("dismissing max"+this.eventBaseName+": required "+parameterName+": "+maxValue+", current value: "+value),!1)}validateBool(parameterName,value){var requiredValue=this.boolParameters[parameterName];return null!=requiredValue&&null!=value&&requiredValue===value||(null==requiredValue||(1==this.DEBUG&&console.log("[Gestures] dismissing "+this.eventBaseName+": "+parameterName+" required: "+requiredValue+", actual value: "+value),!1))}getMinMaxParameters(contact){var primaryPointerInput=contact.getPrimaryPointerInput();return{pointerCount:Object.keys(contact.activePointerInputs).length,duration:primaryPointerInput.globalParameters.duration,currentSpeed:primaryPointerInput.liveParameters.speed,averageSpeed:primaryPointerInput.globalParameters.averageSpeed,finalSpeed:primaryPointerInput.globalParameters.finalSpeed,distance:primaryPointerInput.liveParameters.vector.vectorLength}}getBoolParameters(contact){var primaryPointerInput=contact.getPrimaryPointerInput();return{requiresPointerUp:!1===primaryPointerInput.isActive,requiresActivePointer:!0===primaryPointerInput.isActive,requiresPointerMove:!0===primaryPointerInput.globalParameters.hasBeenMoved}}validate(contact){if(this.state==GESTURE_STATE_BLOCKED)return!1;var primaryPointerInput=contact.getPrimaryPointerInput();1==this.DEBUG&&console.log("[Gestures] running recognition for "+this.eventBaseName);var contactBoolParameters=this.getBoolParameters(contact);for(let boolParameterName in this.boolParameters){let boolValue=contactBoolParameters[boolParameterName];if(0==this.validateBool(boolParameterName,boolValue))return!1}var minMaxParameters,contactMinMaxParameters=this.getMinMaxParameters(contact);minMaxParameters=1==this.isActive?this.activeStateMinMaxParameters:this.initialMinMaxParameters;for(let minMaxParameterName in minMaxParameters){let value=contactMinMaxParameters[minMaxParameterName];if(0==this.validateMinMax(minMaxParameters,minMaxParameterName,value))return!1}return!(1==Object.prototype.hasOwnProperty.call(this.options,"supportedDirections")&&this.options.supportedDirections.length>0&&-1==this.options.supportedDirections.indexOf(primaryPointerInput.liveParameters.vector.direction))||(1==this.DEBUG&&console.log("[Gestures] dismissing "+this.eventBaseName+": supported directions: "+this.options.supportedDirections+", current direction: "+primaryPointerInput.liveParameters.vector.direction),!1)}recognize(contact){var isValid=this.validate(contact);1==isValid&&0==this.isActive&&this.state==GESTURE_STATE_POSSIBLE&&this.onStart(contact),1==isValid&&1==this.isActive&&this.state==GESTURE_STATE_POSSIBLE?this.emit(contact):1==this.isActive&&0==isValid&&this.onEnd(contact)}block(gesture){-1==this.options.blocks.indexOf(gesture)&&this.options.blocks.push(gesture)}unblock(gesture){-1!=this.options.blocks.indexOf(gesture)&&this.options.blocks.splice(this.options.blocks.indexOf(gesture),1)}blockGestures(){for(let g=0;gthis.initialMinMaxParameters.distance[1]&&(this.hasBeenInvalidatedForContactId=contact.id),1==isValid&&0==this.hasBeenEmitted&&null==this.hasBeenInvalidatedForContactId)this.initialPointerEvent=contact.currentPointerEvent,this.emit(contact),this.hasBeenEmitted=!0;else{let duration=primaryPointerInput.globalParameters.duration;1==this.hasBeenEmitted&&duration<=this.initialMinMaxParameters.duration[0]&&(this.hasBeenEmitted=!1)}}}class MultiPointerGesture extends Gesture{constructor(domElement,options){super(domElement,options=options||{}),this.boolParameters={requiresPointerMove:null,requiresActivePointer:null},this.initialMinMaxParameters={pointerCount:[2,null]},this.activeStateMinMaxParameters={pointerCount:[2,null]},this.options=options||{}}}class TwoPointerGesture extends MultiPointerGesture{constructor(domElement,options){super(domElement,options=options||{}),this.boolParameters.requiresPointerMove=!0,this.boolParameters.requiresActivePointer=!0,this.initialMinMaxParameters.pointerCount=[2,2],this.initialMinMaxParameters.centerMovement=[null,null],this.initialMinMaxParameters.distanceChange=[null,null],this.initialMinMaxParameters.rotationAngle=[null,null],this.initialMinMaxParameters.vectorAngle=[null,null],this.activeStateMinMaxParameters.pointerCount=[2,2],this.activeStateMinMaxParameters.centerMovement=[null,null],this.activeStateMinMaxParameters.distanceChange=[null,null],this.activeStateMinMaxParameters.rotationAngle=[null,null],this.activeStateMinMaxParameters.vectorAngle=[null,null]}getMinMaxParameters(contact){var minMaxParameters=super.getMinMaxParameters(contact);return minMaxParameters.centerMovement=contact.multipointer.liveParameters.centerMovement,minMaxParameters.distanceChange=Math.abs(contact.multipointer.liveParameters.distanceChange),minMaxParameters.rotationAngle=Math.abs(contact.multipointer.liveParameters.rotationAngle),minMaxParameters.vectorAngle=contact.multipointer.liveParameters.vectorAngle,minMaxParameters}getEventData(contact){var eventData=super.getEventData(contact),globalDuration=contact.currentPointerEvent.timeStamp-this.initialPointerEvent.timeStamp,globalParameters=contact.multipointer.globalParameters,liveParameters=contact.multipointer.liveParameters;return eventData.global={deltaX:globalParameters.centerMovementVector.x,deltaY:globalParameters.centerMovementVector.y,distance:globalParameters.centerMovement,speedX:globalParameters.centerMovementVector.x/globalDuration,speedY:globalParameters.centerMovementVector.y/globalDuration,speed:globalParameters.centerMovementVector.vectorLength/globalDuration,direction:globalParameters.centerMovementVector.direction,scale:globalParameters.relativeDistanceChange,rotation:globalParameters.rotationAngle,srcEvent:contact.currentPointerEvent},eventData.live={deltaX:liveParameters.centerMovementVector.x,deltaY:liveParameters.centerMovementVector.y,distance:liveParameters.centerMovement,speedX:liveParameters.centerMovementVector.x/globalDuration,speedY:liveParameters.centerMovementVector.y/globalDuration,speed:liveParameters.centerMovementVector.vectorLength/globalDuration,direction:liveParameters.centerMovementVector.direction,scale:liveParameters.relativeDistanceChange,rotation:liveParameters.rotationAngle,center:{x:liveParameters.centerMovementVector.startPoint.x,y:liveParameters.centerMovementVector.startPoint.y},srcEvent:contact.currentPointerEvent},eventData}}class Pinch extends TwoPointerGesture{constructor(domElement,options){super(domElement,options=options||{}),this.eventBaseName="pinch",this.initialMinMaxParameters.centerMovement=[0,50],this.initialMinMaxParameters.distanceChange=[5,null],this.initialMinMaxParameters.rotationAngle=[null,20],this.initialMinMaxParameters.vectorAngle=[10,null]}}class Rotate extends TwoPointerGesture{constructor(domElement,options){super(domElement,options=options||{}),this.eventBaseName="rotate",this.initialMinMaxParameters.centerMovement=[0,50],this.initialMinMaxParameters.distanceChange=[null,50],this.initialMinMaxParameters.rotationAngle=[5,null]}}class TwoFingerPan extends TwoPointerGesture{constructor(domElement,options){super(domElement,options=options||{}),this.eventBaseName="twofingerpan",this.initialMinMaxParameters.centerMovement=[3,null],this.initialMinMaxParameters.distanceChange=[null,50],this.initialMinMaxParameters.rotationAngle=[null,null],this.initialMinMaxParameters.vectorAngle=[null,150]}}var ALL_GESTURE_CLASSES=[Tap,Press,Pan,Pinch,Rotate,TwoFingerPan];class PointerListener{constructor(domElement,options){this.eventHandlers={},this.lastRecognitionTimestamp=null,this.idleRecognitionIntervalId=null,this.pointerEventHandlers={},this.touchEventHandlers={},options=options||{},this.options={bubbles:!0,handleTouchEvents:!0,DEBUG:!1,DEBUG_GESTURES:!1,DEBUG_CONTACT:!1};for(let key in options)"supportedGestures"!=key&&(this.options[key]=options[key]);this.DEBUG=this.options.DEBUG;var supportedGestures=ALL_GESTURE_CLASSES,instantiatedGestures=[];1==Object.prototype.hasOwnProperty.call(options,"supportedGestures")&&(supportedGestures=options.supportedGestures);for(let i=0;i100)&&(this.contact.onIdle(),1==this.DEBUG&&console.log("[PointerListener] onIdle - running idle recognition"),this.recognizeGestures())}}clearIdleRecognitionInterval(){null!=this.idleRecognitionIntervalId&&(clearInterval(this.idleRecognitionIntervalId),this.idleRecognitionIntervalId=null)}recognizeGestures(){this.lastRecognitionTimestamp=(new Date).getTime();for(let g=0;g=0&&(handlerReferences.splice(index,1),this.eventHandlers[eventType]=handlerReferences),this.domElement.removeEventListener(eventType,handlerReference,!1)}}}destroy(){for(let event in this.eventHandlers){let handlerList=this.eventHandlers[event];for(let h=0;h 0 && vector_2.vectorLength > 0){ + + var cos = ( (vector_1.x * vector_2.x) + (vector_1.y * vector_2.y) ) / (vector_1.vectorLength * vector_2.vectorLength); + + var angleRad = Math.acos(cos); + angleDeg = rad2deg(angleRad); + + } + + return angleDeg; + } } @@ -396,15 +451,23 @@ class PointerInput { constructor (pointerdownEvent, options) { - this.DEBUG = false; - options = options || {}; + + this.options = { + "DEBUG" : false + }; + + for (let key in options){ + this.options[key] = options[key]; + } + + this.DEBUG = this.options.DEBUG; var now = new Date().getTime(); this.pointerId = pointerdownEvent.pointerId; - var hasVectorTimespan = Object.prototype.hasOwnProperty.call(options, "vectorTimespan"); - this.vectorTimespan = hasVectorTimespan == true ? options.vectorTimespan : 100; // milliseconds + var hasVectorTimespan = Object.prototype.hasOwnProperty.call(this.options, "vectorTimespan"); + this.vectorTimespan = hasVectorTimespan == true ? this.options.vectorTimespan : 100; // milliseconds // events used for vector calculation this.initialPointerEvent = pointerdownEvent; @@ -662,7 +725,7 @@ class Vector { this.y = this.deltaY; // determine length - this.vectorLength = Math.sqrt( Math.pow(this.deltaX,2) + Math.pow(this.deltaY, 2) ); + this.vectorLength = Math.sqrt( Math.pow(this.deltaX, 2) + Math.pow(this.deltaY, 2) ); // determine direction if (Math.abs(this.deltaX) > Math.abs(this.deltaY)){ @@ -758,8 +821,6 @@ function calcAngleRad (point) { class Gesture { constructor (domElement, options){ - - this.DEBUG = false; this.domElement = domElement; @@ -798,7 +859,9 @@ class Gesture { } let defaultOptions = { - "bubbles" : true + "bubbles" : true, + "blocks" : [], + "DEBUG" : false }; this.options = options || {}; @@ -808,6 +871,8 @@ class Gesture { this.options[key] = defaultOptions[key]; } } + + this.DEBUG = this.options.DEBUG; } @@ -824,7 +889,7 @@ class Gesture { if (minValue != null && value != null && value < minValue){ if (this.DEBUG == true){ - console.log("dismissing min" + this.constructor.name + ": required " + parameterName + ": " + minValue + ", current value: " + value); + console.log("dismissing min" + this.eventBaseName + ": required " + parameterName + ": " + minValue + ", current value: " + value); } return false; @@ -833,7 +898,7 @@ class Gesture { if (maxValue != null && value != null && value > maxValue){ if (this.DEBUG == true){ - console.log("dismissing max" + this.constructor.name + ": required " + parameterName + ": " + maxValue + ", current value: " + value); + console.log("dismissing max" + this.eventBaseName + ": required " + parameterName + ": " + maxValue + ", current value: " + value); } return false; @@ -856,7 +921,7 @@ class Gesture { } if (this.DEBUG == true){ - console.log("[Gestures] dismissing " + this.constructor.name + ": " + parameterName + " required: " + requiredValue + ", actual value: " + value); + console.log("[Gestures] dismissing " + this.eventBaseName + ": " + parameterName + " required: " + requiredValue + ", actual value: " + value); } return false; @@ -898,11 +963,15 @@ class Gesture { validate (contact){ var isValid = false; + + if (this.state == GESTURE_STATE_BLOCKED) { + return false; + } var primaryPointerInput = contact.getPrimaryPointerInput(); if (this.DEBUG == true){ - console.log("[Gestures] running recognition for " + this.constructor.name); + console.log("[Gestures] running recognition for " + this.eventBaseName); } @@ -943,7 +1012,7 @@ class Gesture { if (this.options.supportedDirections.indexOf(primaryPointerInput.liveParameters.vector.direction) == -1){ if (this.DEBUG == true){ - console.log("[Gestures] dismissing " + this.constructor.name + ": supported directions: " + this.options.supportedDirections + ", current direction: " + primaryPointerInput.liveParameters.vector.direction); + console.log("[Gestures] dismissing " + this.eventBaseName + ": supported directions: " + this.options.supportedDirections + ", current direction: " + primaryPointerInput.liveParameters.vector.direction); } return false; @@ -959,19 +1028,51 @@ class Gesture { var isValid = this.validate(contact); - if (isValid == true && this.isActive == false){ + if (isValid == true && this.isActive == false && this.state == GESTURE_STATE_POSSIBLE){ this.onStart(contact); } - if (isValid == true && this.state == GESTURE_STATE_POSSIBLE){ + if (isValid == true && this.isActive == true && this.state == GESTURE_STATE_POSSIBLE){ this.emit(contact); } else if (this.isActive == true && isValid == false){ this.onEnd(contact); + } } + + block (gesture) { + if (this.options.blocks.indexOf(gesture) == -1){ + this.options.blocks.push(gesture); + } + } + + unblock (gesture) { + if (this.options.blocks.indexOf(gesture) != -1){ + this.options.blocks.splice(this.options.blocks.indexOf(gesture), 1); + } + } + + blockGestures () { + for (let g=0; g this.initialMinMaxParameters["distance"][1]){ + this.hasBeenInvalidatedForContactId = contact.id; + } + } + + if (isValid == true && this.hasBeenEmitted == false && this.hasBeenInvalidatedForContactId == null){ this.initialPointerEvent = contact.currentPointerEvent; @@ -1345,7 +1477,6 @@ class Press extends SinglePointerGesture { } else { - var primaryPointerInput = contact.getPrimaryPointerInput(); let duration = primaryPointerInput.globalParameters.duration; if (this.hasBeenEmitted == true && duration <= this.initialMinMaxParameters["duration"][0]){ @@ -1403,11 +1534,13 @@ class TwoPointerGesture extends MultiPointerGesture { this.initialMinMaxParameters["centerMovement"] = [null,null]; //px this.initialMinMaxParameters["distanceChange"] = [null, null]; //px - distance between 2 fingers this.initialMinMaxParameters["rotationAngle"] = [null, null]; // degrees: positive = clockwise, negative = counter-clockwise (js convention, not mathematical convention) + this.initialMinMaxParameters["vectorAngle"] = [null, null]; this.activeStateMinMaxParameters["pointerCount"] = [2, 2]; this.activeStateMinMaxParameters["centerMovement"] = [null,null]; this.activeStateMinMaxParameters["distanceChange"] = [null, null]; this.activeStateMinMaxParameters["rotationAngle"] = [null, null]; + this.activeStateMinMaxParameters["vectorAngle"] = [null, null]; } @@ -1421,6 +1554,8 @@ class TwoPointerGesture extends MultiPointerGesture { minMaxParameters.rotationAngle = Math.abs(contact.multipointer.liveParameters.rotationAngle); + minMaxParameters.vectorAngle = contact.multipointer.liveParameters.vectorAngle; + return minMaxParameters; } @@ -1487,10 +1622,14 @@ class Pinch extends TwoPointerGesture { options = options || {}; super(domElement, options); + + this.eventBaseName = "pinch"; this.initialMinMaxParameters["centerMovement"] = [0, 50]; //px this.initialMinMaxParameters["distanceChange"] = [5, null]; // distance between 2 fingers this.initialMinMaxParameters["rotationAngle"] = [null, 20]; // distance between 2 fingers + this.initialMinMaxParameters["vectorAngle"] = [10, null]; + } @@ -1510,6 +1649,8 @@ class Rotate extends TwoPointerGesture { options = options || {}; super(domElement, options); + + this.eventBaseName = "rotate"; this.initialMinMaxParameters["centerMovement"] = [0, 50]; this.initialMinMaxParameters["distanceChange"] = [null, 50]; @@ -1520,6 +1661,9 @@ class Rotate extends TwoPointerGesture { } +/* +* 2 fingers are moved across the surface, in the same direction +*/ class TwoFingerPan extends TwoPointerGesture { constructor (domElement, options) { @@ -1527,10 +1671,13 @@ class TwoFingerPan extends TwoPointerGesture { options = options || {}; super(domElement, options); + + this.eventBaseName = "twofingerpan"; - this.initialMinMaxParameters["centerMovement"] = [5, null]; - this.initialMinMaxParameters["distanceChange"] = [null, 500]; + this.initialMinMaxParameters["centerMovement"] = [3, null]; + this.initialMinMaxParameters["distanceChange"] = [null, 50]; this.initialMinMaxParameters["rotationAngle"] = [null, null]; + this.initialMinMaxParameters["vectorAngle"] = [null, 150]; } @@ -1550,21 +1697,28 @@ class TwoFingerPan extends TwoPointerGesture { var ALL_GESTURE_CLASSES = [Tap, Press, Pan, Pinch, Rotate, TwoFingerPan]; + class PointerListener { constructor (domElement, options){ - - this.DEBUG = false; - - var self = this; + + // registry for events like "pan", "rotate", which have to be removed on this.destroy(); + this.eventHandlers = {}; this.lastRecognitionTimestamp = null; this.idleRecognitionIntervalId = null; + this.pointerEventHandlers = {}; + this.touchEventHandlers = {}; + options = options || {}; this.options = { - "bubbles" : true + "bubbles" : true, + "handleTouchEvents" : true, + "DEBUG" : false, + "DEBUG_GESTURES" : false, + "DEBUG_CONTACT" : false }; // add user-defined options to this.options @@ -1576,6 +1730,8 @@ class PointerListener { this.options[key] = options[key]; } + this.DEBUG = this.options.DEBUG; + // add instantiatedGestures to options.supportedGestures var supportedGestures = ALL_GESTURE_CLASSES; var instantiatedGestures = []; @@ -1591,7 +1747,8 @@ class PointerListener { let gesture; let GestureClass = supportedGestures[i]; let gestureOptions = { - bubbles : this.options.bubbles + "bubbles" : this.options.bubbles, + "DEBUG" : this.options.DEBUG_GESTURES }; if (typeof GestureClass == "function"){ @@ -1619,16 +1776,35 @@ class PointerListener { return false; });*/ - // javascript fires the events "pointerdown", "pointermove", "pointerup" and "pointercancel" - // on each of these events, the contact instance is updated and GestureRecognizers of this.supported_events are run - domElement.addEventListener("pointerdown", function(event){ + this.addPointerListeners(); + + this.addTouchListeners(); + } + + + addPointerListeners () { + + var self = this; + + var domElement = this.domElement; + // javascript fires the events "pointerdown", "pointermove", "pointerup" and "pointercancel" + // on each of these events, the contact instance is updated and GestureRecognizers of this.supported_events are run + var onPointerDown = function (event) { + + if (self.DEBUG == true){ + console.log("[PointerListener] pointerdown event detected"); + } + // re-target all pointerevents to the current element // see https://developer.mozilla.org/en-US/docs/Web/API/Element/setPointerCapture domElement.setPointerCapture(event.pointerId); if (self.contact == null || self.contact.isActive == false) { - self.contact = new Contact(event); + let contactOptions = { + "DEBUG" : self.options.DEBUG_CONTACT + }; + self.contact = new Contact(event, contactOptions); } else { // use existing contact instance if a second pointer becomes present @@ -1649,9 +1825,9 @@ class PointerListener { self.onIdle(); }, 100); - }, { "passive": true }); + } - domElement.addEventListener("pointermove", function(event){ + var onPointerMove = function (event) { // pointermove is also firing if the mouse button is not pressed @@ -1668,10 +1844,14 @@ class PointerListener { self.options.pointermove(event, self); } } - - }, { "passive": true }); - domElement.addEventListener("pointerup", function(event){ + } + + var onPointerUp = function (event) { + + if (self.DEBUG == true){ + console.log("[PointerListener] pointerup event detected"); + } domElement.releasePointerCapture(event.pointerId); @@ -1690,8 +1870,8 @@ class PointerListener { } self.clearIdleRecognitionInterval(); - - }); + + } /* * case: user presses mouse button and moves element. while moving, the cursor leaves the element (fires pointerout) @@ -1699,24 +1879,25 @@ class PointerListener { * during pan, pan should not end if the pointer leaves the element. * MDN: Pointer capture allows events for a particular pointer event (PointerEvent) to be re-targeted to a particular element instead of the normal (or hit test) target at a pointer's location. This can be used to ensure that an element continues to receive pointer events even if the pointer device's contact moves off the element (such as by scrolling or panning). */ + var onPointerLeave = function (event) { + + if (self.DEBUG == true){ + console.log("[PointerListener] pointerleave detected"); + } - domElement.addEventListener("pointerleave", function(event){ - if (self.contact != null && self.contact.isActive == true){ self.contact.onPointerLeave(event); self.recognizeGestures(); } self.clearIdleRecognitionInterval() - - }); - + } - domElement.addEventListener("pointercancel", function(event){ + var onPointerCancel = function (event) { domElement.releasePointerCapture(event.pointerId); - if (this.DEBUG == true){ + if (self.DEBUG == true){ console.log("[PointerListener] pointercancel detected"); } @@ -1731,12 +1912,32 @@ class PointerListener { if (hasPointerCancelHook == true){ self.options.pointercancel(event, self); } - - - }, { "passive": true }); + } - this.addTouchListeners(); + domElement.addEventListener("pointerdown", onPointerDown, { "passive": true }); + domElement.addEventListener("pointermove", onPointerMove, { "passive": true }); + domElement.addEventListener("pointerup", onPointerUp, { "passive": true }); + domElement.addEventListener("pointerleave", onPointerLeave, {"passive": true}); + domElement.addEventListener("pointercancel", onPointerCancel, { "passive": true }); + + this.pointerEventHandlers = { + "pointerdown" : onPointerDown, + "pointermove" : onPointerMove, + "pointerup" : onPointerUp, + "pointerleave" : onPointerLeave, + "pointercancel" : onPointerCancel + }; + + } + + removePointerListeners () { + + for (let event in this.pointerEventHandlers){ + let handler = this.pointerEventHandlers[event]; + this.domElement.removeEventListener(event, handler); + } + } // provide the ability to interact/prevent touch events @@ -1748,12 +1949,8 @@ class PointerListener { if (self.options.handleTouchEvents == true){ - /*this.domElement.addEventListener("touchstart", function(event){ - - });*/ - - this.domElement.addEventListener("touchmove", function(event){ - + + var onTouchMove = function (event) { // fire onTouchMove for all gestures for (let g=0; g= 0) { + handlerReferences.splice(index, 1); + + this.eventHandlers[eventType] = handlerReferences; + } + + this.domElement.removeEventListener(eventType, handlerReference, false); + + } + + } + } + + destroy () { + + // remove all EventListeners from self.domElement + for (let event in this.eventHandlers){ + let handlerList = this.eventHandlers[event]; + for (let h=0; h maxValue){ if (this.DEBUG == true){ - console.log("dismissing max" + this.constructor.name + ": required " + parameterName + ": " + maxValue + ", current value: " + value); + console.log("dismissing max" + this.eventBaseName + ": required " + parameterName + ": " + maxValue + ", current value: " + value); } return false; @@ -897,7 +919,7 @@ class Gesture { } if (this.DEBUG == true){ - console.log("[Gestures] dismissing " + this.constructor.name + ": " + parameterName + " required: " + requiredValue + ", actual value: " + value); + console.log("[Gestures] dismissing " + this.eventBaseName + ": " + parameterName + " required: " + requiredValue + ", actual value: " + value); } return false; @@ -947,7 +969,7 @@ class Gesture { var primaryPointerInput = contact.getPrimaryPointerInput(); if (this.DEBUG == true){ - console.log("[Gestures] running recognition for " + this.constructor.name); + console.log("[Gestures] running recognition for " + this.eventBaseName); } @@ -988,7 +1010,7 @@ class Gesture { if (this.options.supportedDirections.indexOf(primaryPointerInput.liveParameters.vector.direction) == -1){ if (this.DEBUG == true){ - console.log("[Gestures] dismissing " + this.constructor.name + ": supported directions: " + this.options.supportedDirections + ", current direction: " + primaryPointerInput.liveParameters.vector.direction); + console.log("[Gestures] dismissing " + this.eventBaseName + ": supported directions: " + this.options.supportedDirections + ", current direction: " + primaryPointerInput.liveParameters.vector.direction); } return false; @@ -1036,7 +1058,7 @@ class Gesture { let gesture = this.options.blocks[g]; if (gesture.isActive == false) { if (this.DEBUG == false){ - console.log("[Gesture] blocking " + gesture.constructor.name); + console.log("[Gesture] blocking " + gesture.eventBaseName); } gesture.state = GESTURE_STATE_BLOCKED; } @@ -1069,7 +1091,7 @@ class Gesture { emit (contact, eventName) { // fire general event like "pan" , "pinch", "rotate" - eventName = eventName || this.constructor.name.toLowerCase(); + eventName = eventName || this.eventBaseName; if (this.DEBUG === true){ console.log("[Gestures] detected and firing event " + eventName); @@ -1134,7 +1156,7 @@ class Gesture { this.initialPointerEvent = contact.currentPointerEvent; - var eventName = "" + this.constructor.name.toLowerCase() + "start"; + var eventName = "" + this.eventBaseName + "start"; if (this.DEBUG === true) { console.log("[Gestures] firing event: " + eventName); @@ -1156,7 +1178,7 @@ class Gesture { this.isActive = false; - var eventName = "" + this.constructor.name.toLowerCase() + "end"; + var eventName = "" + this.eventBaseName + "end"; if (this.DEBUG === true) { console.log("[Gestures] firing event: " + eventName); @@ -1265,6 +1287,8 @@ class Pan extends SinglePointerGesture { options = options || {}; super(domElement, options); + + this.eventBaseName = "pan"; this.initialMinMaxParameters["pointerCount"] = [1,1]; // 1: no pan recognized at the pointerup event. 0: pan recognized at pointerup this.initialMinMaxParameters["duration"] = [0, null]; @@ -1352,6 +1376,8 @@ class Tap extends SinglePointerGesture { options = options || {}; super(domElement, options); + + this.eventBaseName = "tap"; this.initialMinMaxParameters["pointerCount"] = [0,0]; // count of fingers touching the surface. a tap is fired AFTER the user removed his finger this.initialMinMaxParameters["duration"] = [0, 200]; // milliseconds. after a certain touch duration, it is not a TAP anymore @@ -1390,6 +1416,8 @@ class Press extends SinglePointerGesture { options = options || {}; super(domElement, options); + + this.eventBaseName = "press"; this.initialMinMaxParameters["pointerCount"] = [1, 1]; // count of fingers touching the surface. a press is fired during an active contact this.initialMinMaxParameters["duration"] = [600, null]; // milliseconds. after a certain touch duration, it is not a TAP anymore @@ -1592,6 +1620,8 @@ class Pinch extends TwoPointerGesture { options = options || {}; super(domElement, options); + + this.eventBaseName = "pinch"; this.initialMinMaxParameters["centerMovement"] = [0, 50]; //px this.initialMinMaxParameters["distanceChange"] = [5, null]; // distance between 2 fingers @@ -1617,6 +1647,8 @@ class Rotate extends TwoPointerGesture { options = options || {}; super(domElement, options); + + this.eventBaseName = "rotate"; this.initialMinMaxParameters["centerMovement"] = [0, 50]; this.initialMinMaxParameters["distanceChange"] = [null, 50]; @@ -1637,6 +1669,8 @@ class TwoFingerPan extends TwoPointerGesture { options = options || {}; super(domElement, options); + + this.eventBaseName = "twofingerpan"; this.initialMinMaxParameters["centerMovement"] = [3, null]; this.initialMinMaxParameters["distanceChange"] = [null, 50]; @@ -1665,8 +1699,6 @@ var ALL_GESTURE_CLASSES = [Tap, Press, Pan, Pinch, Rotate, TwoFingerPan]; class PointerListener { constructor (domElement, options){ - - this.DEBUG = false; // registry for events like "pan", "rotate", which have to be removed on this.destroy(); this.eventHandlers = {}; @@ -1681,7 +1713,10 @@ class PointerListener { this.options = { "bubbles" : true, - "handleTouchEvents" : false + "handleTouchEvents" : true, + "DEBUG" : false, + "DEBUG_GESTURES" : false, + "DEBUG_CONTACT" : false }; // add user-defined options to this.options @@ -1693,6 +1728,8 @@ class PointerListener { this.options[key] = options[key]; } + this.DEBUG = this.options.DEBUG; + // add instantiatedGestures to options.supportedGestures var supportedGestures = ALL_GESTURE_CLASSES; var instantiatedGestures = []; @@ -1708,7 +1745,8 @@ class PointerListener { let gesture; let GestureClass = supportedGestures[i]; let gestureOptions = { - bubbles : this.options.bubbles + "bubbles" : this.options.bubbles, + "DEBUG" : this.options.DEBUG_GESTURES }; if (typeof GestureClass == "function"){ @@ -1751,13 +1789,20 @@ class PointerListener { // javascript fires the events "pointerdown", "pointermove", "pointerup" and "pointercancel" // on each of these events, the contact instance is updated and GestureRecognizers of this.supported_events are run var onPointerDown = function (event) { + + if (self.DEBUG == true){ + console.log("[PointerListener] pointerdown event detected"); + } // re-target all pointerevents to the current element // see https://developer.mozilla.org/en-US/docs/Web/API/Element/setPointerCapture domElement.setPointerCapture(event.pointerId); if (self.contact == null || self.contact.isActive == false) { - self.contact = new Contact(event); + let contactOptions = { + "DEBUG" : self.options.DEBUG_CONTACT + }; + self.contact = new Contact(event, contactOptions); } else { // use existing contact instance if a second pointer becomes present @@ -1801,6 +1846,10 @@ class PointerListener { } var onPointerUp = function (event) { + + if (self.DEBUG == true){ + console.log("[PointerListener] pointerup event detected"); + } domElement.releasePointerCapture(event.pointerId); @@ -1829,6 +1878,10 @@ class PointerListener { * MDN: Pointer capture allows events for a particular pointer event (PointerEvent) to be re-targeted to a particular element instead of the normal (or hit test) target at a pointer's location. This can be used to ensure that an element continues to receive pointer events even if the pointer device's contact moves off the element (such as by scrolling or panning). */ var onPointerLeave = function (event) { + + if (self.DEBUG == true){ + console.log("[PointerListener] pointerleave detected"); + } if (self.contact != null && self.contact.isActive == true){ self.contact.onPointerLeave(event); @@ -1842,7 +1895,7 @@ class PointerListener { domElement.releasePointerCapture(event.pointerId); - if (this.DEBUG == true){ + if (self.DEBUG == true){ console.log("[PointerListener] pointercancel detected"); } @@ -1935,10 +1988,6 @@ class PointerListener { // to recognize Press, recognition has to be run if the user does nothing while having contact with the surfave (no pointermove, no pointerup, no pointercancel) onIdle () { - - if (this.DEBUG == true){ - console.log("[PointerListener] onIdle"); - } if (this.contact == null || this.contact.isActive == false){ this.clearIdleRecognitionInterval(); @@ -1957,7 +2006,7 @@ class PointerListener { this.contact.onIdle(); if (this.DEBUG == true){ - console.log("[PointerListener] run idle recognition"); + console.log("[PointerListener] onIdle - running idle recognition"); } this.recognizeGestures(); diff --git a/package.json b/package.json index f471abb..a8eddf9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "contactjs", - "version": "1.3.1", + "version": "1.4.0", "description": "Pointer gestures for your webapp", "main": "lib/contact.module.js", "directories": { diff --git a/src/contact.js b/src/contact.js index 3fbdd86..7c84cb8 100644 --- a/src/contact.js +++ b/src/contact.js @@ -18,9 +18,19 @@ */ class Contact { - constructor (pointerdownEvent) { + constructor (pointerdownEvent, options) { - this.DEBUG = false; + options = options || {}; + + this.options = { + "DEBUG" : false + }; + + for (let key in options){ + this.options[key] = options[key]; + } + + this.DEBUG = this.options.DEBUG; this.id = new Date().getTime(); @@ -69,8 +79,12 @@ class Contact { addPointer (pointerdownEvent) { this.currentPointerEvent = pointerdownEvent; + + var pointerInputOptions = { + "DEBUG" : this.DEBUG + }; - var pointerInput = new PointerInput(pointerdownEvent); + var pointerInput = new PointerInput(pointerdownEvent, pointerInputOptions); this.pointerInputs[pointerdownEvent.pointerId] = pointerInput; this.activePointerInputs[pointerdownEvent.pointerId] = pointerInput; } @@ -418,15 +432,23 @@ class PointerInput { constructor (pointerdownEvent, options) { - this.DEBUG = false; - options = options || {}; + + this.options = { + "DEBUG" : false + }; + + for (let key in options){ + this.options[key] = options[key]; + } + + this.DEBUG = this.options.DEBUG; var now = new Date().getTime(); this.pointerId = pointerdownEvent.pointerId; - var hasVectorTimespan = Object.prototype.hasOwnProperty.call(options, "vectorTimespan"); - this.vectorTimespan = hasVectorTimespan == true ? options.vectorTimespan : 100; // milliseconds + var hasVectorTimespan = Object.prototype.hasOwnProperty.call(this.options, "vectorTimespan"); + this.vectorTimespan = hasVectorTimespan == true ? this.options.vectorTimespan : 100; // milliseconds // events used for vector calculation this.initialPointerEvent = pointerdownEvent; diff --git a/src/gestures.js b/src/gestures.js index d787229..a16ccf8 100644 --- a/src/gestures.js +++ b/src/gestures.js @@ -70,7 +70,7 @@ class Gesture { if (minValue != null && value != null && value < minValue){ if (this.DEBUG == true){ - console.log("dismissing min" + this.constructor.name + ": required " + parameterName + ": " + minValue + ", current value: " + value); + console.log("dismissing min" + this.eventBaseName + ": required " + parameterName + ": " + minValue + ", current value: " + value); } return false; @@ -79,7 +79,7 @@ class Gesture { if (maxValue != null && value != null && value > maxValue){ if (this.DEBUG == true){ - console.log("dismissing max" + this.constructor.name + ": required " + parameterName + ": " + maxValue + ", current value: " + value); + console.log("dismissing max" + this.eventBaseName + ": required " + parameterName + ": " + maxValue + ", current value: " + value); } return false; @@ -102,7 +102,7 @@ class Gesture { } if (this.DEBUG == true){ - console.log("[Gestures] dismissing " + this.constructor.name + ": " + parameterName + " required: " + requiredValue + ", actual value: " + value); + console.log("[Gestures] dismissing " + this.eventBaseName + ": " + parameterName + " required: " + requiredValue + ", actual value: " + value); } return false; @@ -152,7 +152,7 @@ class Gesture { var primaryPointerInput = contact.getPrimaryPointerInput(); if (this.DEBUG == true){ - console.log("[Gestures] running recognition for " + this.constructor.name); + console.log("[Gestures] running recognition for " + this.eventBaseName); } @@ -193,7 +193,7 @@ class Gesture { if (this.options.supportedDirections.indexOf(primaryPointerInput.liveParameters.vector.direction) == -1){ if (this.DEBUG == true){ - console.log("[Gestures] dismissing " + this.constructor.name + ": supported directions: " + this.options.supportedDirections + ", current direction: " + primaryPointerInput.liveParameters.vector.direction); + console.log("[Gestures] dismissing " + this.eventBaseName + ": supported directions: " + this.options.supportedDirections + ", current direction: " + primaryPointerInput.liveParameters.vector.direction); } return false; @@ -241,7 +241,7 @@ class Gesture { let gesture = this.options.blocks[g]; if (gesture.isActive == false) { if (this.DEBUG == false){ - console.log("[Gesture] blocking " + gesture.constructor.name); + console.log("[Gesture] blocking " + gesture.eventBaseName); } gesture.state = GESTURE_STATE_BLOCKED; } @@ -274,7 +274,7 @@ class Gesture { emit (contact, eventName) { // fire general event like "pan" , "pinch", "rotate" - eventName = eventName || this.constructor.name.toLowerCase(); + eventName = eventName || this.eventBaseName; if (this.DEBUG === true){ console.log("[Gestures] detected and firing event " + eventName); @@ -339,7 +339,7 @@ class Gesture { this.initialPointerEvent = contact.currentPointerEvent; - var eventName = "" + this.constructor.name.toLowerCase() + "start"; + var eventName = "" + this.eventBaseName + "start"; if (this.DEBUG === true) { console.log("[Gestures] firing event: " + eventName); @@ -361,7 +361,7 @@ class Gesture { this.isActive = false; - var eventName = "" + this.constructor.name.toLowerCase() + "end"; + var eventName = "" + this.eventBaseName + "end"; if (this.DEBUG === true) { console.log("[Gestures] firing event: " + eventName); @@ -470,6 +470,8 @@ class Pan extends SinglePointerGesture { options = options || {}; super(domElement, options); + + this.eventBaseName = "pan"; this.initialMinMaxParameters["pointerCount"] = [1,1]; // 1: no pan recognized at the pointerup event. 0: pan recognized at pointerup this.initialMinMaxParameters["duration"] = [0, null]; @@ -557,6 +559,8 @@ class Tap extends SinglePointerGesture { options = options || {}; super(domElement, options); + + this.eventBaseName = "tap"; this.initialMinMaxParameters["pointerCount"] = [0,0]; // count of fingers touching the surface. a tap is fired AFTER the user removed his finger this.initialMinMaxParameters["duration"] = [0, 200]; // milliseconds. after a certain touch duration, it is not a TAP anymore @@ -595,6 +599,8 @@ class Press extends SinglePointerGesture { options = options || {}; super(domElement, options); + + this.eventBaseName = "press"; this.initialMinMaxParameters["pointerCount"] = [1, 1]; // count of fingers touching the surface. a press is fired during an active contact this.initialMinMaxParameters["duration"] = [600, null]; // milliseconds. after a certain touch duration, it is not a TAP anymore @@ -797,6 +803,8 @@ class Pinch extends TwoPointerGesture { options = options || {}; super(domElement, options); + + this.eventBaseName = "pinch"; this.initialMinMaxParameters["centerMovement"] = [0, 50]; //px this.initialMinMaxParameters["distanceChange"] = [5, null]; // distance between 2 fingers @@ -822,6 +830,8 @@ class Rotate extends TwoPointerGesture { options = options || {}; super(domElement, options); + + this.eventBaseName = "rotate"; this.initialMinMaxParameters["centerMovement"] = [0, 50]; this.initialMinMaxParameters["distanceChange"] = [null, 50]; @@ -842,6 +852,8 @@ class TwoFingerPan extends TwoPointerGesture { options = options || {}; super(domElement, options); + + this.eventBaseName = "twofingerpan"; this.initialMinMaxParameters["centerMovement"] = [3, null]; this.initialMinMaxParameters["distanceChange"] = [null, 50]; diff --git a/src/pointer-listener.js b/src/pointer-listener.js index 9fa33e9..e432cd9 100644 --- a/src/pointer-listener.js +++ b/src/pointer-listener.js @@ -16,8 +16,6 @@ var ALL_GESTURE_CLASSES = [Tap, Press, Pan, Pinch, Rotate, TwoFingerPan]; class PointerListener { constructor (domElement, options){ - - this.DEBUG = false; // registry for events like "pan", "rotate", which have to be removed on this.destroy(); this.eventHandlers = {}; @@ -32,7 +30,10 @@ class PointerListener { this.options = { "bubbles" : true, - "handleTouchEvents" : false + "handleTouchEvents" : true, + "DEBUG" : false, + "DEBUG_GESTURES" : false, + "DEBUG_CONTACT" : false }; // add user-defined options to this.options @@ -44,6 +45,8 @@ class PointerListener { this.options[key] = options[key]; } + this.DEBUG = this.options.DEBUG; + // add instantiatedGestures to options.supportedGestures var supportedGestures = ALL_GESTURE_CLASSES; var instantiatedGestures = []; @@ -59,7 +62,8 @@ class PointerListener { let gesture; let GestureClass = supportedGestures[i]; let gestureOptions = { - bubbles : this.options.bubbles + "bubbles" : this.options.bubbles, + "DEBUG" : this.options.DEBUG_GESTURES }; if (typeof GestureClass == "function"){ @@ -102,13 +106,20 @@ class PointerListener { // javascript fires the events "pointerdown", "pointermove", "pointerup" and "pointercancel" // on each of these events, the contact instance is updated and GestureRecognizers of this.supported_events are run var onPointerDown = function (event) { + + if (self.DEBUG == true){ + console.log("[PointerListener] pointerdown event detected"); + } // re-target all pointerevents to the current element // see https://developer.mozilla.org/en-US/docs/Web/API/Element/setPointerCapture domElement.setPointerCapture(event.pointerId); if (self.contact == null || self.contact.isActive == false) { - self.contact = new Contact(event); + let contactOptions = { + "DEBUG" : self.options.DEBUG_CONTACT + }; + self.contact = new Contact(event, contactOptions); } else { // use existing contact instance if a second pointer becomes present @@ -152,6 +163,10 @@ class PointerListener { } var onPointerUp = function (event) { + + if (self.DEBUG == true){ + console.log("[PointerListener] pointerup event detected"); + } domElement.releasePointerCapture(event.pointerId); @@ -180,6 +195,10 @@ class PointerListener { * MDN: Pointer capture allows events for a particular pointer event (PointerEvent) to be re-targeted to a particular element instead of the normal (or hit test) target at a pointer's location. This can be used to ensure that an element continues to receive pointer events even if the pointer device's contact moves off the element (such as by scrolling or panning). */ var onPointerLeave = function (event) { + + if (self.DEBUG == true){ + console.log("[PointerListener] pointerleave detected"); + } if (self.contact != null && self.contact.isActive == true){ self.contact.onPointerLeave(event); @@ -193,7 +212,7 @@ class PointerListener { domElement.releasePointerCapture(event.pointerId); - if (this.DEBUG == true){ + if (self.DEBUG == true){ console.log("[PointerListener] pointercancel detected"); } @@ -286,10 +305,6 @@ class PointerListener { // to recognize Press, recognition has to be run if the user does nothing while having contact with the surfave (no pointermove, no pointerup, no pointercancel) onIdle () { - - if (this.DEBUG == true){ - console.log("[PointerListener] onIdle"); - } if (this.contact == null || this.contact.isActive == false){ this.clearIdleRecognitionInterval(); @@ -308,7 +323,7 @@ class PointerListener { this.contact.onIdle(); if (this.DEBUG == true){ - console.log("[PointerListener] run idle recognition"); + console.log("[PointerListener] onIdle - running idle recognition"); } this.recognizeGestures(); diff --git a/test/test.html b/test/test.html index 36bdbbd..da93170 100644 --- a/test/test.html +++ b/test/test.html @@ -38,7 +38,7 @@ - + diff --git a/test/test.js b/test/test.js index 2f06d28..451cd80 100644 --- a/test/test.js +++ b/test/test.js @@ -43,6 +43,7 @@ function loadContact (){ twoFingerPan.block(pinch);*/ var pointerListener = new PointerListener(rectangle, { + //"DEBUG_CONTACT" : true, //supportedGestures : [pinch, twoFingerPan], //supportedGestures : [TwoFingerPan, Pinch], pointerup: function (event, pointerListener){