/* * OpenSeadragon - MouseTracker * * Copyright (C) 2009 CodePlex Foundation * Copyright (C) 2010-2022 OpenSeadragon contributors * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * - Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * - Neither the name of CodePlex Foundation nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ (function ( $ ) { // All MouseTracker instances var MOUSETRACKERS = []; // dictionary from hash to private properties var THIS = {}; /** * @class MouseTracker * @classdesc Provides simplified handling of common pointer device (mouse, touch, pen, etc.) gestures * and keyboard events on a specified element. * @memberof OpenSeadragon * @param {Object} options * Allows configurable properties to be entirely specified by passing * an options object to the constructor. The constructor also supports * 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 pointer/key * events will be monitored. * @param {Boolean} [options.startDisabled=false] * If true, event tracking on the element will not start until * {@link OpenSeadragon.MouseTracker.setTracking|setTracking} is called. * @param {Number} options.clickTimeThreshold * The number of milliseconds within which a pointer down-up event combination * will be treated as a click gesture. * @param {Number} options.clickDistThreshold * The maximum distance allowed between a pointer down event and a pointer up event * to be treated as a click gesture. * @param {Number} options.dblClickTimeThreshold * The number of milliseconds within which two pointer down-up event combinations * will be treated as a double-click gesture. * @param {Number} options.dblClickDistThreshold * The maximum distance allowed between two pointer click events * to be treated as a click gesture. * @param {Number} [options.stopDelay=50] * The number of milliseconds without pointer move before the stop * event is fired. * @param {OpenSeadragon.EventHandler} [options.preProcessEventHandler=null] * An optional handler for controlling DOM event propagation and processing. * @param {OpenSeadragon.EventHandler} [options.contextMenuHandler=null] * An optional handler for contextmenu. * @param {OpenSeadragon.EventHandler} [options.enterHandler=null] * An optional handler for pointer enter. * @param {OpenSeadragon.EventHandler} [options.leaveHandler=null] * An optional handler for pointer leave. * @param {OpenSeadragon.EventHandler} [options.exitHandler=null] * An optional handler for pointer leave. Deprecated. Use leaveHandler instead. * @param {OpenSeadragon.EventHandler} [options.overHandler=null] * An optional handler for pointer over. * @param {OpenSeadragon.EventHandler} [options.outHandler=null] * An optional handler for pointer out. * @param {OpenSeadragon.EventHandler} [options.pressHandler=null] * An optional handler for pointer press. * @param {OpenSeadragon.EventHandler} [options.nonPrimaryPressHandler=null] * An optional handler for pointer non-primary button press. * @param {OpenSeadragon.EventHandler} [options.releaseHandler=null] * An optional handler for pointer release. * @param {OpenSeadragon.EventHandler} [options.nonPrimaryReleaseHandler=null] * An optional handler for pointer non-primary button release. * @param {OpenSeadragon.EventHandler} [options.moveHandler=null] * An optional handler for pointer move. * @param {OpenSeadragon.EventHandler} [options.scrollHandler=null] * An optional handler for mouse wheel scroll. * @param {OpenSeadragon.EventHandler} [options.clickHandler=null] * An optional handler for pointer click. * @param {OpenSeadragon.EventHandler} [options.dblClickHandler=null] * An optional handler for pointer double-click. * @param {OpenSeadragon.EventHandler} [options.dragHandler=null] * 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.keyDownHandler=null] * An optional handler for keydown. * @param {OpenSeadragon.EventHandler} [options.keyUpHandler=null] * An optional handler for keyup. * @param {OpenSeadragon.EventHandler} [options.keyHandler=null] * An optional handler for keypress. * @param {OpenSeadragon.EventHandler} [options.focusHandler=null] * An optional handler for focus. * @param {OpenSeadragon.EventHandler} [options.blurHandler=null] * An optional handler for blur. * @param {Object} [options.userData=null] * Arbitrary object to be passed unchanged to any attached handler methods. */ $.MouseTracker = function ( options ) { MOUSETRACKERS.push( this ); var args = arguments; if ( !$.isPlainObject( options ) ) { options = { element: args[ 0 ], clickTimeThreshold: args[ 1 ], clickDistThreshold: args[ 2 ] }; } this.hash = Math.random(); // An unique hash for this tracker. /** * 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 a pointer down-up event combination * will be treated as a click gesture. * @member {Number} clickTimeThreshold * @memberof OpenSeadragon.MouseTracker# */ this.clickTimeThreshold = options.clickTimeThreshold || $.DEFAULT_SETTINGS.clickTimeThreshold; /** * The maximum distance allowed between a pointer down event and a pointer up event * to be treated as a click gesture. * @member {Number} clickDistThreshold * @memberof OpenSeadragon.MouseTracker# */ this.clickDistThreshold = options.clickDistThreshold || $.DEFAULT_SETTINGS.clickDistThreshold; /** * The number of milliseconds within which two pointer down-up event combinations * will be treated as a double-click gesture. * @member {Number} dblClickTimeThreshold * @memberof OpenSeadragon.MouseTracker# */ this.dblClickTimeThreshold = options.dblClickTimeThreshold || $.DEFAULT_SETTINGS.dblClickTimeThreshold; /** * The maximum distance allowed between two pointer click events * to be treated as a double-click gesture. * @member {Number} dblClickDistThreshold * @memberof OpenSeadragon.MouseTracker# */ this.dblClickDistThreshold = options.dblClickDistThreshold || $.DEFAULT_SETTINGS.dblClickDistThreshold; /*eslint-disable no-multi-spaces*/ this.userData = options.userData || null; this.stopDelay = options.stopDelay || 50; this.preProcessEventHandler = options.preProcessEventHandler || null; this.contextMenuHandler = options.contextMenuHandler || null; this.enterHandler = options.enterHandler || null; this.leaveHandler = options.leaveHandler || null; this.exitHandler = options.exitHandler || null; // Deprecated v2.5.0 this.overHandler = options.overHandler || null; this.outHandler = options.outHandler || null; this.pressHandler = options.pressHandler || null; this.nonPrimaryPressHandler = options.nonPrimaryPressHandler || null; this.releaseHandler = options.releaseHandler || null; this.nonPrimaryReleaseHandler = options.nonPrimaryReleaseHandler || null; this.moveHandler = options.moveHandler || null; this.scrollHandler = options.scrollHandler || null; this.clickHandler = options.clickHandler || null; this.dblClickHandler = options.dblClickHandler || null; this.dragHandler = options.dragHandler || null; this.dragEndHandler = options.dragEndHandler || null; this.pinchHandler = options.pinchHandler || null; this.stopHandler = options.stopHandler || null; this.keyDownHandler = options.keyDownHandler || null; this.keyUpHandler = options.keyUpHandler || null; this.keyHandler = options.keyHandler || null; this.focusHandler = options.focusHandler || null; this.blurHandler = options.blurHandler || null; /*eslint-enable no-multi-spaces*/ //Store private properties in a scope sealed hash map var _this = this; /** * @private * @property {Boolean} tracking * Are we currently tracking pointer events for this element. */ THIS[ this.hash ] = { click: function ( event ) { onClick( _this, event ); }, dblclick: function ( event ) { onDblClick( _this, event ); }, keydown: function ( event ) { onKeyDown( _this, event ); }, keyup: function ( event ) { onKeyUp( _this, event ); }, keypress: function ( event ) { onKeyPress( _this, event ); }, focus: function ( event ) { onFocus( _this, event ); }, blur: function ( event ) { onBlur( _this, event ); }, contextmenu: function ( event ) { onContextMenu( _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 ); }, losecapture: function ( event ) { onLoseCapture( _this, event ); }, mouseenter: function ( event ) { onPointerEnter( _this, event ); }, mouseleave: function ( event ) { onPointerLeave( _this, event ); }, mouseover: function ( event ) { onPointerOver( _this, event ); }, mouseout: function ( event ) { onPointerOut( _this, event ); }, mousedown: function ( event ) { onPointerDown( _this, event ); }, mouseup: function ( event ) { onPointerUp( _this, event ); }, mousemove: function ( event ) { onPointerMove( _this, event ); }, touchstart: function ( event ) { onTouchStart( _this, event ); }, touchend: function ( event ) { onTouchEnd( _this, event ); }, touchmove: function ( event ) { onTouchMove( _this, event ); }, touchcancel: function ( event ) { onTouchCancel( _this, event ); }, gesturestart: function ( event ) { onGestureStart( _this, event ); }, // Safari/Safari iOS gesturechange: function ( event ) { onGestureChange( _this, event ); }, // Safari/Safari iOS gotpointercapture: function ( event ) { onGotPointerCapture( _this, event ); }, lostpointercapture: function ( event ) { onLostPointerCapture( _this, event ); }, pointerenter: function ( event ) { onPointerEnter( _this, event ); }, pointerleave: function ( event ) { onPointerLeave( _this, event ); }, pointerover: function ( event ) { onPointerOver( _this, event ); }, pointerout: function ( event ) { onPointerOut( _this, event ); }, pointerdown: function ( event ) { onPointerDown( _this, event ); }, pointerup: function ( event ) { onPointerUp( _this, event ); }, pointermove: function ( event ) { onPointerMove( _this, event ); }, pointercancel: function ( event ) { onPointerCancel( _this, event ); }, pointerupcaptured: function ( event ) { onPointerUpCaptured( _this, event ); }, pointermovecaptured: function ( event ) { onPointerMoveCaptured( _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: [], // Tracking for double-click gesture lastClickPos: null, dblClickTimeOut: null, // Tracking for pinch gesture pinchGPoints: [], lastPinchDist: 0, currentPinchDist: 0, lastPinchCenter: null, currentPinchCenter: null, // Tracking for drag sentDragEvent: false }; this.hasGestureHandlers = !!( this.pressHandler || this.nonPrimaryPressHandler || this.releaseHandler || this.nonPrimaryReleaseHandler || this.clickHandler || this.dblClickHandler || this.dragHandler || this.dragEndHandler || this.pinchHandler ); this.hasScrollHandler = !!this.scrollHandler; if ( $.MouseTracker.havePointerEvents ) { $.setElementPointerEvents( this.element, 'auto' ); } if (this.exitHandler) { $.console.error("MouseTracker.exitHandler is deprecated. Use MouseTracker.leaveHandler instead."); } if ( !options.startDisabled ) { this.setTracking( true ); } }; /** @lends OpenSeadragon.MouseTracker.prototype */ $.MouseTracker.prototype = { /** * Clean up any events or objects created by the tracker. * @function */ destroy: function () { var i; stopTracking( this ); this.element = null; for ( i = 0; i < MOUSETRACKERS.length; i++ ) { if ( MOUSETRACKERS[ i ] === this ) { MOUSETRACKERS.splice( i, 1 ); break; } } THIS[ this.hash ] = null; delete THIS[ this.hash ]; }, /** * Are we currently tracking events on this element. * @deprecated Just use this.tracking * @function * @returns {Boolean} Are we currently tracking events on this element. */ isTracking: function () { return THIS[ this.hash ].tracking; }, /** * Enable or disable whether or not we are tracking events on this element. * @function * @param {Boolean} track True to start tracking, false to stop tracking. * @returns {OpenSeadragon.MouseTracker} Chainable. */ setTracking: function ( track ) { if ( track ) { startTracking( this ); } else { stopTracking( this ); } //chain 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; }, /** * Returns the total number of pointers currently active on the tracked element. * @function * @returns {Number} */ getActivePointerCount: function () { var delegate = THIS[ this.hash ], i, len = delegate.activePointersLists.length, count = 0; for ( i = 0; i < len; i++ ) { count += delegate.activePointersLists[ i ].getLength(); } return count; }, /** * Implement or assign implementation to these handlers during or after * calling the constructor. * @function * @param {OpenSeadragon.MouseTracker.EventProcessInfo} eventInfo */ preProcessEventHandler: 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 {OpenSeadragon.Point} event.position * The position of the event relative to the tracked element. * @param {Object} event.originalEvent * The original event object. * @param {Boolean} event.preventDefault * Set to true to prevent the default user-agent's handling of the contextmenu event. * @param {Object} event.userData * Arbitrary user-defined object. */ contextMenuHandler: 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 {Number} event.pointers * Number of pointers (all types) active in the tracked element. * @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. Deprecated. Use buttons instead. * @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 {Object} event.userData * Arbitrary user-defined object. */ enterHandler: function () { }, /** * Implement or assign implementation to these handlers during or after * calling the constructor. * @function * @since v2.5.0 * @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 {Number} event.pointers * Number of pointers (all types) active in the tracked element. * @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. Deprecated. Use buttons instead. * @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 {Object} event.userData * Arbitrary user-defined object. */ leaveHandler: function () { }, /** * Implement or assign implementation to these handlers during or after * calling the constructor. * @function * @deprecated v2.5.0 Use leaveHandler instead * @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 {Number} event.pointers * Number of pointers (all types) active in the tracked element. * @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. Deprecated. Use buttons instead. * @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 {Object} event.userData * Arbitrary user-defined object. */ exitHandler: function () { }, /** * Implement or assign implementation to these handlers during or after * calling the constructor. * @function * @since v2.5.0 * @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 {Number} event.pointers * Number of pointers (all types) active in the tracked element. * @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. Deprecated. Use buttons instead. * @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 {Object} event.userData * Arbitrary user-defined object. */ overHandler: function () { }, /** * Implement or assign implementation to these handlers during or after * calling the constructor. * @function * @since v2.5.0 * @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 {Number} event.pointers * Number of pointers (all types) active in the tracked element. * @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. Deprecated. Use buttons instead. * @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 {Object} event.userData * Arbitrary user-defined object. */ outHandler: 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 {Object} event.userData * Arbitrary user-defined object. */ pressHandler: 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.button * Button which caused the event. * -1: none, 0: primary/left, 1: aux/middle, 2: secondary/right, 3: X1/back, 4: X2/forward, 5: pen eraser. * @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 {Object} event.userData * Arbitrary user-defined object. */ nonPrimaryPressHandler: 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.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 inside the tracked element when the button was released. * @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 {Object} event.userData * Arbitrary user-defined object. */ releaseHandler: 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.button * Button which caused the event. * -1: none, 0: primary/left, 1: aux/middle, 2: secondary/right, 3: X1/back, 4: X2/forward, 5: pen eraser. * @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 {Object} event.userData * Arbitrary user-defined object. */ nonPrimaryReleaseHandler: 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 {Object} event.userData * Arbitrary user-defined object. */ moveHandler: 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.scroll * The scroll delta for the event. * @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. 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.preventDefault * Set to true to prevent the default user-agent's handling of the wheel event. * @param {Object} event.userData * Arbitrary user-defined object. */ scrollHandler: 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 {Boolean} event.quick * True only if the clickDistThreshold and clickTimeThreshold are both passed. Useful for ignoring drag 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. Deprecated. Use pointerType and/or originalEvent instead. * @param {Object} event.originalEvent * The original event object. * @param {Element} event.originalTarget * The DOM element clicked on. * @param {Object} event.userData * Arbitrary user-defined object. */ clickHandler: 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 {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. Deprecated. Use pointerType and/or originalEvent instead. * @param {Object} event.originalEvent * The original event object. * @param {Object} event.userData * Arbitrary user-defined object. */ dblClickHandler: 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 {OpenSeadragon.Point} event.delta * 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. Deprecated. Use pointerType and/or originalEvent instead. * @param {Object} event.originalEvent * The original event object. * @param {Object} event.userData * Arbitrary user-defined object. */ dragHandler: 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.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. Deprecated. Use pointerType and/or originalEvent instead. * @param {Object} event.originalEvent * The original event object. * @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 {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 {Object} event.userData * Arbitrary user-defined object. */ stopHandler: 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 {Number} event.keyCode * The key code that was pressed. * @param {Boolean} event.ctrl * True if the ctrl key was pressed during this event. * @param {Boolean} event.shift * True if the shift key was pressed during this event. * @param {Boolean} event.alt * True if the alt key was pressed during this event. * @param {Boolean} event.meta * True if the meta key was pressed during this event. * @param {Object} event.originalEvent * The original event object. * @param {Boolean} event.preventDefault * Set to true to prevent the default user-agent's handling of the keydown event. * @param {Object} event.userData * Arbitrary user-defined object. */ keyDownHandler: 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 {Number} event.keyCode * The key code that was pressed. * @param {Boolean} event.ctrl * True if the ctrl key was pressed during this event. * @param {Boolean} event.shift * True if the shift key was pressed during this event. * @param {Boolean} event.alt * True if the alt key was pressed during this event. * @param {Boolean} event.meta * True if the meta key was pressed during this event. * @param {Object} event.originalEvent * The original event object. * @param {Boolean} event.preventDefault * Set to true to prevent the default user-agent's handling of the keyup event. * @param {Object} event.userData * Arbitrary user-defined object. */ keyUpHandler: 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 {Number} event.keyCode * The key code that was pressed. * @param {Boolean} event.ctrl * True if the ctrl key was pressed during this event. * @param {Boolean} event.shift * True if the shift key was pressed during this event. * @param {Boolean} event.alt * True if the alt key was pressed during this event. * @param {Boolean} event.meta * True if the meta key was pressed during this event. * @param {Object} event.originalEvent * The original event object. * @param {Boolean} event.preventDefault * Set to true to prevent the default user-agent's handling of the keypress event. * @param {Object} event.userData * Arbitrary user-defined object. */ keyHandler: 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 {Object} event.originalEvent * The original event object. * @param {Object} event.userData * Arbitrary user-defined object. */ focusHandler: 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 {Object} event.originalEvent * The original event object. * @param {Object} event.userData * Arbitrary user-defined object. */ blurHandler: function () { } }; // https://github.com/openseadragon/openseadragon/pull/790 /** * True if inside an iframe, otherwise false. * @member {Boolean} isInIframe * @private * @inner */ var isInIframe = (function() { try { return window.self !== window.top; } catch (e) { return true; } })(); // https://github.com/openseadragon/openseadragon/pull/790 /** * @function * @private * @inner * @returns {Boolean} True if the target supports DOM Level 2 event subscription methods, otherwise false. */ function canAccessEvents (target) { try { return target.addEventListener && target.removeEventListener; } catch (e) { return false; } } /** * 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 /////////////////////////////////////////////////////////////////////////////// $.MouseTracker.captureElement = document; /** * 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' document.onmousewheel !== undefined ? 'mousewheel' : // Webkit and IE support at least 'mousewheel' 'DOMMouseScroll'; // Assume old Firefox /** * Detect browser pointer device event model(s) and build appropriate list of events to subscribe to. */ $.MouseTracker.subscribeEvents = [ "click", "dblclick", "keydown", "keyup", "keypress", "focus", "blur", "contextmenu", $.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.havePointerEvents = true; $.MouseTracker.subscribeEvents.push( "pointerenter", "pointerleave", "pointerover", "pointerout", "pointerdown", "pointerup", "pointermove", "pointercancel" ); // Pointer events capture support $.MouseTracker.havePointerCapture = (function () { var divElement = document.createElement( 'div' ); return $.isFunction( divElement.setPointerCapture ) && $.isFunction( divElement.releasePointerCapture ); }()); if ( $.MouseTracker.havePointerCapture ) { $.MouseTracker.subscribeEvents.push( "gotpointercapture", "lostpointercapture" ); } } else { // Legacy W3C mouse events $.MouseTracker.havePointerEvents = false; $.MouseTracker.subscribeEvents.push( "mouseenter", "mouseleave", "mouseover", "mouseout", "mousedown", "mouseup", "mousemove" ); $.MouseTracker.mousePointerId = "legacy-mouse"; // Legacy mouse events capture support (IE/Firefox only?) $.MouseTracker.havePointerCapture = (function () { var divElement = document.createElement( 'div' ); return $.isFunction( divElement.setCapture ) && $.isFunction( divElement.releaseCapture ); }()); if ( $.MouseTracker.havePointerCapture ) { $.MouseTracker.subscribeEvents.push( "losecapture" ); } // Legacy touch events if ( 'ontouchstart' in window ) { // iOS, Android, and other W3c Touch Event implementations // (see http://www.w3.org/TR/touch-events/) // (see https://developer.apple.com/library/ios/documentation/AppleApplications/Reference/SafariWebContent/HandlingEvents/HandlingEvents.html) // (see https://developer.apple.com/library/safari/documentation/AppleApplications/Reference/SafariWebContent/HandlingEvents/HandlingEvents.html) $.MouseTracker.subscribeEvents.push( "touchstart", "touchend", "touchmove", "touchcancel" ); } if ( 'ongesturestart' in window ) { // iOS (see https://developer.apple.com/library/ios/documentation/AppleApplications/Reference/SafariWebContent/HandlingEvents/HandlingEvents.html) // Subscribe to these to prevent default gesture handling $.MouseTracker.subscribeEvents.push( "gesturestart", "gesturechange" ); } } /////////////////////////////////////////////////////////////////////////////// // Classes and typedefs /////////////////////////////////////////////////////////////////////////////// /** * Used for the processing/disposition of DOM events (propagation, default handling, capture, etc.) * * @typedef {Object} EventProcessInfo * @memberof OpenSeadragon.MouseTracker * @since v2.5.0 * * @property {OpenSeadragon.MouseTracker} eventSource * A reference to the tracker instance. * @property {Object} originalEvent * The original DOM event object. * @property {Number} eventPhase * 0 == NONE, 1 == CAPTURING_PHASE, 2 == AT_TARGET, 3 == BUBBLING_PHASE. * @property {String} eventType * "keydown", "keyup", "keypress", "focus", "blur", "contextmenu", "gotpointercapture", "lostpointercapture", "pointerenter", "pointerleave", "pointerover", "pointerout", "pointerdown", "pointerup", "pointermove", "pointercancel", "wheel", "click", "dblclick". * @property {String} pointerType * "mouse", "touch", "pen", etc. * @property {Boolean} isEmulated * True if this is an emulated event. If true, originalEvent is either the event that caused * the emulated event, a synthetic event object created with values from the actual DOM event, * or null if no DOM event applies. Emulated events can occur on eventType "wheel" on legacy mouse-scroll * event emitting user agents. * @property {Boolean} isStoppable * True if propagation of the event (e.g. bubbling) can be stopped with stopPropagation/stopImmediatePropagation. * @property {Boolean} isCancelable * True if the event's default handling by the browser can be prevented with preventDefault. * @property {Boolean} defaultPrevented * True if the event's default handling has already been prevented by a descendent element. * @property {Boolean} preventDefault * Set to true to prevent the event's default handling by the browser. * @property {Boolean} preventGesture * Set to true to prevent this MouseTracker from generating a gesture from the event. * Valid on eventType "pointerdown". * @property {Boolean} stopPropagation * Set to true prevent the event from propagating to ancestor/descendent elements on capture/bubble phase. * @property {Boolean} shouldCapture * (Internal Use) Set to true if the pointer should be captured (events (re)targeted to tracker element). * @property {Boolean} shouldReleaseCapture * (Internal Use) Set to true if the captured pointer should be released. * @property {Object} userData * Arbitrary user-defined object. */ /** * 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; /** * Current number of clicks for the device. Used for multiple click gesture tracking. * @member {Number} clicks * @memberof OpenSeadragon.MouseTracker.GesturePointList# */ this.clicks = 0; /** * Current number of captured pointers for the device. * @member {Number} captureCount * @memberof OpenSeadragon.MouseTracker.GesturePointList# */ this.captureCount = 0; }; /** @lends OpenSeadragon.MouseTracker.GesturePointList.prototype */ $.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; }, /** * Increment this pointer list's contact count. * It will evaluate whether this pointer type is allowed to have multiple contacts. * @function */ addContact: function() { ++this.contacts; if (this.contacts > 1 && (this.type === "mouse" || this.type === "pen")) { $.console.warn('GesturePointList.addContact() Implausible contacts value'); this.contacts = 1; } }, /** * Decrement this pointer list's contact count. * It will make sure the count does not go below 0. * @function */ removeContact: function() { --this.contacts; if (this.contacts < 0) { this.contacts = 0; } } }; /////////////////////////////////////////////////////////////////////////////// // Utility functions /////////////////////////////////////////////////////////////////////////////// /** * Removes all tracked pointers. * @private * @inner */ function clearTrackedPointers( tracker ) { var delegate = THIS[ tracker.hash ], i, j, pointsList, gPoints, gPointsToRemove, pointerListCount = delegate.activePointersLists.length; for ( i = 0; i < pointerListCount; i++ ) { pointsList = delegate.activePointersLists[ i ]; if ( pointsList.getLength() > 0 ) { // Make an array containing references to the gPoints in the pointer list // (because calls to stopTrackingPointer() are going to modify the pointer list) gPointsToRemove = []; gPoints = pointsList.asArray(); for ( j = 0; j < gPoints.length; j++ ) { gPointsToRemove.push( gPoints[ j ] ); } // Release and remove all gPoints from the pointer list for ( j = 0; j < gPointsToRemove.length; j++ ) { stopTrackingPointer( tracker, pointsList, gPointsToRemove[ j ] ); } } } for ( i = 0; i < pointerListCount; i++ ) { delegate.activePointersLists.pop(); } delegate.sentDragEvent = false; } /** * Starts tracking pointer events on the tracked element. * @private * @inner */ function startTracking( tracker ) { var delegate = THIS[ tracker.hash ], event, i; if ( !delegate.tracking ) { for ( i = 0; i < $.MouseTracker.subscribeEvents.length; i++ ) { event = $.MouseTracker.subscribeEvents[ i ]; $.addEvent( tracker.element, event, delegate[ event ], event === $.MouseTracker.wheelEventName ? { passive: false, capture: false } : false ); } clearTrackedPointers( tracker ); delegate.tracking = true; } } /** * Stops tracking pointer events on the tracked element. * @private * @inner */ function stopTracking( tracker ) { var delegate = THIS[ tracker.hash ], event, i; if ( delegate.tracking ) { for ( i = 0; i < $.MouseTracker.subscribeEvents.length; i++ ) { event = $.MouseTracker.subscribeEvents[ i ]; $.removeEvent( tracker.element, event, delegate[ event ], false ); } clearTrackedPointers( tracker ); delegate.tracking = false; } } /** * @private * @inner */ function getCaptureEventParams( tracker, pointerType ) { var delegate = THIS[ tracker.hash ]; if ( pointerType === 'pointerevent' ) { return { upName: 'pointerup', upHandler: delegate.pointerupcaptured, moveName: 'pointermove', moveHandler: delegate.pointermovecaptured }; } else if ( pointerType === 'mouse' ) { return { upName: 'pointerup', upHandler: delegate.pointerupcaptured, moveName: 'pointermove', moveHandler: delegate.pointermovecaptured }; } else if ( pointerType === 'touch' ) { return { upName: 'touchend', upHandler: delegate.touchendcaptured, moveName: 'touchmove', moveHandler: delegate.touchmovecaptured }; } else { throw new Error( "MouseTracker.getCaptureEventParams: Unknown pointer type." ); } } /** * Begin capturing pointer events to the tracked element. * @private * @inner */ function capturePointer( tracker, gPoint ) { var eventParams; if ( $.MouseTracker.havePointerCapture ) { if ( $.MouseTracker.havePointerEvents ) { // Can throw NotFoundError (InvalidPointerId Firefox < 82) // (should never happen so we'll log a warning) try { tracker.element.setPointerCapture( gPoint.id ); //$.console.log('element.setPointerCapture() called'); } catch ( e ) { $.console.warn('setPointerCapture() called on invalid pointer ID'); return; } } else { tracker.element.setCapture( true ); //$.console.log('element.setCapture() called'); } } else { // Emulate mouse capture by hanging listeners on the document object. // (Note we listen on the capture phase so the captured handlers will get called first) // eslint-disable-next-line no-use-before-define //$.console.log('Emulated mouse capture set'); eventParams = getCaptureEventParams( tracker, $.MouseTracker.havePointerEvents ? 'pointerevent' : gPoint.type ); // https://github.com/openseadragon/openseadragon/pull/790 if (isInIframe && canAccessEvents(window.top)) { $.addEvent( window.top, eventParams.upName, eventParams.upHandler, true ); } $.addEvent( $.MouseTracker.captureElement, eventParams.upName, eventParams.upHandler, true ); $.addEvent( $.MouseTracker.captureElement, eventParams.moveName, eventParams.moveHandler, true ); } updatePointerCaptured( tracker, gPoint, true ); } /** * Stop capturing pointer events to the tracked element. * @private * @inner */ function releasePointer( tracker, gPoint ) { var eventParams; var pointsList; var cachedGPoint; if ( $.MouseTracker.havePointerCapture ) { if ( $.MouseTracker.havePointerEvents ) { pointsList = tracker.getActivePointersListByType( gPoint.type ); cachedGPoint = pointsList.getById( gPoint.id ); if ( !cachedGPoint || !cachedGPoint.captured ) { return; } // Can throw NotFoundError (InvalidPointerId Firefox < 82) // (should never happen, but it does on Firefox 79 touch so we won't log a warning) try { tracker.element.releasePointerCapture( gPoint.id ); //$.console.log('element.releasePointerCapture() called'); } catch ( e ) { //$.console.warn('releasePointerCapture() called on invalid pointer ID'); } } else { tracker.element.releaseCapture(); //$.console.log('element.releaseCapture() called'); } } else { // Emulate mouse capture by hanging listeners on the document object. // (Note we listen on the capture phase so the captured handlers will get called first) //$.console.log('Emulated mouse capture release'); eventParams = getCaptureEventParams( tracker, $.MouseTracker.havePointerEvents ? 'pointerevent' : gPoint.type ); // https://github.com/openseadragon/openseadragon/pull/790 if (isInIframe && canAccessEvents(window.top)) { $.removeEvent( window.top, eventParams.upName, eventParams.upHandler, true ); } $.removeEvent( $.MouseTracker.captureElement, eventParams.moveName, eventParams.moveHandler, true ); $.removeEvent( $.MouseTracker.captureElement, eventParams.upName, eventParams.upHandler, true ); } updatePointerCaptured( tracker, gPoint, false ); } /** * Note: Called for both pointer events and legacy mouse events * ($.MouseTracker.havePointerEvents determines which) * @private * @inner */ function getPointerId( event ) { return ( $.MouseTracker.havePointerEvents ) ? event.pointerId : $.MouseTracker.mousePointerId; } /** * 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. * * Note: Called for both pointer events and legacy mouse events * ($.MouseTracker.havePointerEvents determines which) * @private * @inner */ function getPointerType( event ) { if ( $.MouseTracker.havePointerEvents ) { // Note: IE pointer events bug - sends invalid pointerType on lostpointercapture events // and possibly other events. We rely on sane, valid property values in DOM events, so for // IE, when the pointerType is missing, we'll default to 'mouse'...should be right most of the time return event.pointerType || (( $.Browser.vendor === $.BROWSERS.IE ) ? 'mouse' : ''); } else { return 'mouse'; } } /** * Note: Called for both pointer events and legacy mouse events * ($.MouseTracker.havePointerEvents determines which) * @private * @inner */ function getIsPrimary( event ) { return ( $.MouseTracker.havePointerEvents ) ? event.isPrimary : true; } /** * @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 ) { //$.console.log('click ' + (tracker.userData ? tracker.userData.toString() : '')); var eventInfo = { originalEvent: event, eventType: 'click', pointerType: 'mouse', isEmulated: false }; preProcessEvent( tracker, eventInfo ); if ( eventInfo.preventDefault && !eventInfo.defaultPrevented ) { $.cancelEvent( event ); } if ( eventInfo.stopPropagation ) { $.stopEvent( event ); } } /** * @private * @inner */ function onDblClick( tracker, event ) { //$.console.log('dblclick ' + (tracker.userData ? tracker.userData.toString() : '')); var eventInfo = { originalEvent: event, eventType: 'dblclick', pointerType: 'mouse', isEmulated: false }; preProcessEvent( tracker, eventInfo ); if ( eventInfo.preventDefault && !eventInfo.defaultPrevented ) { $.cancelEvent( event ); } if ( eventInfo.stopPropagation ) { $.stopEvent( event ); } } /** * @private * @inner */ function onKeyDown( tracker, event ) { //$.console.log( "keydown %s %s %s %s %s", event.keyCode, event.charCode, event.ctrlKey, event.shiftKey, event.altKey ); var eventArgs = null; var eventInfo = { originalEvent: event, eventType: 'keydown', pointerType: '', isEmulated: false }; preProcessEvent( tracker, eventInfo ); if ( tracker.keyDownHandler && !eventInfo.preventGesture && !eventInfo.defaultPrevented ) { eventArgs = { eventSource: tracker, keyCode: event.keyCode ? event.keyCode : event.charCode, ctrl: event.ctrlKey, shift: event.shiftKey, alt: event.altKey, meta: event.metaKey, originalEvent: event, preventDefault: eventInfo.preventDefault || eventInfo.defaultPrevented, userData: tracker.userData }; tracker.keyDownHandler( eventArgs ); } if ( ( eventArgs && eventArgs.preventDefault ) || ( eventInfo.preventDefault && !eventInfo.defaultPrevented ) ) { $.cancelEvent( event ); } if ( eventInfo.stopPropagation ) { $.stopEvent( event ); } } /** * @private * @inner */ function onKeyUp( tracker, event ) { //$.console.log( "keyup %s %s %s %s %s", event.keyCode, event.charCode, event.ctrlKey, event.shiftKey, event.altKey ); var eventArgs = null; var eventInfo = { originalEvent: event, eventType: 'keyup', pointerType: '', isEmulated: false }; preProcessEvent( tracker, eventInfo ); if ( tracker.keyUpHandler && !eventInfo.preventGesture && !eventInfo.defaultPrevented ) { eventArgs = { eventSource: tracker, keyCode: event.keyCode ? event.keyCode : event.charCode, ctrl: event.ctrlKey, shift: event.shiftKey, alt: event.altKey, meta: event.metaKey, originalEvent: event, preventDefault: eventInfo.preventDefault || eventInfo.defaultPrevented, userData: tracker.userData }; tracker.keyUpHandler( eventArgs ); } if ( ( eventArgs && eventArgs.preventDefault ) || ( eventInfo.preventDefault && !eventInfo.defaultPrevented ) ) { $.cancelEvent( event ); } if ( eventInfo.stopPropagation ) { $.stopEvent( 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 eventArgs = null; var eventInfo = { originalEvent: event, eventType: 'keypress', pointerType: '', isEmulated: false }; preProcessEvent( tracker, eventInfo ); if ( tracker.keyHandler && !eventInfo.preventGesture && !eventInfo.defaultPrevented ) { eventArgs = { eventSource: tracker, keyCode: event.keyCode ? event.keyCode : event.charCode, ctrl: event.ctrlKey, shift: event.shiftKey, alt: event.altKey, meta: event.metaKey, originalEvent: event, preventDefault: eventInfo.preventDefault || eventInfo.defaultPrevented, userData: tracker.userData }; tracker.keyHandler( eventArgs ); } if ( ( eventArgs && eventArgs.preventDefault ) || ( eventInfo.preventDefault && !eventInfo.defaultPrevented ) ) { $.cancelEvent( event ); } if ( eventInfo.stopPropagation ) { $.stopEvent( event ); } } /** * @private * @inner */ function onFocus( tracker, event ) { //$.console.log('focus ' + (tracker.userData ? tracker.userData.toString() : '')); // focus doesn't bubble and is not cancelable, but we call // preProcessEvent() so it's dispatched to preProcessEventHandler // if necessary var eventInfo = { originalEvent: event, eventType: 'focus', pointerType: '', isEmulated: false }; preProcessEvent( tracker, eventInfo ); if ( tracker.focusHandler && !eventInfo.preventGesture ) { tracker.focusHandler( { eventSource: tracker, originalEvent: event, userData: tracker.userData } ); } } /** * @private * @inner */ function onBlur( tracker, event ) { //$.console.log('blur ' + (tracker.userData ? tracker.userData.toString() : '')); // blur doesn't bubble and is not cancelable, but we call // preProcessEvent() so it's dispatched to preProcessEventHandler // if necessary var eventInfo = { originalEvent: event, eventType: 'blur', pointerType: '', isEmulated: false }; preProcessEvent( tracker, eventInfo ); if ( tracker.blurHandler && !eventInfo.preventGesture ) { tracker.blurHandler( { eventSource: tracker, originalEvent: event, userData: tracker.userData } ); } } /** * @private * @inner */ function onContextMenu( tracker, event ) { //$.console.log('contextmenu ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); var eventArgs = null; var eventInfo = { originalEvent: event, eventType: 'contextmenu', pointerType: 'mouse', isEmulated: false }; preProcessEvent( tracker, eventInfo ); // ContextMenu if ( tracker.contextMenuHandler && !eventInfo.preventGesture && !eventInfo.defaultPrevented ) { eventArgs = { eventSource: tracker, position: getPointRelativeToAbsolute( getMouseAbsolute( event ), tracker.element ), originalEvent: eventInfo.originalEvent, preventDefault: eventInfo.preventDefault || eventInfo.defaultPrevented, userData: tracker.userData }; tracker.contextMenuHandler( eventArgs ); } if ( ( eventArgs && eventArgs.preventDefault ) || ( eventInfo.preventDefault && !eventInfo.defaultPrevented ) ) { $.cancelEvent( event ); } if ( eventInfo.stopPropagation ) { $.stopEvent( event ); } } /** * Handler for 'wheel' events * * @private * @inner */ function onWheel( tracker, event ) { handleWheelEvent( tracker, event, event ); } /** * Handler for 'mousewheel', 'DOMMouseScroll', and 'MozMousePixelScroll' events * * @private * @inner */ function onMouseWheel( tracker, event ) { // Simulate a 'wheel' event var simulatedEvent = { target: event.target || event.srcElement, type: "wheel", shiftKey: event.shiftKey || false, clientX: event.clientX, clientY: event.clientY, pageX: event.pageX ? event.pageX : event.clientX, pageY: event.pageY ? event.pageY : event.clientY, deltaMode: event.type === "MozMousePixelScroll" ? 0 : 1, // 0=pixel, 1=line, 2=page deltaX: 0, deltaZ: 0 }; // Calculate deltaY if ( $.MouseTracker.wheelEventName === "mousewheel" ) { simulatedEvent.deltaY = -event.wheelDelta / $.DEFAULT_SETTINGS.pixelsPerWheelLine; } else { simulatedEvent.deltaY = event.detail; } handleWheelEvent( tracker, simulatedEvent, event ); } /** * Handles 'wheel' events. * The event may be simulated by the legacy mouse wheel event handler (onMouseWheel()). * * @private * @inner */ function handleWheelEvent( tracker, event, originalEvent ) { var nDelta = 0, eventInfo; var eventArgs = null; // 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. // event.deltaMode: 0=pixel, 1=line, 2=page // TODO: Deltas in pixel mode should be accumulated then a scroll value computed after $.DEFAULT_SETTINGS.pixelsPerWheelLine threshold reached nDelta = event.deltaY < 0 ? 1 : -1; eventInfo = { originalEvent: event, eventType: 'wheel', pointerType: 'mouse', isEmulated: event !== originalEvent }; preProcessEvent( tracker, eventInfo ); if ( tracker.scrollHandler && !eventInfo.preventGesture && !eventInfo.defaultPrevented ) { eventArgs = { eventSource: tracker, pointerType: 'mouse', position: getMouseRelative( event, tracker.element ), scroll: nDelta, shift: event.shiftKey, isTouchEvent: false, originalEvent: originalEvent, preventDefault: eventInfo.preventDefault || eventInfo.defaultPrevented, userData: tracker.userData }; tracker.scrollHandler( eventArgs ); } if ( eventInfo.stopPropagation ) { $.stopEvent( originalEvent ); } if ( ( eventArgs && eventArgs.preventDefault ) || ( eventInfo.preventDefault && !eventInfo.defaultPrevented ) ) { $.cancelEvent( originalEvent ); } } /** * TODO Never actually seen this event fired, and documentation is tough to find * @private * @inner */ function onLoseCapture( tracker, event ) { //$.console.log('losecapture ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); var gPoint = { id: $.MouseTracker.mousePointerId, type: 'mouse' }; var eventInfo = { originalEvent: event, eventType: 'lostpointercapture', pointerType: 'mouse', isEmulated: false }; preProcessEvent( tracker, eventInfo ); if ( event.target === tracker.element ) { updatePointerCaptured( tracker, gPoint, false ); } if ( eventInfo.stopPropagation ) { $.stopEvent( event ); } } /** * @private * @inner */ function onTouchStart( tracker, event ) { var time, i, touchCount = event.changedTouches.length, gPoint, pointsList = tracker.getActivePointersListByType( 'touch' ); time = $.now(); //$.console.log('touchstart ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); if ( pointsList.getLength() > event.touches.length - touchCount ) { $.console.warn('Tracked touch contact count doesn\'t match event.touches.length'); } var eventInfo = { originalEvent: event, eventType: 'pointerdown', pointerType: 'touch', isEmulated: false }; preProcessEvent( tracker, eventInfo ); for ( i = 0; i < touchCount; i++ ) { gPoint = { id: event.changedTouches[ i ].identifier, type: 'touch', // Simulate isPrimary isPrimary: pointsList.getLength() === 0, currentPos: getMouseAbsolute( event.changedTouches[ i ] ), currentTime: time }; // simulate touchenter on our tracked element updatePointerEnter( tracker, eventInfo, gPoint ); updatePointerDown( tracker, eventInfo, gPoint, 0 ); updatePointerCaptured( tracker, gPoint, true ); } if ( eventInfo.preventDefault && !eventInfo.defaultPrevented ) { $.cancelEvent( event ); } if ( eventInfo.stopPropagation ) { $.stopEvent( event ); } } /** * @private * @inner */ function onTouchEnd( tracker, event ) { var time, i, touchCount = event.changedTouches.length, gPoint; time = $.now(); //$.console.log('touchend ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); var eventInfo = { originalEvent: event, eventType: 'pointerup', pointerType: 'touch', isEmulated: false }; preProcessEvent( tracker, eventInfo ); for ( i = 0; i < touchCount; i++ ) { gPoint = { id: event.changedTouches[ i ].identifier, type: 'touch', currentPos: getMouseAbsolute( event.changedTouches[ i ] ), currentTime: time }; updatePointerUp( tracker, eventInfo, gPoint, 0 ); updatePointerCaptured( tracker, gPoint, false ); // simulate touchleave on our tracked element updatePointerLeave( tracker, eventInfo, gPoint ); } if ( eventInfo.preventDefault && !eventInfo.defaultPrevented ) { $.cancelEvent( event ); } if ( eventInfo.stopPropagation ) { $.stopEvent( event ); } } /** * @private * @inner */ function onTouchMove( tracker, event ) { var time, i, touchCount = event.changedTouches.length, gPoint; time = $.now(); var eventInfo = { originalEvent: event, eventType: 'pointermove', pointerType: 'touch', isEmulated: false }; preProcessEvent( tracker, eventInfo ); for ( i = 0; i < touchCount; i++ ) { gPoint = { id: event.changedTouches[ i ].identifier, type: 'touch', currentPos: getMouseAbsolute( event.changedTouches[ i ] ), currentTime: time }; updatePointerMove( tracker, eventInfo, gPoint ); } if ( eventInfo.preventDefault && !eventInfo.defaultPrevented ) { $.cancelEvent( event ); } if ( eventInfo.stopPropagation ) { $.stopEvent( event ); } } /** * @private * @inner */ function onTouchCancel( tracker, event ) { var touchCount = event.changedTouches.length, i, gPoint; //$.console.log('touchcancel ' + (tracker.userData ? tracker.userData.toString() : '')); var eventInfo = { originalEvent: event, eventType: 'pointercancel', pointerType: 'touch', isEmulated: false }; preProcessEvent( tracker, eventInfo ); for ( i = 0; i < touchCount; i++ ) { gPoint = { id: event.changedTouches[ i ].identifier, type: 'touch' }; //TODO need to only do this if our element is target? updatePointerCancel( tracker, eventInfo, gPoint ); } if ( eventInfo.stopPropagation ) { $.stopEvent( event ); } } /** * @private * @inner */ function onGestureStart( tracker, event ) { if ( !$.eventIsCanceled( event ) ) { event.preventDefault(); } return false; } /** * @private * @inner */ function onGestureChange( tracker, event ) { if ( !$.eventIsCanceled( event ) ) { event.preventDefault(); } return false; } /** * @private * @inner */ function onGotPointerCapture( tracker, event ) { //$.console.log('gotpointercapture ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); var eventInfo = { originalEvent: event, eventType: 'gotpointercapture', pointerType: getPointerType( event ), isEmulated: false }; preProcessEvent( tracker, eventInfo ); if ( event.target === tracker.element ) { //$.console.log('gotpointercapture ' + (tracker.userData ? tracker.userData.toString() : '')); updatePointerCaptured( tracker, { id: event.pointerId, type: getPointerType( event ) }, true ); } if ( eventInfo.stopPropagation ) { $.stopEvent( event ); } } /** * @private * @inner */ function onLostPointerCapture( tracker, event ) { //$.console.log('lostpointercapture ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); var eventInfo = { originalEvent: event, eventType: 'lostpointercapture', pointerType: getPointerType( event ), isEmulated: false }; preProcessEvent( tracker, eventInfo ); if ( event.target === tracker.element ) { //$.console.log('lostpointercapture ' + (tracker.userData ? tracker.userData.toString() : '')); updatePointerCaptured( tracker, { id: event.pointerId, type: getPointerType( event ) }, false ); } if ( eventInfo.stopPropagation ) { $.stopEvent( event ); } } /** * Note: Called for both pointer events and legacy mouse events * ($.MouseTracker.havePointerEvents determines which) * * @private * @inner */ function onPointerEnter( tracker, event ) { //$.console.log('pointerenter ' + (tracker.userData ? tracker.userData.toString() : '')); var gPoint = { id: getPointerId( event ), type: getPointerType( event ), isPrimary: getIsPrimary( event ), currentPos: getMouseAbsolute( event ), currentTime: $.now() }; // pointerenter doesn't bubble and is not cancelable, but we call // preProcessEvent() so it's dispatched to preProcessEventHandler // if necessary var eventInfo = { originalEvent: event, eventType: 'pointerenter', pointerType: gPoint.type, isEmulated: false }; preProcessEvent( tracker, eventInfo ); updatePointerEnter( tracker, eventInfo, gPoint ); } /** * Note: Called for both pointer events and legacy mouse events * ($.MouseTracker.havePointerEvents determines which) * * @private * @inner */ function onPointerLeave( tracker, event ) { //$.console.log('pointerleave ' + (tracker.userData ? tracker.userData.toString() : '')); var gPoint = { id: getPointerId( event ), type: getPointerType( event ), isPrimary: getIsPrimary( event ), currentPos: getMouseAbsolute( event ), currentTime: $.now() }; // pointerleave doesn't bubble and is not cancelable, but we call // preProcessEvent() so it's dispatched to preProcessEventHandler // if necessary var eventInfo = { originalEvent: event, eventType: 'pointerleave', pointerType: gPoint.type, isEmulated: false }; preProcessEvent( tracker, eventInfo ); updatePointerLeave( tracker, eventInfo, gPoint ); } /** * Note: Called for both pointer events and legacy mouse events * ($.MouseTracker.havePointerEvents determines which) * * @private * @inner */ function onPointerOver( tracker, event ) { //$.console.log('pointerover ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); var gPoint = { id: getPointerId( event ), type: getPointerType( event ), isPrimary: getIsPrimary( event ), currentPos: getMouseAbsolute( event ), currentTime: $.now() }; var eventInfo = { originalEvent: event, eventType: 'pointerover', pointerType: gPoint.type, isEmulated: false }; preProcessEvent( tracker, eventInfo ); updatePointerOver( tracker, eventInfo, gPoint ); if ( eventInfo.preventDefault && !eventInfo.defaultPrevented ) { $.cancelEvent( event ); } if ( eventInfo.stopPropagation ) { $.stopEvent( event ); } } /** * Note: Called for both pointer events and legacy mouse events * ($.MouseTracker.havePointerEvents determines which) * * @private * @inner */ function onPointerOut( tracker, event ) { //$.console.log('pointerout ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); var gPoint = { id: getPointerId( event ), type: getPointerType( event ), isPrimary: getIsPrimary( event ), currentPos: getMouseAbsolute( event ), currentTime: $.now() }; var eventInfo = { originalEvent: event, eventType: 'pointerout', pointerType: gPoint.type, isEmulated: false }; preProcessEvent( tracker, eventInfo ); updatePointerOut( tracker, eventInfo, gPoint ); if ( eventInfo.preventDefault && !eventInfo.defaultPrevented ) { $.cancelEvent( event ); } if ( eventInfo.stopPropagation ) { $.stopEvent( event ); } } /** * Note: Called for both pointer events and legacy mouse events * ($.MouseTracker.havePointerEvents determines which) * * @private * @inner */ function onPointerDown( tracker, event ) { var gPoint = { id: getPointerId( event ), type: getPointerType( event ), isPrimary: getIsPrimary( event ), currentPos: getMouseAbsolute( event ), currentTime: $.now() }; // Most browsers implicitly capture touch pointer events // Note no IE versions have element.hasPointerCapture() so no implicit // pointer capture possible // var implicitlyCaptured = ($.MouseTracker.havePointerEvents && // event.target.hasPointerCapture && // $.Browser.vendor !== $.BROWSERS.IE) ? // event.target.hasPointerCapture(event.pointerId) : false; var implicitlyCaptured = $.MouseTracker.havePointerEvents && gPoint.type === 'touch' && $.Browser.vendor !== $.BROWSERS.IE; //$.console.log('pointerdown ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); var eventInfo = { originalEvent: event, eventType: 'pointerdown', pointerType: gPoint.type, isEmulated: false }; preProcessEvent( tracker, eventInfo ); updatePointerDown( tracker, eventInfo, gPoint, event.button ); if ( eventInfo.preventDefault && !eventInfo.defaultPrevented ) { $.cancelEvent( event ); } if ( eventInfo.stopPropagation ) { $.stopEvent( event ); } if ( eventInfo.shouldCapture ) { if ( implicitlyCaptured ) { updatePointerCaptured( tracker, gPoint, true ); } else { capturePointer( tracker, gPoint ); } } } /** * Note: Called for both pointer events and legacy mouse events * ($.MouseTracker.havePointerEvents determines which) * * @private * @inner */ function onPointerUp( tracker, event ) { handlePointerUp( tracker, event ); } /** * Note: Called for both pointer events and legacy mouse events * ($.MouseTracker.havePointerEvents determines which) * * This handler is attached to the window object (on the capture phase) to emulate mouse capture. * onPointerUp is still attached to the tracked element, so stop propagation to avoid processing twice. * * @private * @inner */ function onPointerUpCaptured( tracker, event ) { var pointsList = tracker.getActivePointersListByType( getPointerType( event ) ); if ( pointsList.getById( event.pointerId ) ) { handlePointerUp( tracker, event ); } $.stopEvent( event ); } /** * Note: Called for both pointer events and legacy mouse events * ($.MouseTracker.havePointerEvents determines which) * * @private * @inner */ function handlePointerUp( tracker, event ) { var gPoint; //$.console.log('pointerup ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); gPoint = { id: getPointerId( event ), type: getPointerType( event ), isPrimary: getIsPrimary( event ), currentPos: getMouseAbsolute( event ), currentTime: $.now() }; var eventInfo = { originalEvent: event, eventType: 'pointerup', pointerType: gPoint.type, isEmulated: false }; preProcessEvent( tracker, eventInfo ); updatePointerUp( tracker, eventInfo, gPoint, event.button ); if ( eventInfo.preventDefault && !eventInfo.defaultPrevented ) { $.cancelEvent( event ); } if ( eventInfo.stopPropagation ) { $.stopEvent( event ); } // Per spec, pointerup events are supposed to release capture. Not all browser // versions have adhered to the spec, and there's no harm in releasing // explicitly if ( eventInfo.shouldReleaseCapture ) { if ( event.target === tracker.element ) { releasePointer( tracker, gPoint ); } else { updatePointerCaptured( tracker, gPoint, false ); } } } /** * Note: Called for both pointer events and legacy mouse events * ($.MouseTracker.havePointerEvents determines which) * * @private * @inner */ function onPointerMove( tracker, event ) { handlePointerMove( tracker, event ); } /** * Note: Called for both pointer events and legacy mouse events * ($.MouseTracker.havePointerEvents determines which) * * This handler is attached to the window object (on the capture phase) to emulate mouse capture. * onPointerMove is still attached to the tracked element, so stop propagation to avoid processing twice. * * @private * @inner */ function onPointerMoveCaptured( tracker, event ) { var pointsList = tracker.getActivePointersListByType( getPointerType( event ) ); if ( pointsList.getById( event.pointerId ) ) { handlePointerMove( tracker, event ); } $.stopEvent( event ); } /** * Note: Called for both pointer events and legacy mouse events * ($.MouseTracker.havePointerEvents determines which) * * @private * @inner */ function handlePointerMove( tracker, event ) { // Pointer changed coordinates, button state, pressure, tilt, or contact geometry (e.g. width and height) var gPoint = { id: getPointerId( event ), type: getPointerType( event ), isPrimary: getIsPrimary( event ), currentPos: getMouseAbsolute( event ), currentTime: $.now() }; var eventInfo = { originalEvent: event, eventType: 'pointermove', pointerType: gPoint.type, isEmulated: false }; preProcessEvent( tracker, eventInfo ); updatePointerMove( tracker, eventInfo, gPoint ); if ( eventInfo.preventDefault && !eventInfo.defaultPrevented ) { $.cancelEvent( event ); } if ( eventInfo.stopPropagation ) { $.stopEvent( event ); } } /** * @private * @inner */ function onPointerCancel( tracker, event ) { //$.console.log('pointercancel ' + (tracker.userData ? tracker.userData.toString() : '') + ' ' + (event.target === tracker.element ? 'tracker.element' : '')); var gPoint = { id: event.pointerId, type: getPointerType( event ) }; var eventInfo = { originalEvent: event, eventType: 'pointercancel', pointerType: gPoint.type, isEmulated: false }; preProcessEvent( tracker, eventInfo ); //TODO need to only do this if our element is target? updatePointerCancel( tracker, eventInfo, gPoint ); if ( eventInfo.stopPropagation ) { $.stopEvent( event ); } } /////////////////////////////////////////////////////////////////////////////// // 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 ) { //$.console.log('startTrackingPointer *** ' + pointsList.type + ' ' + gPoint.id.toString()); 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} tracker * A reference to the MouseTracker instance. * @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( tracker, pointsList, gPoint ) { //$.console.log('stopTrackingPointer *** ' + pointsList.type + ' ' + gPoint.id.toString()); var listLength; var trackedGPoint = pointsList.getById( gPoint.id ); if ( trackedGPoint ) { if ( trackedGPoint.captured ) { $.console.warn('stopTrackingPointer() called on captured pointer'); releasePointer( tracker, trackedGPoint ); } // If child element relinquishes capture to a parent we may get here // from a pointerleave event while a pointerup event will never be received. // In that case, we'll clean up the contact count pointsList.removeContact(); listLength = pointsList.removeById( gPoint.id ); } else { listLength = pointsList.getLength(); } return listLength; } /** * @function * @private * @inner */ function getEventProcessDefaults( tracker, eventInfo ) { switch ( eventInfo.eventType ) { case 'pointermove': eventInfo.isStoppable = true; eventInfo.isCancelable = true; eventInfo.preventDefault = false; eventInfo.preventGesture = !tracker.hasGestureHandlers; eventInfo.stopPropagation = false; break; case 'pointerover': case 'pointerout': case 'contextmenu': case 'keydown': case 'keyup': case 'keypress': eventInfo.isStoppable = true; eventInfo.isCancelable = true; eventInfo.preventDefault = false; // onContextMenu(), onKeyDown(), onKeyUp(), onKeyPress() may set true eventInfo.preventGesture = false; eventInfo.stopPropagation = false; break; case 'pointerdown': eventInfo.isStoppable = true; eventInfo.isCancelable = true; eventInfo.preventDefault = false; // updatePointerDown() may set true (tracker.hasGestureHandlers) eventInfo.preventGesture = !tracker.hasGestureHandlers; eventInfo.stopPropagation = false; break; case 'pointerup': eventInfo.isStoppable = true; eventInfo.isCancelable = true; eventInfo.preventDefault = false; eventInfo.preventGesture = !tracker.hasGestureHandlers; eventInfo.stopPropagation = false; break; case 'wheel': eventInfo.isStoppable = true; eventInfo.isCancelable = true; eventInfo.preventDefault = false; // handleWheelEvent() may set true eventInfo.preventGesture = !tracker.hasScrollHandler; eventInfo.stopPropagation = false; break; case 'gotpointercapture': case 'lostpointercapture': case 'pointercancel': eventInfo.isStoppable = true; eventInfo.isCancelable = false; eventInfo.preventDefault = false; eventInfo.preventGesture = false; eventInfo.stopPropagation = false; break; case 'click': eventInfo.isStoppable = true; eventInfo.isCancelable = true; eventInfo.preventDefault = !!tracker.clickHandler; eventInfo.preventGesture = false; eventInfo.stopPropagation = false; break; case 'dblclick': eventInfo.isStoppable = true; eventInfo.isCancelable = true; eventInfo.preventDefault = !!tracker.dblClickHandler; eventInfo.preventGesture = false; eventInfo.stopPropagation = false; break; case 'focus': case 'blur': case 'pointerenter': case 'pointerleave': default: eventInfo.isStoppable = false; eventInfo.isCancelable = false; eventInfo.preventDefault = false; eventInfo.preventGesture = false; eventInfo.stopPropagation = false; break; } } /** * Sets up for and calls preProcessEventHandler. Call with the following parameters - * this function will fill in the rest of the preProcessEventHandler event object * properties * * @function * @private * @inner * @param {OpenSeadragon.MouseTracker} tracker * A reference to the MouseTracker instance. * @param {OpenSeadragon.MouseTracker.EventProcessInfo} eventInfo * @param {Object} eventInfo.originalEvent * @param {String} eventInfo.eventType * @param {String} eventInfo.pointerType * @param {Boolean} eventInfo.isEmulated */ function preProcessEvent( tracker, eventInfo ) { eventInfo.eventSource = tracker; eventInfo.eventPhase = eventInfo.originalEvent ? ((typeof eventInfo.originalEvent.eventPhase !== 'undefined') ? eventInfo.originalEvent.eventPhase : 0) : 0; eventInfo.defaultPrevented = $.eventIsCanceled( eventInfo.originalEvent ); eventInfo.shouldCapture = false; eventInfo.shouldReleaseCapture = false; eventInfo.userData = tracker.userData; getEventProcessDefaults( tracker, eventInfo ); if ( tracker.preProcessEventHandler ) { tracker.preProcessEventHandler( eventInfo ); } } /** * Sets or resets the captured property on the tracked pointer matching the passed gPoint's id/type * * @function * @private * @inner * @param {OpenSeadragon.MouseTracker} tracker * A reference to the MouseTracker instance. * @param {Object} gPoint * An object with id and type properties describing the pointer to update. * @param {Boolean} isCaptured * Value to set the captured property to. */ function updatePointerCaptured( tracker, gPoint, isCaptured ) { var pointsList = tracker.getActivePointersListByType( gPoint.type ); var updateGPoint = pointsList.getById( gPoint.id ); if ( updateGPoint ) { if ( isCaptured && !updateGPoint.captured ) { updateGPoint.captured = true; pointsList.captureCount++; } else if ( !isCaptured && updateGPoint.captured ) { updateGPoint.captured = false; pointsList.captureCount--; if ( pointsList.captureCount < 0 ) { pointsList.captureCount = 0; $.console.warn('updatePointerCaptured() - pointsList.captureCount went negative'); } } } else { $.console.warn('updatePointerCaptured() called on untracked pointer'); } } /** * @function * @private * @inner * @param {OpenSeadragon.MouseTracker} tracker * A reference to the MouseTracker instance. * @param {OpenSeadragon.MouseTracker.EventProcessInfo} eventInfo * Processing info for originating DOM event. * @param {OpenSeadragon.MouseTracker.GesturePoint} gPoint * Gesture point associated with the event. */ function updatePointerEnter( tracker, eventInfo, gPoint ) { var pointsList = tracker.getActivePointersListByType( gPoint.type ), updateGPoint; updateGPoint = pointsList.getById( gPoint.id ); if ( updateGPoint ) { // Already tracking the pointer...update it updateGPoint.insideElement = true; updateGPoint.lastPos = updateGPoint.currentPos; updateGPoint.lastTime = updateGPoint.currentTime; updateGPoint.currentPos = gPoint.currentPos; updateGPoint.currentTime = gPoint.currentTime; gPoint = updateGPoint; } else { // Initialize for tracking and add to the tracking list gPoint.captured = false; // Handled by updatePointerCaptured() gPoint.insideElementPressed = false; gPoint.insideElement = true; startTrackingPointer( pointsList, gPoint ); } // Enter (doesn't bubble and not cancelable) if ( tracker.enterHandler ) { tracker.enterHandler( { eventSource: tracker, pointerType: gPoint.type, position: getPointRelativeToAbsolute( gPoint.currentPos, tracker.element ), buttons: pointsList.buttons, pointers: tracker.getActivePointerCount(), insideElementPressed: gPoint.insideElementPressed, buttonDownAny: pointsList.buttons !== 0, isTouchEvent: gPoint.type === 'touch', originalEvent: eventInfo.originalEvent, userData: tracker.userData } ); } } /** * @function * @private * @inner * @param {OpenSeadragon.MouseTracker} tracker * A reference to the MouseTracker instance. * @param {OpenSeadragon.MouseTracker.EventProcessInfo} eventInfo * Processing info for originating DOM event. * @param {OpenSeadragon.MouseTracker.GesturePoint} gPoint * Gesture point associated with the event. */ function updatePointerLeave( tracker, eventInfo, gPoint ) { var pointsList = tracker.getActivePointersListByType(gPoint.type), updateGPoint, dispatchEventObj; updateGPoint = pointsList.getById( gPoint.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 = gPoint.currentPos; updateGPoint.currentTime = gPoint.currentTime; } else { stopTrackingPointer( tracker, pointsList, updateGPoint ); } gPoint = updateGPoint; } else { gPoint.captured = false; // Handled by updatePointerCaptured() gPoint.insideElementPressed = false; } // Leave (doesn't bubble and not cancelable) // Note: exitHandler is deprecated (v2.5.0), replaced by leaveHandler if ( tracker.leaveHandler || tracker.exitHandler ) { dispatchEventObj = { eventSource: tracker, pointerType: gPoint.type, // GitHub PR: https://github.com/openseadragon/openseadragon/pull/1754 (gPoint.currentPos && ) position: gPoint.currentPos && getPointRelativeToAbsolute( gPoint.currentPos, tracker.element ), buttons: pointsList.buttons, pointers: tracker.getActivePointerCount(), insideElementPressed: gPoint.insideElementPressed, buttonDownAny: pointsList.buttons !== 0, isTouchEvent: gPoint.type === 'touch', originalEvent: eventInfo.originalEvent, userData: tracker.userData }; if ( tracker.leaveHandler ) { tracker.leaveHandler( dispatchEventObj ); } // Deprecated if ( tracker.exitHandler ) { tracker.exitHandler( dispatchEventObj ); } } } /** * @function * @private * @inner * @param {OpenSeadragon.MouseTracker} tracker * A reference to the MouseTracker instance. * @param {OpenSeadragon.MouseTracker.EventProcessInfo} eventInfo * Processing info for originating DOM event. * @param {OpenSeadragon.MouseTracker.GesturePoint} gPoint * Gesture point associated with the event. */ function updatePointerOver( tracker, eventInfo, gPoint ) { var pointsList, updateGPoint; pointsList = tracker.getActivePointersListByType( gPoint.type ); updateGPoint = pointsList.getById( gPoint.id ); if ( updateGPoint ) { gPoint = updateGPoint; } else { gPoint.captured = false; gPoint.insideElementPressed = false; //gPoint.insideElement = true; // Tracked by updatePointerEnter } if ( tracker.overHandler ) { // Over tracker.overHandler( { eventSource: tracker, pointerType: gPoint.type, position: getPointRelativeToAbsolute( gPoint.currentPos, tracker.element ), buttons: pointsList.buttons, pointers: tracker.getActivePointerCount(), insideElementPressed: gPoint.insideElementPressed, buttonDownAny: pointsList.buttons !== 0, isTouchEvent: gPoint.type === 'touch', originalEvent: eventInfo.originalEvent, userData: tracker.userData } ); } } /** * @function * @private * @inner * @param {OpenSeadragon.MouseTracker} tracker * A reference to the MouseTracker instance. * @param {OpenSeadragon.MouseTracker.EventProcessInfo} eventInfo * Processing info for originating DOM event. * @param {OpenSeadragon.MouseTracker.GesturePoint} gPoint * Gesture point associated with the event. */ function updatePointerOut( tracker, eventInfo, gPoint ) { var pointsList, updateGPoint; pointsList = tracker.getActivePointersListByType(gPoint.type); updateGPoint = pointsList.getById( gPoint.id ); if ( updateGPoint ) { gPoint = updateGPoint; } else { gPoint.captured = false; gPoint.insideElementPressed = false; //gPoint.insideElement = true; // Tracked by updatePointerEnter } if ( tracker.outHandler ) { // Out tracker.outHandler( { eventSource: tracker, pointerType: gPoint.type, position: gPoint.currentPos && getPointRelativeToAbsolute( gPoint.currentPos, tracker.element ), buttons: pointsList.buttons, pointers: tracker.getActivePointerCount(), insideElementPressed: gPoint.insideElementPressed, buttonDownAny: pointsList.buttons !== 0, isTouchEvent: gPoint.type === 'touch', originalEvent: eventInfo.originalEvent, userData: tracker.userData } ); } } /** * @function * @private * @inner * @param {OpenSeadragon.MouseTracker} tracker * A reference to the MouseTracker instance. * @param {OpenSeadragon.MouseTracker.EventProcessInfo} eventInfo * Processing info for originating DOM event. * @param {OpenSeadragon.MouseTracker.GesturePoint} gPoint * Gesture point associated with the event. * @param {Number} buttonChanged * The button involved in the event: -1: none, 0: primary/left, 1: aux/middle, 2: secondary/right, 3: X1/back, 4: X2/forward, 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. */ function updatePointerDown( tracker, eventInfo, gPoint, buttonChanged ) { var delegate = THIS[ tracker.hash ], pointsList = tracker.getActivePointersListByType( gPoint.type ), updateGPoint; if ( typeof eventInfo.originalEvent.buttons !== 'undefined' ) { pointsList.buttons = eventInfo.originalEvent.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 ) { eventInfo.shouldCapture = false; eventInfo.shouldReleaseCapture = false; // Aux Press if ( tracker.nonPrimaryPressHandler && !eventInfo.preventGesture && !eventInfo.defaultPrevented ) { eventInfo.preventDefault = true; tracker.nonPrimaryPressHandler( { eventSource: tracker, pointerType: gPoint.type, position: getPointRelativeToAbsolute( gPoint.currentPos, tracker.element ), button: buttonChanged, buttons: pointsList.buttons, isTouchEvent: gPoint.type === 'touch', originalEvent: eventInfo.originalEvent, userData: tracker.userData } ); } return; } updateGPoint = pointsList.getById( gPoint.id ); if ( updateGPoint ) { // Already tracking the pointer...update it //updateGPoint.captured = true; // Handled by updatePointerCaptured() updateGPoint.insideElementPressed = true; updateGPoint.insideElement = true; updateGPoint.originalTarget = eventInfo.originalEvent.target; updateGPoint.contactPos = gPoint.currentPos; updateGPoint.contactTime = gPoint.currentTime; updateGPoint.lastPos = updateGPoint.currentPos; updateGPoint.lastTime = updateGPoint.currentTime; updateGPoint.currentPos = gPoint.currentPos; updateGPoint.currentTime = gPoint.currentTime; gPoint = updateGPoint; } else { // Initialize for tracking and add to the tracking list (no pointerenter event occurred before this) // NOTE: pointerdown event on untracked pointer gPoint.captured = false; // Handled by updatePointerCaptured() gPoint.insideElementPressed = true; gPoint.insideElement = true; gPoint.originalTarget = eventInfo.originalEvent.target; startTrackingPointer( pointsList, gPoint ); } pointsList.addContact(); //$.console.log('contacts++ ', pointsList.contacts); if ( !eventInfo.preventGesture && !eventInfo.defaultPrevented ) { eventInfo.shouldCapture = true; eventInfo.shouldReleaseCapture = false; eventInfo.preventDefault = true; if ( tracker.dragHandler || tracker.dragEndHandler || tracker.pinchHandler ) { $.MouseTracker.gesturePointVelocityTracker.addPoint( tracker, gPoint ); } if ( pointsList.contacts === 1 ) { // Press if ( tracker.pressHandler && !eventInfo.preventGesture ) { tracker.pressHandler( { eventSource: tracker, pointerType: gPoint.type, position: getPointRelativeToAbsolute( gPoint.contactPos, tracker.element ), buttons: pointsList.buttons, isTouchEvent: gPoint.type === 'touch', originalEvent: eventInfo.originalEvent, userData: tracker.userData } ); } } else if ( pointsList.contacts === 2 ) { if ( tracker.pinchHandler && gPoint.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 ); } } } else { eventInfo.shouldCapture = false; eventInfo.shouldReleaseCapture = false; } } /** * @function * @private * @inner * @param {OpenSeadragon.MouseTracker} tracker * A reference to the MouseTracker instance. * @param {OpenSeadragon.MouseTracker.EventProcessInfo} eventInfo * Processing info for originating DOM event. * @param {OpenSeadragon.MouseTracker.GesturePoint} gPoint * Gesture points associated with the event. * @param {Number} buttonChanged * The button involved in the event: -1: none, 0: primary/left, 1: aux/middle, 2: secondary/right, 3: X1/back, 4: X2/forward, 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. */ function updatePointerUp( tracker, eventInfo, gPoint, buttonChanged ) { var delegate = THIS[ tracker.hash ], pointsList = tracker.getActivePointersListByType( gPoint.type ), releasePoint, releaseTime, updateGPoint, wasCaptured = false, quick; if ( typeof eventInfo.originalEvent.buttons !== 'undefined' ) { pointsList.buttons = eventInfo.originalEvent.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; } } eventInfo.shouldCapture = false; // Only capture and track primary button, pen, and touch contacts if ( buttonChanged !== 0 ) { eventInfo.shouldReleaseCapture = false; // Aux Release if ( tracker.nonPrimaryReleaseHandler && !eventInfo.preventGesture && !eventInfo.defaultPrevented ) { eventInfo.preventDefault = true; tracker.nonPrimaryReleaseHandler( { eventSource: tracker, pointerType: gPoint.type, position: getPointRelativeToAbsolute(gPoint.currentPos, tracker.element), button: buttonChanged, buttons: pointsList.buttons, isTouchEvent: gPoint.type === 'touch', originalEvent: eventInfo.originalEvent, userData: tracker.userData } ); } return; } updateGPoint = pointsList.getById( gPoint.id ); if ( updateGPoint ) { pointsList.removeContact(); //$.console.log('contacts-- ', pointsList.contacts); // Update the pointer, stop tracking it if not still in this element if ( updateGPoint.captured ) { //updateGPoint.captured = false; // Handled by updatePointerCaptured() wasCaptured = true; } updateGPoint.lastPos = updateGPoint.currentPos; updateGPoint.lastTime = updateGPoint.currentTime; updateGPoint.currentPos = gPoint.currentPos; updateGPoint.currentTime = gPoint.currentTime; if ( !updateGPoint.insideElement ) { stopTrackingPointer( tracker, pointsList, updateGPoint ); } releasePoint = updateGPoint.currentPos; releaseTime = updateGPoint.currentTime; } else { // NOTE: updatePointerUp(): pointerup on untracked gPoint // ...we'll start to track pointer again gPoint.captured = false; // Handled by updatePointerCaptured() gPoint.insideElementPressed = false; gPoint.insideElement = true; startTrackingPointer( pointsList, gPoint ); updateGPoint = gPoint; } if ( !eventInfo.preventGesture && !eventInfo.defaultPrevented ) { if ( wasCaptured ) { // Pointer was activated in our element but could have been removed in any element since events are captured to our element eventInfo.shouldReleaseCapture = true; eventInfo.preventDefault = true; if ( tracker.dragHandler || tracker.dragEndHandler || tracker.pinchHandler ) { $.MouseTracker.gesturePointVelocityTracker.removePoint( tracker, updateGPoint ); } if ( pointsList.contacts === 0 ) { // Release (pressed in our element) if ( tracker.releaseHandler && releasePoint ) { 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: eventInfo.originalEvent, userData: tracker.userData } ); } // Drag End if ( tracker.dragEndHandler && delegate.sentDragEvent ) { tracker.dragEndHandler( { eventSource: tracker, pointerType: updateGPoint.type, position: getPointRelativeToAbsolute( updateGPoint.currentPos, tracker.element ), speed: updateGPoint.speed, direction: updateGPoint.direction, shift: eventInfo.originalEvent.shiftKey, isTouchEvent: updateGPoint.type === 'touch', originalEvent: eventInfo.originalEvent, userData: tracker.userData } ); } // We want to clear this flag regardless of whether we fired the dragEndHandler delegate.sentDragEvent = false; // Click / Double-Click if ( ( tracker.clickHandler || tracker.dblClickHandler ) && updateGPoint.insideElement ) { quick = releaseTime - updateGPoint.contactTime <= tracker.clickTimeThreshold && updateGPoint.contactPos.distanceTo( releasePoint ) <= tracker.clickDistThreshold; // Click if ( tracker.clickHandler ) { tracker.clickHandler( { eventSource: tracker, pointerType: updateGPoint.type, position: getPointRelativeToAbsolute( updateGPoint.currentPos, tracker.element ), quick: quick, shift: eventInfo.originalEvent.shiftKey, isTouchEvent: updateGPoint.type === 'touch', originalEvent: eventInfo.originalEvent, originalTarget: updateGPoint.originalTarget, userData: tracker.userData } ); } // Double-Click if ( tracker.dblClickHandler && quick ) { pointsList.clicks++; if ( pointsList.clicks === 1 ) { delegate.lastClickPos = releasePoint; /*jshint loopfunc:true*/ delegate.dblClickTimeOut = setTimeout( function() { pointsList.clicks = 0; }, tracker.dblClickTimeThreshold ); /*jshint loopfunc:false*/ } else if ( pointsList.clicks === 2 ) { clearTimeout( delegate.dblClickTimeOut ); pointsList.clicks = 0; if ( delegate.lastClickPos.distanceTo( releasePoint ) <= tracker.dblClickDistThreshold ) { tracker.dblClickHandler( { eventSource: tracker, pointerType: updateGPoint.type, position: getPointRelativeToAbsolute( updateGPoint.currentPos, tracker.element ), shift: eventInfo.originalEvent.shiftKey, isTouchEvent: updateGPoint.type === 'touch', originalEvent: eventInfo.originalEvent, userData: tracker.userData } ); } delegate.lastClickPos = null; } } } } 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 eventInfo.shouldReleaseCapture = false; // Release (pressed in another element) if ( tracker.releaseHandler && releasePoint ) { 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: eventInfo.originalEvent, userData: tracker.userData } ); eventInfo.preventDefault = true; } } } } /** * 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 {OpenSeadragon.MouseTracker.EventProcessInfo} eventInfo * Processing info for originating DOM event. * @param {OpenSeadragon.MouseTracker.GesturePoint} gPoint * Gesture points associated with the event. */ function updatePointerMove( tracker, eventInfo, gPoint ) { var delegate = THIS[ tracker.hash ], pointsList = tracker.getActivePointersListByType( gPoint.type ), updateGPoint, gPointArray, delta; if ( typeof eventInfo.originalEvent.buttons !== 'undefined' ) { pointsList.buttons = eventInfo.originalEvent.buttons; } updateGPoint = pointsList.getById( gPoint.id ); if ( updateGPoint ) { // Already tracking the pointer...update it updateGPoint.lastPos = updateGPoint.currentPos; updateGPoint.lastTime = updateGPoint.currentTime; updateGPoint.currentPos = gPoint.currentPos; updateGPoint.currentTime = gPoint.currentTime; } else { // Should never get here, but due to user agent bugs (e.g. legacy touch) it sometimes happens return; } eventInfo.shouldCapture = false; eventInfo.shouldReleaseCapture = false; // Stop (mouse only) if ( tracker.stopHandler && gPoint.type === 'mouse' ) { clearTimeout( tracker.stopTimeOut ); tracker.stopTimeOut = setTimeout( function() { handlePointerStop( tracker, eventInfo.originalEvent, gPoint.type ); }, tracker.stopDelay ); } if ( pointsList.contacts === 0 ) { // Move (no contacts: hovering mouse or other hover-capable device) if ( tracker.moveHandler ) { tracker.moveHandler( { eventSource: tracker, pointerType: gPoint.type, position: getPointRelativeToAbsolute( gPoint.currentPos, tracker.element ), buttons: pointsList.buttons, isTouchEvent: gPoint.type === 'touch', originalEvent: eventInfo.originalEvent, userData: tracker.userData } ); } } else if ( pointsList.contacts === 1 ) { // Move (1 contact) if ( tracker.moveHandler ) { updateGPoint = pointsList.asArray()[ 0 ]; tracker.moveHandler( { eventSource: tracker, pointerType: updateGPoint.type, position: getPointRelativeToAbsolute( updateGPoint.currentPos, tracker.element ), buttons: pointsList.buttons, isTouchEvent: updateGPoint.type === 'touch', originalEvent: eventInfo.originalEvent, userData: tracker.userData } ); } // Drag if ( tracker.dragHandler && !eventInfo.preventGesture && !eventInfo.defaultPrevented ) { updateGPoint = pointsList.asArray()[ 0 ]; delta = updateGPoint.currentPos.minus( updateGPoint.lastPos ); 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: eventInfo.originalEvent.shiftKey, isTouchEvent: updateGPoint.type === 'touch', originalEvent: eventInfo.originalEvent, userData: tracker.userData } ); eventInfo.preventDefault = true; delegate.sentDragEvent = true; } } else if ( pointsList.contacts === 2 ) { // Move (2 contacts, use center) if ( tracker.moveHandler ) { gPointArray = pointsList.asArray(); 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: eventInfo.originalEvent, userData: tracker.userData } ); } // Pinch if ( tracker.pinchHandler && gPoint.type === 'touch' && !eventInfo.preventGesture && !eventInfo.defaultPrevented ) { 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 ); 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: eventInfo.originalEvent.shiftKey, originalEvent: eventInfo.originalEvent, userData: tracker.userData } ); eventInfo.preventDefault = true; } } } } /** * @function * @private * @inner * @param {OpenSeadragon.MouseTracker} tracker * A reference to the MouseTracker instance. * @param {OpenSeadragon.MouseTracker.EventProcessInfo} eventInfo * Processing info for originating DOM event. * @param {OpenSeadragon.MouseTracker.GesturePoint} gPoint * Gesture points associated with the event. */ function updatePointerCancel( tracker, eventInfo, gPoint ) { var pointsList = tracker.getActivePointersListByType( gPoint.type ), updateGPoint; updateGPoint = pointsList.getById( gPoint.id ); if ( updateGPoint ) { stopTrackingPointer( tracker, pointsList, updateGPoint ); } } /** * @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, userData: tracker.userData } ); } } }(OpenSeadragon));