diff --git a/changelog.txt b/changelog.txt index b1405ba9..98a94fd0 100644 --- a/changelog.txt +++ b/changelog.txt @@ -38,6 +38,7 @@ OPENSEADRAGON CHANGELOG * Margins option to push the home region in from the edges of the Viewer (#505) * Rect and Point toString() functions are now consistent: rounding values to nearest hundredth * Overlays appear in the DOM immediately on open or addOverlay (#507) +* imageLoaderLimit now works (#544) 1.2.0: (in progress) @@ -58,6 +59,7 @@ OPENSEADRAGON CHANGELOG * Added option for home button to fill viewer (#474) * Better handling of mid-update image loaded callbacks (#409) * Tracked pointers are now cleaned up when Viewer.setMouseNavEnabled(false) is called (#518) +* Added explicit pointer capture for touch event model touchstart events (#552) 1.1.1: diff --git a/src/mousetracker.js b/src/mousetracker.js index 3485c84d..8e89e43a 100644 --- a/src/mousetracker.js +++ b/src/mousetracker.js @@ -170,8 +170,6 @@ * @private * @property {Boolean} tracking * Are we currently tracking pointer events for this element. - * @property {Boolean} capturing - * Are we curruently capturing mouse events (legacy mouse events only). */ THIS[ this.hash ] = { click: function ( event ) { onClick( _this, event ); }, @@ -197,7 +195,9 @@ touchleave: function ( event ) { onTouchLeave( _this, event ); }, touchstart: function ( event ) { onTouchStart( _this, event ); }, touchend: function ( event ) { onTouchEnd( _this, event ); }, + touchendcaptured: function ( event ) { onTouchEndCaptured( _this, event ); }, touchmove: function ( event ) { onTouchMove( _this, event ); }, + touchmovecaptured: function ( event ) { onTouchMoveCaptured( _this, event ); }, touchcancel: function ( event ) { onTouchCancel( _this, event ); }, gesturestart: function ( event ) { onGestureStart( _this, event ); }, @@ -227,12 +227,6 @@ // of the element (for hover-capable devices) and/or have contact or a button press initiated in the element. activePointersLists: [], - // Legacy mouse capture tracking - capturing: false, - - // Pointer event model capture tracking - pointerCaptureCount: 0, - // Tracking for double-click gesture lastClickPos: null, dblClickTimeOut: null, @@ -944,6 +938,12 @@ * @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; }; $.MouseTracker.GesturePointList.prototype = /** @lends OpenSeadragon.MouseTracker.GesturePointList.prototype */{ /** @@ -1042,33 +1042,47 @@ i, pointerListCount = delegate.activePointersLists.length; - if ( delegate.pointerCaptureCount > 0 ) { - $.removeEvent( - $.MouseTracker.captureElement, - 'mousemove', - delegate.mousemovecaptured, - true - ); - $.removeEvent( - $.MouseTracker.captureElement, - 'mouseup', - delegate.mouseupcaptured, - true - ); - $.removeEvent( - $.MouseTracker.captureElement, - $.MouseTracker.unprefixedPointerEvents ? 'pointermove' : 'MSPointerMove', - delegate.pointermovecaptured, - true - ); - $.removeEvent( - $.MouseTracker.captureElement, - $.MouseTracker.unprefixedPointerEvents ? 'pointerup' : 'MSPointerUp', - delegate.pointerupcaptured, - true - ); + for ( i = 0; i < pointerListCount; i++ ) { + if ( delegate.activePointersLists[ i ].captureCount > 0 ) { + $.removeEvent( + $.MouseTracker.captureElement, + 'mousemove', + delegate.mousemovecaptured, + true + ); + $.removeEvent( + $.MouseTracker.captureElement, + 'mouseup', + delegate.mouseupcaptured, + true + ); + $.removeEvent( + $.MouseTracker.captureElement, + $.MouseTracker.unprefixedPointerEvents ? 'pointermove' : 'MSPointerMove', + delegate.pointermovecaptured, + true + ); + $.removeEvent( + $.MouseTracker.captureElement, + $.MouseTracker.unprefixedPointerEvents ? 'pointerup' : 'MSPointerUp', + delegate.pointerupcaptured, + true + ); + $.removeEvent( + $.MouseTracker.captureElement, + 'touchmove', + delegate.touchmovecaptured, + true + ); + $.removeEvent( + $.MouseTracker.captureElement, + 'touchend', + delegate.touchendcaptured, + true + ); - delegate.pointerCaptureCount = 0; + delegate.activePointersLists[ i ].captureCount = 0; + } } for ( i = 0; i < pointerListCount; i++ ) { @@ -1130,29 +1144,62 @@ } } + /** + * @private + * @inner + */ + function getCaptureEventParams( tracker, pointerType ) { + var delegate = THIS[ tracker.hash ]; + + if ( pointerType === 'mouse' ) { + return { + upName: 'mouseup', + upHandler: delegate.mouseupcaptured, + moveName: 'mousemove', + moveHandler: delegate.mousemovecaptured + }; + } else if ( pointerType === 'touch' ) { + return { + upName: 'touchend', + upHandler: delegate.touchendcaptured, + moveName: 'touchmove', + moveHandler: delegate.touchmovecaptured + }; + } else { + return { + upName: $.MouseTracker.unprefixedPointerEvents ? 'pointerup' : 'MSPointerUp', + upHandler: delegate.pointerupcaptured, + moveName: $.MouseTracker.unprefixedPointerEvents ? 'pointermove' : 'MSPointerMove', + moveHandler: delegate.pointermovecaptured + }; + } + } + /** * Begin capturing pointer events to the tracked element. * @private * @inner */ - function capturePointer( tracker, isLegacyMouse ) { - var delegate = THIS[ tracker.hash ]; + function capturePointer( tracker, pointerType ) { + var delegate = THIS[ tracker.hash ], + pointsList = tracker.getActivePointersListByType( pointerType ), + eventParams = getCaptureEventParams( tracker, pointerType ); - delegate.pointerCaptureCount++; + pointsList.captureCount++; - if ( delegate.pointerCaptureCount === 1 ) { + if ( pointsList.captureCount === 1 ) { // We emulate mouse capture by hanging listeners on the window object. // (Note we listen on the capture phase so the captured handlers will get called first) $.addEvent( $.MouseTracker.captureElement, - isLegacyMouse ? 'mouseup' : ($.MouseTracker.unprefixedPointerEvents ? 'pointerup' : 'MSPointerUp'), - isLegacyMouse ? delegate.mouseupcaptured : delegate.pointerupcaptured, + eventParams.upName, + eventParams.upHandler, true ); $.addEvent( $.MouseTracker.captureElement, - isLegacyMouse ? 'mousemove' : ($.MouseTracker.unprefixedPointerEvents ? 'pointermove' : 'MSPointerMove'), - isLegacyMouse ? delegate.mousemovecaptured : delegate.pointermovecaptured, + eventParams.moveName, + eventParams.moveHandler, true ); } @@ -1164,24 +1211,26 @@ * @private * @inner */ - function releasePointer( tracker, isLegacyMouse ) { - var delegate = THIS[ tracker.hash ]; + function releasePointer( tracker, pointerType ) { + var delegate = THIS[ tracker.hash ], + pointsList = tracker.getActivePointersListByType( pointerType ), + eventParams = getCaptureEventParams( tracker, pointerType ); - delegate.pointerCaptureCount--; + pointsList.captureCount--; - if ( delegate.pointerCaptureCount === 0 ) { + if ( pointsList.captureCount === 0 ) { // We emulate mouse capture by hanging listeners on the window object. // (Note we listen on the capture phase so the captured handlers will get called first) $.removeEvent( $.MouseTracker.captureElement, - isLegacyMouse ? 'mousemove' : ($.MouseTracker.unprefixedPointerEvents ? 'pointermove' : 'MSPointerMove'), - isLegacyMouse ? delegate.mousemovecaptured : delegate.pointermovecaptured, + eventParams.moveName, + eventParams.moveHandler, true ); $.removeEvent( $.MouseTracker.captureElement, - isLegacyMouse ? 'mouseup' : ($.MouseTracker.unprefixedPointerEvents ? 'pointerup' : 'MSPointerUp'), - isLegacyMouse ? delegate.mouseupcaptured : delegate.pointerupcaptured, + eventParams.upName, + eventParams.upHandler, true ); } @@ -1526,7 +1575,7 @@ if ( updatePointersDown( tracker, event, [ gPoint ], event.button ) ) { $.stopEvent( event ); - capturePointer( tracker, true ); + capturePointer( tracker, 'mouse' ); } if ( tracker.clickHandler || tracker.dblClickHandler || tracker.pressHandler || tracker.dragHandler || tracker.dragEndHandler ) { @@ -1574,7 +1623,7 @@ }; if ( updatePointersUp( tracker, event, [ gPoint ], event.button ) ) { - releasePointer( tracker, true ); + releasePointer( tracker, 'mouse' ); } } @@ -1695,8 +1744,8 @@ } if ( updatePointersDown( tracker, event, gPoints, 0 ) ) { // 0 means primary button press/release or touch contact - // Touch event model start, end, and move events are always captured so we don't need to capture explicitly $.stopEvent( event ); + capturePointer( tracker, 'touch' ); } $.cancelEvent( event ); @@ -1708,6 +1757,28 @@ * @inner */ function onTouchEnd( tracker, event ) { + handleTouchEnd( tracker, event ); + } + + + /** + * This handler is attached to the window object (on the capture phase) to emulate pointer capture. + * onTouchEnd is still attached to the tracked element, so stop propagation to avoid processing twice. + * + * @private + * @inner + */ + function onTouchEndCaptured( tracker, event ) { + handleTouchEnd( tracker, event ); + $.stopEvent( event ); + } + + + /** + * @private + * @inner + */ + function handleTouchEnd( tracker, event ) { var time, i, touchCount = event.changedTouches.length, @@ -1725,9 +1796,9 @@ } ); } - // Touch event model start, end, and move events are always captured so we don't need to release capture. - // We'll ignore the should-release-capture return value here - updatePointersUp( tracker, event, gPoints, 0 ); // 0 means primary button press/release or touch contact + if ( updatePointersUp( tracker, event, gPoints, 0 ) ) { + releasePointer( tracker, 'touch' ); + } // simulate touchleave if not natively available if ( !$.MouseTracker.haveTouchEnter && touchCount > 0 ) { @@ -1743,6 +1814,28 @@ * @inner */ function onTouchMove( tracker, event ) { + handleTouchMove( tracker, event ); + } + + + /** + * This handler is attached to the window object (on the capture phase) to emulate pointer capture. + * onTouchMove is still attached to the tracked element, so stop propagation to avoid processing twice. + * + * @private + * @inner + */ + function onTouchMoveCaptured( tracker, event ) { + handleTouchMove( tracker, event ); + $.stopEvent( event ); + } + + + /** + * @private + * @inner + */ + function handleTouchMove( tracker, event ) { var i, touchCount = event.changedTouches.length, gPoints = []; @@ -1866,8 +1959,8 @@ }; if ( updatePointersDown( tracker, event, [ gPoint ], event.button ) ) { - capturePointer( tracker, false ); $.stopEvent( event ); + capturePointer( tracker, 'pointer' ); } if ( tracker.clickHandler || tracker.dblClickHandler || tracker.pressHandler || tracker.dragHandler || tracker.dragEndHandler || tracker.pinchHandler ) { @@ -1917,8 +2010,7 @@ }; if ( updatePointersUp( tracker, event, [ gPoint ], event.button ) ) { - releasePointer( tracker, false ); - //$.stopEvent( event ); + releasePointer( tracker, 'pointer' ); } } diff --git a/src/navigator.js b/src/navigator.js index bea23ddd..8f234248 100644 --- a/src/navigator.js +++ b/src/navigator.js @@ -245,14 +245,10 @@ $.Navigator = function( options ){ }); viewer.world.addHandler("remove-item", function(event) { - var count = _this.world.getItemCount(); - var item; - for (var i = 0; i < count; i++) { - item = _this.world.getItemAt(i); - if (item._originalForNavigator === event.item) { - _this.world.removeItem(item); - break; - } + var theirItem = event.item; + var myItem = _this._getMatchingItem(theirItem); + if (myItem) { + _this.world.removeItem(myItem); } }); @@ -343,16 +339,45 @@ $.extend( $.Navigator.prototype, $.EventSource.prototype, $.Viewer.prototype, /* // overrides Viewer.addTiledImage addTiledImage: function(options) { + var _this = this; + var original = options.originalTiledImage; delete options.original; var optionsClone = $.extend({}, options, { success: function(event) { - event.item._originalForNavigator = original; + var myItem = event.item; + myItem._originalForNavigator = original; + _this._matchBounds(myItem, original, true); + + original.addHandler('bounds-change', function() { + _this._matchBounds(myItem, original); + }); } }); return $.Viewer.prototype.addTiledImage.apply(this, [optionsClone]); + }, + + // private + _getMatchingItem: function(theirItem) { + var count = this.world.getItemCount(); + var item; + for (var i = 0; i < count; i++) { + item = this.world.getItemAt(i); + if (item._originalForNavigator === theirItem) { + return item; + } + } + + return null; + }, + + // private + _matchBounds: function(myItem, theirItem, immediately) { + var bounds = theirItem.getBounds(); + myItem.setPosition(bounds.getTopLeft(), immediately); + myItem.setWidth(bounds.width, immediately); } }); diff --git a/src/viewer.js b/src/viewer.js index f6650203..781af981 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -36,6 +36,7 @@ // dictionary from hash to private properties var THIS = {}; +var nextHash = 1; /** * @@ -89,7 +90,7 @@ $.Viewer = function( options ) { //internal state and dom identifiers id: options.id, - hash: options.hash || options.id, + hash: options.hash || nextHash++, //dom nodes /** @@ -498,7 +499,7 @@ $.Viewer = function( options ) { } // Open initial tilesources - if ( this.tileSources && this.tileSources.length) { + if (this.tileSources) { this.open( this.tileSources ); } diff --git a/src/viewport.js b/src/viewport.js index 77893189..44654f7e 100644 --- a/src/viewport.js +++ b/src/viewport.js @@ -230,7 +230,16 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{ * @function */ getHomeBounds: function() { - return this.homeBounds.clone(); + var center = this.homeBounds.getCenter( ), + width = 1.0 / this.getHomeZoom( ), + height = width / this.getAspectRatio(); + + return new $.Rect( + center.x - ( width / 2.0 ), + center.y - ( height / 2.0 ), + width, + height + ); }, /** diff --git a/src/world.js b/src/world.js index a61e2f69..4b1c9826 100644 --- a/src/world.js +++ b/src/world.js @@ -104,7 +104,7 @@ $.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.W * @returns {OpenSeadragon.TiledImage} The item at the specified index. */ getItemAt: function( index ) { - $.console.assert(index !== 'undefined', "[World.getItemAt] index is required"); + $.console.assert(index !== undefined, "[World.getItemAt] index is required"); return this._items[ index ]; }, @@ -133,7 +133,7 @@ $.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.W */ setItemIndex: function( item, index ) { $.console.assert(item, "[World.setItemIndex] item is required"); - $.console.assert(index !== 'undefined', "[World.setItemIndex] index is required"); + $.console.assert(index !== undefined, "[World.setItemIndex] index is required"); var oldIndex = this.getIndexOfItem( item ); diff --git a/test/coverage.html b/test/coverage.html index c55fd31f..2c3bc093 100644 --- a/test/coverage.html +++ b/test/coverage.html @@ -65,7 +65,7 @@ - + diff --git a/test/demo/collections/main.js b/test/demo/collections/main.js index cc18a064..83563085 100644 --- a/test/demo/collections/main.js +++ b/test/demo/collections/main.js @@ -17,13 +17,15 @@ zoomPerScroll: 1.02, showNavigator: testNavigator, useCanvas: true, + // defaultZoomLevel: 2, + // homeFillsViewer: true, // sequenceMode: true, // showReferenceStrip: true, // referenceStripScroll: 'vertical', navPrevNextWrap: false, preserveViewport: false, - collectionMode: true, - collectionRows: 1, + // collectionMode: true, + // collectionRows: 1, // collectionLayout: 'vertical', // collectionTileSize: 10, // collectionTileMargin: 10, @@ -33,6 +35,20 @@ prefixUrl: "../../../build/openseadragon/images/" }; + var highsmith = { + Image: { + xmlns: "http://schemas.microsoft.com/deepzoom/2008", + Url: "http://openseadragon.github.io/example-images/highsmith/highsmith_files/", + Format: "jpg", + Overlap: "2", + TileSize: "256", + Size: { + Height: "9221", + Width: "7026" + } + } + }; + if (testInitialOpen) { config.tileSources = [ { @@ -55,6 +71,11 @@ height: 1 } ]; + + // config.tileSources = { + // tileSource: highsmith, + // width: 1 + // }; } if (testOverlays) { diff --git a/test/demo/item-animation.html b/test/demo/item-animation.html index a6cbf3a6..254458a6 100644 --- a/test/demo/item-animation.html +++ b/test/demo/item-animation.html @@ -14,12 +14,18 @@ margin: 0; } + .controls { + position: absolute; + right: 10px; + top: 10px; + } + - +