openseadragon/src/mousetracker.js

1437 lines
49 KiB
JavaScript
Raw Normal View History

/*
* OpenSeadragon - MouseTracker
*
* Copyright (C) 2009 CodePlex Foundation
* Copyright (C) 2010-2013 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 ( $ ) {
// is any button currently being pressed while mouse events occur
var IS_BUTTON_DOWN = false,
// is any tracker currently capturing?
IS_CAPTURING = false,
// dictionary from hash to MouseTracker
ACTIVE = {},
// list of trackers interested in capture
CAPTURING = [],
// dictionary from hash to private properties
THIS = {};
/**
* The MouseTracker allows other classes to set handlers for common mouse
* events on a specific element like, 'enter', 'exit', 'press', 'release',
* 'scroll', 'click', and 'drag'.
* @class
* @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 'elements', 'clickTimeThreshold',
* and 'clickDistThreshold' in that order.
* @param {Element|String} options.element
* A reference to an element or an element id for which the mouse
* events will be monitored.
* @param {Number} options.clickTimeThreshold
2013-10-23 00:54:04 +04:00
* The number of milliseconds within which multiple mouse clicks
* will be treated as a single event.
* @param {Number} options.clickDistThreshold
* The distance between mouse click within multiple mouse clicks
* will be treated as a single event.
2013-10-23 00:54:04 +04:00
* @param {Number} options.stopDelay
* The number of milliseconds without mouse move before the mouse stop
* event is fired.
* @param {Function} options.enterHandler
* An optional handler for mouse enter.
* @param {Function} options.exitHandler
* An optional handler for mouse exit.
* @param {Function} options.pressHandler
* An optional handler for mouse press.
* @param {Function} options.releaseHandler
* An optional handler for mouse release.
* @param {Function} options.moveHandler
* An optional handler for mouse move.
* @param {Function} options.scrollHandler
* An optional handler for mouse scroll.
* @param {Function} options.clickHandler
* An optional handler for mouse click.
* @param {Function} options.dragHandler
* An optional handler for mouse drag.
* @param {Function} options.keyHandler
* An optional handler for keypress.
* @param {Function} options.focusHandler
* An optional handler for focus.
* @param {Function} options.blurHandler
* An optional handler for blur.
* @param {Object} [options.userData=null]
* Arbitrary object to be passed unchanged to any attached handler methods.
* @property {Number} hash
* An unique hash for this tracker.
* @property {Element} element
* The element for which mouse event are being monitored.
* @property {Number} clickTimeThreshold
* The number of milliseconds within which mutliple mouse clicks
* will be treated as a single event.
* @property {Number} clickDistThreshold
* The distance between mouse click within multiple mouse clicks
* will be treated as a single event.
*/
$.MouseTracker = function ( options ) {
var args = arguments;
if ( !$.isPlainObject( options ) ) {
options = {
element: args[ 0 ],
clickTimeThreshold: args[ 1 ],
clickDistThreshold: args[ 2 ]
};
}
this.hash = Math.random();
this.element = $.getElement( options.element );
this.clickTimeThreshold = options.clickTimeThreshold;
this.clickDistThreshold = options.clickDistThreshold;
this.userData = options.userData || null;
2013-10-23 00:54:04 +04:00
this.stopDelay = options.stopDelay || 50;
this.enterHandler = options.enterHandler || null;
this.exitHandler = options.exitHandler || null;
this.pressHandler = options.pressHandler || null;
this.releaseHandler = options.releaseHandler || null;
this.moveHandler = options.moveHandler || null;
this.scrollHandler = options.scrollHandler || null;
this.clickHandler = options.clickHandler || null;
this.dragHandler = options.dragHandler || null;
2013-10-23 00:54:04 +04:00
this.stopHandler = options.stopHandler || null;
this.keyHandler = options.keyHandler || null;
this.focusHandler = options.focusHandler || null;
this.blurHandler = options.blurHandler || null;
//Store private properties in a scope sealed hash map
var _this = this;
/**
* @private
* @property {Boolean} tracking
* Are we currently tracking mouse events.
* @property {Boolean} capturing
* Are we curruently capturing mouse events.
* @property {Boolean} insideElementPressed
* True if the left mouse button is currently being pressed and was
* initiated inside the tracked element, otherwise false.
* @property {Boolean} insideElement
* Are we currently inside the screen area of the tracked element.
* @property {OpenSeadragon.Point} lastPoint
* Position of last mouse down/move
* @property {Number} lastMouseDownTime
* Time of last mouse down.
* @property {OpenSeadragon.Point} lastMouseDownPoint
* Position of last mouse down
*/
THIS[ this.hash ] = {
mouseover: function ( event ) { onMouseOver( _this, event, false ); },
mouseout: function ( event ) { onMouseOut( _this, event, false ); },
mousedown: function ( event ) { onMouseDown( _this, event ); },
mouseup: function ( event ) { onMouseUp( _this, event, false ); },
mousemove: function ( event ) { onMouseMove( _this, event ); },
click: function ( event ) { onMouseClick( _this, event ); },
wheel: function ( event ) { onWheel( _this, event ); },
mousewheel: function ( event ) { onMouseWheel( _this, event ); },
DOMMouseScroll: function ( event ) { onMouseWheel( _this, event ); },
MozMousePixelScroll: function ( event ) { onMouseWheel( _this, event ); },
mouseupie: function ( event ) { onMouseUpIE( _this, event ); },
mousemovecapturedie: function ( event ) { onMouseMoveCapturedIE( _this, event ); },
mouseupcaptured: function ( event ) { onMouseUpCaptured( _this, event ); },
mousemovecaptured: function ( event ) { onMouseMoveCaptured( _this, event, false ); },
touchstart: function ( event ) { onTouchStart( _this, event ); },
touchmove: function ( event ) { onTouchMove( _this, event ); },
touchend: function ( event ) { onTouchEnd( _this, event ); },
keypress: function ( event ) { onKeyPress( _this, event ); },
focus: function ( event ) { onFocus( _this, event ); },
blur: function ( event ) { onBlur( _this, event ); },
tracking: false,
capturing: false,
insideElementPressed: false,
insideElement: false,
lastPoint: null,
lastMouseDownTime: null,
lastMouseDownPoint: null,
lastPinchDelta: 0
};
};
$.MouseTracker.prototype = {
/**
* Clean up any events or objects created by the mouse tracker.
* @function
*/
destroy: function () {
stopTracking( this );
this.element = null;
},
/**
* 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 );
2011-12-14 05:04:38 +04:00
} else {
stopTracking( this );
2011-12-14 05:04:38 +04:00
}
//chain
return this;
},
/**
2013-10-31 05:28:10 +04:00
* 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 {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.
* @param {Boolean} event.isTouchEvent
* True if the original event is a touch event, otherwise false.
* @param {Object} event.originalEvent
* The original event object.
* @param {Object} event.userData
* Arbitrary user-defined object.
*/
enterHandler: function () { },
/**
2013-10-31 05:28:10 +04:00
* 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 {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.
* @param {Boolean} event.isTouchEvent
* True if the original event is a touch event, otherwise false.
* @param {Object} event.originalEvent
* The original event object.
* @param {Object} event.userData
* Arbitrary user-defined object.
*/
exitHandler: function () { },
/**
2013-10-31 05:28:10 +04:00
* 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 {Boolean} event.isTouchEvent
* True if the original event is a touch event, otherwise false.
* @param {Object} event.originalEvent
* The original event object.
* @param {Object} event.userData
* Arbitrary user-defined object.
*/
pressHandler: function () { },
/**
2013-10-31 05:28:10 +04:00
* 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 {Boolean} event.insideElementPressed
* True if the left mouse button is currently being pressed and was
* initiated inside the tracked element, otherwise false.
* @param {Boolean} event.insideElementReleased
* True if the cursor still inside the tracked element when the button was released.
* @param {Boolean} event.isTouchEvent
* True if the original event is a touch event, otherwise false.
* @param {Object} event.originalEvent
* The original event object.
* @param {Object} event.userData
* Arbitrary user-defined object.
*/
releaseHandler: function () { },
/**
2013-10-31 05:28:10 +04:00
* 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 {Boolean} event.isTouchEvent
* True if the original event is a touch event, otherwise false.
* @param {Object} event.originalEvent
* The original event object.
* @param {Object} event.userData
* Arbitrary user-defined object.
*/
moveHandler: function () { },
/**
2013-10-31 05:28:10 +04:00
* 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 {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.
* @param {Object} event.originalEvent
* The original event object.
* @param {Object} event.userData
* Arbitrary user-defined object.
*/
scrollHandler: function () { },
/**
2013-10-31 05:28:10 +04:00
* 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 {Number} event.quick
* True only if the clickDistThreshold and clickDeltaThreshold are both pased. Useful for ignoring events.
* @param {Boolean} event.shift
* True if the shift key was pressed during this event.
* @param {Boolean} event.isTouchEvent
* True if the original event is a touch event, otherwise false.
* @param {Object} event.originalEvent
* The original event object.
* @param {Object} event.userData
* Arbitrary user-defined object.
*/
clickHandler: function () { },
/**
2013-10-31 05:28:10 +04:00
* 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 {OpenSeadragon.Point} event.delta
* The x,y components of the difference between start drag and end drag. Usefule for ignoring or weighting the 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.
* @param {Object} event.originalEvent
* The original event object.
* @param {Object} event.userData
* Arbitrary user-defined object.
*/
dragHandler: function () { },
2012-03-16 19:36:28 +04:00
/**
2013-10-23 00:54:04 +04:00
* 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 {Boolean} event.isTouchEvent
* True if the original event is a touch event, otherwise false.
* @param {Object} event.originalEvent
* The original event object.
* @param {Object} event.userData
* Arbitrary user-defined object.
*/
stopHandler: function () { },
2012-03-16 19:36:28 +04:00
/**
2013-10-31 05:28:10 +04:00
* 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.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.
*/
keyHandler: function () { },
/**
2013-10-31 05:28:10 +04:00
* 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 () { },
/**
2013-10-31 05:28:10 +04:00
* 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 () { }
};
/**
* Detect available mouse wheel event.
*/
$.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
/**
* Starts tracking mouse events on this element.
* @private
* @inner
*/
function startTracking( tracker ) {
var events = [
"mouseover", "mouseout", "mousedown", "mouseup", "mousemove",
"click",
$.MouseTracker.wheelEventName,
2012-03-16 19:36:28 +04:00
"touchstart", "touchmove", "touchend",
"keypress",
"focus", "blur"
],
delegate = THIS[ tracker.hash ],
event,
i;
// Add 'MozMousePixelScroll' event handler for older Firefox
if( $.MouseTracker.wheelEventName == "DOMMouseScroll" ) {
events.push( "MozMousePixelScroll" );
}
if ( !delegate.tracking ) {
for ( i = 0; i < events.length; i++ ) {
event = events[ i ];
$.addEvent(
tracker.element,
event,
delegate[ event ],
false
);
}
delegate.tracking = true;
ACTIVE[ tracker.hash ] = tracker;
}
}
/**
* Stops tracking mouse events on this element.
* @private
* @inner
*/
function stopTracking( tracker ) {
var events = [
"mouseover", "mouseout", "mousedown", "mouseup", "mousemove",
"click",
$.MouseTracker.wheelEventName,
2012-03-16 19:36:28 +04:00
"touchstart", "touchmove", "touchend",
"keypress",
"focus", "blur"
],
delegate = THIS[ tracker.hash ],
event,
i;
// Remove 'MozMousePixelScroll' event handler for older Firefox
if( $.MouseTracker.wheelEventName == "DOMMouseScroll" ) {
events.push( "MozMousePixelScroll" );
}
if ( delegate.tracking ) {
for ( i = 0; i < events.length; i++ ) {
event = events[ i ];
$.removeEvent(
tracker.element,
event,
delegate[ event ],
false
);
}
releaseMouse( tracker );
delegate.tracking = false;
delete ACTIVE[ tracker.hash ];
}
}
/**
* @private
* @inner
*/
function hasMouse( tracker ) {
return THIS[ tracker.hash ].insideElement;
}
/**
* Begin capturing mouse events on this element.
* @private
* @inner
*/
function captureMouse( tracker ) {
var delegate = THIS[ tracker.hash ];
if ( !delegate.capturing ) {
if ( $.Browser.vendor == $.BROWSERS.IE && $.Browser.version < 9 ) {
$.removeEvent(
tracker.element,
"mouseup",
delegate.mouseup,
false
);
$.addEvent(
tracker.element,
"mouseup",
delegate.mouseupie,
true
);
$.addEvent(
tracker.element,
"mousemove",
delegate.mousemovecapturedie,
true
);
} else {
$.addEvent(
window,
"mouseup",
delegate.mouseupcaptured,
true
);
$.addEvent(
window,
"mousemove",
delegate.mousemovecaptured,
true
);
}
delegate.capturing = true;
}
}
/**
* Stop capturing mouse events on this element.
* @private
* @inner
*/
function releaseMouse( tracker ) {
var delegate = THIS[ tracker.hash ];
if ( delegate.capturing ) {
if ( $.Browser.vendor == $.BROWSERS.IE && $.Browser.version < 9 ) {
$.removeEvent(
tracker.element,
"mousemove",
delegate.mousemovecapturedie,
true
);
$.removeEvent(
tracker.element,
"mouseup",
delegate.mouseupie,
true
);
$.addEvent(
tracker.element,
"mouseup",
delegate.mouseup,
false
);
} else {
$.removeEvent(
window,
"mousemove",
delegate.mousemovecaptured,
true
);
$.removeEvent(
window,
"mouseup",
delegate.mouseupcaptured,
true
);
}
delegate.capturing = false;
}
}
/**
* @private
* @inner
*/
function triggerOthers( tracker, handler, event, isTouch ) {
var otherHash;
for ( otherHash in ACTIVE ) {
if ( ACTIVE.hasOwnProperty( otherHash ) && tracker.hash != otherHash ) {
handler( ACTIVE[ otherHash ], event, isTouch );
}
}
}
2012-03-16 19:36:28 +04:00
/**
* @private
* @inner
*/
function onFocus( tracker, event ) {
//console.log( "focus %s", event );
var propagate;
2012-03-16 19:36:28 +04:00
if ( tracker.focusHandler ) {
propagate = tracker.focusHandler(
{
eventSource: tracker,
originalEvent: event,
userData: tracker.userData
}
);
if ( propagate === false ) {
$.cancelEvent( event );
2012-03-16 19:36:28 +04:00
}
}
}
2012-03-16 19:36:28 +04:00
/**
* @private
* @inner
*/
function onBlur( tracker, event ) {
//console.log( "blur %s", event );
var propagate;
2012-03-16 19:36:28 +04:00
if ( tracker.blurHandler ) {
propagate = tracker.blurHandler(
{
eventSource: tracker,
originalEvent: event,
userData: tracker.userData
}
);
if ( propagate === false ) {
$.cancelEvent( event );
2012-03-16 19:36:28 +04:00
}
}
}
2012-03-16 19:36:28 +04:00
2012-03-16 19:36:28 +04:00
/**
* @private
* @inner
*/
function onKeyPress( tracker, event ) {
//console.log( "keypress %s %s %s %s %s", event.keyCode, event.charCode, event.ctrlKey, event.shiftKey, event.altKey );
var propagate;
2012-03-16 19:36:28 +04:00
if ( tracker.keyHandler ) {
propagate = tracker.keyHandler(
{
eventSource: tracker,
position: getMouseRelative( event, tracker.element ),
keyCode: event.keyCode ? event.keyCode : event.charCode,
shift: event.shiftKey,
originalEvent: event,
userData: tracker.userData
}
);
if ( !propagate ) {
$.cancelEvent( event );
2012-03-16 19:36:28 +04:00
}
}
}
2012-03-16 19:36:28 +04:00
/**
* @private
* @inner
*/
function onMouseOver( tracker, event, isTouch ) {
var delegate = THIS[ tracker.hash ],
propagate;
isTouch = isTouch || false;
event = $.getEvent( event );
if ( !isTouch ) {
if ( $.Browser.vendor == $.BROWSERS.IE &&
$.Browser.version < 9 &&
delegate.capturing &&
!isChild( event.srcElement, tracker.element ) ) {
triggerOthers( tracker, onMouseOver, event, isTouch );
}
var to = event.target ?
event.target :
event.srcElement,
from = event.relatedTarget ?
event.relatedTarget :
event.fromElement;
if ( !isChild( tracker.element, to ) ||
isChild( tracker.element, from ) ) {
return;
}
}
delegate.insideElement = true;
if ( tracker.enterHandler ) {
propagate = tracker.enterHandler(
{
eventSource: tracker,
position: getMouseRelative( isTouch ? event.changedTouches[ 0 ] : event, tracker.element ),
insideElementPressed: delegate.insideElementPressed,
buttonDownAny: IS_BUTTON_DOWN,
isTouchEvent: isTouch,
originalEvent: event,
userData: tracker.userData
}
);
if ( propagate === false ) {
$.cancelEvent( event );
}
}
}
/**
* @private
* @inner
*/
function onMouseOut( tracker, event, isTouch ) {
var delegate = THIS[ tracker.hash ],
propagate;
isTouch = isTouch || false;
event = $.getEvent( event );
if ( !isTouch ) {
if ( $.Browser.vendor == $.BROWSERS.IE &&
$.Browser.version < 9 &&
delegate.capturing &&
!isChild( event.srcElement, tracker.element ) ) {
triggerOthers( tracker, onMouseOut, event, isTouch );
}
var from = event.target ?
event.target :
event.srcElement,
to = event.relatedTarget ?
event.relatedTarget :
event.toElement;
if ( !isChild( tracker.element, from ) ||
isChild( tracker.element, to ) ) {
return;
}
}
delegate.insideElement = false;
if ( tracker.exitHandler ) {
propagate = tracker.exitHandler(
{
eventSource: tracker,
position: getMouseRelative( isTouch ? event.changedTouches[ 0 ] : event, tracker.element ),
insideElementPressed: delegate.insideElementPressed,
buttonDownAny: IS_BUTTON_DOWN,
isTouchEvent: isTouch,
originalEvent: event,
userData: tracker.userData
}
);
if ( propagate === false ) {
$.cancelEvent( event );
}
}
}
/**
* @private
* @inner
*/
function onMouseDown( tracker, event, noCapture, isTouch ) {
var delegate = THIS[ tracker.hash ],
propagate;
isTouch = isTouch || false;
event = $.getEvent(event);
var eventOrTouchPoint = isTouch ? event.touches[ 0 ] : event;
if ( event.button == 2 ) {
return;
}
delegate.insideElementPressed = true;
delegate.lastPoint = getMouseAbsolute( eventOrTouchPoint );
delegate.lastMouseDownPoint = delegate.lastPoint;
delegate.lastMouseDownTime = $.now();
if ( tracker.pressHandler ) {
propagate = tracker.pressHandler(
{
eventSource: tracker,
position: getMouseRelative( eventOrTouchPoint, tracker.element ),
isTouchEvent: isTouch,
originalEvent: event,
userData: tracker.userData
}
);
if ( propagate === false ) {
$.cancelEvent( event );
}
}
if ( tracker.pressHandler || tracker.dragHandler ) {
$.cancelEvent( event );
}
if ( noCapture ) {
return;
}
if ( isTouch ||
!( $.Browser.vendor == $.BROWSERS.IE && $.Browser.version < 9 ) ||
!IS_CAPTURING ) {
captureMouse( tracker );
IS_CAPTURING = true;
// reset to empty & add us
CAPTURING = [ tracker ];
} else if ( $.Browser.vendor == $.BROWSERS.IE && $.Browser.version < 9 ) {
// add us to the list
CAPTURING.push( tracker );
}
}
/**
* @private
* @inner
*/
function onTouchStart( tracker, event ) {
var touchA,
touchB;
if ( event.touches.length == 1 &&
event.targetTouches.length == 1 &&
event.changedTouches.length == 1 ) {
THIS[ tracker.hash ].lastTouch = event.touches[ 0 ];
onMouseOver( tracker, event, true );
// call with no capture as the onMouseMoveCaptured will
// be triggered by onTouchMove
onMouseDown( tracker, event, true, true );
}
if ( event.touches.length == 2 ) {
touchA = getMouseAbsolute( event.touches[ 0 ] );
touchB = getMouseAbsolute( event.touches[ 1 ] );
THIS[ tracker.hash ].lastPinchDelta =
Math.abs( touchA.x - touchB.x ) +
Math.abs( touchA.y - touchB.y );
THIS[ tracker.hash ].pinchMidpoint = new $.Point(
( touchA.x + touchB.x ) / 2,
( touchA.y + touchB.y ) / 2
);
//$.console.debug("pinch start : "+THIS[ tracker.hash ].lastPinchDelta);
}
event.preventDefault();
}
/**
* @private
* @inner
*/
function onMouseUp( tracker, event, isTouch ) {
var delegate = THIS[ tracker.hash ],
//were we inside the tracked element when we were pressed
insideElementPressed = delegate.insideElementPressed,
//are we still inside the tracked element when we released
insideElementReleased = delegate.insideElement,
propagate;
isTouch = isTouch || false;
event = $.getEvent(event);
if ( event.button == 2 ) {
return;
}
delegate.insideElementPressed = false;
if ( tracker.releaseHandler ) {
propagate = tracker.releaseHandler(
{
eventSource: tracker,
position: getMouseRelative( isTouch ? event.changedTouches[ 0 ] : event, tracker.element ),
insideElementPressed: insideElementPressed,
insideElementReleased: insideElementReleased,
isTouchEvent: isTouch,
originalEvent: event,
userData: tracker.userData
}
);
if ( propagate === false ) {
$.cancelEvent( event );
}
}
if ( insideElementPressed && insideElementReleased ) {
handleMouseClick( tracker, event, isTouch );
}
}
/**
* @private
* @inner
*/
function onTouchEnd( tracker, event ) {
if ( event.touches.length === 0 &&
event.targetTouches.length === 0 &&
event.changedTouches.length == 1 ) {
THIS[ tracker.hash ].lastTouch = null;
// call with no release, as the mouse events are
// not registered in onTouchStart
onMouseUpCaptured( tracker, event, true, true );
onMouseOut( tracker, event, true );
}
if ( event.touches.length + event.changedTouches.length == 2 ) {
THIS[ tracker.hash ].lastPinchDelta = null;
THIS[ tracker.hash ].pinchMidpoint = null;
//$.console.debug("pinch end");
}
event.preventDefault();
}
/**
* Only triggered once by the deepest element that initially received
* the mouse down event. We want to make sure THIS event doesn't bubble.
* Instead, we want to trigger the elements that initially received the
* mouse down event (including this one) only if the mouse is no longer
* inside them. Then, we want to release capture, and emulate a regular
* mouseup on the event that this event was meant for.
* @private
* @inner
*/
function onMouseUpIE( tracker, event ) {
var othertracker,
i;
event = $.getEvent( event );
if ( event.button == 2 ) {
return;
}
for ( i = 0; i < CAPTURING.length; i++ ) {
othertracker = CAPTURING[ i ];
if ( !hasMouse( othertracker ) ) {
onMouseUp( othertracker, event, false );
}
}
releaseMouse( tracker );
IS_CAPTURING = false;
event.srcElement.fireEvent(
"on" + event.type,
document.createEventObject( event )
);
$.stopEvent( event );
}
/**
* Only triggered in W3C browsers by elements within which the mouse was
* initially pressed, since they are now listening to the window for
* mouseup during the capture phase. We shouldn't handle the mouseup
* here if the mouse is still inside this element, since the regular
* mouseup handler will still fire.
* @private
* @inner
*/
function onMouseUpCaptured( tracker, event, noRelease, isTouch ) {
isTouch = isTouch || false;
2013-10-03 23:11:33 +04:00
if ( !THIS[ tracker.hash ].insideElement || isTouch ) {
onMouseUp( tracker, event, isTouch );
}
if ( noRelease ) {
return;
}
releaseMouse( tracker );
}
/**
* @private
* @inner
*/
function onMouseMove( tracker, event ) {
if ( tracker.moveHandler ) {
event = $.getEvent( event );
var propagate = tracker.moveHandler(
{
eventSource: tracker,
position: getMouseRelative( event, tracker.element ),
isTouchEvent: false,
originalEvent: event,
userData: tracker.userData
}
);
if ( propagate === false ) {
$.cancelEvent( event );
}
}
2013-10-23 00:54:04 +04:00
if ( tracker.stopHandler ) {
clearTimeout( tracker.stopTimeOut );
tracker.stopTimeOut = setTimeout( function() {
onMouseStop( tracker, event );
}, tracker.stopDelay );
}
}
2013-10-23 00:54:04 +04:00
/**
* @private
* @inner
*/
2013-11-01 00:16:08 +04:00
function onMouseStop( tracker, originalMoveEvent ) {
2013-10-23 00:54:04 +04:00
if ( tracker.stopHandler ) {
2013-10-31 05:28:10 +04:00
tracker.stopHandler( {
2013-10-23 00:54:04 +04:00
eventSource: tracker,
2013-11-01 00:16:08 +04:00
position: getMouseRelative( originalMoveEvent, tracker.element ),
2013-10-23 00:54:04 +04:00
isTouchEvent: false,
2013-11-01 00:16:08 +04:00
originalEvent: originalMoveEvent,
2013-10-23 00:54:04 +04:00
userData: tracker.userData
} );
}
}
/**
* @private
* @inner
*/
function onMouseClick( tracker, event ) {
if ( tracker.clickHandler ) {
$.cancelEvent( event );
}
}
/**
* Handler for 'wheel' events
*
* @private
* @inner
*/
function onWheel( tracker, event ) {
handleWheelEvent( tracker, event, event, false );
}
/**
* Handler for 'mousewheel', 'DOMMouseScroll', and 'MozMousePixelScroll' events
*
* @private
* @inner
*/
function onMouseWheel( tracker, event ) {
// For legacy IE, access the global (window) event object
event = event || window.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 = - 1 / $.DEFAULT_SETTINGS.pixelsPerWheelLine * event.wheelDelta;
} else {
simulatedEvent.deltaY = event.detail;
}
handleWheelEvent( tracker, simulatedEvent, event, false );
}
/**
* Handles 'wheel' events.
* The event may be simulated by the legacy mouse wheel event handler (onMouseWheel()) or onTouchMove().
*
* @private
* @inner
*/
function handleWheelEvent( tracker, event, originalEvent, isTouch ) {
var nDelta = 0,
propagate;
isTouch = isTouch || false;
// The nDelta variable is gated to provide smooth z-index scrolling
// since the mouse wheel allows for substantial deltas meant for rapid
// y-index scrolling.
// 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;
if ( tracker.scrollHandler ) {
propagate = tracker.scrollHandler(
{
eventSource: tracker,
position: getMouseRelative( event, tracker.element ),
scroll: nDelta,
shift: event.shiftKey,
isTouchEvent: isTouch,
originalEvent: originalEvent,
userData: tracker.userData
}
);
if ( propagate === false ) {
$.cancelEvent( originalEvent );
}
}
}
/**
* @private
* @inner
*/
function handleMouseClick( tracker, event, isTouch ) {
var delegate = THIS[ tracker.hash ],
propagate;
isTouch = isTouch || false;
event = $.getEvent( event );
var eventOrTouchPoint = isTouch ? event.changedTouches[ 0 ] : event;
if ( event.button == 2 ) {
return;
}
var time = $.now() - delegate.lastMouseDownTime,
point = getMouseAbsolute( eventOrTouchPoint ),
distance = delegate.lastMouseDownPoint.distanceTo( point ),
quick = time <= tracker.clickTimeThreshold &&
distance <= tracker.clickDistThreshold;
if ( tracker.clickHandler ) {
propagate = tracker.clickHandler(
{
eventSource: tracker,
position: getMouseRelative( eventOrTouchPoint, tracker.element ),
quick: quick,
shift: event.shiftKey,
isTouchEvent: isTouch,
originalEvent: event,
userData: tracker.userData
}
);
if ( propagate === false ) {
$.cancelEvent( event );
}
}
}
/**
* @private
* @inner
*/
function onMouseMoveCaptured( tracker, event, isTouch ) {
var delegate = THIS[ tracker.hash ],
delta,
propagate,
point;
isTouch = isTouch || false;
event = $.getEvent(event);
var eventOrTouchPoint = isTouch ? event.touches[ 0 ] : event;
point = getMouseAbsolute( eventOrTouchPoint );
delta = point.minus( delegate.lastPoint );
delegate.lastPoint = point;
if ( tracker.dragHandler ) {
propagate = tracker.dragHandler(
{
eventSource: tracker,
position: getMouseRelative( eventOrTouchPoint, tracker.element ),
delta: delta,
shift: event.shiftKey,
isTouchEvent: isTouch,
originalEvent: event,
userData: tracker.userData
}
);
if ( propagate === false ) {
$.cancelEvent( event );
}
}
}
/**
* @private
* @inner
*/
function onTouchMove( tracker, event ) {
var touchA,
touchB,
pinchDelta;
if ( !THIS[ tracker.hash ].lastTouch ) {
return;
}
if ( event.touches.length === 1 &&
event.targetTouches.length === 1 &&
event.changedTouches.length === 1 &&
THIS[ tracker.hash ].lastTouch.identifier === event.touches[ 0 ].identifier ) {
onMouseMoveCaptured( tracker, event, true );
} else if ( event.touches.length === 2 ) {
touchA = getMouseAbsolute( event.touches[ 0 ] );
touchB = getMouseAbsolute( event.touches[ 1 ] );
pinchDelta =
Math.abs( touchA.x - touchB.x ) +
Math.abs( touchA.y - touchB.y );
//TODO: make the 75px pinch threshold configurable
if ( Math.abs( THIS[ tracker.hash ].lastPinchDelta - pinchDelta ) > 75 ) {
//$.console.debug( "pinch delta : " + pinchDelta + " | previous : " + THIS[ tracker.hash ].lastPinchDelta);
// Simulate a 'wheel' event
var simulatedEvent = {
target: event.target || event.srcElement,
type: "wheel",
shiftKey: event.shiftKey || false,
clientX: THIS[ tracker.hash ].pinchMidpoint.x,
clientY: THIS[ tracker.hash ].pinchMidpoint.y,
pageX: THIS[ tracker.hash ].pinchMidpoint.x,
pageY: THIS[ tracker.hash ].pinchMidpoint.y,
deltaMode: 1, // 0=pixel, 1=line, 2=page
deltaX: 0,
deltaY: ( THIS[ tracker.hash ].lastPinchDelta > pinchDelta ) ? 1 : -1,
deltaZ: 0
};
handleWheelEvent( tracker, simulatedEvent, event, true );
THIS[ tracker.hash ].lastPinchDelta = pinchDelta;
}
}
event.preventDefault();
}
/**
* Only triggered once by the deepest element that initially received
* the mouse down event. Since no other element has captured the mouse,
* we want to trigger the elements that initially received the mouse
* down event (including this one). The the param tracker isn't used
* but for consistency with the other event handlers we include it.
* @private
* @inner
*/
function onMouseMoveCapturedIE( tracker, event ) {
var i;
for ( i = 0; i < CAPTURING.length; i++ ) {
onMouseMoveCaptured( CAPTURING[ i ], event, false );
}
$.stopEvent( event );
}
/**
* @private
* @inner
*/
2011-12-14 05:04:38 +04:00
function getMouseAbsolute( event ) {
return $.getMousePosition( event );
}
/**
* @private
* @inner
*/
function getMouseRelative( event, element ) {
var mouse = $.getMousePosition( event ),
offset = $.getElementOffset( element );
return mouse.minus( offset );
}
2011-12-14 05:04:38 +04:00
/**
* @private
* @inner
* Returns true if elementB is a child node of elementA, or if they're equal.
*/
function isChild( elementA, elementB ) {
2011-12-14 05:04:38 +04:00
var body = document.body;
while ( elementB && elementA != elementB && body != elementB ) {
2011-12-14 05:04:38 +04:00
try {
elementB = elementB.parentNode;
} catch ( e ) {
2011-12-14 05:04:38 +04:00
return false;
}
2011-12-14 05:04:38 +04:00
}
return elementA == elementB;
}
2011-12-14 05:04:38 +04:00
/**
* @private
* @inner
*/
2011-12-14 05:04:38 +04:00
function onGlobalMouseDown() {
IS_BUTTON_DOWN = true;
}
2011-12-14 05:04:38 +04:00
/**
* @private
* @inner
*/
2011-12-14 05:04:38 +04:00
function onGlobalMouseUp() {
IS_BUTTON_DOWN = false;
}
2011-12-14 05:04:38 +04:00
(function () {
if ( $.Browser.vendor == $.BROWSERS.IE && $.Browser.version < 9 ) {
$.addEvent( document, "mousedown", onGlobalMouseDown, false );
$.addEvent( document, "mouseup", onGlobalMouseUp, false );
2011-12-14 05:04:38 +04:00
} else {
$.addEvent( window, "mousedown", onGlobalMouseDown, true );
$.addEvent( window, "mouseup", onGlobalMouseUp, true );
2011-12-14 05:04:38 +04:00
}
} )();
} ( OpenSeadragon ) );