diff --git a/changelog.txt b/changelog.txt
index 66730e0e..3206e6f7 100644
--- a/changelog.txt
+++ b/changelog.txt
@@ -7,8 +7,12 @@ OPENSEADRAGON CHANGELOG
* The drawer element is no longer accessible via viewer.canvas.firstChild but via viewer.drawersContainer.firstChild or viewer.drawer.canvas.
* The overlays elements are no longer accessible via viewer.canvas.childNodes but via viewer.overlaysContainer.childNodes or viewer.currentOverlays[i].element.
* BREAKING CHANGE: Pseudo full screen mode on IE<11 using activex has been dropped. OpenSeadragon will run in full page if full screen mode is requested.
+* BREAKING CHANGE: MouseTracker touch pinch gestures are no longer converted to scroll events. MouseTracker.pinchHandler should be used instead. (#369)
* DEPRECATION: overlay functions have been moved from Drawer to Viewer (#331)
* DEPRECATION: OpenSeadragon.cancelFullScreen has been renamed OpenSeadragon.exitFullScreen (#358)
+* DEPRECATION: The 'isTouchEvent' property passed in MouseTracker events is deprecated and has been replaced with 'pointerType', which is a String value "mouse", "touch", "pen", etc. to support multiple simultaneous pointing devices (#369)
+* DEPRECATION: The 'buttonDownAny' property passed in MouseTracker enter and exit events (enterHandler/exitHandler) is deprecated and has been replaced with 'buttons', which indicates the button(s) currently pressed (#369)
+* DEPRECATION: The 'buttonDownAny' property passed in Viewer's 'container-enter' and 'container-exit' events is deprecated and has been replaced with 'buttons', which indicates the button(s) currently pressed (#369)
* Added layers support. Multiple images can now been displayed on top of each other with transparency via the Viewer.addLayer method (#298)
* Improved overlay functions (#331)
* Fixed: Nav button highlight states aren't quite aligned on Firefox (#303)
@@ -17,7 +21,7 @@ OPENSEADRAGON CHANGELOG
* Added crossOriginPolicy drawer configuration to enable or disable CORS image requests (#364)
* Disabled CORS by default (#377)
* Added a ControlAnchor.ABSOLUTE enumeration. Enables absolute positioning of control elements in the viewer (#310)
-* Added a 'navigator-scroll' event to Navigator. Fired when mousewheel/pinch events occur in the navigator (#310)
+* Added a 'navigator-scroll' event to Navigator. Fired when mousewheel events occur in the navigator (#310)
* Added a navigatorMaintainSizeRatio option. If set to true, the navigator minimap resizes when the viewer element is resized (#310)
* Added 'ABSOLUTE' as a navigatorPosition option, along with corresponding navigatorTop, navigatorLeft options. Allows the navigator minimap to be placed anywhere in the viewer (#310)
* Enhanced the navigatorTop, navigatorLeft, navigatorHeight, and navigatorWidth options to allow a number for pixel units or a string for other element units (%, em, etc.) (#310)
@@ -30,6 +34,20 @@ OPENSEADRAGON CHANGELOG
* Various fixes to bring OpenSeadragon into W3C compliance (#375)
* Added separate flags for turning off each of the nav buttons (#376)
* Added support for query parameters in DZI tileSource URL (#378)
+* Enhanced MouseTracker for multi-touch (#369)
+ * Added support for tracking multiple touch-points on multiple/simultaneous pointing devices
+ * Added support for the W3C Pointer Events event model. Enables touch/multi-touch on IE10+
+ * Added a dragEndHandler event callback, called when a drag gesture ends
+ * Added a pinchHandler event callback, called as a pinch gesture (2 touch points) is occurring
+ * Added real-time velocity (speed and direction) tracking to drag operations. 'speed' and 'direction' values are passed in the dragHandler and dragEndHandler event data
+* Enhanced Viewer for multi-touch (#369)
+ * Added pinch zoom with the new MouseTracker pinchHandler. The 'pan' and 'zoom' Viewer events can be used to detect changes resulting in pinch gestures
+ * Added a "canvas-pinch" event fired by the pinch event handler
+ * Added flick gesture with the new MouseTracker dragEndHandler
+ * Added a "canvas-drag-end" event fired by the drag-end event handler
+ * Added a GestureSettings class for per-device gesture options. Currently has settings to enable/disable zoom-on-scroll, zoom-on-pinch, zoom-on-click, and flick gesture settings.
+ * Added GestureSettings objects for mouse, touch, and pen devices to the Viewer options giving users the ability to customize gesture handling in the viewer
+ * Added velocity (speed and direction) properties to the "canvas-drag" event
1.0.0:
diff --git a/src/mousetracker.js b/src/mousetracker.js
index 86238d8c..4405e93e 100644
--- a/src/mousetracker.js
+++ b/src/mousetracker.js
@@ -34,20 +34,13 @@
(function ( $ ) {
- // is any button currently being pressed while mouse events occur
- var IS_BUTTON_DOWN = false,
- // is any tracker currently capturing?
- IS_CAPTURING = false,
- // dictionary from hash to MouseTracker
- ACTIVE = {},
- // list of trackers interested in capture
- CAPTURING = [],
// dictionary from hash to private properties
- THIS = {};
+ var THIS = {};
+
/**
* @class MouseTracker
- * @classdesc Provides simplified handling of common mouse, touch, and keyboard
+ * @classdesc Provides simplified handling of common pointer device (mouse, touch, pen, etc.) and keyboard
* events on a specific element, like 'enter', 'exit', 'press', 'release',
* 'scroll', 'click', and 'drag'.
*
@@ -58,33 +51,37 @@
* 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 mouse drag.
+ * An optional handler for the drag gesture.
+ * @param {OpenSeadragon.EventHandler} [options.dragEndHandler=null]
+ * An optional handler for after a drag gesture.
+ * @param {OpenSeadragon.EventHandler} [options.pinchHandler=null]
+ * An optional handler for the pinch gesture.
* @param {OpenSeadragon.EventHandler} [options.keyHandler=null]
* An optional handler for keypress.
* @param {OpenSeadragon.EventHandler} [options.focusHandler=null]
@@ -108,19 +105,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#
*/
@@ -136,6 +133,8 @@
this.scrollHandler = options.scrollHandler || null;
this.clickHandler = options.clickHandler || null;
this.dragHandler = options.dragHandler || null;
+ this.dragEndHandler = options.dragEndHandler || null;
+ this.pinchHandler = options.pinchHandler || null;
this.stopHandler = options.stopHandler || null;
this.keyHandler = options.keyHandler || null;
this.focusHandler = options.focusHandler || null;
@@ -147,50 +146,73 @@
/**
* @private
* @property {Boolean} tracking
- * Are we currently tracking mouse events.
+ * Are we currently tracking pointer events for this element.
* @property {Boolean} capturing
- * Are we curruently capturing mouse events.
- * @property {Boolean} insideElementPressed
- * True if the left mouse button is currently being pressed and was
- * initiated inside the tracked element, otherwise false.
- * @property {Boolean} insideElement
- * Are we currently inside the screen area of the tracked element.
- * @property {OpenSeadragon.Point} lastPoint
- * Position of last mouse down/move
- * @property {Number} lastMouseDownTime
- * Time of last mouse down.
- * @property {OpenSeadragon.Point} lastMouseDownPoint
- * Position of last mouse down
+ * Are we curruently capturing mouse events (legacy mouse events only).
*/
THIS[ this.hash ] = {
- mouseover: function ( event ) { onMouseOver( _this, event, false ); },
- mouseout: function ( event ) { onMouseOut( _this, event, false ); },
- mousedown: function ( event ) { onMouseDown( _this, event ); },
- mouseup: function ( event ) { onMouseUp( _this, event, false ); },
- mousemove: function ( event ) { onMouseMove( _this, event ); },
- click: function ( event ) { onMouseClick( _this, event ); },
+ setCaptureCapable: !!this.element.setCapture && !!this.element.releaseCapture,
+
+ click: function ( event ) { onClick( _this, event ); },
+ keypress: function ( event ) { onKeyPress( _this, event ); },
+ focus: function ( event ) { onFocus( _this, event ); },
+ blur: function ( event ) { onBlur( _this, event ); },
+
wheel: function ( event ) { onWheel( _this, event ); },
mousewheel: function ( event ) { onMouseWheel( _this, event ); },
DOMMouseScroll: function ( event ) { onMouseWheel( _this, event ); },
MozMousePixelScroll: function ( event ) { onMouseWheel( _this, event ); },
- mouseupie: function ( event ) { onMouseUpIE( _this, event ); },
- mousemovecapturedie: function ( event ) { onMouseMoveCapturedIE( _this, event ); },
+
+ mouseover: function ( event ) { onMouseOver( _this, event ); },
+ mouseout: function ( event ) { onMouseOut( _this, event ); },
+ mouseenter: function ( event ) { onMouseEnter( _this, event ); },
+ mouseleave: function ( event ) { onMouseLeave( _this, event ); },
+ mousedown: function ( event ) { onMouseDown( _this, event ); },
+ mouseup: function ( event ) { onMouseUp( _this, event ); },
mouseupcaptured: function ( event ) { onMouseUpCaptured( _this, event ); },
- mousemovecaptured: function ( event ) { onMouseMoveCaptured( _this, event, false ); },
+ mousemove: function ( event ) { onMouseMove( _this, event ); },
+ mousemovecaptured: function ( event ) { onMouseMoveCaptured( _this, event ); },
+
+ touchenter: function ( event ) { onTouchEnter( _this, event ); },
+ touchleave: function ( event ) { onTouchLeave( _this, event ); },
touchstart: function ( event ) { onTouchStart( _this, event ); },
- touchmove: function ( event ) { onTouchMove( _this, event ); },
touchend: function ( event ) { onTouchEnd( _this, event ); },
- keypress: function ( event ) { onKeyPress( _this, event ); },
- focus: function ( event ) { onFocus( _this, event ); },
- blur: function ( event ) { onBlur( _this, event ); },
+ touchmove: function ( event ) { onTouchMove( _this, event ); },
+ touchcancel: function ( event ) { onTouchCancel( _this, event ); },
+
+ gesturestart: function ( event ) { onGestureStart( _this, event ); },
+ gesturechange: function ( event ) { onGestureChange( _this, event ); },
+
+ pointerenter: function ( event ) { onPointerEnter( _this, event ); },
+ MSPointerEnter: function ( event ) { onPointerEnter( _this, event ); },
+ pointerleave: function ( event ) { onPointerLeave( _this, event ); },
+ MSPointerLeave: function ( event ) { onPointerLeave( _this, event ); },
+ pointerdown: function ( event ) { onPointerDown( _this, event ); },
+ MSPointerDown: function ( event ) { onPointerDown( _this, event ); },
+ pointerup: function ( event ) { onPointerUp( _this, event ); },
+ MSPointerUp: function ( event ) { onPointerUp( _this, event ); },
+ pointermove: function ( event ) { onPointerMove( _this, event ); },
+ MSPointerMove: function ( event ) { onPointerMove( _this, event ); },
+ pointercancel: function ( event ) { onPointerCancel( _this, event ); },
+ MSPointerCancel: function ( event ) { onPointerCancel( _this, event ); },
+
tracking: false,
+
+ // Active pointers lists. Array of GesturePointList objects, one for each pointer device type.
+ // GesturePointList objects are added each time a pointer is tracked by a new pointer device type (see getActivePointersListByType()).
+ // Active pointers are any pointer being tracked for this element which are in the hit-test area
+ // of the element (for hover-capable devices) and/or have contact or a button press initiated in the element.
+ activePointersLists: [],
+
+ // Legacy mouse event tracking
capturing: false,
- insideElementPressed: false,
- insideElement: false,
- lastPoint: null,
- lastMouseDownTime: null,
- lastMouseDownPoint: null,
- lastPinchDelta: 0
+
+ // Tracking for pinch gesture
+ pinchGPoints: [],
+ lastPinchDist: 0,
+ currentPinchDist: 0,
+ lastPinchCenter: null,
+ currentPinchCenter: null
};
};
@@ -198,7 +220,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 () {
@@ -232,6 +254,30 @@
return this;
},
+ /**
+ * Returns the {@link OpenSeadragon.MouseTracker.GesturePointList|GesturePointList} for the given pointer device type,
+ * creating and caching a new {@link OpenSeadragon.MouseTracker.GesturePointList|GesturePointList} if one doesn't already exist for the type.
+ * @function
+ * @param {String} type - The pointer device type: "mouse", "touch", "pen", etc.
+ * @returns {OpenSeadragon.MouseTracker.GesturePointList}
+ */
+ getActivePointersListByType: function ( type ) {
+ var delegate = THIS[ this.hash ],
+ i,
+ len = delegate.activePointersLists.length,
+ list;
+
+ for ( i = 0; i < len; i++ ) {
+ if ( delegate.activePointersLists[ i ].type === type ) {
+ return delegate.activePointersLists[ i ];
+ }
+ }
+
+ list = new $.MouseTracker.GesturePointList( type );
+ delegate.activePointersLists.push( list );
+ return list;
+ },
+
/**
* Implement or assign implementation to these handlers during or after
* calling the constructor.
@@ -239,15 +285,20 @@
* @param {Object} event
* @param {OpenSeadragon.MouseTracker} event.eventSource
* A reference to the tracker instance.
+ * @param {String} event.pointerType
+ * "mouse", "touch", "pen", etc.
* @param {OpenSeadragon.Point} event.position
* The position of the event relative to the tracked element.
+ * @param {Number} event.buttons
+ * Current buttons pressed.
+ * Combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser.
* @param {Boolean} event.insideElementPressed
* True if the left mouse button is currently being pressed and was
* initiated inside the tracked element, otherwise false.
* @param {Boolean} event.buttonDownAny
- * Was the button down anywhere in the screen during the event.
+ * Was the button down anywhere in the screen during the event. Deprecated. Use buttons instead.
* @param {Boolean} event.isTouchEvent
- * True if the original event is a touch event, otherwise false.
+ * True if the original event is a touch event, otherwise false. Deprecated. Use pointerType and/or originalEvent instead.
* @param {Object} event.originalEvent
* The original event object.
* @param {Boolean} event.preventDefaultAction
@@ -264,15 +315,20 @@
* @param {Object} event
* @param {OpenSeadragon.MouseTracker} event.eventSource
* A reference to the tracker instance.
+ * @param {String} event.pointerType
+ * "mouse", "touch", "pen", etc.
* @param {OpenSeadragon.Point} event.position
* The position of the event relative to the tracked element.
+ * @param {Number} event.buttons
+ * Current buttons pressed.
+ * Combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser.
* @param {Boolean} event.insideElementPressed
* True if the left mouse button is currently being pressed and was
* initiated inside the tracked element, otherwise false.
* @param {Boolean} event.buttonDownAny
- * Was the button down anywhere in the screen during the event.
+ * Was the button down anywhere in the screen during the event. Deprecated. Use buttons instead.
* @param {Boolean} event.isTouchEvent
- * True if the original event is a touch event, otherwise false.
+ * True if the original event is a touch event, otherwise false. Deprecated. Use pointerType and/or originalEvent instead.
* @param {Object} event.originalEvent
* The original event object.
* @param {Boolean} event.preventDefaultAction
@@ -289,10 +345,15 @@
* @param {Object} event
* @param {OpenSeadragon.MouseTracker} event.eventSource
* A reference to the tracker instance.
+ * @param {String} event.pointerType
+ * "mouse", "touch", "pen", etc.
* @param {OpenSeadragon.Point} event.position
* The position of the event relative to the tracked element.
+ * @param {Number} event.buttons
+ * Current buttons pressed.
+ * Combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser.
* @param {Boolean} event.isTouchEvent
- * True if the original event is a touch event, otherwise false.
+ * True if the original event is a touch event, otherwise false. Deprecated. Use pointerType and/or originalEvent instead.
* @param {Object} event.originalEvent
* The original event object.
* @param {Boolean} event.preventDefaultAction
@@ -309,15 +370,20 @@
* @param {Object} event
* @param {OpenSeadragon.MouseTracker} event.eventSource
* A reference to the tracker instance.
+ * @param {String} event.pointerType
+ * "mouse", "touch", "pen", etc.
* @param {OpenSeadragon.Point} event.position
* The position of the event relative to the tracked element.
+ * @param {Number} event.buttons
+ * Current buttons pressed.
+ * Combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser.
* @param {Boolean} event.insideElementPressed
* True if the left mouse button is currently being pressed and was
* initiated inside the tracked element, otherwise false.
* @param {Boolean} event.insideElementReleased
- * True if the cursor still inside the tracked element when the button was released.
+ * True if the cursor inside the tracked element when the button was released.
* @param {Boolean} event.isTouchEvent
- * True if the original event is a touch event, otherwise false.
+ * True if the original event is a touch event, otherwise false. Deprecated. Use pointerType and/or originalEvent instead.
* @param {Object} event.originalEvent
* The original event object.
* @param {Boolean} event.preventDefaultAction
@@ -334,10 +400,15 @@
* @param {Object} event
* @param {OpenSeadragon.MouseTracker} event.eventSource
* A reference to the tracker instance.
+ * @param {String} event.pointerType
+ * "mouse", "touch", "pen", etc.
* @param {OpenSeadragon.Point} event.position
* The position of the event relative to the tracked element.
+ * @param {Number} event.buttons
+ * Current buttons pressed.
+ * Combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser.
* @param {Boolean} event.isTouchEvent
- * True if the original event is a touch event, otherwise false.
+ * True if the original event is a touch event, otherwise false. Deprecated. Use pointerType and/or originalEvent instead.
* @param {Object} event.originalEvent
* The original event object.
* @param {Boolean} event.preventDefaultAction
@@ -354,6 +425,8 @@
* @param {Object} event
* @param {OpenSeadragon.MouseTracker} event.eventSource
* A reference to the tracker instance.
+ * @param {String} event.pointerType
+ * "mouse", "touch", "pen", etc.
* @param {OpenSeadragon.Point} event.position
* The position of the event relative to the tracked element.
* @param {Number} event.scroll
@@ -361,7 +434,7 @@
* @param {Boolean} event.shift
* True if the shift key was pressed during this event.
* @param {Boolean} event.isTouchEvent
- * True if the original event is a touch event, otherwise false.
+ * True if the original event is a touch event, otherwise false. Deprecated. Use pointerType and/or originalEvent instead. Touch devices no longer generate scroll event.
* @param {Object} event.originalEvent
* The original event object.
* @param {Boolean} event.preventDefaultAction
@@ -378,14 +451,16 @@
* @param {Object} event
* @param {OpenSeadragon.MouseTracker} event.eventSource
* A reference to the tracker instance.
+ * @param {String} event.pointerType
+ * "mouse", "touch", "pen", etc.
* @param {OpenSeadragon.Point} event.position
* The position of the event relative to the tracked element.
- * @param {Number} event.quick
+ * @param {Boolean} event.quick
* True only if the clickDistThreshold and clickDeltaThreshold are both passed. Useful for ignoring events.
* @param {Boolean} event.shift
* True if the shift key was pressed during this event.
* @param {Boolean} event.isTouchEvent
- * True if the original event is a touch event, otherwise false.
+ * True if the original event is a touch event, otherwise false. Deprecated. Use pointerType and/or originalEvent instead.
* @param {Object} event.originalEvent
* The original event object.
* @param {Boolean} event.preventDefaultAction
@@ -402,14 +477,23 @@
* @param {Object} event
* @param {OpenSeadragon.MouseTracker} event.eventSource
* A reference to the tracker instance.
+ * @param {String} event.pointerType
+ * "mouse", "touch", "pen", etc.
* @param {OpenSeadragon.Point} event.position
* The position of the event relative to the tracked element.
+ * @param {Number} event.buttons
+ * Current buttons pressed.
+ * Combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser.
* @param {OpenSeadragon.Point} event.delta
- * The x,y components of the difference between start drag and end drag. Usefule for ignoring or weighting the events.
+ * The x,y components of the difference between the current position and the last drag event position. Useful for ignoring or weighting the events.
+ * @param {Number} event.speed
+ * Current computed speed, in pixels per second.
+ * @param {Number} event.direction
+ * Current computed direction, expressed as an angle counterclockwise relative to the positive X axis (-pi to pi, in radians). Only valid if speed > 0.
* @param {Boolean} event.shift
* True if the shift key was pressed during this event.
* @param {Boolean} event.isTouchEvent
- * True if the original event is a touch event, otherwise false.
+ * True if the original event is a touch event, otherwise false. Deprecated. Use pointerType and/or originalEvent instead.
* @param {Object} event.originalEvent
* The original event object.
* @param {Boolean} event.preventDefaultAction
@@ -426,10 +510,73 @@
* @param {Object} event
* @param {OpenSeadragon.MouseTracker} event.eventSource
* A reference to the tracker instance.
+ * @param {String} event.pointerType
+ * "mouse", "touch", "pen", etc.
* @param {OpenSeadragon.Point} event.position
* The position of the event relative to the tracked element.
+ * @param {Number} event.speed
+ * Speed at the end of a drag gesture, in pixels per second.
+ * @param {Number} event.direction
+ * Direction at the end of a drag gesture, expressed as an angle counterclockwise relative to the positive X axis (-pi to pi, in radians). Only valid if speed > 0.
+ * @param {Boolean} event.shift
+ * True if the shift key was pressed during this event.
* @param {Boolean} event.isTouchEvent
- * True if the original event is a touch event, otherwise false.
+ * True if the original event is a touch event, otherwise false. Deprecated. Use pointerType and/or originalEvent instead.
+ * @param {Object} event.originalEvent
+ * The original event object.
+ * @param {Boolean} event.preventDefaultAction
+ * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false.
+ * @param {Object} event.userData
+ * Arbitrary user-defined object.
+ */
+ dragEndHandler: function () { },
+
+ /**
+ * Implement or assign implementation to these handlers during or after
+ * calling the constructor.
+ * @function
+ * @param {Object} event
+ * @param {OpenSeadragon.MouseTracker} event.eventSource
+ * A reference to the tracker instance.
+ * @param {String} event.pointerType
+ * "mouse", "touch", "pen", etc.
+ * @param {Array.} event.gesturePoints
+ * Gesture points associated with the gesture. Velocity data can be found here.
+ * @param {OpenSeadragon.Point} event.lastCenter
+ * The previous center point of the two pinch contact points relative to the tracked element.
+ * @param {OpenSeadragon.Point} event.center
+ * The center point of the two pinch contact points relative to the tracked element.
+ * @param {Number} event.lastDistance
+ * The previous distance between the two pinch contact points in CSS pixels.
+ * @param {Number} event.distance
+ * The distance between the two pinch contact points in CSS pixels.
+ * @param {Boolean} event.shift
+ * True if the shift key was pressed during this event.
+ * @param {Object} event.originalEvent
+ * The original event object.
+ * @param {Boolean} event.preventDefaultAction
+ * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false.
+ * @param {Object} event.userData
+ * Arbitrary user-defined object.
+ */
+ pinchHandler: function () { },
+
+ /**
+ * Implement or assign implementation to these handlers during or after
+ * calling the constructor.
+ * @function
+ * @param {Object} event
+ * @param {OpenSeadragon.MouseTracker} event.eventSource
+ * A reference to the tracker instance.
+ * @param {String} event.pointerType
+ * "mouse", "touch", "pen", etc.
+ * @param {OpenSeadragon.Point} event.position
+ * The position of the event relative to the tracked element.
+ * @param {Number} event.buttons
+ * Current buttons pressed.
+ * Combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser.
+ * @param {Boolean} event.isTouchEvent
+ * True if the original event is a touch event, otherwise false. Deprecated. Use pointerType and/or originalEvent instead.
* @param {Object} event.originalEvent
* The original event object.
* @param {Boolean} event.preventDefaultAction
@@ -492,8 +639,104 @@
blurHandler: function () { }
};
+
/**
- * Detect available mouse wheel event.
+ * Provides continuous computation of velocity (speed and direction) of active pointers.
+ * This is a singleton, used by all MouseTracker instances, as it is unlikely there will ever be more than
+ * two active gesture pointers at a time.
+ *
+ * @private
+ * @member gesturePointVelocityTracker
+ * @memberof OpenSeadragon.MouseTracker
+ */
+ $.MouseTracker.gesturePointVelocityTracker = (function () {
+ var trackerPoints = [],
+ intervalId = 0,
+ lastTime = 0;
+
+ // Generates a unique identifier for a tracked gesture point
+ var _generateGuid = function ( tracker, gPoint ) {
+ return tracker.hash.toString() + gPoint.type + gPoint.id.toString();
+ };
+
+ // Interval timer callback. Computes velocity for all tracked gesture points.
+ var _doTracking = function () {
+ var i,
+ len = trackerPoints.length,
+ trackPoint,
+ gPoint,
+ now = $.now(),
+ elapsedTime,
+ distance,
+ speed;
+
+ elapsedTime = now - lastTime;
+ lastTime = now;
+
+ for ( i = 0; i < len; i++ ) {
+ trackPoint = trackerPoints[ i ];
+ gPoint = trackPoint.gPoint;
+ // Math.atan2 gives us just what we need for a velocity vector, as we can simply
+ // use cos()/sin() to extract the x/y velocity components.
+ gPoint.direction = Math.atan2( gPoint.currentPos.y - trackPoint.lastPos.y, gPoint.currentPos.x - trackPoint.lastPos.x );
+ // speed = distance / elapsed time
+ distance = trackPoint.lastPos.distanceTo( gPoint.currentPos );
+ trackPoint.lastPos = gPoint.currentPos;
+ speed = 1000 * distance / ( elapsedTime + 1 );
+ // Simple biased average, favors the most recent speed computation. Smooths out erratic gestures a bit.
+ gPoint.speed = 0.75 * speed + 0.25 * gPoint.speed;
+ }
+ };
+
+ // Public. Add a gesture point to be tracked
+ var addPoint = function ( tracker, gPoint ) {
+ var guid = _generateGuid( tracker, gPoint );
+
+ trackerPoints.push(
+ {
+ guid: guid,
+ gPoint: gPoint,
+ lastPos: gPoint.currentPos
+ } );
+
+ // Only fire up the interval timer when there's gesture pointers to track
+ if ( trackerPoints.length === 1 ) {
+ lastTime = $.now();
+ intervalId = window.setInterval( _doTracking, 50 );
+ }
+ };
+
+ // Public. Stop tracking a gesture point
+ var removePoint = function ( tracker, gPoint ) {
+ var guid = _generateGuid( tracker, gPoint ),
+ i,
+ len = trackerPoints.length;
+ for ( i = 0; i < len; i++ ) {
+ if ( trackerPoints[ i ].guid === guid ) {
+ trackerPoints.splice( i, 1 );
+ // Only run the interval timer if theres gesture pointers to track
+ len--;
+ if ( len === 0 ) {
+ window.clearInterval( intervalId );
+ }
+ break;
+ }
+ }
+ };
+
+ return {
+ addPoint: addPoint,
+ removePoint: removePoint
+ };
+ } )();
+
+
+///////////////////////////////////////////////////////////////////////////////
+// Pointer event model and feature detection
+///////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Detect available mouse wheel event name.
*/
$.MouseTracker.wheelEventName = ( $.Browser.vendor == $.BROWSERS.IE && $.Browser.version > 8 ) ||
( 'onwheel' in document.createElement( 'div' ) ) ? 'wheel' : // Modern browsers support 'wheel'
@@ -501,31 +744,240 @@
'DOMMouseScroll'; // Assume old Firefox
/**
- * Starts tracking mouse events on this element.
+ * Detect browser pointer device event model(s) and build appropriate list of events to subscribe to.
+ */
+ $.MouseTracker.subscribeEvents = [ "click", "keypress", "focus", "blur", $.MouseTracker.wheelEventName ];
+
+ if( $.MouseTracker.wheelEventName == "DOMMouseScroll" ) {
+ // Older Firefox
+ $.MouseTracker.subscribeEvents.push( "MozMousePixelScroll" );
+ }
+
+ if ( window.PointerEvent ) {
+ // IE11 and other W3C Pointer Event implementations (see http://www.w3.org/TR/pointerevents)
+ $.MouseTracker.subscribeEvents.push( "pointerenter", "pointerleave", "pointerdown", "pointerup", "pointermove", "pointercancel" );
+ $.MouseTracker.unprefixedPointerEvents = true;
+ if( navigator.maxTouchPoints ) {
+ $.MouseTracker.maxTouchPoints = navigator.maxTouchPoints;
+ } else {
+ $.MouseTracker.maxTouchPoints = 0;
+ }
+ $.MouseTracker.haveTouchEnter = true;
+ $.MouseTracker.haveMouseEnter = true;
+ } else if ( window.MSPointerEvent ) {
+ // IE10
+ $.MouseTracker.subscribeEvents.push( "MSPointerEnter", "MSPointerLeave", "MSPointerDown", "MSPointerUp", "MSPointerMove", "MSPointerCancel" );
+ $.MouseTracker.unprefixedPointerEvents = false;
+ if( navigator.msMaxTouchPoints ) {
+ $.MouseTracker.maxTouchPoints = navigator.msMaxTouchPoints;
+ } else {
+ $.MouseTracker.maxTouchPoints = 0;
+ }
+ $.MouseTracker.haveTouchEnter = true;
+ $.MouseTracker.haveMouseEnter = true;
+ } else {
+ // Legacy W3C mouse events
+ $.MouseTracker.subscribeEvents.push( "mousedown", "mouseup", "mousemove" );
+ if ( 'onmouseenter' in window ) {
+ $.MouseTracker.subscribeEvents.push( "mouseenter", "mouseleave" );
+ $.MouseTracker.haveMouseEnter = true;
+ } else {
+ $.MouseTracker.subscribeEvents.push( "mouseover", "mouseout" );
+ $.MouseTracker.haveMouseEnter = false;
+ }
+ if ( 'ontouchstart' in window ) {
+ // iOS, Android, and other W3c Touch Event implementations (see http://www.w3.org/TR/2011/WD-touch-events-20110505)
+ $.MouseTracker.subscribeEvents.push( "touchstart", "touchend", "touchmove", "touchcancel" );
+ if ( 'ontouchenter' in window ) {
+ $.MouseTracker.subscribeEvents.push( "touchenter", "touchleave" );
+ $.MouseTracker.haveTouchEnter = true;
+ } else {
+ $.MouseTracker.haveTouchEnter = false;
+ }
+ } else {
+ $.MouseTracker.haveTouchEnter = false;
+ }
+ if ( 'ongesturestart' in window ) {
+ // iOS (see https://developer.apple.com/library/safari/documentation/UserExperience/Reference/GestureEventClassReference/GestureEvent/GestureEvent.html)
+ // Subscribe to these to prevent default gesture handling
+ $.MouseTracker.subscribeEvents.push( "gesturestart", "gesturechange" );
+ }
+ $.MouseTracker.mousePointerId = "legacy-mouse";
+ $.MouseTracker.maxTouchPoints = 10;
+ }
+
+
+///////////////////////////////////////////////////////////////////////////////
+// Classes and typedefs
+///////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Represents a point of contact on the screen made by a mouse cursor, pen, touch, or other pointer device.
+ *
+ * @typedef {Object} GesturePoint
+ * @memberof OpenSeadragon.MouseTracker
+ *
+ * @property {Number} id
+ * Identifier unique from all other active GesturePoints for a given pointer device.
+ * @property {String} type
+ * The pointer device type: "mouse", "touch", "pen", etc.
+ * @property {Boolean} captured
+ * True if events for the gesture point are captured to the tracked element.
+ * @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 button pressed or contact point initiated inside the screen area of the tracked element.
+ * @property {Boolean} insideElement
+ * True if pointer or contact point is currently inside the bounds of the tracked element.
+ * @property {Number} speed
+ * Current computed speed, in pixels per second.
+ * @property {Number} direction
+ * 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} contactPos
+ * The initial pointer contact position, relative to the page including any scrolling. Only valid if the pointer has contact (pressed, touch contact, pen contact).
+ * @property {Number} contactTime
+ * The initial pointer contact time, in milliseconds. Only valid if the pointer has contact (pressed, touch contact, pen contact).
+ * @property {OpenSeadragon.Point} lastPos
+ * The last pointer position, relative to the page including any scrolling.
+ * @property {Number} lastTime
+ * The last pointer contact time, in milliseconds.
+ * @property {OpenSeadragon.Point} currentPos
+ * The current pointer position, relative to the page including any scrolling.
+ * @property {Number} currentTime
+ * The current pointer contact time, in milliseconds.
+ */
+
+
+ /**
+ * @class GesturePointList
+ * @classdesc Provides an abstraction for a set of active {@link OpenSeadragon.MouseTracker.GesturePoint|GesturePoint} objects for a given pointer device type.
+ * Active pointers are any pointer being tracked for this element which are in the hit-test area
+ * of the element (for hover-capable devices) and/or have contact or a button press initiated in the element.
+ * @memberof OpenSeadragon.MouseTracker
+ * @param {String} type - The pointer device type: "mouse", "touch", "pen", etc.
+ */
+ $.MouseTracker.GesturePointList = function ( type ) {
+ this._gPoints = [];
+ /**
+ * The pointer device type: "mouse", "touch", "pen", etc.
+ * @member {String} type
+ * @memberof OpenSeadragon.MouseTracker.GesturePointList#
+ */
+ this.type = type;
+ /**
+ * Current buttons pressed for the device.
+ * Combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser.
+ * @member {Number} buttons
+ * @memberof OpenSeadragon.MouseTracker.GesturePointList#
+ */
+ this.buttons = 0;
+ /**
+ * Current number of contact points (touch points, mouse down, etc.) for the device.
+ * @member {Number} contacts
+ * @memberof OpenSeadragon.MouseTracker.GesturePointList#
+ */
+ this.contacts = 0;
+ };
+ $.MouseTracker.GesturePointList.prototype = /** @lends OpenSeadragon.MouseTracker.GesturePointList.prototype */{
+ /**
+ * @function
+ * @returns {Number} Number of gesture points in the list.
+ */
+ getLength: function () {
+ return this._gPoints.length;
+ },
+ /**
+ * @function
+ * @returns {Array.} The list of gesture points in the list as an array (read-only).
+ */
+ asArray: function () {
+ return this._gPoints;
+ },
+ /**
+ * @function
+ * @param {OpenSeadragon.MouseTracker.GesturePoint} gesturePoint - A gesture point to add to the list.
+ * @returns {Number} Number of gesture points in the list.
+ */
+ add: function ( gp ) {
+ return this._gPoints.push( gp );
+ },
+ /**
+ * @function
+ * @param {Number} id - The id of the gesture point to remove from the list.
+ * @returns {Number} Number of gesture points in the list.
+ */
+ removeById: function ( id ) {
+ var i,
+ len = this._gPoints.length;
+ for ( i = 0; i < len; i++ ) {
+ if ( this._gPoints[ i ].id === id ) {
+ this._gPoints.splice( i, 1 );
+ break;
+ }
+ }
+ 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.
+ * @returns {OpenSeadragon.MouseTracker.GesturePoint|null} The gesture point with the given id, or null if not found.
+ */
+ getById: function ( id ) {
+ var i,
+ len = this._gPoints.length;
+ for ( i = 0; i < len; i++ ) {
+ if ( this._gPoints[ i ].id === id ) {
+ return this._gPoints[ i ];
+ }
+ }
+ 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 pointer events on the tracked element.
* @private
* @inner
*/
function startTracking( tracker ) {
- var events = [
- "mouseover", "mouseout", "mousedown", "mouseup", "mousemove",
- "click",
- $.MouseTracker.wheelEventName,
- "touchstart", "touchmove", "touchend",
- "keypress",
- "focus", "blur"
- ],
- delegate = THIS[ tracker.hash ],
+ var delegate = THIS[ tracker.hash ],
event,
i;
- // Add 'MozMousePixelScroll' event handler for older Firefox
- if( $.MouseTracker.wheelEventName == "DOMMouseScroll" ) {
- events.push( "MozMousePixelScroll" );
- }
-
if ( !delegate.tracking ) {
- for ( i = 0; i < events.length; i++ ) {
- event = events[ i ];
+ for ( i = 0; i < $.MouseTracker.subscribeEvents.length; i++ ) {
+ event = $.MouseTracker.subscribeEvents[ i ];
$.addEvent(
tracker.element,
event,
@@ -534,36 +986,22 @@
);
}
delegate.tracking = true;
- ACTIVE[ tracker.hash ] = tracker;
}
}
/**
- * Stops tracking mouse events on this element.
+ * Stops tracking pointer events on the tracked element.
* @private
* @inner
*/
function stopTracking( tracker ) {
- var events = [
- "mouseover", "mouseout", "mousedown", "mouseup", "mousemove",
- "click",
- $.MouseTracker.wheelEventName,
- "touchstart", "touchmove", "touchend",
- "keypress",
- "focus", "blur"
- ],
- delegate = THIS[ tracker.hash ],
+ var delegate = THIS[ tracker.hash ],
event,
i;
- // Remove 'MozMousePixelScroll' event handler for older Firefox
- if( $.MouseTracker.wheelEventName == "DOMMouseScroll" ) {
- events.push( "MozMousePixelScroll" );
- }
-
if ( delegate.tracking ) {
- for ( i = 0; i < events.length; i++ ) {
- event = events[ i ];
+ for ( i = 0; i < $.MouseTracker.subscribeEvents.length; i++ ) {
+ event = $.MouseTracker.subscribeEvents[ i ];
$.removeEvent(
tracker.element,
event,
@@ -574,47 +1012,24 @@
releaseMouse( tracker );
delegate.tracking = false;
- delete ACTIVE[ tracker.hash ];
}
}
/**
- * @private
- * @inner
- */
- function hasMouse( tracker ) {
- return THIS[ tracker.hash ].insideElement;
- }
-
- /**
- * Begin capturing mouse events on this element.
+ * Begin capturing mouse events to the tracked element (legacy mouse events only).
* @private
* @inner
*/
function captureMouse( tracker ) {
var delegate = THIS[ tracker.hash ];
- if ( !delegate.capturing ) {
- if ( $.Browser.vendor == $.BROWSERS.IE && $.Browser.version < 9 ) {
- $.removeEvent(
- tracker.element,
- "mouseup",
- delegate.mouseup,
- false
- );
- $.addEvent(
- tracker.element,
- "mouseup",
- delegate.mouseupie,
- true
- );
- $.addEvent(
- tracker.element,
- "mousemove",
- delegate.mousemovecapturedie,
- true
- );
+ if ( !delegate.capturing ) {
+ if ( delegate.setCaptureCapable ) {
+ // IE<10, Firefox, other browsers with setCapture()/releaseCapture()
+ tracker.element.setCapture( true );
} else {
+ // For browsers without setCapture()/releaseCapture(), we emulate mouse capture by hanging listeners on the window object.
+ // (Note we listen on the capture phase so the captured handlers will get called first)
$.addEvent(
window,
"mouseup",
@@ -634,34 +1049,20 @@
/**
- * Stop capturing mouse events on this element.
+ * Stop capturing mouse events to the tracked element (legacy mouse events only).
* @private
* @inner
*/
function releaseMouse( tracker ) {
var delegate = THIS[ tracker.hash ];
- if ( delegate.capturing ) {
- if ( $.Browser.vendor == $.BROWSERS.IE && $.Browser.version < 9 ) {
- $.removeEvent(
- tracker.element,
- "mousemove",
- delegate.mousemovecapturedie,
- true
- );
- $.removeEvent(
- tracker.element,
- "mouseup",
- delegate.mouseupie,
- true
- );
- $.addEvent(
- tracker.element,
- "mouseup",
- delegate.mouseup,
- false
- );
+ if ( delegate.capturing ) {
+ if ( delegate.setCaptureCapable ) {
+ // IE<10, Firefox, other browsers with setCapture()/releaseCapture()
+ tracker.element.releaseCapture();
} else {
+ // For browsers without setCapture()/releaseCapture(), we emulate mouse capture by hanging listeners on the window object.
+ // (Note we listen on the capture phase so the captured handlers will get called first)
$.removeEvent(
window,
"mousemove",
@@ -681,14 +1082,110 @@
/**
+ * Gets a W3C Pointer Events model compatible pointer type string from a DOM pointer event.
+ * IE10 used a long integer value, but the W3C specification (and IE11+) use a string "mouse", "touch", "pen", etc.
* @private
* @inner
*/
- function triggerOthers( tracker, handler, event, isTouch ) {
- var otherHash;
- for ( otherHash in ACTIVE ) {
- if ( ACTIVE.hasOwnProperty( otherHash ) && tracker.hash != otherHash ) {
- handler( ACTIVE[ otherHash ], event, isTouch );
+ function getPointerType( event ) {
+ var pointerTypeStr;
+ if ( $.MouseTracker.unprefixedPointerEvents ) {
+ pointerTypeStr = event.pointerType;
+ } else {
+ // IE10
+ // MSPOINTER_TYPE_TOUCH: 0x00000002
+ // MSPOINTER_TYPE_PEN: 0x00000003
+ // MSPOINTER_TYPE_MOUSE: 0x00000004
+ switch( event.pointerType )
+ {
+ case 0x00000002:
+ pointerTypeStr = 'touch';
+ break;
+ case 0x00000003:
+ pointerTypeStr = 'pen';
+ break;
+ case 0x00000004:
+ pointerTypeStr = 'mouse';
+ break;
+ default:
+ pointerTypeStr = '';
+ }
+ }
+ return pointerTypeStr;
+ }
+
+
+ /**
+ * @private
+ * @inner
+ */
+ function getMouseAbsolute( event ) {
+ return $.getMousePosition( event );
+ }
+
+ /**
+ * @private
+ * @inner
+ */
+ function getMouseRelative( event, element ) {
+ return getPointRelativeToAbsolute( getMouseAbsolute( event ), element );
+ }
+
+ /**
+ * @private
+ * @inner
+ */
+ function getPointRelativeToAbsolute( point, element ) {
+ var offset = $.getElementOffset( element );
+ return point.minus( offset );
+ }
+
+ /**
+ * @private
+ * @inner
+ */
+ function getCenterPoint( point1, point2 ) {
+ return new $.Point( ( point1.x + point2.x ) / 2, ( point1.y + point2.y ) / 2 );
+ }
+
+
+///////////////////////////////////////////////////////////////////////////////
+// Device-specific DOM event handlers
+///////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * @private
+ * @inner
+ */
+ function onClick( tracker, event ) {
+ if ( tracker.clickHandler ) {
+ $.cancelEvent( event );
+ }
+ }
+
+
+ /**
+ * @private
+ * @inner
+ */
+ function onKeyPress( tracker, event ) {
+ //console.log( "keypress %s %s %s %s %s", event.keyCode, event.charCode, event.ctrlKey, event.shiftKey, event.altKey );
+ var propagate;
+ if ( tracker.keyHandler ) {
+ event = $.getEvent( event );
+ propagate = tracker.keyHandler(
+ {
+ eventSource: tracker,
+ position: getMouseRelative( event, tracker.element ),
+ keyCode: event.keyCode ? event.keyCode : event.charCode,
+ shift: event.shiftKey,
+ originalEvent: event,
+ preventDefaultAction: false,
+ userData: tracker.userData
+ }
+ );
+ if ( !propagate ) {
+ $.cancelEvent( event );
}
}
}
@@ -702,6 +1199,7 @@
//console.log( "focus %s", event );
var propagate;
if ( tracker.focusHandler ) {
+ event = $.getEvent( event );
propagate = tracker.focusHandler(
{
eventSource: tracker,
@@ -725,6 +1223,7 @@
//console.log( "blur %s", event );
var propagate;
if ( tracker.blurHandler ) {
+ event = $.getEvent( event );
propagate = tracker.blurHandler(
{
eventSource: tracker,
@@ -740,437 +1239,6 @@
}
- /**
- * @private
- * @inner
- */
- function onKeyPress( tracker, event ) {
- //console.log( "keypress %s %s %s %s %s", event.keyCode, event.charCode, event.ctrlKey, event.shiftKey, event.altKey );
- var propagate;
- if ( tracker.keyHandler ) {
- propagate = tracker.keyHandler(
- {
- eventSource: tracker,
- position: getMouseRelative( event, tracker.element ),
- keyCode: event.keyCode ? event.keyCode : event.charCode,
- shift: event.shiftKey,
- originalEvent: event,
- preventDefaultAction: false,
- userData: tracker.userData
- }
- );
- if ( !propagate ) {
- $.cancelEvent( event );
- }
- }
- }
-
-
- /**
- * @private
- * @inner
- */
- function onMouseOver( tracker, event, isTouch ) {
-
- var delegate = THIS[ tracker.hash ],
- propagate;
-
- isTouch = isTouch || false;
-
- event = $.getEvent( event );
-
- 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: getMouseRelative( isTouch ? event.changedTouches[ 0 ] : event, tracker.element ),
- insideElementPressed: delegate.insideElementPressed,
- buttonDownAny: IS_BUTTON_DOWN,
- isTouchEvent: isTouch,
- originalEvent: event,
- preventDefaultAction: false,
- userData: tracker.userData
- }
- );
- if ( propagate === false ) {
- $.cancelEvent( event );
- }
- }
- }
-
-
- /**
- * @private
- * @inner
- */
- function onMouseOut( tracker, event, isTouch ) {
- var delegate = THIS[ tracker.hash ],
- propagate;
-
- isTouch = isTouch || false;
-
- event = $.getEvent( event );
-
- 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: getMouseRelative( isTouch ? event.changedTouches[ 0 ] : event, tracker.element ),
- insideElementPressed: delegate.insideElementPressed,
- buttonDownAny: IS_BUTTON_DOWN,
- isTouchEvent: isTouch,
- originalEvent: event,
- preventDefaultAction: false,
- userData: tracker.userData
- }
- );
-
- if ( propagate === false ) {
- $.cancelEvent( event );
- }
- }
- }
-
-
- /**
- * @private
- * @inner
- */
- function onMouseDown( tracker, event, noCapture, isTouch ) {
- var delegate = THIS[ tracker.hash ],
- propagate;
-
- isTouch = isTouch || false;
-
- event = $.getEvent(event);
-
- var eventOrTouchPoint = isTouch ? event.touches[ 0 ] : event;
-
- if ( event.button == 2 ) {
- return;
- }
-
- delegate.insideElementPressed = true;
-
- delegate.lastPoint = getMouseAbsolute( eventOrTouchPoint );
- delegate.lastMouseDownPoint = delegate.lastPoint;
- delegate.lastMouseDownTime = $.now();
-
- if ( tracker.pressHandler ) {
- propagate = tracker.pressHandler(
- {
- eventSource: tracker,
- position: getMouseRelative( eventOrTouchPoint, tracker.element ),
- isTouchEvent: isTouch,
- originalEvent: event,
- preventDefaultAction: false,
- userData: tracker.userData
- }
- );
- if ( propagate === false ) {
- $.cancelEvent( event );
- }
- }
-
- if ( tracker.pressHandler || tracker.dragHandler ) {
- $.cancelEvent( event );
- }
-
- if ( noCapture ) {
- return;
- }
-
- if ( isTouch ||
- !( $.Browser.vendor == $.BROWSERS.IE && $.Browser.version < 9 ) ||
- !IS_CAPTURING ) {
- captureMouse( tracker );
- IS_CAPTURING = true;
- // reset to empty & add us
- CAPTURING = [ tracker ];
- } else if ( $.Browser.vendor == $.BROWSERS.IE && $.Browser.version < 9 ) {
- // add us to the list
- CAPTURING.push( tracker );
- }
- }
-
- /**
- * @private
- * @inner
- */
- function onTouchStart( tracker, event ) {
- var touchA,
- touchB;
-
- if ( event.touches.length == 1 &&
- event.targetTouches.length == 1 &&
- event.changedTouches.length == 1 ) {
-
- THIS[ tracker.hash ].lastTouch = event.touches[ 0 ];
- onMouseOver( tracker, event, true );
- // call with no capture as the onMouseMoveCaptured will
- // be triggered by onTouchMove
- onMouseDown( tracker, event, true, true );
- }
-
- if ( event.touches.length == 2 ) {
-
- touchA = getMouseAbsolute( event.touches[ 0 ] );
- touchB = getMouseAbsolute( event.touches[ 1 ] );
- THIS[ tracker.hash ].lastPinchDelta =
- Math.abs( touchA.x - touchB.x ) +
- Math.abs( touchA.y - touchB.y );
- THIS[ tracker.hash ].pinchMidpoint = new $.Point(
- ( touchA.x + touchB.x ) / 2,
- ( touchA.y + touchB.y ) / 2
- );
- //$.console.debug("pinch start : "+THIS[ tracker.hash ].lastPinchDelta);
- }
-
- event.preventDefault();
- }
-
-
- /**
- * @private
- * @inner
- */
- function onMouseUp( tracker, event, isTouch ) {
- var delegate = THIS[ tracker.hash ],
- //were we inside the tracked element when we were pressed
- insideElementPressed = delegate.insideElementPressed,
- //are we still inside the tracked element when we released
- insideElementReleased = delegate.insideElement,
- propagate;
-
- isTouch = isTouch || false;
-
- event = $.getEvent(event);
-
- if ( event.button == 2 ) {
- return;
- }
-
- delegate.insideElementPressed = false;
-
- if ( tracker.releaseHandler ) {
- propagate = tracker.releaseHandler(
- {
- eventSource: tracker,
- position: getMouseRelative( isTouch ? event.changedTouches[ 0 ] : event, tracker.element ),
- insideElementPressed: insideElementPressed,
- insideElementReleased: insideElementReleased,
- isTouchEvent: isTouch,
- originalEvent: event,
- preventDefaultAction: false,
- userData: tracker.userData
- }
- );
- if ( propagate === false ) {
- $.cancelEvent( event );
- }
- }
-
- if ( insideElementPressed && insideElementReleased ) {
- handleMouseClick( tracker, event, isTouch );
- }
- }
-
-
- /**
- * @private
- * @inner
- */
- function onTouchEnd( tracker, event ) {
-
- if ( event.touches.length === 0 &&
- event.targetTouches.length === 0 &&
- event.changedTouches.length == 1 ) {
-
- THIS[ tracker.hash ].lastTouch = null;
-
- // call with no release, as the mouse events are
- // not registered in onTouchStart
- onMouseUpCaptured( tracker, event, true, true );
- onMouseOut( tracker, event, true );
- }
- if ( event.touches.length + event.changedTouches.length == 2 ) {
- THIS[ tracker.hash ].lastPinchDelta = null;
- THIS[ tracker.hash ].pinchMidpoint = null;
- //$.console.debug("pinch end");
- }
- event.preventDefault();
- }
-
-
- /**
- * Only triggered once by the deepest element that initially received
- * the mouse down event. We want to make sure THIS event doesn't bubble.
- * Instead, we want to trigger the elements that initially received the
- * mouse down event (including this one) only if the mouse is no longer
- * inside them. Then, we want to release capture, and emulate a regular
- * mouseup on the event that this event was meant for.
- * @private
- * @inner
- */
- function onMouseUpIE( tracker, event ) {
- var othertracker,
- i;
-
- event = $.getEvent( event );
-
- if ( event.button == 2 ) {
- return;
- }
-
- for ( i = 0; i < CAPTURING.length; i++ ) {
- othertracker = CAPTURING[ i ];
- if ( !hasMouse( othertracker ) ) {
- onMouseUp( othertracker, event, false );
- }
- }
-
- releaseMouse( tracker );
- IS_CAPTURING = false;
- event.srcElement.fireEvent(
- "on" + event.type,
- document.createEventObject( event )
- );
-
- $.stopEvent( event );
- }
-
-
- /**
- * Only triggered in W3C browsers by elements within which the mouse was
- * initially pressed, since they are now listening to the window for
- * mouseup during the capture phase. We shouldn't handle the mouseup
- * here if the mouse is still inside this element, since the regular
- * mouseup handler will still fire.
- * @private
- * @inner
- */
- function onMouseUpCaptured( tracker, event, noRelease, isTouch ) {
- isTouch = isTouch || false;
-
- if ( !THIS[ tracker.hash ].insideElement || isTouch ) {
- onMouseUp( tracker, event, isTouch );
- }
-
- if ( noRelease ) {
- return;
- }
-
- releaseMouse( tracker );
- }
-
-
- /**
- * @private
- * @inner
- */
- function onMouseMove( tracker, event ) {
- if ( tracker.moveHandler ) {
- event = $.getEvent( event );
-
- var propagate = tracker.moveHandler(
- {
- eventSource: tracker,
- position: getMouseRelative( event, tracker.element ),
- isTouchEvent: false,
- originalEvent: event,
- preventDefaultAction: false,
- userData: tracker.userData
- }
- );
- if ( propagate === false ) {
- $.cancelEvent( event );
- }
- }
- if ( tracker.stopHandler ) {
- clearTimeout( tracker.stopTimeOut );
- tracker.stopTimeOut = setTimeout( function() {
- onMouseStop( tracker, event );
- }, tracker.stopDelay );
- }
- }
-
- /**
- * @private
- * @inner
- */
- function onMouseStop( tracker, originalMoveEvent ) {
- if ( tracker.stopHandler ) {
- tracker.stopHandler( {
- eventSource: tracker,
- position: getMouseRelative( originalMoveEvent, tracker.element ),
- isTouchEvent: false,
- originalEvent: originalMoveEvent,
- preventDefaultAction: false,
- userData: tracker.userData
- } );
- }
- }
-
- /**
- * @private
- * @inner
- */
- function onMouseClick( tracker, event ) {
- if ( tracker.clickHandler ) {
- $.cancelEvent( event );
- }
- }
-
-
/**
* Handler for 'wheel' events
*
@@ -1178,7 +1246,7 @@
* @inner
*/
function onWheel( tracker, event ) {
- handleWheelEvent( tracker, event, event, false );
+ handleWheelEvent( tracker, event, event );
}
@@ -1189,8 +1257,7 @@
* @inner
*/
function onMouseWheel( tracker, event ) {
- // For legacy IE, access the global (window) event object
- event = event || window.event;
+ event = $.getEvent( event );
// Simulate a 'wheel' event
var simulatedEvent = {
@@ -1213,23 +1280,21 @@
simulatedEvent.deltaY = event.detail;
}
- handleWheelEvent( tracker, simulatedEvent, event, false );
+ handleWheelEvent( tracker, simulatedEvent, event );
}
/**
* Handles 'wheel' events.
- * The event may be simulated by the legacy mouse wheel event handler (onMouseWheel()) or onTouchMove().
+ * The event may be simulated by the legacy mouse wheel event handler (onMouseWheel()).
*
* @private
* @inner
*/
- function handleWheelEvent( tracker, event, originalEvent, isTouch ) {
+ function handleWheelEvent( tracker, event, originalEvent ) {
var nDelta = 0,
propagate;
- isTouch = isTouch || false;
-
// The nDelta variable is gated to provide smooth z-index scrolling
// since the mouse wheel allows for substantial deltas meant for rapid
// y-index scrolling.
@@ -1241,10 +1306,11 @@
propagate = tracker.scrollHandler(
{
eventSource: tracker,
+ pointerType: 'mouse',
position: getMouseRelative( event, tracker.element ),
scroll: nDelta,
shift: event.shiftKey,
- isTouchEvent: isTouch,
+ isTouchEvent: false,
originalEvent: originalEvent,
preventDefaultAction: false,
userData: tracker.userData
@@ -1261,42 +1327,134 @@
* @private
* @inner
*/
- function handleMouseClick( tracker, event, isTouch ) {
- var delegate = THIS[ tracker.hash ],
- propagate;
+ function isParentChild( parent, child )
+ {
+ if ( parent === child ) {
+ return false;
+ }
+ while ( child && child !== parent ) {
+ child = child.parentNode;
+ }
+ return child === parent;
+ }
- isTouch = isTouch || false;
+
+ /**
+ * @private
+ * @inner
+ */
+ function onMouseOver( tracker, event ) {
+ var gPoint;
event = $.getEvent( event );
- var eventOrTouchPoint = isTouch ? event.changedTouches[ 0 ] : event;
-
- if ( event.button == 2 ) {
+ if ( this === event.relatedTarget || isParentChild( this, event.relatedTarget ) ) {
return;
}
- var time = $.now() - delegate.lastMouseDownTime,
- point = getMouseAbsolute( eventOrTouchPoint ),
- distance = delegate.lastMouseDownPoint.distanceTo( point ),
- quick = time <= tracker.clickTimeThreshold &&
- distance <= tracker.clickDistThreshold;
+ gPoint = {
+ id: $.MouseTracker.mousePointerId,
+ type: 'mouse',
+ isPrimary: true,
+ currentPos: getMouseAbsolute( event ),
+ currentTime: $.now()
+ };
- 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 );
- }
+ updatePointersEnter( tracker, event, [ gPoint ] );
+ }
+
+
+ /**
+ * @private
+ * @inner
+ */
+ function onMouseOut( tracker, event ) {
+ var gPoint;
+
+ event = $.getEvent( event );
+
+ if ( this === event.relatedTarget || isParentChild( this, event.relatedTarget ) ) {
+ return;
+ }
+
+ gPoint = {
+ id: $.MouseTracker.mousePointerId,
+ type: 'mouse',
+ isPrimary: true,
+ currentPos: getMouseAbsolute( event ),
+ currentTime: $.now()
+ };
+
+ updatePointersExit( tracker, event, [ gPoint ] );
+ }
+
+
+ /**
+ * @private
+ * @inner
+ */
+ function onMouseEnter( tracker, event ) {
+ var gPoint;
+
+ event = $.getEvent( event );
+
+ gPoint = {
+ id: $.MouseTracker.mousePointerId,
+ type: 'mouse',
+ isPrimary: true,
+ currentPos: getMouseAbsolute( event ),
+ currentTime: $.now()
+ };
+
+ updatePointersEnter( tracker, event, [ gPoint ] );
+ }
+
+
+ /**
+ * @private
+ * @inner
+ */
+ function onMouseLeave( tracker, event ) {
+ var gPoint;
+
+ event = $.getEvent( event );
+
+ gPoint = {
+ id: $.MouseTracker.mousePointerId,
+ type: 'mouse',
+ isPrimary: true,
+ currentPos: getMouseAbsolute( event ),
+ currentTime: $.now()
+ };
+
+ updatePointersExit( tracker, event, [ gPoint ] );
+ }
+
+
+ /**
+ * @private
+ * @inner
+ */
+ function onMouseDown( tracker, event ) {
+ var gPoint;
+
+ event = $.getEvent( event );
+
+ gPoint = {
+ id: $.MouseTracker.mousePointerId,
+ type: 'mouse',
+ isPrimary: true,
+ currentPos: getMouseAbsolute( event ),
+ currentTime: $.now()
+ };
+
+ if ( updatePointersDown( tracker, event, [ gPoint ], event.button ) ) {
+ $.stopEvent( event );
+ captureMouse( tracker );
+ }
+
+ if ( tracker.clickHandler || tracker.pressHandler || tracker.dragHandler || tracker.dragEndHandler ) {
+ $.cancelEvent( event );
}
}
@@ -1305,175 +1463,1135 @@
* @private
* @inner
*/
- function onMouseMoveCaptured( tracker, event, isTouch ) {
- var delegate = THIS[ tracker.hash ],
- delta,
- propagate,
- point;
+ function onMouseUp( tracker, event ) {
+ handleMouseUp( tracker, event );
+ }
- isTouch = isTouch || false;
+ /**
+ * This handler is attached to the window object (on the capture phase) to emulate mouse capture.
+ * Only triggered in W3C browsers that don't have setCapture/releaseCapture
+ * methods or don't support the new pointer events model.
+ * onMouseUp is still attached to the tracked element, so stop propagation to avoid processing twice.
+ *
+ * @private
+ * @inner
+ */
+ function onMouseUpCaptured( tracker, event ) {
+ handleMouseUp( tracker, event );
+ $.stopEvent( event );
+ }
- event = $.getEvent(event);
- var eventOrTouchPoint = isTouch ? event.touches[ 0 ] : event;
- point = getMouseAbsolute( eventOrTouchPoint );
- delta = point.minus( delegate.lastPoint );
- delegate.lastPoint = point;
+ /**
+ * @private
+ * @inner
+ */
+ function handleMouseUp( tracker, event ) {
+ var gPoint;
- if ( tracker.dragHandler ) {
- propagate = tracker.dragHandler(
- {
- eventSource: tracker,
- position: getMouseRelative( eventOrTouchPoint, tracker.element ),
- delta: delta,
- shift: event.shiftKey,
- isTouchEvent: isTouch,
- originalEvent: event,
- preventDefaultAction: false,
- userData: tracker.userData
- }
- );
- if ( propagate === false ) {
- $.cancelEvent( event );
- }
+ event = $.getEvent( event );
+
+ gPoint = {
+ id: $.MouseTracker.mousePointerId,
+ type: 'mouse',
+ isPrimary: true,
+ currentPos: getMouseAbsolute( event ),
+ currentTime: $.now()
+ };
+
+ if ( updatePointersUp( tracker, event, [ gPoint ], event.button ) ) {
+ releaseMouse( tracker );
}
}
+ /**
+ * @private
+ * @inner
+ */
+ function onMouseMove( tracker, event ) {
+ handleMouseMove( tracker, event );
+ }
+
+
+ /**
+ * This handler is attached to the window object (on the capture phase) to emulate mouse capture.
+ * Only triggered in W3C browsers that don't have setCapture/releaseCapture
+ * methods or don't support the new pointer events model.
+ * onMouseMove is still attached to the tracked element, so stop propagation to avoid processing twice.
+ *
+ * @private
+ * @inner
+ */
+ function onMouseMoveCaptured( tracker, event ) {
+ handleMouseMove( tracker, event );
+ $.stopEvent( event );
+ }
+
+
+ /**
+ * @private
+ * @inner
+ */
+ function handleMouseMove( tracker, event ) {
+ var gPoint;
+
+ event = $.getEvent( event );
+
+ gPoint = {
+ id: $.MouseTracker.mousePointerId,
+ type: 'mouse',
+ isPrimary: true,
+ currentPos: getMouseAbsolute( event ),
+ currentTime: $.now()
+ };
+
+ updatePointersMove( tracker, event, [ gPoint ] );
+ }
+
+
+ /**
+ * @private
+ * @inner
+ */
+ function onTouchEnter( tracker, event ) {
+ var i,
+ touchCount = event.changedTouches.length,
+ gPoints = [];
+
+ for ( i = 0; i < touchCount; i++ ) {
+ gPoints.push( {
+ id: event.changedTouches[ i ].identifier,
+ type: 'touch',
+ // isPrimary not set - let the updatePointers functions determine it
+ currentPos: getMouseAbsolute( event.changedTouches[ i ] ),
+ currentTime: $.now()
+ } );
+ }
+
+ updatePointersEnter( tracker, event, gPoints );
+ }
+
+
+ /**
+ * @private
+ * @inner
+ */
+ function onTouchLeave( tracker, event ) {
+ var i,
+ touchCount = event.changedTouches.length,
+ gPoints = [];
+
+ for ( i = 0; i < touchCount; i++ ) {
+ gPoints.push( {
+ id: event.changedTouches[ i ].identifier,
+ type: 'touch',
+ // isPrimary not set - let the updatePointers functions determine it
+ currentPos: getMouseAbsolute( event.changedTouches[ i ] ),
+ currentTime: $.now()
+ } );
+ }
+
+ updatePointersExit( tracker, event, gPoints );
+ }
+
+
+ /**
+ * @private
+ * @inner
+ */
+ function onTouchStart( tracker, event ) {
+ var time,
+ i,
+ touchCount = event.changedTouches.length,
+ gPoints = [];
+
+ time = $.now();
+
+ for ( i = 0; i < touchCount; i++ ) {
+ gPoints.push( {
+ id: event.changedTouches[ i ].identifier,
+ type: 'touch',
+ // isPrimary not set - let the updatePointers functions determine it
+ currentPos: getMouseAbsolute( event.changedTouches[ i ] ),
+ currentTime: time
+ } );
+ }
+
+ // simulate touchenter if not natively available
+ if ( !$.MouseTracker.haveTouchEnter ) {
+ updatePointersEnter( tracker, event, gPoints );
+ }
+
+ if ( updatePointersDown( tracker, event, gPoints, 0 ) ) { // 0 means primary button press/release or touch contact
+ // Touch event model start, end, and move events are always captured so we don't need to capture explicitly
+ }
+
+ $.stopEvent( event );
+ $.cancelEvent( event );
+ }
+
+
+ /**
+ * @private
+ * @inner
+ */
+ function onTouchEnd( tracker, event ) {
+ var time,
+ i,
+ touchCount = event.changedTouches.length,
+ gPoints = [];
+
+ time = $.now();
+
+ for ( i = 0; i < touchCount; i++ ) {
+ gPoints.push( {
+ id: event.changedTouches[ i ].identifier,
+ type: 'touch',
+ // isPrimary not set - let the updatePointers functions determine it
+ currentPos: getMouseAbsolute( event.changedTouches[ i ] ),
+ currentTime: time
+ } );
+ }
+
+ // Touch event model start, end, and move events are always captured so we don't need to release capture.
+ // We'll ignore the should-release-capture return value here
+ updatePointersUp( tracker, event, gPoints, 0 ); // 0 means primary button press/release or touch contact
+
+ // simulate touchleave if not natively available
+ if ( !$.MouseTracker.haveTouchEnter && touchCount > 0 ) {
+ updatePointersExit( tracker, event, gPoints );
+ }
+
+ $.stopEvent( event );
+ $.cancelEvent( event );
+ }
+
+
/**
* @private
* @inner
*/
function onTouchMove( tracker, event ) {
- var touchA,
- touchB,
- pinchDelta;
+ var i,
+ touchCount = event.changedTouches.length,
+ gPoints = [];
- if ( !THIS[ tracker.hash ].lastTouch ) {
- return;
+ for ( i = 0; i < touchCount; i++ ) {
+ gPoints.push( {
+ id: event.changedTouches[ i ].identifier,
+ type: 'touch',
+ // isPrimary not set - let the updatePointers functions determine it
+ currentPos: getMouseAbsolute( event.changedTouches[ i ] ),
+ currentTime: $.now()
+ } );
}
- if ( event.touches.length === 1 &&
- event.targetTouches.length === 1 &&
- event.changedTouches.length === 1 &&
- THIS[ tracker.hash ].lastTouch.identifier === event.touches[ 0 ].identifier ) {
-
- onMouseMoveCaptured( tracker, event, true );
-
- } else if ( event.touches.length === 2 ) {
-
- touchA = getMouseAbsolute( event.touches[ 0 ] );
- touchB = getMouseAbsolute( event.touches[ 1 ] );
- pinchDelta =
- Math.abs( touchA.x - touchB.x ) +
- Math.abs( touchA.y - touchB.y );
-
- //TODO: make the 75px pinch threshold configurable
- if ( Math.abs( THIS[ tracker.hash ].lastPinchDelta - pinchDelta ) > 75 ) {
- //$.console.debug( "pinch delta : " + pinchDelta + " | previous : " + THIS[ tracker.hash ].lastPinchDelta);
-
- // Simulate a 'wheel' event
- var simulatedEvent = {
- target: event.target || event.srcElement,
- type: "wheel",
- shiftKey: event.shiftKey || false,
- clientX: THIS[ tracker.hash ].pinchMidpoint.x,
- clientY: THIS[ tracker.hash ].pinchMidpoint.y,
- pageX: THIS[ tracker.hash ].pinchMidpoint.x,
- pageY: THIS[ tracker.hash ].pinchMidpoint.y,
- deltaMode: 1, // 0=pixel, 1=line, 2=page
- deltaX: 0,
- deltaY: ( THIS[ tracker.hash ].lastPinchDelta > pinchDelta ) ? 1 : -1,
- deltaZ: 0
- };
-
- handleWheelEvent( tracker, simulatedEvent, event, true );
-
- THIS[ tracker.hash ].lastPinchDelta = pinchDelta;
- }
- }
- event.preventDefault();
- }
-
- /**
- * Only triggered once by the deepest element that initially received
- * the mouse down event. Since no other element has captured the mouse,
- * we want to trigger the elements that initially received the mouse
- * down event (including this one). The the param tracker isn't used
- * but for consistency with the other event handlers we include it.
- * @private
- * @inner
- */
- function onMouseMoveCapturedIE( tracker, event ) {
- var i;
- for ( i = 0; i < CAPTURING.length; i++ ) {
- onMouseMoveCaptured( CAPTURING[ i ], event, false );
- }
+ updatePointersMove( tracker, event, gPoints );
$.stopEvent( event );
+ $.cancelEvent( event );
}
+
/**
* @private
* @inner
*/
- function getMouseAbsolute( event ) {
- return $.getMousePosition( event );
+ function onTouchCancel( tracker, event ) {
+ var i,
+ touchCount = event.changedTouches.length,
+ gPoints = [];
+
+ for ( i = 0; i < touchCount; i++ ) {
+ gPoints.push( {
+ id: event.changedTouches[ i ].identifier,
+ type: 'touch',
+ } );
+ }
+
+ updatePointersCancel( tracker, event, gPoints );
}
+
/**
* @private
* @inner
*/
- function getMouseRelative( event, element ) {
- var mouse = $.getMousePosition( event ),
- offset = $.getElementOffset( element );
-
- return mouse.minus( offset );
+ function onGestureStart( tracker, event ) {
+ event.stopPropagation();
+ event.preventDefault();
+ return false;
}
+
/**
* @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;
+ function onGestureChange( tracker, event ) {
+ event.stopPropagation();
+ event.preventDefault();
+ return false;
+ }
+
+
+ /**
+ * @private
+ * @inner
+ */
+ function onPointerEnter( tracker, event ) {
+ var gPoint;
+
+ gPoint = {
+ id: event.pointerId,
+ type: getPointerType( event ),
+ isPrimary: event.isPrimary,
+ currentPos: getMouseAbsolute( event ),
+ currentTime: $.now()
+ };
+
+ updatePointersEnter( tracker, event, [ gPoint ] );
+ }
+
+
+ /**
+ * @private
+ * @inner
+ */
+ function onPointerLeave( tracker, event ) {
+ var gPoint;
+
+ gPoint = {
+ id: event.pointerId,
+ type: getPointerType( event ),
+ isPrimary: event.isPrimary,
+ currentPos: getMouseAbsolute( event ),
+ currentTime: $.now()
+ };
+
+ updatePointersExit( tracker, event, [ gPoint ] );
+ }
+
+
+ /**
+ * @private
+ * @inner
+ */
+ function onPointerDown( tracker, event ) {
+ var gPoint;
+
+ gPoint = {
+ id: event.pointerId,
+ type: getPointerType( event ),
+ isPrimary: event.isPrimary,
+ currentPos: getMouseAbsolute( event ),
+ currentTime: $.now()
+ };
+
+ if ( updatePointersDown( tracker, event, [ gPoint ], event.button ) ) {
+ if ( $.MouseTracker.unprefixedPointerEvents ) {
+ event.currentTarget.setPointerCapture( event.pointerId );
+ } else {
+ event.currentTarget.msSetPointerCapture( event.pointerId );
+ }
+ $.stopEvent( event );
+ }
+
+ if ( tracker.clickHandler || tracker.pressHandler || tracker.dragHandler || tracker.dragEndHandler || tracker.pinchHandler ) {
+ $.cancelEvent( event );
+ }
+ }
+
+
+ /**
+ * @private
+ * @inner
+ */
+ function onPointerUp( tracker, event ) {
+ var gPoint;
+
+ gPoint = {
+ id: event.pointerId,
+ type: getPointerType( event ),
+ isPrimary: event.isPrimary,
+ currentPos: getMouseAbsolute( event ),
+ currentTime: $.now()
+ };
+
+ if ( updatePointersUp( tracker, event, [ gPoint ], event.button ) ) {
+ if ( $.MouseTracker.unprefixedPointerEvents ) {
+ event.currentTarget.releasePointerCapture( event.pointerId );
+ } else {
+ event.currentTarget.msReleasePointerCapture( event.pointerId );
}
}
- return elementA == elementB;
}
+
/**
* @private
* @inner
*/
- function onGlobalMouseDown() {
- IS_BUTTON_DOWN = true;
+ function onPointerMove( tracker, event ) {
+ // Pointer changed coordinates, button state, pressure, tilt, or contact geometry (e.g. width and height)
+ var gPoint;
+
+ gPoint = {
+ id: event.pointerId,
+ type: getPointerType( event ),
+ isPrimary: event.isPrimary,
+ currentPos: getMouseAbsolute( event ),
+ currentTime: $.now()
+ };
+
+ updatePointersMove( tracker, event, [ gPoint ] );
}
+
/**
* @private
* @inner
*/
- function onGlobalMouseUp() {
- IS_BUTTON_DOWN = false;
+ function onPointerCancel( tracker, event ) {
+ var gPoint;
+
+ gPoint = {
+ id: event.pointerId,
+ type: getPointerType( event ),
+ };
+
+ updatePointersCancel( tracker, event, [ gPoint ] );
}
- (function () {
- if ( $.Browser.vendor == $.BROWSERS.IE && $.Browser.version < 9 ) {
- $.addEvent( document, "mousedown", onGlobalMouseDown, false );
- $.addEvent( document, "mouseup", onGlobalMouseUp, false );
- } else {
- $.addEvent( window, "mousedown", onGlobalMouseDown, true );
- $.addEvent( window, "mouseup", onGlobalMouseUp, true );
+///////////////////////////////////////////////////////////////////////////////
+// Device-agnostic DOM event handlers
+///////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * @function
+ * @private
+ * @inner
+ * @param {OpenSeadragon.MouseTracker.GesturePointList} pointsList
+ * The GesturePointList to track the pointer in.
+ * @param {OpenSeadragon.MouseTracker.GesturePoint} gPoint
+ * Gesture point to track.
+ * @returns {Number} Number of gesture points in pointsList.
+ */
+ function startTrackingPointer( pointsList, gPoint ) {
+
+ // If isPrimary is not known for the pointer then set it according to our rules:
+ // true if the first pointer in the gesture, otherwise false
+ if ( !gPoint.hasOwnProperty( 'isPrimary' ) ) {
+ if ( pointsList.getLength() === 0 ) {
+ gPoint.isPrimary = true;
+ } else {
+ gPoint.isPrimary = false;
+ }
}
- } )();
+ gPoint.speed = 0;
+ gPoint.direction = 0;
+ gPoint.contactPos = gPoint.currentPos;
+ gPoint.contactTime = gPoint.currentTime;
+ gPoint.lastPos = gPoint.currentPos;
+ gPoint.lastTime = gPoint.currentTime;
+
+ return pointsList.add( gPoint );
+ }
+
+
+ /**
+ * @function
+ * @private
+ * @inner
+ * @param {OpenSeadragon.MouseTracker.GesturePointList} pointsList
+ * The GesturePointList to stop tracking the pointer on.
+ * @param {OpenSeadragon.MouseTracker.GesturePoint} gPoint
+ * Gesture point to stop tracking.
+ * @returns {Number} Number of gesture points in pointsList.
+ */
+ function stopTrackingPointer( pointsList, gPoint ) {
+ var listLength,
+ primaryPoint;
+
+ if ( pointsList.getById( gPoint.id ) ) {
+ listLength = pointsList.removeById( gPoint.id );
+
+ // If isPrimary is not known for the pointer and we just removed the primary pointer from the list then we need to set another pointer as primary
+ if ( !gPoint.hasOwnProperty( 'isPrimary' ) ) {
+ primaryPoint = pointsList.getPrimary();
+ if ( !primaryPoint ) {
+ primaryPoint = pointsList.getByIndex( 0 );
+ if ( primaryPoint ) {
+ primaryPoint.isPrimary = true;
+ }
+ }
+ }
+ } else {
+ listLength = pointsList.getLength();
+ }
+
+ return listLength;
+ }
+
+
+ /**
+ * @function
+ * @private
+ * @inner
+ * @param {OpenSeadragon.MouseTracker} tracker
+ * A reference to the MouseTracker instance.
+ * @param {Object} event
+ * A reference to the originating DOM event.
+ * @param {Array.} gPoints
+ * Gesture points associated with the event.
+ */
+ function updatePointersEnter( tracker, event, gPoints ) {
+ var pointsList = tracker.getActivePointersListByType( gPoints[ 0 ].type ),
+ i,
+ gPointCount = gPoints.length,
+ curGPoint,
+ updateGPoint,
+ propagate;
+
+ for ( i = 0; i < gPointCount; i++ ) {
+ curGPoint = gPoints[ i ];
+ updateGPoint = pointsList.getById( curGPoint.id );
+
+ if ( updateGPoint ) {
+ // Already tracking the pointer...update it
+ updateGPoint.insideElement = true;
+ updateGPoint.lastPos = updateGPoint.currentPos;
+ updateGPoint.lastTime = updateGPoint.currentTime;
+ updateGPoint.currentPos = curGPoint.currentPos;
+ updateGPoint.currentTime = curGPoint.currentTime;
+
+ curGPoint = updateGPoint;
+ } else {
+ // Initialize for tracking and add to the tracking list
+ curGPoint.captured = false;
+ curGPoint.insideElementPressed = false;
+ curGPoint.insideElement = true;
+ startTrackingPointer( pointsList, curGPoint );
+ }
+
+ // Enter
+ if ( tracker.enterHandler ) {
+ propagate = tracker.enterHandler(
+ {
+ eventSource: tracker,
+ pointerType: curGPoint.type,
+ position: getPointRelativeToAbsolute( curGPoint.currentPos, tracker.element ),
+ buttons: pointsList.buttons,
+ insideElementPressed: curGPoint.insideElementPressed,
+ buttonDownAny: pointsList.buttons !== 0,
+ isTouchEvent: curGPoint.type === 'touch',
+ originalEvent: event,
+ preventDefaultAction: false,
+ userData: tracker.userData
+ }
+ );
+ if ( propagate === false ) {
+ $.cancelEvent( event );
+ }
+ }
+ }
+ }
+
+
+ /**
+ * @function
+ * @private
+ * @inner
+ * @param {OpenSeadragon.MouseTracker} tracker
+ * A reference to the MouseTracker instance.
+ * @param {Object} event
+ * A reference to the originating DOM event.
+ * @param {Array.} gPoints
+ * Gesture points associated with the event.
+ */
+ function updatePointersExit( tracker, event, gPoints ) {
+ var delegate = THIS[ tracker.hash ],
+ pointsList = tracker.getActivePointersListByType( gPoints[ 0 ].type ),
+ i,
+ gPointCount = gPoints.length,
+ curGPoint,
+ updateGPoint,
+ propagate;
+
+ for ( i = 0; i < gPointCount; i++ ) {
+ curGPoint = gPoints[ i ];
+ updateGPoint = pointsList.getById( curGPoint.id );
+
+ if ( updateGPoint ) {
+ // Already tracking the pointer. If captured then update it, else stop tracking it
+ if ( updateGPoint.captured ) {
+ updateGPoint.insideElement = false;
+ updateGPoint.lastPos = updateGPoint.currentPos;
+ updateGPoint.lastTime = updateGPoint.currentTime;
+ updateGPoint.currentPos = curGPoint.currentPos;
+ updateGPoint.currentTime = curGPoint.currentTime;
+ } else {
+ stopTrackingPointer( pointsList, updateGPoint );
+ }
+
+ curGPoint = updateGPoint;
+ }
+
+ // Exit
+ if ( tracker.exitHandler ) {
+ propagate = tracker.exitHandler(
+ {
+ eventSource: tracker,
+ pointerType: curGPoint.type,
+ position: getPointRelativeToAbsolute( curGPoint.currentPos, tracker.element ),
+ buttons: pointsList.buttons,
+ insideElementPressed: updateGPoint ? updateGPoint.insideElementPressed : false,
+ buttonDownAny: pointsList.buttons !== 0,
+ isTouchEvent: curGPoint.type === 'touch',
+ originalEvent: event,
+ preventDefaultAction: false,
+ userData: tracker.userData
+ }
+ );
+
+ if ( propagate === false ) {
+ $.cancelEvent( event );
+ }
+ }
+ }
+ }
+
+
+ /**
+ * @function
+ * @private
+ * @inner
+ * @param {OpenSeadragon.MouseTracker} tracker
+ * A reference to the MouseTracker instance.
+ * @param {Object} event
+ * A reference to the originating DOM event.
+ * @param {Array.} gPoints
+ * Gesture points associated with the event.
+ * @param {Number} buttonChanged
+ * The button involved in the event: -1: none, 0: primary, 1: aux, 2: secondary, 3: X1, 4: X2, 5: pen eraser.
+ * Note on chorded button presses (a button pressed when another button is already pressed): In the W3C Pointer Events model,
+ * only one pointerdown/pointerup event combo is fired. Chorded button state changes instead fire pointermove events.
+ *
+ * @returns {Boolean} True if pointers should be captured to the tracked element, otherwise false.
+ */
+ function updatePointersDown( tracker, event, gPoints, buttonChanged ) {
+ var delegate = THIS[ tracker.hash ],
+ propagate,
+ pointsList = tracker.getActivePointersListByType( gPoints[ 0 ].type ),
+ i,
+ gPointCount = gPoints.length,
+ curGPoint,
+ updateGPoint;
+
+ if ( typeof event.buttons !== 'undefined' ) {
+ pointsList.buttons = event.buttons;
+ } else {
+ if ( buttonChanged === 0 ) {
+ // Primary
+ pointsList.buttons |= 1;
+ } else if ( buttonChanged === 1 ) {
+ // Aux
+ pointsList.buttons |= 4;
+ } else if ( buttonChanged === 2 ) {
+ // Secondary
+ pointsList.buttons |= 2;
+ } else if ( buttonChanged === 3 ) {
+ // X1 (Back)
+ pointsList.buttons |= 8;
+ } else if ( buttonChanged === 4 ) {
+ // X2 (Forward)
+ pointsList.buttons |= 16;
+ } else if ( buttonChanged === 5 ) {
+ // Pen Eraser
+ pointsList.buttons |= 32;
+ }
+ }
+
+ // Only capture and track primary button, pen, and touch contacts
+ //if ( buttonChanged !== 0 ) {
+ if ( buttonChanged !== 0 && buttonChanged !== 1 ) { //TODO Remove this IE8 compatibility and use the commented line above
+ return false;
+ }
+
+ for ( i = 0; i < gPointCount; i++ ) {
+ curGPoint = gPoints[ i ];
+ updateGPoint = pointsList.getById( curGPoint.id );
+
+ if ( updateGPoint ) {
+ // Already tracking the pointer...update it
+ updateGPoint.captured = true;
+ updateGPoint.insideElementPressed = true;
+ updateGPoint.insideElement = true;
+ updateGPoint.contactPos = curGPoint.currentPos;
+ updateGPoint.contactTime = curGPoint.currentTime;
+ updateGPoint.lastPos = updateGPoint.currentPos;
+ updateGPoint.lastTime = updateGPoint.currentTime;
+ updateGPoint.currentPos = curGPoint.currentPos;
+ updateGPoint.currentTime = curGPoint.currentTime;
+
+ curGPoint = updateGPoint;
+ } else {
+ // Initialize for tracking and add to the tracking list (no pointerover or pointermove event occurred before this)
+ curGPoint.captured = true;
+ curGPoint.insideElementPressed = true;
+ curGPoint.insideElement = true;
+ startTrackingPointer( pointsList, curGPoint );
+ }
+
+ pointsList.contacts++;
+
+ if ( tracker.dragHandler || tracker.dragEndHandler || tracker.pinchHandler ) {
+ $.MouseTracker.gesturePointVelocityTracker.addPoint( tracker, curGPoint );
+ }
+
+ if ( pointsList.contacts === 1 ) {
+ // Press
+ if ( tracker.pressHandler ) {
+ propagate = tracker.pressHandler(
+ {
+ eventSource: tracker,
+ pointerType: curGPoint.type,
+ position: getPointRelativeToAbsolute( curGPoint.contactPos, tracker.element ),
+ buttons: pointsList.buttons,
+ isTouchEvent: curGPoint.type === 'touch',
+ originalEvent: event,
+ preventDefaultAction: false,
+ userData: tracker.userData
+ }
+ );
+ if ( propagate === false ) {
+ $.cancelEvent( event );
+ }
+ }
+ } else if ( pointsList.contacts === 2 ) {
+ if ( tracker.pinchHandler && curGPoint.type === 'touch' ) {
+ // Initialize for pinch
+ delegate.pinchGPoints = pointsList.asArray();
+ delegate.lastPinchDist = delegate.currentPinchDist = delegate.pinchGPoints[ 0 ].currentPos.distanceTo( delegate.pinchGPoints[ 1 ].currentPos );
+ delegate.lastPinchCenter = delegate.currentPinchCenter = getCenterPoint( delegate.pinchGPoints[ 0 ].currentPos, delegate.pinchGPoints[ 1 ].currentPos );
+ }
+ }
+ }
+
+ return true;
+ }
+
+
+ /**
+ * @function
+ * @private
+ * @inner
+ * @param {OpenSeadragon.MouseTracker} tracker
+ * A reference to the MouseTracker instance.
+ * @param {Object} event
+ * A reference to the originating DOM event.
+ * @param {Array.} gPoints
+ * Gesture points associated with the event.
+ * @param {Number} buttonChanged
+ * The button involved in the event: -1: none, 0: primary, 1: aux, 2: secondary, 3: X1, 4: X2, 5: pen eraser.
+ * Note on chorded button presses (a button pressed when another button is already pressed): In the W3C Pointer Events model,
+ * only one pointerdown/pointerup event combo is fired. Chorded button state changes instead fire pointermove events.
+ *
+ * @returns {Boolean} True if pointer capture should be released from the tracked element, otherwise false.
+ */
+ function updatePointersUp( tracker, event, gPoints, buttonChanged ) {
+ var delegate = THIS[ tracker.hash ],
+ pointsList = tracker.getActivePointersListByType( gPoints[ 0 ].type ),
+ propagate,
+ insideElementReleased,
+ releasePoint,
+ releaseTime,
+ i,
+ gPointCount = gPoints.length,
+ curGPoint,
+ updateGPoint,
+ releaseCapture = false,
+ wasCaptured = false;
+
+ if ( typeof event.buttons !== 'undefined' ) {
+ pointsList.buttons = event.buttons;
+ } else {
+ if ( buttonChanged === 0 ) {
+ // Primary
+ pointsList.buttons ^= ~1;
+ } else if ( buttonChanged === 1 ) {
+ // Aux
+ pointsList.buttons ^= ~4;
+ } else if ( buttonChanged === 2 ) {
+ // Secondary
+ pointsList.buttons ^= ~2;
+ } else if ( buttonChanged === 3 ) {
+ // X1 (Back)
+ pointsList.buttons ^= ~8;
+ } else if ( buttonChanged === 4 ) {
+ // X2 (Forward)
+ pointsList.buttons ^= ~16;
+ } else if ( buttonChanged === 5 ) {
+ // Pen Eraser
+ pointsList.buttons ^= ~32;
+ }
+ }
+
+ // Only capture and track primary button, pen, and touch contacts
+ //if ( buttonChanged !== 0 ) {
+ if ( buttonChanged !== 0 && buttonChanged !== 1 ) { //TODO Remove this IE8 compatibility and use the commented line above
+ return false;
+ }
+
+ for ( i = 0; i < gPointCount; i++ ) {
+ curGPoint = gPoints[ i ];
+ updateGPoint = pointsList.getById( curGPoint.id );
+
+ if ( updateGPoint ) {
+ // Update the pointer, stop tracking it if not still in this element
+ if ( updateGPoint.captured ) {
+ updateGPoint.captured = false;
+ releaseCapture = true;
+ wasCaptured = true;
+ }
+ updateGPoint.lastPos = updateGPoint.currentPos;
+ updateGPoint.lastTime = updateGPoint.currentTime;
+ updateGPoint.currentPos = curGPoint.currentPos;
+ updateGPoint.currentTime = curGPoint.currentTime;
+ if ( !updateGPoint.insideElement ) {
+ stopTrackingPointer( pointsList, updateGPoint );
+ }
+
+ releasePoint = updateGPoint.currentPos;
+ releaseTime = updateGPoint.currentTime;
+
+ if ( wasCaptured ) {
+ // Pointer was activated in our element but could have been removed in any element since events are captured to our element
+
+ pointsList.contacts--;
+
+ if ( tracker.dragHandler || tracker.dragEndHandler || tracker.pinchHandler ) {
+ $.MouseTracker.gesturePointVelocityTracker.removePoint( tracker, updateGPoint );
+ }
+
+ if ( pointsList.contacts === 0 ) {
+
+ // Release (pressed in our element)
+ if ( tracker.releaseHandler ) {
+ propagate = tracker.releaseHandler(
+ {
+ eventSource: tracker,
+ pointerType: updateGPoint.type,
+ position: getPointRelativeToAbsolute( releasePoint, tracker.element ),
+ buttons: pointsList.buttons,
+ insideElementPressed: updateGPoint.insideElementPressed,
+ insideElementReleased: updateGPoint.insideElement,
+ isTouchEvent: updateGPoint.type === 'touch',
+ originalEvent: event,
+ preventDefaultAction: false,
+ userData: tracker.userData
+ }
+ );
+ if ( propagate === false ) {
+ $.cancelEvent( event );
+ }
+ }
+
+ // Drag End
+ if ( tracker.dragEndHandler && !updateGPoint.currentPos.equals( updateGPoint.contactPos ) ) {
+ propagate = tracker.dragEndHandler(
+ {
+ eventSource: tracker,
+ pointerType: updateGPoint.type,
+ position: getPointRelativeToAbsolute( updateGPoint.currentPos, tracker.element ),
+ speed: updateGPoint.speed,
+ direction: updateGPoint.direction,
+ shift: event.shiftKey,
+ isTouchEvent: updateGPoint.type === 'touch',
+ originalEvent: event,
+ preventDefaultAction: false,
+ userData: tracker.userData
+ }
+ );
+ if ( propagate === false ) {
+ $.cancelEvent( event );
+ }
+ }
+
+ // Click
+ if ( tracker.clickHandler && updateGPoint.insideElementPressed && updateGPoint.insideElement ) {
+ var time = releaseTime - updateGPoint.contactTime,
+ distance = updateGPoint.contactPos.distanceTo( releasePoint ),
+ quick = time <= tracker.clickTimeThreshold &&
+ distance <= tracker.clickDistThreshold;
+
+ propagate = tracker.clickHandler(
+ {
+ eventSource: tracker,
+ pointerType: updateGPoint.type,
+ position: getPointRelativeToAbsolute( updateGPoint.currentPos, tracker.element ),
+ quick: quick,
+ shift: event.shiftKey,
+ isTouchEvent: updateGPoint.type === 'touch',
+ originalEvent: event,
+ preventDefaultAction: false,
+ userData: tracker.userData
+ }
+ );
+ if ( propagate === false ) {
+ $.cancelEvent( event );
+ }
+ }
+ } else if ( pointsList.contacts === 2 ) {
+ if ( tracker.pinchHandler && updateGPoint.type === 'touch' ) {
+ // Reset for pinch
+ delegate.pinchGPoints = pointsList.asArray();
+ delegate.lastPinchDist = delegate.currentPinchDist = delegate.pinchGPoints[ 0 ].currentPos.distanceTo( delegate.pinchGPoints[ 1 ].currentPos );
+ delegate.lastPinchCenter = delegate.currentPinchCenter = getCenterPoint( delegate.pinchGPoints[ 0 ].currentPos, delegate.pinchGPoints[ 1 ].currentPos );
+ }
+ }
+ } else {
+ // Pointer was activated in another element but removed in our element
+
+ // Release (pressed in another element)
+ if ( tracker.releaseHandler ) {
+ propagate = tracker.releaseHandler(
+ {
+ eventSource: tracker,
+ pointerType: updateGPoint.type,
+ position: getPointRelativeToAbsolute( releasePoint, tracker.element ),
+ buttons: pointsList.buttons,
+ insideElementPressed: updateGPoint.insideElementPressed,
+ insideElementReleased: updateGPoint.insideElement,
+ isTouchEvent: updateGPoint.type === 'touch',
+ originalEvent: event,
+ preventDefaultAction: false,
+ userData: tracker.userData
+ }
+ );
+ if ( propagate === false ) {
+ $.cancelEvent( event );
+ }
+ }
+ }
+ }
+ }
+
+ return releaseCapture;
+ }
+
+
+ /**
+ * Call when pointer(s) change coordinates, button state, pressure, tilt, or contact geometry (e.g. width and height)
+ *
+ * @function
+ * @private
+ * @inner
+ * @param {OpenSeadragon.MouseTracker} tracker
+ * A reference to the MouseTracker instance.
+ * @param {Object} event
+ * A reference to the originating DOM event.
+ * @param {Array.} gPoints
+ * Gesture points associated with the event.
+ */
+ function updatePointersMove( tracker, event, gPoints ) {
+ var delegate = THIS[ tracker.hash ],
+ pointsList = tracker.getActivePointersListByType( gPoints[ 0 ].type ),
+ i,
+ gPointCount = gPoints.length,
+ curGPoint,
+ updateGPoint,
+ gPointArray,
+ delta,
+ propagate;
+
+ if ( typeof event.buttons !== 'undefined' ) {
+ pointsList.buttons = event.buttons;
+ }
+
+ for ( i = 0; i < gPointCount; i++ ) {
+ curGPoint = gPoints[ i ];
+ updateGPoint = pointsList.getById( curGPoint.id );
+
+ if ( updateGPoint ) {
+ // Already tracking the pointer...update it
+ if ( curGPoint.hasOwnProperty( 'isPrimary' ) ) {
+ updateGPoint.isPrimary = curGPoint.isPrimary;
+ }
+ updateGPoint.lastPos = updateGPoint.currentPos;
+ updateGPoint.lastTime = updateGPoint.currentTime;
+ updateGPoint.currentPos = curGPoint.currentPos;
+ updateGPoint.currentTime = curGPoint.currentTime;
+ } else {
+ // Initialize for tracking and add to the tracking list (no pointerover or pointerdown event occurred before this)
+ curGPoint.captured = false;
+ curGPoint.insideElementPressed = false;
+ curGPoint.insideElement = true;
+ startTrackingPointer( pointsList, curGPoint );
+ }
+ }
+
+ // Stop (mouse only)
+ if ( tracker.stopHandler && gPoints[ 0 ].type === 'mouse' ) {
+ clearTimeout( tracker.stopTimeOut );
+ tracker.stopTimeOut = setTimeout( function() {
+ handlePointerStop( tracker, event, gPoints[ 0 ].type );
+ }, tracker.stopDelay );
+ }
+
+ if ( pointsList.contacts === 0 ) {
+ // Move (no contacts: hovering mouse or other hover-capable device)
+ if ( tracker.moveHandler ) {
+ propagate = tracker.moveHandler(
+ {
+ eventSource: tracker,
+ pointerType: gPoints[ 0 ].type,
+ position: getPointRelativeToAbsolute( gPoints[ 0 ].currentPos, tracker.element ),
+ buttons: pointsList.buttons,
+ isTouchEvent: gPoints[ 0 ].type === 'touch',
+ originalEvent: event,
+ preventDefaultAction: false,
+ userData: tracker.userData
+ }
+ );
+ if ( propagate === false ) {
+ $.cancelEvent( event );
+ }
+ }
+ } else if ( pointsList.contacts === 1 ) {
+ // Move (1 contact)
+ if ( tracker.moveHandler ) {
+ updateGPoint = pointsList.asArray()[ 0 ];
+ propagate = tracker.moveHandler(
+ {
+ eventSource: tracker,
+ pointerType: updateGPoint.type,
+ position: getPointRelativeToAbsolute( updateGPoint.currentPos, tracker.element ),
+ buttons: pointsList.buttons,
+ isTouchEvent: updateGPoint.type === 'touch',
+ originalEvent: event,
+ preventDefaultAction: false,
+ userData: tracker.userData
+ }
+ );
+ if ( propagate === false ) {
+ $.cancelEvent( event );
+ }
+ }
+
+ // Drag
+ if ( tracker.dragHandler ) {
+ updateGPoint = pointsList.asArray()[ 0 ];
+ delta = updateGPoint.currentPos.minus( updateGPoint.lastPos );
+ propagate = tracker.dragHandler(
+ {
+ eventSource: tracker,
+ pointerType: updateGPoint.type,
+ position: getPointRelativeToAbsolute( updateGPoint.currentPos, tracker.element ),
+ buttons: pointsList.buttons,
+ delta: delta,
+ speed: updateGPoint.speed,
+ direction: updateGPoint.direction,
+ shift: event.shiftKey,
+ isTouchEvent: updateGPoint.type === 'touch',
+ originalEvent: event,
+ preventDefaultAction: false,
+ userData: tracker.userData
+ }
+ );
+ if ( propagate === false ) {
+ $.cancelEvent( event );
+ }
+ }
+ } else if ( pointsList.contacts === 2 ) {
+ // Move (2 contacts, use center)
+ if ( tracker.moveHandler ) {
+ gPointArray = pointsList.asArray();
+ propagate = tracker.moveHandler(
+ {
+ eventSource: tracker,
+ pointerType: gPointArray[ 0 ].type,
+ position: getPointRelativeToAbsolute( getCenterPoint( gPointArray[ 0 ].currentPos, gPointArray[ 1 ].currentPos ), tracker.element ),
+ buttons: pointsList.buttons,
+ isTouchEvent: gPointArray[ 0 ].type === 'touch',
+ originalEvent: event,
+ preventDefaultAction: false,
+ userData: tracker.userData
+ }
+ );
+ if ( propagate === false ) {
+ $.cancelEvent( event );
+ }
+ }
+
+ // Pinch
+ if ( tracker.pinchHandler && gPoints[ 0 ].type === 'touch' ) {
+ delta = delegate.pinchGPoints[ 0 ].currentPos.distanceTo( delegate.pinchGPoints[ 1 ].currentPos );
+ if ( delta != delegate.currentPinchDist ) {
+ delegate.lastPinchDist = delegate.currentPinchDist;
+ delegate.currentPinchDist = delta;
+ delegate.lastPinchCenter = delegate.currentPinchCenter;
+ delegate.currentPinchCenter = getCenterPoint( delegate.pinchGPoints[ 0 ].currentPos, delegate.pinchGPoints[ 1 ].currentPos );
+ propagate = tracker.pinchHandler(
+ {
+ eventSource: tracker,
+ pointerType: 'touch',
+ gesturePoints: delegate.pinchGPoints,
+ lastCenter: getPointRelativeToAbsolute( delegate.lastPinchCenter, tracker.element ),
+ center: getPointRelativeToAbsolute( delegate.currentPinchCenter, tracker.element ),
+ lastDistance: delegate.lastPinchDist,
+ distance: delegate.currentPinchDist,
+ shift: event.shiftKey,
+ originalEvent: event,
+ preventDefaultAction: false,
+ userData: tracker.userData
+ }
+ );
+ if ( propagate === false ) {
+ $.cancelEvent( event );
+ }
+ }
+ }
+ }
+ }
+
+
+ /**
+ * @function
+ * @private
+ * @inner
+ * @param {OpenSeadragon.MouseTracker} tracker
+ * A reference to the MouseTracker instance.
+ * @param {Object} event
+ * A reference to the originating DOM event.
+ * @param {Array.} gPoints
+ * Gesture points associated with the event.
+ */
+ function updatePointersCancel( tracker, event, gPoints ) {
+ updatePointersUp( tracker, event, gPoints, 0 );
+ updatePointersExit( tracker, event, gPoints );
+ }
+
+
+ /**
+ * @private
+ * @inner
+ */
+ function handlePointerStop( tracker, originalMoveEvent, pointerType ) {
+ if ( tracker.stopHandler ) {
+ tracker.stopHandler( {
+ eventSource: tracker,
+ pointerType: pointerType,
+ position: getMouseRelative( originalMoveEvent, tracker.element ),
+ buttons: tracker.getActivePointersListByType( pointerType ).buttons,
+ isTouchEvent: pointerType === 'touch',
+ originalEvent: originalMoveEvent,
+ preventDefaultAction: false,
+ userData: tracker.userData
+ } );
+ }
+ }
} ( OpenSeadragon ) );
diff --git a/src/openseadragon.js b/src/openseadragon.js
index 3be339af..c7f69749 100644
--- a/src/openseadragon.js
+++ b/src/openseadragon.js
@@ -185,10 +185,6 @@
*
* @property {String} [debugGridColor='#437AB2']
*
- * @property {Number} [animationTime=1.2]
- * Specifies the animation duration per each {@link OpenSeadragon.Spring}
- * which occur when the image is dragged or zoomed.
- *
* @property {Number} [blendTime=0]
* Specifies the duration of animation as higher or lower level tiles are
* replacing the existing tile.
@@ -265,8 +261,6 @@
* achieved. Setting this to 0 and wrapHorizontal ( or wrapVertical ) to
* true will provide the effect of an infinitely scrolling viewport.
*
- * @property {Number} [springStiffness=7.0]
- *
* @property {Number} [imageLoaderLimit=0]
* The maximum number of image requests to make concurrently. By default
* it is set to 0 allowing the browser to make the maximum number of
@@ -280,11 +274,53 @@
* If a mouse or touch drag occurs and the distance to the starting drag
* point is less than this many pixels, ignore the drag event.
*
+ * @property {Number} [springStiffness=5.0]
+ *
+ * @property {Number} [animationTime=1.2]
+ * Specifies the animation duration per each {@link OpenSeadragon.Spring}
+ * which occur when the image is dragged or zoomed.
+ *
+ * @property {OpenSeadragon.GestureSettings} [gestureSettingsMouse]
+ * Settings for gestures generated by a mouse pointer device. (See {@link OpenSeadragon.GestureSettings})
+ * @property {Boolean} [gestureSettingsMouse.scrollToZoom=true] - Zoom on scroll gesture
+ * @property {Boolean} [gestureSettingsMouse.clickToZoom=true] - Zoom on click gesture
+ * @property {Boolean} [gestureSettingsMouse.pinchToZoom=false] - Zoom on pinch gesture
+ * @property {Boolean} [gestureSettingsMouse.flickEnabled=false] - Enable flick gesture
+ * @property {Number} [gestureSettingsMouse.flickMinSpeed=20] - If flickEnabled is true, the minimum speed to initiate a flick gesture (pixels-per-second)
+ * @property {Number} [gestureSettingsMouse.flickMomentum=0.40] - If flickEnabled is true, the momentum factor for the flick gesture
+ *
+ * @property {OpenSeadragon.GestureSettings} [gestureSettingsTouch]
+ * Settings for gestures generated by a touch pointer device. (See {@link OpenSeadragon.GestureSettings})
+ * @property {Boolean} [gestureSettingsTouch.scrollToZoom=false] - Zoom on scroll gesture
+ * @property {Boolean} [gestureSettingsTouch.clickToZoom=false] - Zoom on click gesture
+ * @property {Boolean} [gestureSettingsTouch.pinchToZoom=true] - Zoom on pinch gesture
+ * @property {Boolean} [gestureSettingsTouch.flickEnabled=true] - Enable flick gesture
+ * @property {Number} [gestureSettingsTouch.flickMinSpeed=20] - If flickEnabled is true, the minimum speed to initiate a flick gesture (pixels-per-second)
+ * @property {Number} [gestureSettingsTouch.flickMomentum=0.40] - If flickEnabled is true, the momentum factor for the flick gesture
+ *
+ * @property {OpenSeadragon.GestureSettings} [gestureSettingsPen]
+ * Settings for gestures generated by a pen pointer device. (See {@link OpenSeadragon.GestureSettings})
+ * @property {Boolean} [gestureSettingsPen.scrollToZoom=false] - Zoom on scroll gesture
+ * @property {Boolean} [gestureSettingsPen.clickToZoom=true] - Zoom on click gesture
+ * @property {Boolean} [gestureSettingsPen.pinchToZoom=false] - Zoom on pinch gesture
+ * @property {Boolean} [gestureSettingsPen.flickEnabled=false] - Enable flick gesture
+ * @property {Number} [gestureSettingsPen.flickMinSpeed=20] - If flickEnabled is true, the minimum speed to initiate a flick gesture (pixels-per-second)
+ * @property {Number} [gestureSettingsPen.flickMomentum=0.40] - If flickEnabled is true, the momentum factor for the flick gesture
+ *
+ * @property {OpenSeadragon.GestureSettings} [gestureSettingsUnknown]
+ * Settings for gestures generated by unknown pointer devices. (See {@link OpenSeadragon.GestureSettings})
+ * @property {Boolean} [gestureSettingsUnknown.scrollToZoom=true] - Zoom on scroll gesture
+ * @property {Boolean} [gestureSettingsUnknown.clickToZoom=false] - Zoom on click gesture
+ * @property {Boolean} [gestureSettingsUnknown.pinchToZoom=true] - Zoom on pinch gesture
+ * @property {Boolean} [gestureSettingsUnknown.flickEnabled=true] - Enable flick gesture
+ * @property {Number} [gestureSettingsUnknown.flickMinSpeed=20] - If flickEnabled is true, the minimum speed to initiate a flick gesture (pixels-per-second)
+ * @property {Number} [gestureSettingsUnknown.flickMomentum=0.40] - If flickEnabled is true, the momentum factor for the flick gesture
+ *
* @property {Number} [zoomPerClick=2.0]
- * The "zoom distance" per mouse click or touch tap. Note: Setting this to 1.0 effectively disables the click-to-zoom feature.
+ * The "zoom distance" per mouse click or touch tap. Note: Setting this to 1.0 effectively disables the click-to-zoom feature (also see gestureSettings[Mouse|Touch|Pen].clickToZoom).
*
* @property {Number} [zoomPerScroll=1.2]
- * The "zoom distance" per mouse scroll or touch pinch. Note: Setting this to 1.0 effectively disables the mouse-wheel zoom feature.
+ * The "zoom distance" per mouse scroll or touch pinch. Note: Setting this to 1.0 effectively disables the mouse-wheel zoom feature (also see gestureSettings[Mouse|Touch|Pen].scrollToZoom}).
*
* @property {Number} [zoomPerSecond=1.0]
* The number of seconds to animate a single zoom event over.
@@ -486,6 +522,34 @@
*
*/
+ /**
+ * Settings for gestures generated by a pointer device.
+ *
+ * @typedef {Object} GestureSettings
+ * @memberof OpenSeadragon
+ *
+ * @property {Boolean} scrollToZoom
+ * Set to false to disable zooming on scroll gestures.
+ *
+ * @property {Boolean} clickToZoom
+ * Set to false to disable zooming on click gestures.
+ *
+ * @property {Boolean} pinchToZoom
+ * Set to false to disable zooming on pinch gestures.
+ *
+ * @property {Boolean} flickEnabled
+ * Set to false to disable the kinetic panning effect (flick) at the end of a drag gesture.
+ *
+ * @property {Number} flickMinSpeed
+ * If flickEnabled is true, the minimum speed (in pixels-per-second) required to cause the kinetic panning effect (flick) at the end of a drag gesture.
+ *
+ * @property {Number} flickMomentum
+ * If flickEnabled is true, a constant multiplied by the velocity to determine the distance of the kinetic panning effect (flick) at the end of a drag gesture.
+ * A larger value will make the flick feel "lighter", while a smaller value will make the flick feel "heavier".
+ * Note: springStiffness and animationTime also affect the "spring" used to stop the flick animation.
+ *
+ */
+
/**
* The names for the image resources used for the image navigation buttons.
*
@@ -825,13 +889,17 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
maxZoomLevel: null,
//UI RESPONSIVENESS AND FEEL
- springStiffness: 7.0,
clickTimeThreshold: 300,
clickDistThreshold: 5,
+ springStiffness: 5.0,
+ animationTime: 1.2,
+ gestureSettingsMouse: { scrollToZoom: true, clickToZoom: true, pinchToZoom: false, flickEnabled: false, flickMinSpeed: 20, flickMomentum: 0.40 },
+ gestureSettingsTouch: { scrollToZoom: false, clickToZoom: false, pinchToZoom: true, flickEnabled: true, flickMinSpeed: 20, flickMomentum: 0.40 },
+ gestureSettingsPen: { scrollToZoom: false, clickToZoom: true, pinchToZoom: false, flickEnabled: false, flickMinSpeed: 20, flickMomentum: 0.40 },
+ gestureSettingsUnknown: { scrollToZoom: false, clickToZoom: false, pinchToZoom: true, flickEnabled: true, flickMinSpeed: 20, flickMomentum: 0.40 },
zoomPerClick: 2,
zoomPerScroll: 1.2,
zoomPerSecond: 1.0,
- animationTime: 1.2,
blendTime: 0,
alwaysBlend: false,
autoHideControls: true,
@@ -1123,6 +1191,21 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
},
+ /**
+ * Determines if a point is within the bounding rectangle of the given element (hit-test).
+ * @function
+ * @param {Element|String} element
+ * @param {OpenSeadragon.Point} point
+ * @returns {Boolean}
+ */
+ pointInElement: function( element, point ) {
+ element = $.getElement( element );
+ var offset = $.getElementOffset( element ),
+ size = $.getElementSize( element );
+ return point.x >= offset.x && point.x < offset.x + size.x && point.y < offset.y + size.y && point.y >= offset.y;
+ },
+
+
/**
* Gets the latest event, really only useful internally since its
* specific to IE behavior.
@@ -1586,9 +1669,6 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
return function ( element, eventName, handler, useCapture ) {
element = $.getElement( element );
element.attachEvent( 'on' + eventName, handler );
- if ( useCapture && element.setCapture ) {
- element.setCapture();
- }
};
} else {
throw new Error( "No known event model." );
@@ -1615,9 +1695,6 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
return function( element, eventName, handler, useCapture ) {
element = $.getElement( element );
element.detachEvent( 'on' + eventName, handler );
- if ( useCapture && element.releaseCapture ) {
- element.releaseCapture();
- }
};
} else {
throw new Error( "No known event model." );
diff --git a/src/viewer.js b/src/viewer.js
index a77cd46f..bd01afe3 100644
--- a/src/viewer.js
+++ b/src/viewer.js
@@ -275,7 +275,13 @@ $.Viewer = function( options ) {
style.position = "absolute";
style.top = "0px";
style.left = "0px";
- }( this.canvas.style ));
+ // Disable browser default touch handling
+ if (style["touch-action"] !== undefined) {
+ style["touch-action"] = "none";
+ } else if (style["-ms-touch-action"] !== undefined) {
+ style["-ms-touch-action"] = "none";
+ }
+ }(this.canvas.style));
//the container is created through applying the ControlDock constructor above
this.container.className = "openseadragon-container";
@@ -384,8 +390,10 @@ $.Viewer = function( options ) {
clickDistThreshold: this.clickDistThreshold,
clickHandler: $.delegate( this, onCanvasClick ),
dragHandler: $.delegate( this, onCanvasDrag ),
+ dragEndHandler: $.delegate( this, onCanvasDragEnd ),
releaseHandler: $.delegate( this, onCanvasRelease ),
- scrollHandler: $.delegate( this, onCanvasScroll )
+ scrollHandler: $.delegate( this, onCanvasScroll ),
+ pinchHandler: $.delegate( this, onCanvasPinch )
}).setTracking( this.mouseNavEnabled ? true : false ); // default state
this.outerTracker = new $.MouseTracker({
@@ -1770,7 +1778,27 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
div.parentNode.removeChild(div);
delete this.messageDiv;
}
+ },
+
+ /**
+ * Gets this viewer's gesture settings for the given pointer device type.
+ * @method
+ * @param {String} type - The pointer device type to get the gesture settings for ("mouse", "touch", "pen", etc.).
+ * @return {OpenSeadragon.GestureSettings}
+ */
+ gestureSettingsByDeviceType: function ( type ) {
+ switch ( type ) {
+ case 'mouse':
+ return this.gestureSettingsMouse;
+ case 'touch':
+ return this.gestureSettingsTouch;
+ case 'pen':
+ return this.gestureSettingsPen;
+ default:
+ return this.gestureSettingsUnknown;
+ }
}
+
});
@@ -2204,16 +2232,17 @@ function onBlur(){
}
function onCanvasClick( event ) {
- var zoomPerClick,
- factor;
- if ( !event.preventDefaultAction && this.viewport && event.quick ) { // ignore clicks where mouse moved
- zoomPerClick = this.zoomPerClick;
- factor = event.shift ? 1.0 / zoomPerClick : zoomPerClick;
- this.viewport.zoomBy(
- factor,
- this.viewport.pointFromPixel( event.position, true )
- );
- this.viewport.applyConstraints();
+ var gestureSettings;
+
+ if ( !event.preventDefaultAction && this.viewport && event.quick ) {
+ gestureSettings = this.gestureSettingsByDeviceType( event.pointerType );
+ if ( gestureSettings.clickToZoom ) {
+ this.viewport.zoomBy(
+ event.shift ? 1.0 / this.zoomPerClick : this.zoomPerClick,
+ this.viewport.pointFromPixel( event.position, true )
+ );
+ this.viewport.applyConstraints();
+ }
}
/**
* Raised when a mouse press/release or touch/remove occurs on the {@link OpenSeadragon.Viewer#canvas} element.
@@ -2239,19 +2268,18 @@ function onCanvasClick( event ) {
}
function onCanvasDrag( event ) {
+ var gestureSettings;
+
if ( !event.preventDefaultAction && this.viewport ) {
+ gestureSettings = this.gestureSettingsByDeviceType( event.pointerType );
if( !this.panHorizontal ){
event.delta.x = 0;
}
if( !this.panVertical ){
event.delta.y = 0;
}
- this.viewport.panBy(
- this.viewport.deltaPointsFromPixels(
- event.delta.negate()
- )
- );
- if( this.constrainDuringPan ){
+ this.viewport.panBy( this.viewport.deltaPointsFromPixels( event.delta.negate() ), gestureSettings.flickEnabled );
+ if( this.constrainDuringPan && !gestureSettings.flickEnabled ){
this.viewport.applyConstraints();
}
}
@@ -2265,6 +2293,8 @@ function onCanvasDrag( event ) {
* @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
* @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
* @property {OpenSeadragon.Point} delta - The x,y components of the difference between start drag and end drag.
+ * @property {Number} speed - Current computed speed, in pixels per second.
+ * @property {Number} direction - 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 {Boolean} shift - True if the shift key was pressed during this event.
* @property {Object} originalEvent - The original DOM event.
* @property {?Object} userData - Arbitrary subscriber-defined object.
@@ -2273,14 +2303,61 @@ function onCanvasDrag( event ) {
tracker: event.eventSource,
position: event.position,
delta: event.delta,
+ speed: event.speed,
+ direction: event.direction,
+ shift: event.shift,
+ originalEvent: event.originalEvent
+ });
+}
+
+function onCanvasDragEnd( event ) {
+ var gestureSettings;
+
+ if ( !event.preventDefaultAction && this.viewport ) {
+ gestureSettings = this.gestureSettingsByDeviceType( event.pointerType );
+ if ( gestureSettings.flickEnabled && event.speed >= gestureSettings.flickMinSpeed && !event.preventDefaultAction && this.viewport ) {
+ var amplitudeX = gestureSettings.flickMomentum * ( event.speed * Math.cos( event.direction ) ),
+ amplitudeY = gestureSettings.flickMomentum * ( event.speed * Math.sin( event.direction ) ),
+ center = this.viewport.pixelFromPoint( this.viewport.getCenter( true ) ),
+ target = this.viewport.pointFromPixel( new $.Point( center.x - amplitudeX, center.y - amplitudeY ) );
+ this.viewport.panTo( target, false );
+ this.viewport.applyConstraints();
+ }
+ }
+ /**
+ * Raised when a mouse or touch drag operation ends on the {@link OpenSeadragon.Viewer#canvas} element.
+ *
+ * @event canvas-drag-end
+ * @memberof OpenSeadragon.Viewer
+ * @type {object}
+ * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
+ * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
+ * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
+ * @property {Number} speed - Speed at the end of a drag gesture, in pixels per second.
+ * @property {Number} direction - Direction at the end of a drag gesture, expressed as an angle counterclockwise relative to the positive X axis (-pi to pi, in radians). Only valid if speed > 0.
+ * @property {Boolean} shift - True if the shift key was pressed during this event.
+ * @property {Object} originalEvent - The original DOM event.
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
+ */
+ this.raiseEvent( 'canvas-drag-end', {
+ tracker: event.eventSource,
+ position: event.position,
+ speed: event.speed,
+ direction: event.direction,
shift: event.shift,
originalEvent: event.originalEvent
});
}
function onCanvasRelease( event ) {
+ var gestureSettings;
+
if ( event.insideElementPressed && this.viewport ) {
- this.viewport.applyConstraints();
+ gestureSettings = this.gestureSettingsByDeviceType( event.pointerType );
+
+ if ( !gestureSettings.flickEnabled ) {
+ this.viewport.applyConstraints();
+ }
}
/**
* Raised when the mouse button is released or touch ends on the {@link OpenSeadragon.Viewer#canvas} element.
@@ -2305,18 +2382,67 @@ function onCanvasRelease( event ) {
});
}
-function onCanvasScroll( event ) {
- var factor;
+function onCanvasPinch( event ) {
+ var gestureSettings;
+
if ( !event.preventDefaultAction && this.viewport ) {
- factor = Math.pow( this.zoomPerScroll, event.scroll );
- this.viewport.zoomBy(
- factor,
- this.viewport.pointFromPixel( event.position, true )
- );
- this.viewport.applyConstraints();
+ gestureSettings = this.gestureSettingsByDeviceType( event.pointerType );
+ if ( gestureSettings.pinchToZoom ) {
+ var centerPt = this.viewport.pointFromPixel( event.center, true ),
+ lastCenterPt = this.viewport.pointFromPixel( event.lastCenter, true );
+ this.viewport.zoomBy( event.distance / event.lastDistance, centerPt, true );
+ this.viewport.panBy( lastCenterPt.minus( centerPt ), true );
+ this.viewport.applyConstraints();
+ }
}
/**
- * Raised when a scroll event occurs on the {@link OpenSeadragon.Viewer#canvas} element (mouse wheel, touch pinch, etc.).
+ * Raised when a pinch event occurs on the {@link OpenSeadragon.Viewer#canvas} element.
+ *
+ * @event canvas-pinch
+ * @memberof OpenSeadragon.Viewer
+ * @type {object}
+ * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
+ * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
+ * @property {Array.} gesturePoints - Gesture points associated with the gesture. Velocity data can be found here.
+ * @property {OpenSeadragon.Point} lastCenter - The previous center point of the two pinch contact points relative to the tracked element.
+ * @property {OpenSeadragon.Point} center - The center point of the two pinch contact points relative to the tracked element.
+ * @property {Number} lastDistance - The previous distance between the two pinch contact points in CSS pixels.
+ * @property {Number} distance - The distance between the two pinch contact points in CSS pixels.
+ * @property {Boolean} shift - True if the shift key was pressed during this event.
+ * @property {Object} originalEvent - The original DOM event.
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
+ */
+ this.raiseEvent('canvas-pinch', {
+ tracker: event.eventSource,
+ gesturePoints: event.gesturePoints,
+ lastCenter: event.lastCenter,
+ center: event.center,
+ lastDistance: event.lastDistance,
+ distance: event.distance,
+ shift: event.shift,
+ originalEvent: event.originalEvent
+ });
+ //cancels event
+ return false;
+}
+
+function onCanvasScroll( event ) {
+ var gestureSettings,
+ factor;
+
+ if ( !event.preventDefaultAction && this.viewport ) {
+ gestureSettings = this.gestureSettingsByDeviceType( event.pointerType );
+ if ( gestureSettings.scrollToZoom ) {
+ factor = Math.pow( this.zoomPerScroll, event.scroll );
+ this.viewport.zoomBy(
+ factor,
+ this.viewport.pointFromPixel( event.position, true )
+ );
+ this.viewport.applyConstraints();
+ }
+ }
+ /**
+ * Raised when a scroll event occurs on the {@link OpenSeadragon.Viewer#canvas} element (mouse wheel).
*
* @event canvas-scroll
* @memberof OpenSeadragon.Viewer
@@ -2356,14 +2482,16 @@ function onContainerExit( event ) {
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
* @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
* @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
+ * @property {Number} buttons - Current buttons pressed. A combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser.
* @property {Boolean} insideElementPressed - True if the left mouse button is currently being pressed and was initiated inside the tracked element, otherwise false.
- * @property {Boolean} buttonDownAny - Was the button down anywhere in the screen during the event.
+ * @property {Boolean} buttonDownAny - Was the button down anywhere in the screen during the event. Deprecated. Use buttons instead.
* @property {Object} originalEvent - The original DOM event.
* @property {?Object} userData - Arbitrary subscriber-defined object.
*/
this.raiseEvent( 'container-exit', {
tracker: event.eventSource,
position: event.position,
+ buttons: event.buttons,
insideElementPressed: event.insideElementPressed,
buttonDownAny: event.buttonDownAny,
originalEvent: event.originalEvent
@@ -2412,14 +2540,16 @@ function onContainerEnter( event ) {
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
* @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
* @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
+ * @property {Number} buttons - Current buttons pressed. A combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser.
* @property {Boolean} insideElementPressed - True if the left mouse button is currently being pressed and was initiated inside the tracked element, otherwise false.
- * @property {Boolean} buttonDownAny - Was the button down anywhere in the screen during the event.
+ * @property {Boolean} buttonDownAny - Was the button down anywhere in the screen during the event. Deprecated. Use buttons instead.
* @property {Object} originalEvent - The original DOM event.
* @property {?Object} userData - Arbitrary subscriber-defined object.
*/
this.raiseEvent( 'container-enter', {
tracker: event.eventSource,
position: event.position,
+ buttons: event.buttons,
insideElementPressed: event.insideElementPressed,
buttonDownAny: event.buttonDownAny,
originalEvent: event.originalEvent
diff --git a/test/events.js b/test/events.js
index 86f007c1..61137046 100644
--- a/test/events.js
+++ b/test/events.js
@@ -25,184 +25,380 @@
} );
// ----------
- asyncTest( 'addHandler without userData', function () {
- var openHandler = function ( event ) {
- viewer.removeHandler( 'open', openHandler );
- ok( event, 'Event handler received event data' );
- if ( event ) {
- strictEqual( event.eventSource, viewer, 'eventSource sent, eventSource is viewer' );
- strictEqual( event.userData, null, 'User data defaulted to null' );
- }
- viewer.close();
- start();
- };
-
- viewer.addHandler( 'open', openHandler );
- viewer.open( '/test/data/testpattern.dzi' );
- } );
-
- // ----------
- asyncTest( 'addHandler with userData', function () {
- var userData = { item1: 'Test user data', item2: Math.random() },
- originalUserData = { item1: userData.item1, item2: userData.item2 };
-
- var openHandler = function ( event ) {
- viewer.removeHandler( 'open', openHandler );
- ok( event, 'Event handler received event data' );
- ok( event && event.userData, 'Event handler received user data' );
- if ( event && event.userData ) {
- deepEqual( event.userData, originalUserData, 'User data was untouched' );
- }
- viewer.close();
- start();
- };
-
- viewer.addHandler( 'open', openHandler, userData );
- viewer.open( '/test/data/testpattern.dzi' );
- } );
-
- // ----------
- asyncTest( 'MouseTracker, EventSource canvas-drag canvas-release canvas-click', function () {
+ asyncTest( 'MouseTracker: mouse gestures', function () {
var $canvas = $( viewer.element ).find( '.openseadragon-canvas' ).not( '.navigator .openseadragon-canvas' ),
- mouseTracker = null,
- userData = { item1: 'Test user data', item2: Math.random() },
- originalUserData = { item1: userData.item1, item2: userData.item2 },
- dragCount = 10,
- dragsHandledEventSource = 0,
- releasesHandledEventSource = 0,
- clicksHandledEventSource = 0,
- eventsHandledMouseTracker = 0,
- eventSourcePassedMouseTracker = 0,
- originalEventsPassedMouseTracker = 0,
- eventsHandledViewer = 0,
- originalEventsPassedViewer = 0,
- releasesExpected = 1,
- clicksExpected = 1;
+ simEvent = {},
+ offset = $canvas.offset(),
+ tracker = viewer.innerTracker,
+ intervalId,
+ origEnterHandler,
+ origExitHandler,
+ origPressHandler,
+ origReleaseHandler,
+ origMoveHandler,
+ origClickHandler,
+ origDragHandler,
+ origDragEndHandler,
+ enterCount,
+ exitCount,
+ pressCount,
+ releaseCount,
+ moveCount,
+ clickCount,
+ dragCount,
+ dragEndCount,
+ insideElementPressed,
+ insideElementReleased,
+ quickClick,
+ speed,
+ direction;
+
+ var hookViewerHandlers = function () {
+ origEnterHandler = tracker.enterHandler;
+ tracker.enterHandler = function ( event ) {
+ enterCount++;
+ if (origEnterHandler) {
+ return origEnterHandler( event );
+ } else {
+ return true;
+ }
+ };
+ origExitHandler = tracker.exitHandler;
+ tracker.exitHandler = function ( event ) {
+ exitCount++;
+ if (origExitHandler) {
+ return origExitHandler( event );
+ } else {
+ return true;
+ }
+ };
+ origPressHandler = tracker.pressHandler;
+ tracker.pressHandler = function ( event ) {
+ pressCount++;
+ if (origPressHandler) {
+ return origPressHandler( event );
+ } else {
+ return true;
+ }
+ };
+ origReleaseHandler = tracker.releaseHandler;
+ tracker.releaseHandler = function ( event ) {
+ releaseCount++;
+ insideElementPressed = event.insideElementPressed;
+ insideElementReleased = event.insideElementReleased;
+ if (origReleaseHandler) {
+ return origReleaseHandler( event );
+ } else {
+ return true;
+ }
+ };
+ origMoveHandler = tracker.moveHandler;
+ tracker.moveHandler = function ( event ) {
+ moveCount++;
+ if (origMoveHandler) {
+ return origMoveHandler( event );
+ } else {
+ return true;
+ }
+ };
+ origClickHandler = tracker.clickHandler;
+ tracker.clickHandler = function ( event ) {
+ clickCount++;
+ quickClick = event.quick;
+ if (origClickHandler) {
+ return origClickHandler( event );
+ } else {
+ return true;
+ }
+ };
+ origDragHandler = tracker.dragHandler;
+ tracker.dragHandler = function ( event ) {
+ dragCount++;
+ if (origDragHandler) {
+ return origDragHandler( event );
+ } else {
+ return true;
+ }
+ };
+ origDragEndHandler = tracker.dragEndHandler;
+ tracker.dragEndHandler = function ( event ) {
+ dragEndCount++;
+ speed = event.speed;
+ direction = event.direction;
+ if (origDragEndHandler) {
+ return origDragEndHandler( event );
+ } else {
+ return true;
+ }
+ };
+ };
+
+ var unhookViewerHandlers = function () {
+ tracker.enterHandler = origEnterHandler;
+ tracker.exitHandler = origExitHandler;
+ tracker.pressHandler = origPressHandler;
+ tracker.releaseHandler = origReleaseHandler;
+ tracker.moveHandler = origMoveHandler;
+ tracker.clickHandler = origClickHandler;
+ tracker.dragHandler = origDragHandler;
+ tracker.dragEndHandler = origDragEndHandler;
+ };
+
+ var simulateEnter = function (x, y) {
+ simEvent.clientX = offset.left + x;
+ simEvent.clientY = offset.top + y;
+ $canvas.simulate( OpenSeadragon.MouseTracker.haveMouseEnter ? 'mouseenter' : 'mouseover', simEvent );
+ };
+
+ var simulateLeave = function (x, y) {
+ simEvent.clientX = offset.left + x;
+ simEvent.clientY = offset.top + y;
+ $canvas.simulate( OpenSeadragon.MouseTracker.haveMouseEnter ? 'mouseleave' : 'mouseout', simEvent );
+ };
+
+ var simulateDown = function (x, y) {
+ simEvent.clientX = offset.left + x;
+ simEvent.clientY = offset.top + y;
+ $canvas.simulate( 'mousedown', simEvent );
+ };
+
+ var simulateUp = function (x, y) {
+ simEvent.clientX = offset.left + x;
+ simEvent.clientY = offset.top + y;
+ $canvas.simulate( 'mouseup', simEvent );
+ };
+
+ var simulateMove = function (dX, dY, count) {
+ var i;
+ for ( i = 0; i < count; i++ ) {
+ simEvent.clientX += dX;
+ simEvent.clientY += dY;
+ $canvas.simulate( 'mousemove', simEvent );
+ }
+ };
+
+ var resetForAssessment = function () {
+ simEvent = {
+ clientX: offset.left,
+ clientY: offset.top
+ };
+ enterCount = 0;
+ exitCount = 0;
+ pressCount = 0;
+ releaseCount = 0;
+ moveCount = 0;
+ clickCount = 0;
+ dragCount = 0;
+ dragEndCount = 0;
+ insideElementPressed = false;
+ insideElementReleased = false;
+ quickClick = false;
+ speed = 0;
+ direction = 2 * Math.PI;
+ };
+
+ var assessGestureExpectations = function (expected) {
+ var pointersList = tracker.getActivePointersListByType('mouse');
+ if ('enterCount' in expected) {
+ equal( enterCount, expected.enterCount, expected.description + 'enterHandler event count matches expected (' + expected.enterCount + ')' );
+ }
+ if ('exitCount' in expected) {
+ equal( exitCount, expected.exitCount, expected.description + 'exitHandler event count matches expected (' + expected.exitCount + ')' );
+ }
+ if ('pressCount' in expected) {
+ equal( pressCount, expected.pressCount, expected.description + 'pressHandler event count matches expected (' + expected.pressCount + ')' );
+ }
+ if ('releaseCount' in expected) {
+ equal( releaseCount, expected.releaseCount, expected.description + 'releaseHandler event count matches expected (' + expected.releaseCount + ')' );
+ }
+ if ('moveCount' in expected) {
+ equal( moveCount, expected.moveCount, expected.description + 'moveHandler event count matches expected (' + expected.moveCount + ')' );
+ }
+ if ('clickCount' in expected) {
+ equal( clickCount, expected.clickCount, expected.description + 'clickHandler event count matches expected (' + expected.clickCount + ')' );
+ }
+ if ('dragCount' in expected) {
+ equal( dragCount, expected.dragCount, expected.description + 'dragHandler event count matches expected (' + expected.dragCount + ')' );
+ }
+ if ('dragEndCount' in expected) {
+ equal( dragEndCount, expected.dragEndCount, expected.description + 'dragEndHandler event count matches expected (' + expected.dragEndCount + ')' );
+ }
+ if ('insideElementPressed' in expected) {
+ equal( insideElementPressed, expected.insideElementPressed, expected.description + 'releaseHandler event.insideElementPressed matches expected (' + expected.insideElementPressed + ')' );
+ }
+ if ('insideElementReleased' in expected) {
+ equal( insideElementReleased, expected.insideElementReleased, expected.description + 'releaseHandler event.insideElementReleased matches expected (' + expected.insideElementReleased + ')' );
+ }
+ if ('contacts' in expected) {
+ equal( pointersList.contacts, expected.contacts, expected.description + 'Remaining pointer contact count matches expected (' + expected.contacts + ')' );
+ }
+ if ('trackedPointers' in expected) {
+ equal( pointersList.getLength(), expected.trackedPointers, expected.description + 'Remaining tracked pointer count matches expected (' + expected.trackedPointers + ')' );
+ }
+ if ('quickClick' in expected) {
+ equal( quickClick, expected.quickClick, expected.description + 'clickHandler event.quick matches expected (' + expected.quickClick + ')' );
+ }
+ if ('speed' in expected) {
+ Util.assessNumericValue(expected.speed, speed, 1.0, expected.description + 'Drag speed ');
+ }
+ if ('direction' in expected) {
+ Util.assessNumericValue(expected.direction, direction, 0.2, expected.description + 'Drag direction ');
+ }
+ };
var onOpen = function ( event ) {
+ var timeStart,
+ timeElapsed;
+
viewer.removeHandler( 'open', onOpen );
- viewer.addHandler( 'canvas-drag', onEventSourceDrag );
- viewer.addHandler( 'canvas-release', onEventSourceRelease );
- viewer.addHandler( 'canvas-click', onEventSourceClick );
+ hookViewerHandlers();
- mouseTracker = new OpenSeadragon.MouseTracker( {
- element: $canvas[0],
- userData: userData,
- clickTimeThreshold: OpenSeadragon.DEFAULT_SETTINGS.clickTimeThreshold,
- clickDistThreshold: OpenSeadragon.DEFAULT_SETTINGS.clickDistThreshold,
- focusHandler: onMouseTrackerFocus,
- blurHandler: onMouseTrackerBlur,
- enterHandler: onMouseTrackerEnter,
- pressHandler: onMouseTrackerPress,
- moveHandler: onMouseTrackerMove,
- dragHandler: onMouseTrackerDrag,
- releaseHandler: onMouseTrackerRelease,
- clickHandler: onMouseTrackerClick,
- exitHandler: onMouseTrackerExit
- } ).setTracking( true );
+ // enter-move-release (release in tracked element, press in unknown element)
+ // (Note we also test to see if the pointer is still being tracked by not simulating a leave event until after assessment)
+ resetForAssessment();
+ simulateEnter(0, 0);
+ simulateMove(1, 1, 10);
+ simulateMove(-1, -1, 10);
+ simulateUp(0, 0);
+ assessGestureExpectations({
+ description: 'enter-move-release (release in tracked element, press in unknown element): ',
+ enterCount: 1,
+ exitCount: 0,
+ pressCount: 0,
+ releaseCount: 1,
+ moveCount: 20,
+ clickCount: 0,
+ dragCount: 0,
+ dragEndCount: 0,
+ insideElementPressed: false,
+ insideElementReleased: true,
+ contacts: 0,
+ trackedPointers: 1
+ //quickClick: false
+ });
+ simulateLeave(-1, -1); // flush tracked pointer
- var event = {
- clientX:1,
- clientY:1
- };
+ // enter-move-exit (fly-over)
+ resetForAssessment();
+ simulateEnter(0, 0);
+ simulateMove(1, 1, 10);
+ simulateMove(-1, -1, 10);
+ simulateLeave(-1, -1);
+ assessGestureExpectations({
+ description: 'enter-move-exit (fly-over): ',
+ enterCount: 1,
+ exitCount: 1,
+ pressCount: 0,
+ releaseCount: 0,
+ moveCount: 20,
+ clickCount: 0,
+ dragCount: 0,
+ dragEndCount: 0,
+ //insideElementPressed: false,
+ //insideElementReleased: false,
+ contacts: 0,
+ trackedPointers: 0
+ //quickClick: false
+ });
- $canvas.simulate( 'focus', event );
- Util.simulateViewerClickWithDrag( {
- viewer: viewer,
- widthFactor: 0.25,
- heightFactor: 0.25,
- dragCount: dragCount,
- dragDx: 1,
- dragDy: 1
- } );
- $canvas.simulate( 'blur', event );
- };
+ // move-exit (fly-over, no enter event)
+ resetForAssessment();
+ simulateMove(1, 1, 10);
+ simulateMove(-1, -1, 10);
+ simulateLeave(-1, -1);
+ assessGestureExpectations({
+ description: 'move-exit (fly-over, no enter event): ',
+ enterCount: 0,
+ exitCount: 1,
+ pressCount: 0,
+ releaseCount: 0,
+ moveCount: 20,
+ clickCount: 0,
+ dragCount: 0,
+ dragEndCount: 0,
+ //insideElementPressed: false,
+ //insideElementReleased: false,
+ contacts: 0,
+ trackedPointers: 0
+ //quickClick: false
+ });
- var checkOriginalEventReceivedViewer = function ( event ) {
- eventsHandledViewer++;
- //TODO Provide a better check for the original event...simulate doesn't currently extend the object
- // with arbitrary user data.
- if ( event && event.originalEvent ) {
- originalEventsPassedViewer++;
- }
- };
+ // enter-press-release-exit
+ resetForAssessment();
+ simulateEnter(0, 0);
+ simulateDown(0, 0);
+ simulateUp(0, 0);
+ simulateLeave(-1, -1);
+ assessGestureExpectations({
+ description: 'enter-press-release-exit (click): ',
+ enterCount: 1,
+ exitCount: 1,
+ pressCount: 1,
+ releaseCount: 1,
+ moveCount: 0,
+ clickCount: 1,
+ dragCount: 0,
+ dragEndCount: 0,
+ insideElementPressed: true,
+ insideElementReleased: true,
+ contacts: 0,
+ trackedPointers: 0,
+ quickClick: true
+ });
- var onEventSourceDrag = function ( event ) {
- checkOriginalEventReceivedViewer( event );
- dragsHandledEventSource++;
- };
+ // enter-press-move-release-move-exit (drag, release in tracked element)
+ resetForAssessment();
+ simulateEnter(0, 0);
+ simulateDown(0, 0);
+ simulateMove(1, 1, 100);
+ simulateUp(10, 10);
+ simulateMove(-1, -1, 100);
+ simulateLeave(-1, -1);
+ assessGestureExpectations({
+ description: 'enter-press-move-release-move-exit (drag, release in tracked element): ',
+ enterCount: 1,
+ exitCount: 1,
+ pressCount: 1,
+ releaseCount: 1,
+ moveCount: 200,
+ clickCount: 1,
+ dragCount: 100,
+ dragEndCount: 1,
+ insideElementPressed: true,
+ insideElementReleased: true,
+ contacts: 0,
+ trackedPointers: 0,
+ quickClick: false
+ });
- var onEventSourceRelease = function ( event ) {
- checkOriginalEventReceivedViewer( event );
- releasesHandledEventSource++;
- };
+ // enter-press-move-exit-move-release (drag, release outside tracked element)
+ resetForAssessment();
+ simulateEnter(0, 0);
+ simulateDown(0, 0);
+ simulateMove(1, 1, 5);
+ simulateMove(-1, -1, 5);
+ simulateLeave(-1, -1);
+ simulateMove(-1, -1, 5);
+ simulateUp(-5, -5);
+ assessGestureExpectations({
+ description: 'enter-press-move-exit-move-release (drag, release outside tracked element): ',
+ enterCount: 1,
+ exitCount: 1,
+ pressCount: 1,
+ releaseCount: 1,
+ moveCount: 15,
+ clickCount: 0,
+ dragCount: 15,
+ dragEndCount: 1,
+ insideElementPressed: true,
+ insideElementReleased: false,
+ contacts: 0,
+ trackedPointers: 0,
+ quickClick: false
+ });
- var onEventSourceClick = function ( event ) {
- checkOriginalEventReceivedViewer( event );
- clicksHandledEventSource++;
- };
-
- var checkOriginalEventReceived = function ( event ) {
- eventsHandledMouseTracker++;
- if ( event && event.eventSource === mouseTracker ) {
- eventSourcePassedMouseTracker++;
- }
- //TODO Provide a better check for the original event...simulate doesn't currently extend the object
- // with arbitrary user data.
- if ( event && event.originalEvent ) {
- originalEventsPassedMouseTracker++;
- }
- };
-
- var onMouseTrackerFocus = function ( event ) {
- checkOriginalEventReceived( event );
- };
-
- var onMouseTrackerBlur = function ( event ) {
- checkOriginalEventReceived( event );
- };
-
- var onMouseTrackerEnter = function ( event ) {
- checkOriginalEventReceived( event );
- };
-
- var onMouseTrackerPress = function ( event ) {
- checkOriginalEventReceived( event );
- };
-
- var onMouseTrackerMove = function ( event ) {
- checkOriginalEventReceived( event );
- };
-
- var onMouseTrackerDrag = function ( event ) {
- checkOriginalEventReceived( event );
- };
-
- var onMouseTrackerRelease = function ( event ) {
- checkOriginalEventReceived( event );
- };
-
- var onMouseTrackerClick = function ( event ) {
- checkOriginalEventReceived( event );
- };
-
- var onMouseTrackerExit = function ( event ) {
- checkOriginalEventReceived( event );
-
- mouseTracker.destroy();
- viewer.removeHandler( 'canvas-drag', onEventSourceDrag );
- viewer.removeHandler( 'canvas-release', onEventSourceRelease );
- viewer.removeHandler( 'canvas-click', onEventSourceClick );
-
- equal( dragsHandledEventSource, dragCount, "'canvas-drag' event count matches 'mousemove' event count (" + dragCount + ")" );
- equal( releasesHandledEventSource, releasesExpected, "'canvas-release' event count matches expected (" + releasesExpected + ")" );
- equal( clicksHandledEventSource, releasesExpected, "'canvas-click' event count matches expected (" + releasesExpected + ")" );
- equal( originalEventsPassedViewer, eventsHandledViewer, "Original event received count matches expected (" + eventsHandledViewer + ")" );
-
- equal( eventSourcePassedMouseTracker, eventsHandledMouseTracker, "Event source received count matches expected (" + eventsHandledMouseTracker + ")" );
- equal( originalEventsPassedMouseTracker, eventsHandledMouseTracker, "Original event received count matches expected (" + eventsHandledMouseTracker + ")" );
- deepEqual( event.userData, originalUserData, 'MouseTracker userData was untouched' );
+ unhookViewerHandlers();
viewer.close();
start();
@@ -213,7 +409,7 @@
} );
// ----------
- asyncTest( 'MouseTracker preventDefaultAction', function () {
+ asyncTest( 'Viewer: preventDefaultAction', function () {
var $canvas = $( viewer.element ).find( '.openseadragon-canvas' ).not( '.navigator .openseadragon-canvas' ),
tracker = viewer.innerTracker,
origClickHandler,
@@ -281,7 +477,209 @@
} );
// ----------
- asyncTest( 'tile-drawing event', function () {
+ asyncTest( 'EventSource/MouseTracker/Viewer: event.originalEvent event.userData canvas-drag canvas-drag-end canvas-release canvas-click', function () {
+ var $canvas = $( viewer.element ).find( '.openseadragon-canvas' ).not( '.navigator .openseadragon-canvas' ),
+ mouseTracker = null,
+ userData = { item1: 'Test user data', item2: Math.random() },
+ originalUserData = { item1: userData.item1, item2: userData.item2 },
+ dragCount = 10,
+ dragsHandledEventSource = 0,
+ dragEndsHandledEventSource = 0,
+ releasesHandledEventSource = 0,
+ clicksHandledEventSource = 0,
+ eventsHandledMouseTracker = 0,
+ eventSourcePassedMouseTracker = 0,
+ originalEventsPassedMouseTracker = 0,
+ eventsHandledViewer = 0,
+ originalEventsPassedViewer = 0,
+ dragEndsExpected = 1,
+ releasesExpected = 1,
+ clicksExpected = 1;
+
+ var onOpen = function ( event ) {
+ viewer.removeHandler( 'open', onOpen );
+
+ viewer.addHandler( 'canvas-drag', onEventSourceDrag );
+ viewer.addHandler( 'canvas-drag-end', onEventSourceDragEnd );
+ viewer.addHandler( 'canvas-release', onEventSourceRelease );
+ viewer.addHandler( 'canvas-click', onEventSourceClick );
+
+ mouseTracker = new OpenSeadragon.MouseTracker( {
+ element: $canvas[0],
+ userData: userData,
+ clickTimeThreshold: OpenSeadragon.DEFAULT_SETTINGS.clickTimeThreshold,
+ clickDistThreshold: OpenSeadragon.DEFAULT_SETTINGS.clickDistThreshold,
+ focusHandler: onMouseTrackerFocus,
+ blurHandler: onMouseTrackerBlur,
+ enterHandler: onMouseTrackerEnter,
+ pressHandler: onMouseTrackerPress,
+ moveHandler: onMouseTrackerMove,
+ dragHandler: onMouseTrackerDrag,
+ dragEndHandler: onMouseTrackerDragEnd,
+ releaseHandler: onMouseTrackerRelease,
+ clickHandler: onMouseTrackerClick,
+ exitHandler: onMouseTrackerExit
+ } ).setTracking( true );
+
+ var event = {
+ clientX:1,
+ clientY:1
+ };
+
+ $canvas.simulate( 'focus', event );
+ Util.simulateViewerClickWithDrag( {
+ viewer: viewer,
+ widthFactor: 0.25,
+ heightFactor: 0.25,
+ dragCount: dragCount,
+ dragDx: 1,
+ dragDy: 1
+ } );
+ $canvas.simulate( 'blur', event );
+ };
+
+ var checkOriginalEventReceivedViewer = function ( event ) {
+ eventsHandledViewer++;
+ //TODO Provide a better check for the original event...simulate doesn't currently extend the object
+ // with arbitrary user data.
+ if ( event && event.originalEvent ) {
+ originalEventsPassedViewer++;
+ }
+ };
+
+ var onEventSourceDrag = function ( event ) {
+ checkOriginalEventReceivedViewer( event );
+ dragsHandledEventSource++;
+ };
+
+ var onEventSourceDragEnd = function ( event ) {
+ checkOriginalEventReceivedViewer( event );
+ dragEndsHandledEventSource++;
+ };
+
+ var onEventSourceRelease = function ( event ) {
+ checkOriginalEventReceivedViewer( event );
+ releasesHandledEventSource++;
+ };
+
+ var onEventSourceClick = function ( event ) {
+ checkOriginalEventReceivedViewer( event );
+ clicksHandledEventSource++;
+ };
+
+ var checkOriginalEventReceived = function ( event ) {
+ eventsHandledMouseTracker++;
+ if ( event && event.eventSource === mouseTracker ) {
+ eventSourcePassedMouseTracker++;
+ }
+ //TODO Provide a better check for the original event...simulate doesn't currently extend the object
+ // with arbitrary user data.
+ if ( event && event.originalEvent ) {
+ originalEventsPassedMouseTracker++;
+ }
+ };
+
+ var onMouseTrackerFocus = function ( event ) {
+ checkOriginalEventReceived( event );
+ };
+
+ var onMouseTrackerBlur = function ( event ) {
+ checkOriginalEventReceived( event );
+ };
+
+ var onMouseTrackerEnter = function ( event ) {
+ checkOriginalEventReceived( event );
+ };
+
+ var onMouseTrackerPress = function ( event ) {
+ checkOriginalEventReceived( event );
+ };
+
+ var onMouseTrackerMove = function ( event ) {
+ checkOriginalEventReceived( event );
+ };
+
+ var onMouseTrackerDrag = function ( event ) {
+ checkOriginalEventReceived( event );
+ };
+
+ var onMouseTrackerDragEnd = function ( event ) {
+ checkOriginalEventReceived( event );
+ };
+
+ var onMouseTrackerRelease = function ( event ) {
+ checkOriginalEventReceived( event );
+ };
+
+ var onMouseTrackerClick = function ( event ) {
+ checkOriginalEventReceived( event );
+ };
+
+ var onMouseTrackerExit = function ( event ) {
+ checkOriginalEventReceived( event );
+
+ mouseTracker.destroy();
+ viewer.removeHandler( 'canvas-drag', onEventSourceDrag );
+ viewer.removeHandler( 'canvas-release', onEventSourceRelease );
+ viewer.removeHandler( 'canvas-click', onEventSourceClick );
+
+ equal( dragsHandledEventSource, dragCount, "'canvas-drag' event count matches 'mousemove' event count (" + dragCount + ")" );
+ equal( dragEndsHandledEventSource, dragEndsExpected, "'canvas-drag-end' event count matches expected (" + dragEndsExpected + ")" );
+ equal( releasesHandledEventSource, releasesExpected, "'canvas-release' event count matches expected (" + releasesExpected + ")" );
+ equal( clicksHandledEventSource, releasesExpected, "'canvas-click' event count matches expected (" + releasesExpected + ")" );
+ equal( originalEventsPassedViewer, eventsHandledViewer, "Original event received count matches expected (" + eventsHandledViewer + ")" );
+
+ equal( eventSourcePassedMouseTracker, eventsHandledMouseTracker, "Event source received count matches expected (" + eventsHandledMouseTracker + ")" );
+ equal( originalEventsPassedMouseTracker, eventsHandledMouseTracker, "Original event received count matches expected (" + eventsHandledMouseTracker + ")" );
+ deepEqual( event.userData, originalUserData, 'MouseTracker userData was untouched' );
+
+ viewer.close();
+ start();
+ };
+
+ viewer.addHandler( 'open', onOpen );
+ viewer.open( '/test/data/testpattern.dzi' );
+ } );
+
+ // ----------
+ asyncTest( 'EventSource: addHandler without userData', function () {
+ var openHandler = function ( event ) {
+ viewer.removeHandler( 'open', openHandler );
+ ok( event, 'Event handler received event data' );
+ if ( event ) {
+ strictEqual( event.eventSource, viewer, 'eventSource sent, eventSource is viewer' );
+ strictEqual( event.userData, null, 'User data defaulted to null' );
+ }
+ viewer.close();
+ start();
+ };
+
+ viewer.addHandler( 'open', openHandler );
+ viewer.open( '/test/data/testpattern.dzi' );
+ } );
+
+ // ----------
+ asyncTest( 'EventSource: addHandler with userData', function () {
+ var userData = { item1: 'Test user data', item2: Math.random() },
+ originalUserData = { item1: userData.item1, item2: userData.item2 };
+
+ var openHandler = function ( event ) {
+ viewer.removeHandler( 'open', openHandler );
+ ok( event, 'Event handler received event data' );
+ ok( event && event.userData, 'Event handler received user data' );
+ if ( event && event.userData ) {
+ deepEqual( event.userData, originalUserData, 'User data was untouched' );
+ }
+ viewer.close();
+ start();
+ };
+
+ viewer.addHandler( 'open', openHandler, userData );
+ viewer.open( '/test/data/testpattern.dzi' );
+ } );
+
+ // ----------
+ asyncTest( 'Viewer: tile-drawing event', function () {
var tileDrawing = function ( event ) {
viewer.removeHandler( 'tile-drawing', tileDrawing );
ok( event, 'Event handler should be invoked' );
diff --git a/test/legacy.mouse.shim.js b/test/legacy.mouse.shim.js
new file mode 100644
index 00000000..9643d610
--- /dev/null
+++ b/test/legacy.mouse.shim.js
@@ -0,0 +1,42 @@
+(function($, undefined) {
+
+ /**
+ * Plugin to force OpenSeadragon to use the legacy mouse pointer event model
+ */
+
+ $.MouseTracker.subscribeEvents = [ "click", "keypress", "focus", "blur", $.MouseTracker.wheelEventName ];
+
+ if( $.MouseTracker.wheelEventName == "DOMMouseScroll" ) {
+ // Older Firefox
+ $.MouseTracker.subscribeEvents.push( "MozMousePixelScroll" );
+ }
+
+ $.MouseTracker.subscribeEvents.push( "mousedown", "mouseup", "mousemove" );
+ if ( 'onmouseenter' in window ) {
+ $.MouseTracker.subscribeEvents.push( "mouseenter", "mouseleave" );
+ $.MouseTracker.haveMouseEnter = true;
+ } else {
+ $.MouseTracker.subscribeEvents.push( "mouseover", "mouseout" );
+ $.MouseTracker.haveMouseEnter = false;
+ }
+ if ( 'ontouchstart' in window ) {
+ // iOS, Android, and other W3c Touch Event implementations (see http://www.w3.org/TR/2011/WD-touch-events-20110505)
+ $.MouseTracker.subscribeEvents.push( "touchstart", "touchend", "touchmove", "touchcancel" );
+ if ( 'ontouchenter' in window ) {
+ $.MouseTracker.subscribeEvents.push( "touchenter", "touchleave" );
+ $.MouseTracker.haveTouchEnter = true;
+ } else {
+ $.MouseTracker.haveTouchEnter = false;
+ }
+ } else {
+ $.MouseTracker.haveTouchEnter = false;
+ }
+ if ( 'ongesturestart' in window ) {
+ // iOS (see https://developer.apple.com/library/safari/documentation/UserExperience/Reference/GestureEventClassReference/GestureEvent/GestureEvent.html)
+ // Subscribe to these to prevent default gesture handling
+ $.MouseTracker.subscribeEvents.push( "gesturestart", "gesturechange" );
+ }
+ $.MouseTracker.mousePointerId = "legacy-mouse";
+ $.MouseTracker.maxTouchPoints = 10;
+
+}(OpenSeadragon));
diff --git a/test/navigator.js b/test/navigator.js
index 95631a91..c59d528a 100644
--- a/test/navigator.js
+++ b/test/navigator.js
@@ -189,7 +189,7 @@ QUnit.config.autostart = false;
clientY:offset.top + locationY
};
$canvas
- .simulate('mouseover', event)
+ .simulate(OpenSeadragon.MouseTracker.haveMouseEnter ? 'mouseenter' : 'mouseover', event)
.simulate('mousedown', event)
.simulate('mouseup', event);
};
diff --git a/test/test.html b/test/test.html
index 21802fa1..64062197 100644
--- a/test/test.html
+++ b/test/test.html
@@ -1,4 +1,4 @@
-
+
@@ -15,6 +15,7 @@
+