diff --git a/changelog.txt b/changelog.txt index d61e154f..40df736f 100644 --- a/changelog.txt +++ b/changelog.txt @@ -8,7 +8,6 @@ OPENSEADRAGON CHANGELOG * The overlays elements are no longer accessible via viewer.canvas.childNodes but via viewer.overlaysContainer.childNodes or viewer.currentOverlays[i].element. * BREAKING CHANGE: Pseudo full screen mode on IE<11 using activex has been dropped. OpenSeadragon will run in full page if full screen mode is requested. * BREAKING CHANGE: MouseTracker touch pinch gestures are no longer converted to scroll events. MouseTracker.pinchHandler should be used instead. (#369) -* BREAKING CHANGE: MouseTracker now uses pointer enter/leave DOM events instead of pointer over/out DOM events for generating enterHandler/exitHandler events and for tracking pointers inside the hit-test bounds of the tracked element. (#369) * DEPRECATION: overlay functions have been moved from Drawer to Viewer (#331) * DEPRECATION: OpenSeadragon.cancelFullScreen has been renamed OpenSeadragon.exitFullScreen (#358) * DEPRECATION: The 'isTouchEvent' property passed in MouseTracker events is deprecated and has been replaced with 'pointerType', which is a String value "mouse", "touch", "pen", etc. to support multiple simultaneous pointing devices (#369) diff --git a/src/mousetracker.js b/src/mousetracker.js index 09a5a8f5..a0f25a42 100644 --- a/src/mousetracker.js +++ b/src/mousetracker.js @@ -163,6 +163,8 @@ DOMMouseScroll: function ( event ) { onMouseWheel( _this, event ); }, MozMousePixelScroll: function ( event ) { onMouseWheel( _this, event ); }, + mouseover: function ( event ) { onMouseOver( _this, event ); }, + mouseout: function ( event ) { onMouseOut( _this, event ); }, mouseenter: function ( event ) { onMouseEnter( _this, event ); }, mouseleave: function ( event ) { onMouseLeave( _this, event ); }, mousedown: function ( event ) { onMouseDown( _this, event ); }, @@ -761,6 +763,7 @@ $.MouseTracker.maxTouchPoints = 0; } $.MouseTracker.haveTouchEnter = true; + $.MouseTracker.haveMouseEnter = true; } else if ( window.MSPointerEvent ) { // IE10 $.MouseTracker.subscribeEvents.push( "MSPointerEnter", "MSPointerLeave", "MSPointerDown", "MSPointerUp", "MSPointerMove", "MSPointerCancel" ); @@ -771,9 +774,17 @@ $.MouseTracker.maxTouchPoints = 0; } $.MouseTracker.haveTouchEnter = true; + $.MouseTracker.haveMouseEnter = true; } else { // Legacy W3C mouse events - $.MouseTracker.subscribeEvents.push( "mouseenter", "mouseleave", "mousedown", "mouseup", "mousemove" ); + $.MouseTracker.subscribeEvents.push( "mousedown", "mouseup", "mousemove" ); + if ( 'onmouseenter' in window ) { + $.MouseTracker.subscribeEvents.push( "mouseenter", "mouseleave" ); + $.MouseTracker.haveMouseEnter = true; + } else { + $.MouseTracker.subscribeEvents.push( "mouseover", "mouseout" ); + $.MouseTracker.haveMouseEnter = false; + } if ( 'ontouchstart' in window ) { // iOS, Android, and other W3c Touch Event implementations (see http://www.w3.org/TR/2011/WD-touch-events-20110505) $.MouseTracker.subscribeEvents.push( "touchstart", "touchend", "touchmove", "touchcancel" ); @@ -862,7 +873,6 @@ this.buttons = 0; /** * Current number of contact points (touch points, mouse down, etc.) 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} contacts * @memberof OpenSeadragon.MouseTracker.GesturePointList# */ @@ -1313,6 +1323,72 @@ } + /** + * @private + * @inner + */ + function isParentChild( parent, child ) + { + if ( parent === child ) { + return false; + } + while ( child && child !== parent ) { + child = child.parentNode; + } + return child === parent; + } + + + /** + * @private + * @inner + */ + function onMouseOver( tracker, event ) { + var gPoint; + + event = $.getEvent( event ); + + if ( this === event.relatedTarget || isParentChild( this, event.relatedTarget ) ) { + return; + } + + gPoint = { + id: $.MouseTracker.mousePointerId, + type: 'mouse', + isPrimary: true, + currentPos: getMouseAbsolute( event ), + currentTime: $.now() + }; + + updatePointersEnter( tracker, event, [ gPoint ] ); + } + + + /** + * @private + * @inner + */ + function onMouseOut( tracker, event ) { + var gPoint; + + event = $.getEvent( event ); + + if ( this === event.relatedTarget || isParentChild( this, event.relatedTarget ) ) { + return; + } + + gPoint = { + id: $.MouseTracker.mousePointerId, + type: 'mouse', + isPrimary: true, + currentPos: getMouseAbsolute( event ), + currentTime: $.now() + }; + + updatePointersExit( tracker, event, [ gPoint ] ); + } + + /** * @private * @inner @@ -1330,7 +1406,7 @@ currentTime: $.now() }; - updatePointersOver( tracker, event, [ gPoint ] ); + updatePointersEnter( tracker, event, [ gPoint ] ); } @@ -1351,7 +1427,7 @@ currentTime: $.now() }; - updatePointersOut( tracker, event, [ gPoint ] ); + updatePointersExit( tracker, event, [ gPoint ] ); } @@ -1493,7 +1569,7 @@ } ); } - updatePointersOver( tracker, event, gPoints ); + updatePointersEnter( tracker, event, gPoints ); } @@ -1516,7 +1592,7 @@ } ); } - updatePointersOut( tracker, event, gPoints ); + updatePointersExit( tracker, event, gPoints ); } @@ -1544,7 +1620,7 @@ // simulate touchenter if not natively available if ( !$.MouseTracker.haveTouchEnter ) { - updatePointersOver( tracker, event, gPoints ); + updatePointersEnter( tracker, event, gPoints ); } if ( updatePointersDown( tracker, event, gPoints, 0 ) ) { // 0 means primary button press/release or touch contact @@ -1584,7 +1660,7 @@ // simulate touchleave if not natively available if ( !$.MouseTracker.haveTouchEnter && touchCount > 0 ) { - updatePointersOut( tracker, event, gPoints ); + updatePointersExit( tracker, event, gPoints ); } $.stopEvent( event ); @@ -1675,7 +1751,7 @@ currentTime: $.now() }; - updatePointersOver( tracker, event, [ gPoint ] ); + updatePointersEnter( tracker, event, [ gPoint ] ); } @@ -1694,7 +1770,7 @@ currentTime: $.now() }; - updatePointersOut( tracker, event, [ gPoint ] ); + updatePointersExit( tracker, event, [ gPoint ] ); } @@ -1871,7 +1947,7 @@ * @param {Array.} gPoints * Gesture points associated with the event. */ - function updatePointersOver( tracker, event, gPoints ) { + function updatePointersEnter( tracker, event, gPoints ) { var pointsList = tracker.getActivePointersListByType( gPoints[ 0 ].type ), i, gPointCount = gPoints.length, @@ -1935,7 +2011,7 @@ * @param {Array.} gPoints * Gesture points associated with the event. */ - function updatePointersOut( tracker, event, gPoints ) { + function updatePointersExit( tracker, event, gPoints ) { var delegate = THIS[ tracker.hash ], pointsList = tracker.getActivePointersListByType( gPoints[ 0 ].type ), i, diff --git a/test/events.js b/test/events.js index 413dade3..44efae27 100644 --- a/test/events.js +++ b/test/events.js @@ -27,8 +27,10 @@ // ---------- asyncTest( 'MouseTracker: mouse gestures', function () { var $canvas = $( viewer.element ).find( '.openseadragon-canvas' ).not( '.navigator .openseadragon-canvas' ), + simEvent = {}, offset = $canvas.offset(), tracker = viewer.innerTracker, + intervalId, origEnterHandler, origExitHandler, origPressHandler, @@ -47,7 +49,9 @@ dragEndCount, insideElementPressed, insideElementReleased, - quickClick; + quickClick, + speed, + direction; var hookViewerHandlers = function () { origEnterHandler = tracker.enterHandler; @@ -119,6 +123,8 @@ origDragEndHandler = tracker.dragEndHandler; tracker.dragEndHandler = function ( event ) { dragEndCount++; + speed = event.speed; + direction = event.direction; if (origDragEndHandler) { return origDragEndHandler( event ); } else { @@ -138,40 +144,76 @@ tracker.dragEndHandler = origDragEndHandler; }; - var simulateEnter = function ($element, event, x, y) { - event.clientX = offset.left + x; - event.clientY = offset.top + y; - $canvas.simulate( 'mouseenter', event ); + var simulateEnter = function ($element, x, y) { + simEvent.clientX = offset.left + x; + simEvent.clientY = offset.top + y; + $canvas.simulate( 'mouseenter', simEvent ); }; - var simulateLeave = function ($element, event, x, y) { - event.clientX = offset.left + x; - event.clientY = offset.top + y; - $canvas.simulate( 'mouseleave', event ); + var simulateLeave = function ($element, x, y) { + simEvent.clientX = offset.left + x; + simEvent.clientY = offset.top + y; + $canvas.simulate( 'mouseleave', simEvent ); }; - var simulateMove = function ($element, event, dX, dY, count) { + var simulateMove = function ($element, dX, dY, count) { var i; for ( i = 0; i < count; i++ ) { - event.clientX += dX; - event.clientY += dY; - $canvas.simulate( 'mousemove', event ); + simEvent.clientX += dX; + simEvent.clientY += dY; + $canvas.simulate( 'mousemove', simEvent ); } }; - var simulateDown = function ($element, event, x, y) { - event.clientX = offset.left + x; - event.clientY = offset.top + y; - $canvas.simulate( 'mousedown', event ); + var simulateTimedMove = function ($element, dX, dY, count, ms, doneCallback) { + var msInterval = Math.floor(ms / count), + dX = dX, + dY = dY, + i = count, + doneCallback = doneCallback, + moves = 0; + + intervalId = window.setInterval(function () { + if (i > 0) { + moves++; + //simEvent.clientX += dX; + //simEvent.clientY += dY; + //$canvas.simulate( 'mousemove', simEvent ); + } + i--; + if (i === 0) { + window.clearInterval( intervalId ); + doneCallback(); + } + }, msInterval ); + + + //for ( i = 0; i < count; i++ ) { + // simEvent.clientX += dX; + // simEvent.clientY += dY; + // $canvas.simulate( 'mousemove', simEvent ); + // targetTime = msWait + OpenSeadragon.now(); + // while (OpenSeadragon.now() < targetTime) {} + //} }; - var simulateUp = function ($element, event, x, y) { - event.clientX = offset.left + x; - event.clientY = offset.top + y; - $canvas.simulate( 'mouseup', event ); + var simulateDown = function ($element, x, y) { + simEvent.clientX = offset.left + x; + simEvent.clientY = offset.top + y; + $canvas.simulate( 'mousedown', simEvent ); + }; + + var simulateUp = function ($element, x, y) { + simEvent.clientX = offset.left + x; + simEvent.clientY = offset.top + y; + $canvas.simulate( 'mouseup', simEvent ); }; var resetForAssessment = function () { + simEvent = { + clientX: offset.left, + clientY: offset.top + }; enterCount = 0; exitCount = 0; pressCount = 0; @@ -183,6 +225,8 @@ insideElementPressed = false; insideElementReleased = false; quickClick = false; + speed = 0; + direction = 2 * Math.PI; }; var assessGestureExpectations = function (expected) { @@ -226,9 +270,18 @@ if ('quickClick' in expected) { equal( quickClick, expected.quickClick, expected.description + 'clickHandler event.quick matches expected (' + expected.quickClick + ')' ); } + if ('speed' in expected) { + equal( speed, expected.speed, expected.description + 'Drag speed matches expected (' + expected.speed + ')' ); + } + if ('direction' in expected) { + equal( direction, expected.direction, expected.description + 'Drag direction matches expected (' + expected.direction + ')' ); + } }; var onOpen = function ( event ) { + var timeStart, + timeElapsed; + viewer.removeHandler( 'open', onOpen ); hookViewerHandlers(); @@ -236,11 +289,10 @@ // enter-move-release (release in tracked element, press in unknown element) // (Note we also test to see if the pointer is still being tracked by not simulating a leave event until after assessment) resetForAssessment(); - var event = {}; - simulateEnter($canvas, event, 0, 0); - simulateMove($canvas, event, 1, 1, 10); - simulateMove($canvas, event, -1, -1, 10); - simulateUp($canvas, event, 0, 0); + simulateEnter($canvas, 0, 0); + simulateMove($canvas, 1, 1, 10); + simulateMove($canvas, -1, -1, 10); + simulateUp($canvas, 0, 0); assessGestureExpectations({ description: 'enter-move-release (release in tracked element, press in unknown element): ', enterCount: 1, @@ -257,15 +309,14 @@ trackedPointers: 1 //quickClick: false }); - simulateLeave($canvas, event, -1, -1); // flush tracked pointer + simulateLeave($canvas, -1, -1); // flush tracked pointer // enter-move-exit (fly-over) resetForAssessment(); - var event = {}; - simulateEnter($canvas, event, 0, 0); - simulateMove($canvas, event, 1, 1, 10); - simulateMove($canvas, event, -1, -1, 10); - simulateLeave($canvas, event, -1, -1); + simulateEnter($canvas, 0, 0); + simulateMove($canvas, 1, 1, 10); + simulateMove($canvas, -1, -1, 10); + simulateLeave($canvas, -1, -1); assessGestureExpectations({ description: 'enter-move-exit (fly-over): ', enterCount: 1, @@ -285,10 +336,9 @@ // move-exit (fly-over, no enter event) resetForAssessment(); - var event = {}; - simulateMove($canvas, event, 1, 1, 10); - simulateMove($canvas, event, -1, -1, 10); - simulateLeave($canvas, event, -1, -1); + simulateMove($canvas, 1, 1, 10); + simulateMove($canvas, -1, -1, 10); + simulateLeave($canvas, -1, -1); assessGestureExpectations({ description: 'move-exit (fly-over, no enter event): ', enterCount: 0, @@ -308,11 +358,10 @@ // enter-press-release-exit resetForAssessment(); - var event = {}; - simulateEnter($canvas, event, 0, 0); - simulateDown($canvas, event, 0, 0); - simulateUp($canvas, event, 0, 0); - simulateLeave($canvas, event, -1, -1); + simulateEnter($canvas, 0, 0); + simulateDown($canvas, 0, 0); + simulateUp($canvas, 0, 0); + simulateLeave($canvas, -1, -1); assessGestureExpectations({ description: 'enter-press-release-exit (click): ', enterCount: 1, @@ -332,22 +381,21 @@ // enter-press-move-release-move-exit (drag, release in tracked element) resetForAssessment(); - var event = {}; - simulateEnter($canvas, event, 0, 0); - simulateDown($canvas, event, 0, 0); - simulateMove($canvas, event, 1, 1, 10); - simulateUp($canvas, event, 10, 10); - simulateMove($canvas, event, -1, -1, 10); - simulateLeave($canvas, event, -1, -1); + simulateEnter($canvas, 0, 0); + simulateDown($canvas, 0, 0); + simulateMove($canvas, 1, 1, 100); + simulateUp($canvas, 10, 10); + simulateMove($canvas, -1, -1, 100); + simulateLeave($canvas, -1, -1); assessGestureExpectations({ description: 'enter-press-move-release-move-exit (drag, release in tracked element): ', enterCount: 1, exitCount: 1, pressCount: 1, releaseCount: 1, - moveCount: 20, + moveCount: 200, clickCount: 1, - dragCount: 10, + dragCount: 100, dragEndCount: 1, insideElementPressed: true, insideElementReleased: true, @@ -358,14 +406,13 @@ // enter-press-move-exit-move-release (drag, release outside tracked element) resetForAssessment(); - var event = {}; - simulateEnter($canvas, event, 0, 0); - simulateDown($canvas, event, 0, 0); - simulateMove($canvas, event, 1, 1, 5); - simulateMove($canvas, event, -1, -1, 5); - simulateLeave($canvas, event, -1, -1); - simulateMove($canvas, event, -1, -1, 5); - simulateUp($canvas, event, -5, -5); + simulateEnter($canvas, 0, 0); + simulateDown($canvas, 0, 0); + simulateMove($canvas, 1, 1, 5); + simulateMove($canvas, -1, -1, 5); + simulateLeave($canvas, -1, -1); + simulateMove($canvas, -1, -1, 5); + simulateUp($canvas, -5, -5); assessGestureExpectations({ description: 'enter-press-move-exit-move-release (drag, release outside tracked element): ', enterCount: 1, diff --git a/test/legacy.mouse.shim.js b/test/legacy.mouse.shim.js index 0402ed6e..9643d610 100644 --- a/test/legacy.mouse.shim.js +++ b/test/legacy.mouse.shim.js @@ -11,7 +11,14 @@ $.MouseTracker.subscribeEvents.push( "MozMousePixelScroll" ); } - $.MouseTracker.subscribeEvents.push( "mouseenter", "mouseleave", "mousedown", "mouseup", "mousemove" ); + $.MouseTracker.subscribeEvents.push( "mousedown", "mouseup", "mousemove" ); + if ( 'onmouseenter' in window ) { + $.MouseTracker.subscribeEvents.push( "mouseenter", "mouseleave" ); + $.MouseTracker.haveMouseEnter = true; + } else { + $.MouseTracker.subscribeEvents.push( "mouseover", "mouseout" ); + $.MouseTracker.haveMouseEnter = false; + } if ( 'ontouchstart' in window ) { // iOS, Android, and other W3c Touch Event implementations (see http://www.w3.org/TR/2011/WD-touch-events-20110505) $.MouseTracker.subscribeEvents.push( "touchstart", "touchend", "touchmove", "touchcancel" );