Multi-Touch MouseTracker Fixes

Removed zoom on click for touch.
Added an isPrimary flag to gesture points.
Fixed documentation error and updated documentation and comments.
Implemented the stopHandler for mouse events.
This commit is contained in:
Mark Salsbery 2014-04-01 09:32:04 -07:00
parent 8df20a5b86
commit 2c8af72f5e
2 changed files with 119 additions and 251 deletions

View File

@ -34,7 +34,7 @@
(function ( $ ) {
// is any button currently being pressed while mouse events occur
// is any button currently being pressed while pointer events occur
var IS_BUTTON_DOWN = false,
// is any tracker currently capturing?
//IS_CAPTURING = false,
@ -48,7 +48,7 @@
/**
* @class MouseTracker
* @classdesc Provides simplified handling of common mouse, touch, and keyboard
* @classdesc Provides simplified handling of common pointing device (mouse, touch, pen) and keyboard
* events on a specific element, like 'enter', 'exit', 'press', 'release',
* 'scroll', 'click', and 'drag'.
*
@ -59,31 +59,31 @@
* the original positional arguments 'element', 'clickTimeThreshold',
* and 'clickDistThreshold' in that order.
* @param {Element|String} options.element
* A reference to an element or an element id for which the mouse/touch/key
* A reference to an element or an element id for which the pointer/key
* events will be monitored.
* @param {Number} options.clickTimeThreshold
* The number of milliseconds within which multiple mouse clicks
* The number of milliseconds within which multiple pointer clicks
* will be treated as a single event.
* @param {Number} options.clickDistThreshold
* The distance between mouse click within multiple mouse clicks
* The distance between pointer click within multiple pointer clicks
* will be treated as a single event.
* @param {Number} [options.stopDelay=50]
* The number of milliseconds without mouse move before the mouse stop
* The number of milliseconds without pointer move before the stop
* event is fired.
* @param {OpenSeadragon.EventHandler} [options.enterHandler=null]
* An optional handler for mouse enter.
* An optional handler for pointer enter.
* @param {OpenSeadragon.EventHandler} [options.exitHandler=null]
* An optional handler for mouse exit.
* An optional handler for pointer exit.
* @param {OpenSeadragon.EventHandler} [options.pressHandler=null]
* An optional handler for mouse press.
* An optional handler for pointer press.
* @param {OpenSeadragon.EventHandler} [options.releaseHandler=null]
* An optional handler for mouse release.
* An optional handler for pointer release.
* @param {OpenSeadragon.EventHandler} [options.moveHandler=null]
* An optional handler for mouse move.
* An optional handler for pointer move.
* @param {OpenSeadragon.EventHandler} [options.scrollHandler=null]
* An optional handler for mouse scroll.
* An optional handler for mouse wheel scroll.
* @param {OpenSeadragon.EventHandler} [options.clickHandler=null]
* An optional handler for mouse click.
* An optional handler for pointer click.
* @param {OpenSeadragon.EventHandler} [options.dragHandler=null]
* An optional handler for the drag gesture.
* @param {OpenSeadragon.EventHandler} [options.dragEndHandler=null]
@ -113,19 +113,19 @@
this.hash = Math.random(); // An unique hash for this tracker.
/**
* The element for which mouse/touch/key events are being monitored.
* The element for which pointer events are being monitored.
* @member {Element} element
* @memberof OpenSeadragon.MouseTracker#
*/
this.element = $.getElement( options.element );
/**
* The number of milliseconds within which mutliple mouse clicks will be treated as a single event.
* The number of milliseconds within which mutliple pointer clicks will be treated as a single event.
* @member {Number} clickTimeThreshold
* @memberof OpenSeadragon.MouseTracker#
*/
this.clickTimeThreshold = options.clickTimeThreshold;
/**
* The distance between mouse click within multiple mouse clicks will be treated as a single event.
* The distance between pointer click within multiple pointer clicks will be treated as a single event.
* @member {Number} clickDistThreshold
* @memberof OpenSeadragon.MouseTracker#
*/
@ -154,9 +154,9 @@
/**
* @private
* @property {Boolean} tracking
* Are we currently tracking mouse events.
* Are we currently tracking pointer events.
* @property {Boolean} capturing
* Are we curruently capturing mouse events.
* Are we curruently capturing mouse events (legacy mouse events only).
*/
THIS[ this.hash ] = {
click: function ( event ) { onClick( _this, event ); },
@ -222,7 +222,7 @@
$.MouseTracker.prototype = /** @lends OpenSeadragon.MouseTracker.prototype */{
/**
* Clean up any events or objects created by the mouse tracker.
* Clean up any events or objects created by the tracker.
* @function
*/
destroy: function () {
@ -598,14 +598,13 @@
/**
* Provides continuous computation of velocity (speed and direction) of active pointers.
* This is a singleton, used by all MouseTracker instances. Currently it is extremely unlikely there will ever be more than
* two active gesture pointers at a time.
*
* @member gesturePointVelocityTracker
* @memberof OpenSeadragon.MouseTracker
* @private
*
* Provides continuous computation of velocity (speed and direction) of active pointers.
*
* This is a singleton, used by all MouseTracker instances. Currently it is extremely unlikely there will ever be more than
* two active gesture pointers at a time.
*/
$.MouseTracker.gesturePointVelocityTracker = (function () {
var trackerPoints = [],
@ -754,10 +753,6 @@
}
//*******************************************************************************************************************************************
//** Utility Types/Classes
/**
* Represents a point of contact on the screen made by a mouse cursor, pen, touch, or other pointing device.
*
@ -768,14 +763,16 @@
* Identifier unique from all other active GesturePoints for a given pointer device.
* @property {String} type
* The pointer device type: "mouse", "touch", "pen", or "".
* @property {Boolean} isPrimary
* True if the gesture point is a master pointer amongst the set of active pointers for each pointer type. True for mouse and primary (first) touch/pen pointers.
* @property {Boolean} insideElementPressed
* True if mouse button pressed or contact point initiated inside the screen area of the tracked element.
* True if button pressed or contact point initiated inside the screen area of the tracked element.
* @property {Boolean} insideElement
* True if mouse cursor or contact point is currently inside the bounds of the tracked element.
* True if pointer or contact point is currently inside the bounds of the tracked element.
* @property {Number} speed
* Continuously computed speed, in pixels per second.
* Current computed speed, in pixels per second.
* @property {Number} direction
* Continuously computed direction, in radians. Only valid if speed > 0.
* Current computed direction, expressed as an angle counterclockwise relative to the positive X axis (-pi to pi, in radians). Only valid if speed > 0.
* @property {OpenSeadragon.Point} startPos
* The initial pointer position, relative to the page including any scrolling.
* @property {Number} startTime
@ -791,10 +788,11 @@
*/
/**
/***
* @class GesturePointList
* @classdesc Provides an abstraction for a set of {@link OpenSeadragon.MouseTracker.GesturePoint} objects.
* @memberof OpenSeadragon.MouseTracker
* @private
*/
$.MouseTracker.GesturePointList = function () {
this._gPoints = [];
@ -838,6 +836,18 @@
}
return this._gPoints.length;
},
/**
* @function
* @param {Number} index - The index of the gesture point to retrieve from the list.
* @returns {OpenSeadragon.MouseTracker.GesturePoint|null} The gesture point at the given index, or null if not found.
*/
getByIndex: function ( index ) {
if ( index < this._gPoints.length) {
return this._gPoints[ index ];
}
return null;
},
/**
* @function
* @param {Number} id - The id of the gesture point to retrieve from the list.
@ -852,16 +862,26 @@
}
}
return null;
},
/**
* @function
* @returns {OpenSeadragon.MouseTracker.GesturePoint|null} The primary gesture point in the list, or null if not found.
*/
getPrimary: function ( id ) {
var i,
len = this._gPoints.length;
for ( i = 0; i < len; i++ ) {
if ( this._gPoints[ i ].isPrimary ) {
return this._gPoints[ i ];
}
}
return null;
}
};
//*******************************************************************************************************************************************
//** Utility Functions
/**
* Starts tracking mouse events on this element.
* Starts tracking pointer events on this element.
* @private
* @inner
*/
@ -886,7 +906,7 @@
}
/**
* Stops tracking mouse events on this element.
* Stops tracking pointer events on this element.
* @private
* @inner
*/
@ -921,7 +941,7 @@
}
/**
* Begin capturing mouse events on this element.
* Begin capturing mouse events on this element (legacy mouse events only).
* @private
* @inner
*/
@ -968,7 +988,7 @@
/**
* Stop capturing mouse events on this element.
* Stop capturing mouse events on this element (legacy mouse events only).
* @private
* @inner
*/
@ -1118,10 +1138,6 @@
}
//*******************************************************************************************************************************************
//** DOM EVent Handlers
/**
* @private
* @inner
@ -1265,6 +1281,7 @@
gPoint = {
id: $.MouseTracker.mousePointerId,
type: 'mouse',
isPrimary: true,
insideElement: true,
currentPos: getMouseAbsolute( event ),
currentTime: $.now()
@ -1286,6 +1303,7 @@
gPoint = {
id: $.MouseTracker.mousePointerId,
type: 'mouse',
isPrimary: true,
insideElement: false,
currentPos: getMouseAbsolute( event ),
currentTime: $.now()
@ -1311,6 +1329,7 @@
gPoint = {
id: $.MouseTracker.mousePointerId,
type: 'mouse',
isPrimary: true,
currentPos: getMouseAbsolute( event ),
currentTime: $.now()
};
@ -1495,6 +1514,8 @@
function onTouchStart( tracker, event ) {
var delegate = THIS[ tracker.hash ],
time,
primaryPoint,
primaryId,
gPoint,
i,
touchCount = event.changedTouches.length,
@ -1513,10 +1534,19 @@
updatePointersOver( tracker, event, [ gPoint ] );
}
primaryPoint = delegate.touchPoints.getPrimary();
if ( primaryPoint ) {
primaryId = primaryPoint.id;
}
else {
primaryId = event.changedTouches[ 0 ].identifier;
}
for ( i = 0; i < touchCount; i++ ) {
gPoints.push( {
id: event.changedTouches[ i ].identifier,
type: 'touch',
isPrimary: event.changedTouches[ i ].identifier === primaryId,
currentPos: getMouseAbsolute( event.changedTouches[ i ] ),
currentTime: time
} );
@ -1539,6 +1569,7 @@
function onTouchEnd( tracker, event ) {
var delegate = THIS[ tracker.hash ],
time,
primaryPoint,
gPoint,
i,
touchCount = event.changedTouches.length,
@ -1557,6 +1588,14 @@
removePointers( tracker, event, gPoints );
primaryPoint = delegate.touchPoints.getPrimary();
if ( !primaryPoint ) {
primaryPoint = delegate.touchPoints.getByIndex( 0 );
if ( primaryPoint ) {
primaryPoint.isPrimary = true;
}
}
if ( !$.MouseTracker.haveTouchEnter && touchCount > 0 && delegate.touchPoints.getLength() === 0 ) {
gPoint = {
id: event.changedTouches[ 0 ].identifier,
@ -1656,6 +1695,7 @@
gPoint = {
id: event.pointerId,
type: getPointerType( event ),
isPrimary: event.isPrimary,
insideElement: true,
currentPos: getMouseAbsolute( event ),
currentTime: $.now()
@ -1675,6 +1715,7 @@
gPoint = {
id: event.pointerId,
type: getPointerType( event ),
isPrimary: event.isPrimary,
insideElement: false,
currentPos: getMouseAbsolute( event ),
currentTime: $.now()
@ -1705,6 +1746,7 @@
gPoint = {
id: event.pointerId,
type: getPointerType( event ),
isPrimary: event.isPrimary,
currentPos: getMouseAbsolute( event ),
currentTime: $.now()
};
@ -1740,6 +1782,7 @@
gPoint = {
id: event.pointerId,
type: getPointerType( event ),
isPrimary: event.isPrimary,
currentPos: getMouseAbsolute( event ),
currentTime: $.now()
};
@ -1765,6 +1808,7 @@
gPoint = {
id: event.pointerId,
type: getPointerType( event ),
isPrimary: event.isPrimary,
currentPos: getMouseAbsolute( event ),
currentTime: $.now()
};
@ -1789,16 +1833,13 @@
gPoint = {
id: event.pointerId,
type: getPointerType( event ),
isPrimary: event.isPrimary,
};
cancelPointers( tracker, event, [ gPoint ] );
}
//*******************************************************************************************************************************************
//** Event Processing Functions
/**
* Handles 'wheel' events.
* The event may be simulated by the legacy mouse wheel event handler (onMouseWheel()).
@ -1850,6 +1891,7 @@
gPoint = {
id: $.MouseTracker.mousePointerId,
type: 'mouse',
isPrimary: true,
currentPos: getMouseAbsolute( event ),
currentTime: $.now()
};
@ -1874,6 +1916,7 @@
gPoint = {
id: $.MouseTracker.mousePointerId,
type: 'mouse',
isPrimary: true,
currentPos: getMouseAbsolute( event ),
currentTime: $.now()
};
@ -1886,13 +1929,13 @@
* @private
* @inner
*/
function handlePointerStop( tracker, originalMoveEvent ) {
function handlePointerStop( tracker, originalMoveEvent, pointerType ) {
if ( tracker.stopHandler ) {
tracker.stopHandler( {
eventSource: tracker,
pointerType: '',
pointerType: pointerType,
position: getMouseRelative( originalMoveEvent, tracker.element ),
isTouchEvent: false,
isTouchEvent: pointerType === 'touch',
originalEvent: originalMoveEvent,
preventDefaultAction: false,
userData: tracker.userData
@ -2107,24 +2150,25 @@
updateGPoint.currentPos = curGPoint.currentPos;
updateGPoint.currentTime = curGPoint.currentTime;
}
}
//if ( tracker.stopHandler ) {
// clearTimeout( tracker.stopTimeOut );
// tracker.stopTimeOut = setTimeout( function() {
// handlePointerStop( tracker, event );
// }, tracker.stopDelay );
//}
// Stop (mouse only)
if ( gPoints[ 0 ].type === 'mouse' && tracker.stopHandler ) {
clearTimeout( tracker.stopTimeOut );
tracker.stopTimeOut = setTimeout( function() {
handlePointerStop( tracker, event, gPoints[ 0 ].type );
}, tracker.stopDelay );
}
if ( pointsListLength === 0 ) {
// Move (no contacts)
// Move (no contacts, mouse or other hover-capable device)
if ( tracker.moveHandler ) {
propagate = tracker.moveHandler(
{
eventSource: tracker,
pointerType: curGPoint.type,
position: getPointRelativeToAbsolute( curGPoint.currentPos, tracker.element ),
isTouchEvent: curGPoint.type === 'touch',
pointerType: gPoints[ 0 ].type,
position: getPointRelativeToAbsolute( gPoints[ 0 ].currentPos, tracker.element ),
isTouchEvent: gPoints[ 0 ].type === 'touch',
originalEvent: event,
preventDefaultAction: false,
userData: tracker.userData
@ -2364,199 +2408,23 @@
}
//*******************************************************************************************************************************************
//*******************************************************************************************************************************************
//function handlePointerOver( tracker, event, position ) {
// var delegate = THIS[ tracker.hash ],
// propagate;
// //if ( !isTouch ) {
// // if ( $.Browser.vendor == $.BROWSERS.IE &&
// // $.Browser.version < 9 &&
// // delegate.capturing &&
// // !isChild( event.srcElement, tracker.element ) ) {
// // triggerOthers( tracker, onMouseOver, event, isTouch );
// // }
// // var to = event.target ?
// // event.target :
// // event.srcElement,
// // from = event.relatedTarget ?
// // event.relatedTarget :
// // event.fromElement;
// // if ( !isChild( tracker.element, to ) ||
// // isChild( tracker.element, from ) ) {
// // return;
// // }
// //}
// //delegate.insideElement = true;
// if ( tracker.enterHandler ) {
// propagate = tracker.enterHandler(
// {
// eventSource: tracker,
// position: position,
// insideElementPressed: delegate.insideElementPressed,
// buttonDownAny: IS_BUTTON_DOWN,
// isTouchEvent: false,//isTouch,
// originalEvent: event,
// preventDefaultAction: false,
// userData: tracker.userData
// }
// );
// if ( propagate === false ) {
// $.cancelEvent( event );
// }
// }
//}
//function handlePointerOut( tracker, event, position ) {
// var delegate = THIS[ tracker.hash ],
// propagate;
// //if ( !isTouch ) {
// // if ( $.Browser.vendor == $.BROWSERS.IE &&
// // $.Browser.version < 9 &&
// // delegate.capturing &&
// // !isChild( event.srcElement, tracker.element ) ) {
// // triggerOthers( tracker, onMouseOut, event, isTouch );
// // }
// // var from = event.target ?
// // event.target :
// // event.srcElement,
// // to = event.relatedTarget ?
// // event.relatedTarget :
// // event.toElement;
// // if ( !isChild( tracker.element, from ) ||
// // isChild( tracker.element, to ) ) {
// // return;
// // }
// //}
// //delegate.insideElement = false;
// if ( tracker.exitHandler ) {
// propagate = tracker.exitHandler(
// {
// eventSource: tracker,
// position: position,
// insideElementPressed: delegate.insideElementPressed,
// buttonDownAny: IS_BUTTON_DOWN,
// isTouchEvent: false,//isTouch,
// originalEvent: event,
// preventDefaultAction: false,
// userData: tracker.userData
// }
// );
// if ( propagate === false ) {
// $.cancelEvent( event );
// }
// }
//}
///**
// * @private
// * @inner
// * Returns true if elementB is a child node of elementA, or if they're equal.
// */
//function handlePointerMove( tracker, event ) {
// var propagate;
// if ( tracker.moveHandler ) {
// propagate = tracker.moveHandler(
// {
// eventSource: tracker,
// position: getMouseRelative( event, tracker.element ),
// isTouchEvent: false,
// originalEvent: event,
// preventDefaultAction: false,
// userData: tracker.userData
// }
// );
// if ( propagate === false ) {
// $.cancelEvent( event );
//function isChild( elementA, elementB ) {
// var body = document.body;
// while ( elementB && elementA != elementB && body != elementB ) {
// try {
// elementB = elementB.parentNode;
// } catch ( e ) {
// return false;
// }
// }
// if ( tracker.stopHandler ) {
// clearTimeout( tracker.stopTimeOut );
// tracker.stopTimeOut = setTimeout( function() {
// handlePointerStop( tracker, event );
// }, tracker.stopDelay );
// }
// return elementA == elementB;
//}
//
///**
// * @private
// * @inner
// */
//function handleMouseClick( tracker, event, isTouch ) {
// var delegate = THIS[ tracker.hash ],
// propagate;
// isTouch = isTouch || false;
// event = $.getEvent( event );
// var eventOrTouchPoint = isTouch ? event.changedTouches[ 0 ] : event;
// if ( event.button == 2 ) {
// return;
// }
// var time = $.now() - delegate.lastMouseDownTime,
// point = getMouseAbsolute( eventOrTouchPoint ),
// distance = delegate.lastMouseDownPoint.distanceTo( point ),
// quick = time <= tracker.clickTimeThreshold &&
// distance <= tracker.clickDistThreshold;
// if ( tracker.clickHandler ) {
// propagate = tracker.clickHandler(
// {
// eventSource: tracker,
// position: getMouseRelative( eventOrTouchPoint, tracker.element ),
// quick: quick,
// shift: event.shiftKey,
// isTouchEvent: isTouch,
// originalEvent: event,
// preventDefaultAction: false,
// userData: tracker.userData
// }
// );
// if ( propagate === false ) {
// $.cancelEvent( event );
// }
// }
//}
/**
* @private
* @inner
* Returns true if elementB is a child node of elementA, or if they're equal.
*/
function isChild( elementA, elementB ) {
var body = document.body;
while ( elementB && elementA != elementB && body != elementB ) {
try {
elementB = elementB.parentNode;
} catch ( e ) {
return false;
}
}
return elementA == elementB;
}
/**
* @private
* @inner

View File

@ -2207,7 +2207,7 @@ function onBlur(){
function onCanvasClick( event ) {
var zoomPerClick,
factor;
if ( !event.preventDefaultAction && this.viewport && event.quick ) { // ignore clicks where mouse moved
if ( event.pointerType !== 'touch' && !event.preventDefaultAction && this.viewport && event.quick ) {
zoomPerClick = this.zoomPerClick;
factor = event.shift ? 1.0 / zoomPerClick : zoomPerClick;
this.viewport.zoomBy(