diff --git a/changelog.txt b/changelog.txt index 028c3300..3908e96a 100644 --- a/changelog.txt +++ b/changelog.txt @@ -15,10 +15,10 @@ OPENSEADRAGON CHANGELOG * DEPRECATION: use World.getItemCount instead of Viewer.getLayersCount * DEPRECATION: use World.setItemIndex instead of Viewer.setLayerLevel * DEPRECATION: use World.removeItem instead of Viewer.removeLayer - * DEPRECATION: use World.needsUpdate instead of Drawer.needsUpdate + * DEPRECATION: use World.needsDraw instead of Drawer.needsUpdate * DEPRECATION: use TileCache.numTilesLoaded instead of Drawer.numTilesLoaded * DEPRECATION: use World.resetItems instead of Drawer.reset - * DEPRECATION: use Drawer.clear and World.update instead of Drawer.update + * DEPRECATION: use Drawer.clear and World.draw instead of Drawer.update * DEPRECATION: the layersAspectRatioEpsilon option is no longer necessary * DEPRECATION: Viewer's add-layer event is now World's add-item event * DEPRECATION: Viewer's layer-level-changed event is now World's item-index-change event diff --git a/src/drawer.js b/src/drawer.js index 3a02dad4..cfd2c71d 100644 --- a/src/drawer.js +++ b/src/drawer.js @@ -168,8 +168,8 @@ $.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{ // deprecated needsUpdate: function() { - $.console.error( "[Drawer.needsUpdate] this function is deprecated. Use World.needsUpdate instead." ); - return this.viewer.world.needsUpdate(); + $.console.error( "[Drawer.needsUpdate] this function is deprecated. Use World.needsDraw instead." ); + return this.viewer.world.needsDraw(); }, // deprecated @@ -187,9 +187,9 @@ $.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{ // deprecated update: function() { - $.console.error( "[Drawer.update] this function is deprecated. Use Drawer.clear and World.update instead." ); + $.console.error( "[Drawer.update] this function is deprecated. Use Drawer.clear and World.draw instead." ); this.clear(); - this.viewer.world.update(); + this.viewer.world.draw(); return this; }, diff --git a/src/navigator.js b/src/navigator.js index 512d4671..bea23ddd 100644 --- a/src/navigator.js +++ b/src/navigator.js @@ -277,7 +277,8 @@ $.extend( $.Navigator.prototype, $.EventSource.prototype, $.Viewer.prototype, /* this.viewport.resize( containerSize, true ); this.viewport.goHome(true); this.oldContainerSize = containerSize; - this.drawer.update(); + this.drawer.clear(); + this.world.draw(); } } }, diff --git a/src/tiledimage.js b/src/tiledimage.js index bb74abb4..fc444b83 100644 --- a/src/tiledimage.js +++ b/src/tiledimage.js @@ -52,6 +52,8 @@ * @param {Number} [options.y=0] - Top position, in viewport coordinates. * @param {Number} [options.width=1] - Width, in viewport coordinates. * @param {Number} [options.height] - Height, in viewport coordinates. + * @param {Number} [options.springStiffness] - See {@link OpenSeadragon.Options}. + * @param {Boolean} [options.animationTime] - See {@link OpenSeadragon.Options}. * @param {Number} [options.minZoomImageRatio] - See {@link OpenSeadragon.Options}. * @param {Boolean} [options.wrapHorizontal] - See {@link OpenSeadragon.Options}. * @param {Boolean} [options.wrapVertical] - See {@link OpenSeadragon.Options}. @@ -82,17 +84,18 @@ $.TiledImage = function( options ) { this._imageLoader = options.imageLoader; delete options.imageLoader; - this._worldX = options.x || 0; + var x = options.x || 0; delete options.x; - this._worldY = options.y || 0; + var y = options.y || 0; delete options.y; // Ratio of zoomable image height to width. this.normHeight = options.source.dimensions.y / options.source.dimensions.x; this.contentAspectX = options.source.dimensions.x / options.source.dimensions.y; + var scale = 1; if ( options.width ) { - this._setScale(options.width); + scale = options.width; delete options.width; if ( options.height ) { @@ -100,10 +103,8 @@ $.TiledImage = function( options ) { delete options.height; } } else if ( options.height ) { - this._setScale(options.height / this.normHeight); + scale = options.height / this.normHeight; delete options.height; - } else { - this._setScale(1); } $.extend( true, this, { @@ -114,10 +115,12 @@ $.TiledImage = function( options ) { coverage: {}, // A '3d' dictionary [level][x][y] --> Boolean. lastDrawn: [], // An unordered list of Tiles drawn last frame. lastResetTime: 0, // Last time for which the tiledImage was reset. - midUpdate: false, // Is the tiledImage currently updating the viewport? - updateAgain: true, // Does the tiledImage need to update the viewport again? + _midDraw: false, // Is the tiledImage currently updating the viewport? + _needsDraw: true, // Does the tiledImage need to update the viewport again? //configurable settings + springStiffness: $.DEFAULT_SETTINGS.springStiffness, + animationTime: $.DEFAULT_SETTINGS.animationTime, minZoomImageRatio: $.DEFAULT_SETTINGS.minZoomImageRatio, wrapHorizontal: $.DEFAULT_SETTINGS.wrapHorizontal, wrapVertical: $.DEFAULT_SETTINGS.wrapVertical, @@ -130,6 +133,26 @@ $.TiledImage = function( options ) { }, options ); + this._xSpring = new $.Spring({ + initial: x, + springStiffness: this.springStiffness, + animationTime: this.animationTime + }); + + this._ySpring = new $.Spring({ + initial: y, + springStiffness: this.springStiffness, + animationTime: this.animationTime + }); + + this._scaleSpring = new $.Spring({ + initial: scale, + springStiffness: this.springStiffness, + animationTime: this.animationTime + }); + + this._updateForScale(); + // We need a callback to give image manipulation a chance to happen this._drawingHandler = function(args) { /** @@ -155,11 +178,10 @@ $.TiledImage = function( options ) { $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.TiledImage.prototype */{ /** - * @returns {Boolean} Whether the TiledImage is scheduled for an update at the - * soonest possible opportunity. + * @returns {Boolean} Whether the TiledImage needs to be drawn. */ - needsUpdate: function() { - return this.updateAgain; + needsDraw: function() { + return this._needsDraw; }, /** @@ -169,16 +191,39 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag reset: function() { this._tileCache.clearTilesFor(this); this.lastResetTime = $.now(); - this.updateAgain = true; + this._needsDraw = true; }, /** - * Forces the TiledImage to update. + * Updates the TiledImage's bounds, animating if needed. + * @returns {Boolean} Whether the TiledImage animated. */ update: function() { - this.midUpdate = true; + var oldX = this._xSpring.current.value; + var oldY = this._ySpring.current.value; + var oldScale = this._scaleSpring.current.value; + + this._xSpring.update(); + this._ySpring.update(); + this._scaleSpring.update(); + + if (this._xSpring.current.value !== oldX || this._ySpring.current.value !== oldY || + this._scaleSpring.current.value !== oldScale) { + this._updateForScale(); + this._needsDraw = true; + return true; + } + + return false; + }, + + /** + * Draws the TiledImage to its Drawer. + */ + draw: function() { + this._midDraw = true; updateViewport( this ); - this.midUpdate = false; + this._midDraw = false; }, /** @@ -190,9 +235,16 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag /** * @returns {OpenSeadragon.Rect} This TiledImage's bounds in viewport coordinates. + * @param {Boolean} [current=false] - Pass true for the current location; false for target location. */ - getBounds: function() { - return new $.Rect( this._worldX, this._worldY, this._worldWidth, this._worldHeight ); + getBounds: function(current) { + if (current) { + return new $.Rect( this._xSpring.current.value, this._ySpring.current.value, + this._worldWidthCurrent, this._worldHeightCurrent ); + } + + return new $.Rect( this._xSpring.target.value, this._ySpring.target.value, + this._worldWidthTarget, this._worldHeightTarget ); }, // deprecated @@ -209,89 +261,96 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag }, // private - _viewportToImageDelta: function( viewerX, viewerY ) { - return new $.Point(viewerX * (this.source.dimensions.x / this._scale), - viewerY * ((this.source.dimensions.y * this.contentAspectX) / this._scale)); + _viewportToImageDelta: function( viewerX, viewerY, current ) { + var scale = current ? this._scaleSpring.current.value : this._scaleSpring.target.value; + return new $.Point(viewerX * (this.source.dimensions.x / scale), + viewerY * ((this.source.dimensions.y * this.contentAspectX) / scale)); }, /** * Translates from OpenSeadragon viewer coordinate system to image coordinate system. - * This method can be called either by passing X,Y coordinates or an - * OpenSeadragon.Point - * @function - * @param {OpenSeadragon.Point} viewerX the point in viewport coordinate system. - * @param {Number} viewerX X coordinate in viewport coordinate system. - * @param {Number} viewerY Y coordinate in viewport coordinate system. - * @return {OpenSeadragon.Point} a point representing the coordinates in the image. + * This method can be called either by passing X,Y coordinates or an {@link OpenSeadragon.Point}. + * @param {Number|OpenSeadragon.Point} viewerX - The X coordinate or point in viewport coordinate system. + * @param {Number} [viewerY] - The Y coordinate in viewport coordinate system. + * @param {Boolean} [current=false] - Pass true to use the current location; false for target location. + * @return {OpenSeadragon.Point} A point representing the coordinates in the image. */ - viewportToImageCoordinates: function( viewerX, viewerY ) { - if ( arguments.length == 1 ) { + viewportToImageCoordinates: function( viewerX, viewerY, current ) { + if (viewerX instanceof $.Point) { //they passed a point instead of individual components - return this.viewportToImageCoordinates( viewerX.x, viewerX.y ); + current = viewerY; + viewerY = viewerX.y; + viewerX = viewerX.x; } - return this._viewportToImageDelta(viewerX - this._worldX, viewerY - this._worldY); + if (current) { + return this._viewportToImageDelta(viewerX - this._xSpring.current.value, + viewerY - this._ySpring.current.value); + } + + return this._viewportToImageDelta(viewerX - this._xSpring.target.value, + viewerY - this._ySpring.target.value); }, // private - _imageToViewportDelta: function( imageX, imageY ) { - return new $.Point((imageX / this.source.dimensions.x) * this._scale, - (imageY / this.source.dimensions.y / this.contentAspectX) * this._scale); + _imageToViewportDelta: function( imageX, imageY, current ) { + var scale = current ? this._scaleSpring.current.value : this._scaleSpring.target.value; + return new $.Point((imageX / this.source.dimensions.x) * scale, + (imageY / this.source.dimensions.y / this.contentAspectX) * scale); }, /** * Translates from image coordinate system to OpenSeadragon viewer coordinate system - * This method can be called either by passing X,Y coordinates or an - * OpenSeadragon.Point - * @function - * @param {OpenSeadragon.Point} imageX the point in image coordinate system. - * @param {Number} imageX X coordinate in image coordinate system. - * @param {Number} imageY Y coordinate in image coordinate system. - * @return {OpenSeadragon.Point} a point representing the coordinates in the viewport. + * This method can be called either by passing X,Y coordinates or an {@link OpenSeadragon.Point}. + * @param {Number|OpenSeadragon.Point} imageX - The X coordinate or point in image coordinate system. + * @param {Number} [imageY] - The Y coordinate in image coordinate system. + * @param {Boolean} [current=false] - Pass true to use the current location; false for target location. + * @return {OpenSeadragon.Point} A point representing the coordinates in the viewport. */ - imageToViewportCoordinates: function( imageX, imageY ) { - if ( arguments.length == 1 ) { + imageToViewportCoordinates: function( imageX, imageY, current ) { + if (imageX instanceof $.Point) { //they passed a point instead of individual components - return this.imageToViewportCoordinates( imageX.x, imageX.y ); + current = imageY; + imageY = imageX.y; + imageX = imageX.x; } var point = this._imageToViewportDelta(imageX, imageY); - point.x += this._worldX; - point.y += this._worldY; + if (current) { + point.x += this._xSpring.current.value; + point.y += this._ySpring.current.value; + } else { + point.x += this._xSpring.target.value; + point.y += this._ySpring.target.value; + } + return point; }, /** * Translates from a rectangle which describes a portion of the image in * pixel coordinates to OpenSeadragon viewport rectangle coordinates. - * This method can be called either by passing X,Y,width,height or an - * OpenSeadragon.Rect - * @function - * @param {OpenSeadragon.Rect} imageX the rectangle in image coordinate system. - * @param {Number} imageX the X coordinate of the top left corner of the rectangle - * in image coordinate system. - * @param {Number} imageY the Y coordinate of the top left corner of the rectangle - * in image coordinate system. - * @param {Number} pixelWidth the width in pixel of the rectangle. - * @param {Number} pixelHeight the height in pixel of the rectangle. + * This method can be called either by passing X,Y,width,height or an {@link OpenSeadragon.Rect}. + * @param {Number|OpenSeadragon.Rect} imageX - The left coordinate or rectangle in image coordinate system. + * @param {Number} [imageY] - The top coordinate in image coordinate system. + * @param {Number} [pixelWidth] - The width in pixel of the rectangle. + * @param {Number} [pixelHeight] - The height in pixel of the rectangle. + * @param {Boolean} [current=false] - Pass true to use the current location; false for target location. + * @return {OpenSeadragon.Rect} A rect representing the coordinates in the viewport. */ - imageToViewportRectangle: function( imageX, imageY, pixelWidth, pixelHeight ) { - var coordA, - coordB, - rect; - if( arguments.length == 1 ) { - //they passed a rectangle instead of individual components - rect = imageX; - return this.imageToViewportRectangle( - rect.x, rect.y, rect.width, rect.height - ); + imageToViewportRectangle: function( imageX, imageY, pixelWidth, pixelHeight, current ) { + if (imageX instanceof $.Rect) { + //they passed a rect instead of individual components + current = imageY; + pixelWidth = imageX.width; + pixelHeight = imageX.height; + imageY = imageX.y; + imageX = imageX.x; } - coordA = this.imageToViewportCoordinates( - imageX, imageY - ); - coordB = this._imageToViewportDelta( - pixelWidth, pixelHeight - ); + + var coordA = this.imageToViewportCoordinates(imageX, imageY, current); + var coordB = this._imageToViewportDelta(pixelWidth, pixelHeight, current); + return new $.Rect( coordA.x, coordA.y, @@ -303,30 +362,27 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag /** * Translates from a rectangle which describes a portion of * the viewport in point coordinates to image rectangle coordinates. - * This method can be called either by passing X,Y,width,height or an - * OpenSeadragon.Rect - * @function - * @param {OpenSeadragon.Rect} viewerX the rectangle in viewport coordinate system. - * @param {Number} viewerX the X coordinate of the top left corner of the rectangle - * in viewport coordinate system. - * @param {Number} imageY the Y coordinate of the top left corner of the rectangle - * in viewport coordinate system. - * @param {Number} pointWidth the width of the rectangle in viewport coordinate system. - * @param {Number} pointHeight the height of the rectangle in viewport coordinate system. + * This method can be called either by passing X,Y,width,height or an {@link OpenSeadragon.Rect}. + * @param {Number|OpenSeadragon.Rect} viewerX - The left coordinate or rectangle in viewport coordinate system. + * @param {Number} [viewerY] - The top coordinate in viewport coordinate system. + * @param {Number} [pointWidth] - The width in viewport coordinate system. + * @param {Number} [pointHeight] - The height in viewport coordinate system. + * @param {Boolean} [current=false] - Pass true to use the current location; false for target location. + * @return {OpenSeadragon.Rect} A rect representing the coordinates in the image. */ - viewportToImageRectangle: function( viewerX, viewerY, pointWidth, pointHeight ) { - var coordA, - coordB, - rect; - if ( arguments.length == 1 ) { - //they passed a rectangle instead of individual components - rect = viewerX; - return this.viewportToImageRectangle( - rect.x, rect.y, rect.width, rect.height - ); + viewportToImageRectangle: function( viewerX, viewerY, pointWidth, pointHeight, current ) { + if (viewerX instanceof $.Rect) { + //they passed a rect instead of individual components + current = viewerY; + pointWidth = viewerX.width; + pointHeight = viewerX.height; + viewerY = viewerX.y; + viewerX = viewerX.x; } - coordA = this.viewportToImageCoordinates( viewerX, viewerY ); - coordB = this._viewportToImageDelta(pointWidth, pointHeight); + + var coordA = this.viewportToImageCoordinates(viewerX, viewerY, current); + var coordB = this._viewportToImageDelta(pointWidth, pointHeight, current); + return new $.Rect( coordA.x, coordA.y, @@ -338,60 +394,96 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag /** * Sets the TiledImage's position in the world. * @param {OpenSeadragon.Point} position - The new position, in viewport coordinates. + * @param {Boolean} [immediately=false] - Whether to animate to the new position or snap immediately. * @fires OpenSeadragon.TiledImage.event:bounds-change */ - setPosition: function(position) { - if (this._worldX === position.x && this._worldY === position.y) { - return; + setPosition: function(position, immediately) { + var sameTarget = (this._xSpring.target.value === position.x && + this._ySpring.target.value === position.y); + + if (immediately) { + if (sameTarget && this._xSpring.current.value === position.x && + this._ySpring.current.value === position.y) { + return; + } + + this._xSpring.resetTo(position.x); + this._ySpring.resetTo(position.y); + this._xSpring.update(); + this._ySpring.update(); + } else { + if (sameTarget) { + return; + } + + this._xSpring.springTo(position.x); + this._ySpring.springTo(position.y); } - this._worldX = position.x; - this._worldY = position.y; - this.updateAgain = true; - this._raiseBoundsChange(); + if (!sameTarget) { + this._raiseBoundsChange(); + } }, /** * Sets the TiledImage's width in the world, adjusting the height to match based on aspect ratio. * @param {Number} width - The new width, in viewport coordinates. + * @param {Boolean} [immediately=false] - Whether to animate to the new size or snap immediately. * @fires OpenSeadragon.TiledImage.event:bounds-change */ - setWidth: function(width) { - if (this._worldWidth === width) { - return; - } - - this._setScale(width); - this.updateAgain = true; - this._raiseBoundsChange(); + setWidth: function(width, immediately) { + this._setScale(width, immediately); }, /** * Sets the TiledImage's height in the world, adjusting the width to match based on aspect ratio. * @param {Number} height - The new height, in viewport coordinates. + * @param {Boolean} [immediately=false] - Whether to animate to the new size or snap immediately. * @fires OpenSeadragon.TiledImage.event:bounds-change */ - setHeight: function(height) { - if (this._worldHeight === height) { - return; - } - - this._setScale(height / this.normHeight); - this.updateAgain = true; - this._raiseBoundsChange(); + setHeight: function(height, immediately) { + this._setScale(height / this.normHeight, immediately); }, // private - _setScale: function(scale) { - this._scale = scale; - this._worldWidth = this._scale; - this._worldHeight = this.normHeight * this._scale; + _setScale: function(scale, immediately) { + var sameTarget = (this._scaleSpring.target.value === scale); + if (immediately) { + if (sameTarget && this._scaleSpring.current.value === scale) { + return; + } + + this._scaleSpring.resetTo(scale); + this._scaleSpring.update(); + this._updateForScale(); + } else { + if (sameTarget) { + return; + } + + this._scaleSpring.springTo(scale); + this._updateForScale(); + } + + if (!sameTarget) { + this._raiseBoundsChange(); + } + }, + + // private + _updateForScale: function() { + this._worldWidthTarget = this._scaleSpring.target.value; + this._worldHeightTarget = this.normHeight * this._scaleSpring.target.value; + this._worldWidthCurrent = this._scaleSpring.current.value; + this._worldHeightCurrent = this.normHeight * this._scaleSpring.current.value; }, // private _raiseBoundsChange: function() { /** * Raised when the TiledImage's bounds are changed. + * Note that this event is triggered only when the animation target is changed; + * not for every frame of animation. * @event bounds-change * @memberOf OpenSeadragon.TiledImage * @type {object} @@ -411,7 +503,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag */ function updateViewport( tiledImage ) { - tiledImage.updateAgain = false; + tiledImage._needsDraw = false; var tile, level, @@ -422,7 +514,7 @@ function updateViewport( tiledImage ) { zeroRatioC = tiledImage.viewport.deltaPixelsFromPoints( tiledImage.source.getPixelRatio( 0 ), true - ).x * tiledImage._scale, + ).x * tiledImage._scaleSpring.current.value, lowestLevel = Math.max( tiledImage.source.minLevel, Math.floor( @@ -445,8 +537,8 @@ function updateViewport( tiledImage ) { levelOpacity, levelVisibility; - viewportBounds.x -= tiledImage._worldX; - viewportBounds.y -= tiledImage._worldY; + viewportBounds.x -= tiledImage._xSpring.current.value; + viewportBounds.y -= tiledImage._ySpring.current.value; // Reset tile's internal drawn state while ( tiledImage.lastDrawn.length > 0 ) { @@ -470,23 +562,23 @@ function updateViewport( tiledImage ) { var viewportBR = viewportBounds.getBottomRight(); //Don't draw if completely outside of the viewport - if ( !tiledImage.wrapHorizontal && (viewportBR.x < 0 || viewportTL.x > tiledImage._worldWidth ) ) { + if ( !tiledImage.wrapHorizontal && (viewportBR.x < 0 || viewportTL.x > tiledImage._worldWidthCurrent ) ) { return; } - if ( !tiledImage.wrapVertical && ( viewportBR.y < 0 || viewportTL.y > tiledImage._worldHeight ) ) { + if ( !tiledImage.wrapVertical && ( viewportBR.y < 0 || viewportTL.y > tiledImage._worldHeightCurrent ) ) { return; } // Calculate viewport rect / bounds if ( !tiledImage.wrapHorizontal ) { viewportTL.x = Math.max( viewportTL.x, 0 ); - viewportBR.x = Math.min( viewportBR.x, tiledImage._worldWidth ); + viewportBR.x = Math.min( viewportBR.x, tiledImage._worldWidthCurrent ); } if ( !tiledImage.wrapVertical ) { viewportTL.y = Math.max( viewportTL.y, 0 ); - viewportBR.y = Math.min( viewportBR.y, tiledImage._worldHeight ); + viewportBR.y = Math.min( viewportBR.y, tiledImage._worldHeightCurrent ); } // Calculations for the interval of levels to draw @@ -503,7 +595,7 @@ function updateViewport( tiledImage ) { renderPixelRatioC = tiledImage.viewport.deltaPixelsFromPoints( tiledImage.source.getPixelRatio( level ), true - ).x * tiledImage._scale; + ).x * tiledImage._scaleSpring.current.value; if ( ( !haveDrawn && renderPixelRatioC >= tiledImage.minPixelRatio ) || ( level == lowestLevel ) ) { @@ -517,7 +609,7 @@ function updateViewport( tiledImage ) { renderPixelRatioT = tiledImage.viewport.deltaPixelsFromPoints( tiledImage.source.getPixelRatio( level ), false - ).x * tiledImage._scale; + ).x * tiledImage._scaleSpring.current.value; zeroRatioT = tiledImage.viewport.deltaPixelsFromPoints( tiledImage.source.getPixelRatio( @@ -527,7 +619,7 @@ function updateViewport( tiledImage ) { ) ), false - ).x * tiledImage._scale; + ).x * tiledImage._scaleSpring.current.value; optimalRatio = tiledImage.immediateRender ? 1 : @@ -567,7 +659,7 @@ function updateViewport( tiledImage ) { if ( best ) { loadTile( tiledImage, best, currentTime ); // because we haven't finished drawing, so - tiledImage.updateAgain = true; + tiledImage._needsDraw = true; } } @@ -615,8 +707,8 @@ function updateLevel( tiledImage, haveDrawn, drawLevel, level, levelOpacity, lev } //OK, a new drawing so do your calculations - tileTL = tiledImage.source.getTileAtPoint( level, viewportTL.divide( tiledImage._scale )); - tileBR = tiledImage.source.getTileAtPoint( level, viewportBR.divide( tiledImage._scale )); + tileTL = tiledImage.source.getTileAtPoint( level, viewportTL.divide( tiledImage._scaleSpring.current.value )); + tileBR = tiledImage.source.getTileAtPoint( level, viewportBR.divide( tiledImage._scaleSpring.current.value )); numberOfTiles = tiledImage.source.getNumTiles( level ); resetCoverage( tiledImage.coverage, level ); @@ -660,8 +752,8 @@ function updateTile( tiledImage, drawLevel, haveDrawn, x, y, level, levelOpacity tiledImage.tilesMatrix, currentTime, numberOfTiles, - tiledImage._worldWidth, - tiledImage._worldHeight + tiledImage._worldWidthCurrent, + tiledImage._worldHeightCurrent ), drawTile = drawLevel; @@ -724,7 +816,7 @@ function updateTile( tiledImage, drawLevel, haveDrawn, x, y, level, levelOpacity } if ( tile.loaded ) { - var needsUpdate = blendTile( + var needsDraw = blendTile( tiledImage, tile, x, y, @@ -733,8 +825,8 @@ function updateTile( tiledImage, drawLevel, haveDrawn, x, y, level, levelOpacity currentTime ); - if ( needsUpdate ) { - tiledImage.updateAgain = true; + if ( needsDraw ) { + tiledImage._needsDraw = true; } } else if ( tile.loading ) { // the tile is already in the download queue @@ -827,29 +919,29 @@ function onTileLoad( tiledImage, tile, time, image ) { // Check if we're mid-update; this can happen on IE8 because image load events for // cached images happen immediately there - if ( !tiledImage.midUpdate ) { + if ( !tiledImage._midDraw ) { finish(); } else { // Wait until after the update, in case caching unloads any tiles window.setTimeout( finish, 1); } - tiledImage.updateAgain = true; + tiledImage._needsDraw = true; } function positionTile( tile, overlap, viewport, viewportCenter, levelVisibility, tiledImage ){ var boundsTL = tile.bounds.getTopLeft(); - boundsTL.x *= tiledImage._scale; - boundsTL.y *= tiledImage._scale; - boundsTL.x += tiledImage._worldX; - boundsTL.y += tiledImage._worldY; + boundsTL.x *= tiledImage._scaleSpring.current.value; + boundsTL.y *= tiledImage._scaleSpring.current.value; + boundsTL.x += tiledImage._xSpring.current.value; + boundsTL.y += tiledImage._ySpring.current.value; var boundsSize = tile.bounds.getSize(); - boundsSize.x *= tiledImage._scale; - boundsSize.y *= tiledImage._scale; + boundsSize.x *= tiledImage._scaleSpring.current.value; + boundsSize.y *= tiledImage._scaleSpring.current.value; var positionC = viewport.pixelFromPoint( boundsTL, true ), positionT = viewport.pixelFromPoint( boundsTL, false ), diff --git a/src/viewer.js b/src/viewer.js index 971eb9d7..00108538 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -666,6 +666,10 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, delete options.index; } + if (options.collectionImmediately === undefined) { + options.collectionImmediately = true; + } + var originalSuccess = options.success; options.success = function(event) { successes++; @@ -1237,6 +1241,8 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, * supports except arrays of images. * Note that you can specify options.width or options.height, but not both. * The other dimension will be calculated according to the item's aspect ratio. + * If collectionMode is on (see {@link OpenSeadragon.Options}), the new image is + * automatically arranged with the others. * @function * @param {Object} options * @param {String|Object|Function} options.tileSource - The TileSource specifier. @@ -1260,6 +1266,8 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, * @param {Function} [options.error] A function that gets called if the image is * unable to be added. It's passed the error event object, which contains "message" * and "source" properties. + * @param {Boolean} [options.collectionImmediately=false] If collectionMode is on, + * specifies whether to snap to the new arrangement immediately or to animate to it. * @fires OpenSeadragon.World.event:add-item * @fires OpenSeadragon.Viewer.event:add-item-failed */ @@ -1337,7 +1345,8 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, y: queueItem.options.y, width: queueItem.options.width, height: queueItem.options.height, - imageLoaderLimit: _this.imageLoaderLimit, + springStiffness: _this.springStiffness, + animationTime: _this.animationTime, minZoomImageRatio: _this.minZoomImageRatio, wrapHorizontal: _this.wrapHorizontal, wrapVertical: _this.wrapVertical, @@ -1345,8 +1354,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, blendTime: _this.blendTime, alwaysBlend: _this.alwaysBlend, minPixelRatio: _this.minPixelRatio, - debugMode: _this.debugMode, - debugGridColor: _this.debugGridColor + debugMode: _this.debugMode }); _this.world.addItem( tiledImage, { @@ -1355,6 +1363,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, if (_this.collectionMode) { _this.world.arrange({ + immediately: queueItem.options.collectionImmediately, rows: _this.collectionRows, layout: _this.collectionLayout, tileSize: _this.collectionTileSize, @@ -2659,6 +2668,7 @@ function updateOnce( viewer ) { } animated = viewer.viewport.update(); + animated = viewer.world.update() || animated; if( viewer.referenceStrip ){ animated = viewer.referenceStrip.update( viewer.viewport ) || animated; @@ -2678,8 +2688,8 @@ function updateOnce( viewer ) { abortControlsAutoHide( viewer ); } - if ( animated || THIS[ viewer.hash ].forceRedraw || viewer.world.needsUpdate() ) { - updateWorld( viewer ); + if ( animated || THIS[ viewer.hash ].forceRedraw || viewer.world.needsDraw() ) { + drawWorld( viewer ); viewer._drawOverlays(); if( viewer.navigator ){ viewer.navigator.update( viewer.viewport ); @@ -2748,9 +2758,9 @@ function resizeViewportAndRecenter( viewer, containerSize, oldBounds, oldCenter viewport.fitBounds( newBounds, true ); } -function updateWorld( viewer ) { +function drawWorld( viewer ) { viewer.drawer.clear(); - viewer.world.update(); + viewer.world.draw(); /** * - Needs documentation - diff --git a/src/world.js b/src/world.js index 9591183b..9e67a1c1 100644 --- a/src/world.js +++ b/src/world.js @@ -51,7 +51,7 @@ $.World = function( options ) { this.viewer = options.viewer; this._items = []; - this._needsUpdate = false; + this._needsDraw = false; this._delegatedFigureSizes = function(event) { _this._figureSizes(); }; @@ -80,7 +80,7 @@ $.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.W } this._figureSizes(); - this._needsUpdate = true; + this._needsDraw = true; item.addHandler('bounds-change', this._delegatedFigureSizes); @@ -147,7 +147,7 @@ $.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.W this._items.splice( oldIndex, 1 ); this._items.splice( index, 0, item ); - this._needsUpdate = true; + this._needsDraw = true; /** * Raised when the order of the indexes has been changed. @@ -185,7 +185,7 @@ $.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.W item.removeHandler('bounds-change', this._delegatedFigureSizes); this._items.splice( index, 1 ); this._figureSizes(); - this._needsUpdate = true; + this._needsDraw = true; this._raiseRemoveItem(item); }, @@ -204,7 +204,7 @@ $.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.W var removedItems = this._items; this._items = []; this._figureSizes(); - this._needsUpdate = true; + this._needsDraw = true; for (i = 0; i < removedItems.length; i++) { item = removedItems[i]; @@ -222,26 +222,38 @@ $.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.W }, /** - * Updates (i.e. draws) all items. + * Updates (i.e. animates bounds of) all items. */ update: function() { + var animated = false; for ( var i = 0; i < this._items.length; i++ ) { - this._items[i].update(); + animated = this._items[i].update() || animated; } - this._needsUpdate = false; + return animated; + }, + + /** + * Draws all items. + */ + draw: function() { + for ( var i = 0; i < this._items.length; i++ ) { + this._items[i].draw(); + } + + this._needsDraw = false; }, /** * @returns {Boolean} true if any items need updating. */ - needsUpdate: function() { + needsDraw: function() { for ( var i = 0; i < this._items.length; i++ ) { - if ( this._items[i].needsUpdate() ) { + if ( this._items[i].needsDraw() ) { return true; } } - return this._needsUpdate; + return this._needsDraw; }, /** @@ -264,6 +276,7 @@ $.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.W /** * Arranges all of the TiledImages with the specified settings. * @param {Object} options - Specifies how to arrange. + * @param {Boolean} [options.immediately=false] - Whether to animate to the new arrangement. * @param {String} [options.layout] - See collectionLayout in {@link OpenSeadragon.Options}. * @param {Number} [options.rows] - See collectionRows in {@link OpenSeadragon.Options}. * @param {Number} [options.tileSize] - See collectionTileSize in {@link OpenSeadragon.Options}. @@ -272,6 +285,7 @@ $.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.W */ arrange: function(options) { options = options || {}; + var immediately = options.immediately || false; var layout = options.layout || $.DEFAULT_SETTINGS.collectionLayout; var rows = options.rows || $.DEFAULT_SETTINGS.collectionRows; var tileSize = options.tileSize || $.DEFAULT_SETTINGS.collectionTileSize; @@ -304,8 +318,8 @@ $.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.W position = new $.Point(x + ((tileSize - width) / 2), y + ((tileSize - height) / 2)); - item.setPosition(position); - item.setWidth(width); + item.setPosition(position, immediately); + item.setWidth(width, immediately); if (layout === 'horizontal') { x += increment; diff --git a/test/demo/collections/main.js b/test/demo/collections/main.js index d18710a2..51f91c83 100644 --- a/test/demo/collections/main.js +++ b/test/demo/collections/main.js @@ -6,7 +6,7 @@ init: function() { var self = this; - var testInitialOpen = true; + var testInitialOpen = false; var testOverlays = false; var testMargins = false; var testNavigator = true; @@ -21,7 +21,7 @@ // referenceStripScroll: 'vertical', navPrevNextWrap: false, preserveViewport: false, - // collectionMode: true, + collectionMode: true, // collectionRows: 3, // collectionLayout: 'vertical', // collectionTileSize: 10, @@ -112,10 +112,39 @@ } if (!testInitialOpen) { - this.collectionTest(); + this.basicTest(); } }, + // ---------- + shrink: function(index) { + index = index || 0; + var image = this.viewer.world.getItemAt(index); + image.setWidth(image.getBounds().width * 0.3); + }, + + // ---------- + move: function(index) { + index = index || 0; + var image = this.viewer.world.getItemAt(index); + var point = image.getBounds().getTopLeft(); + point.x += image.getBounds().width * 0.3; + image.setPosition(point); + }, + + // ---------- + add: function() { + var self = this; + + this.viewer.addTiledImage({ + tileSource: "../../data/testpattern.dzi", + width: 1, + success: function() { + self.viewer.viewport.goHome(); + } + }); + }, + // ---------- toggle: function() { var $el = $(this.viewer.element); @@ -130,7 +159,8 @@ }); this.viewer.open({ - tileSource: "../../data/testpattern.dzi" + tileSource: "../../data/testpattern.dzi", + width: 1 }); }, diff --git a/test/modules/drawer.js b/test/modules/drawer.js index 80f9fa7f..c91915c3 100644 --- a/test/modules/drawer.js +++ b/test/modules/drawer.js @@ -74,10 +74,10 @@ Util.testDeprecation(viewer.drawer, 'updateOverlay', viewer, 'updateOverlay'); Util.testDeprecation(viewer.drawer, 'removeOverlay', viewer, 'removeOverlay'); Util.testDeprecation(viewer.drawer, 'clearOverlays', viewer, 'clearOverlays'); - Util.testDeprecation(viewer.drawer, 'needsUpdate', viewer.world, 'needsUpdate'); + Util.testDeprecation(viewer.drawer, 'needsUpdate', viewer.world, 'needsDraw'); Util.testDeprecation(viewer.drawer, 'numTilesLoaded', viewer.tileCache, 'numTilesLoaded'); Util.testDeprecation(viewer.drawer, 'reset', viewer.world, 'resetItems'); - Util.testDeprecation(viewer.drawer, 'update', viewer.world, 'update'); + Util.testDeprecation(viewer.drawer, 'update', viewer.world, 'draw'); start(); }); diff --git a/test/modules/navigator.js b/test/modules/navigator.js index 8c5aeb27..8ee325e2 100644 --- a/test/modules/navigator.js +++ b/test/modules/navigator.js @@ -140,7 +140,7 @@ currentDisplayRegionLeft = displayRegion.position().left; currentDisplayWidth = displayRegion.width(); viewerAndNavigatorDisplayReady = viewer.drawer !== null && - !viewer.drawer.needsUpdate() && + !viewer.world.needsDraw() && currentDisplayWidth > 0 && Util.equalsWithVariance(lastDisplayRegionLeft, currentDisplayRegionLeft, 0.0001) && Util.equalsWithVariance(lastDisplayWidth, currentDisplayWidth, 0.0001) && @@ -160,7 +160,7 @@ else { if (count === 40) { console.log("waitForViewer:" + - viewer.drawer + ":" + viewer.drawer.needsUpdate() + ":" + + viewer.drawer + ":" + viewer.world.needsDraw() + ":" + viewerAndNavigatorDisplayReady + ":" + lastDisplayRegionLeft + ":" + currentDisplayRegionLeft + ":" + lastDisplayWidth + ":" + currentDisplayWidth + ":" + diff --git a/test/modules/overlays.js b/test/modules/overlays.js index 9fdfdb2e..00341ce8 100644 --- a/test/modules/overlays.js +++ b/test/modules/overlays.js @@ -29,7 +29,7 @@ } var ready = viewer.isOpen() && viewer.drawer !== null && - !viewer.drawer.needsUpdate() && + !viewer.world.needsDraw() && Util.equalsWithVariance( viewer.viewport.getBounds( true ).x, viewer.viewport.getBounds().x, 0.000 ) && Util.equalsWithVariance( viewer.viewport.getBounds( true ).y, @@ -46,7 +46,7 @@ }, 100 ); } else { console.log( "waitForViewer:" + viewer.isOpen( ) + ":" + viewer.drawer + - ":" + viewer.drawer.needsUpdate() ); + ":" + viewer.world.needsDraw() ); handler(); } } diff --git a/test/modules/tiledimage.js b/test/modules/tiledimage.js index 703cc72a..44dc9327 100644 --- a/test/modules/tiledimage.js +++ b/test/modules/tiledimage.js @@ -85,13 +85,39 @@ }); }); + // ---------- + asyncTest('animation', function() { + viewer.addHandler("open", function () { + var image = viewer.world.getItemAt(0); + propEqual(image.getBounds(), new OpenSeadragon.Rect(0, 0, 1, 1), 'target bounds on open'); + propEqual(image.getBounds(true), new OpenSeadragon.Rect(0, 0, 1, 1), 'current bounds on open'); + + image.setPosition(new OpenSeadragon.Point(1, 2)); + propEqual(image.getBounds(), new OpenSeadragon.Rect(1, 2, 1, 1), 'target bounds after position'); + propEqual(image.getBounds(true), new OpenSeadragon.Rect(0, 0, 1, 1), 'current bounds after position'); + + image.setWidth(3); + propEqual(image.getBounds(), new OpenSeadragon.Rect(1, 2, 3, 3), 'target bounds after width'); + propEqual(image.getBounds(true), new OpenSeadragon.Rect(0, 0, 1, 1), 'current bounds after width'); + + viewer.addHandler('animation-finish', function animationHandler() { + viewer.removeHandler('animation-finish', animationHandler); + propEqual(image.getBounds(), new OpenSeadragon.Rect(1, 2, 3, 3), 'target bounds after animation'); + propEqual(image.getBounds(true), new OpenSeadragon.Rect(1, 2, 3, 3), 'current bounds after animation'); + start(); + }); + }); + + viewer.open('/test/data/testpattern.dzi'); + }); + // ---------- asyncTest('update', function() { var handlerCount = 0; viewer.addHandler('open', function(event) { var image = viewer.world.getItemAt(0); - equal(image.needsUpdate(), true, 'needs update after open'); + equal(image.needsDraw(), true, 'needs draw after open'); viewer.addHandler('update-level', function updateLevelHandler(event) { viewer.removeHandler('update-level', updateLevelHandler); @@ -137,7 +163,7 @@ start(); }); - image.update(); + image.draw(); }); viewer.open('/test/data/testpattern.dzi'); diff --git a/test/modules/world.js b/test/modules/world.js index 634165d8..ce63f02a 100644 --- a/test/modules/world.js +++ b/test/modules/world.js @@ -144,24 +144,24 @@ }); // ---------- - asyncTest('update', function() { + asyncTest('draw', function() { var handlerCount = 0; viewer.addHandler('open', function(event) { - equal(viewer.world.needsUpdate(), true, 'needs update after open'); + equal(viewer.world.needsDraw(), true, 'needs draw after open'); viewer.addHandler('update-level', function updateHandler() { viewer.removeHandler('update-level', updateHandler); handlerCount++; }); - viewer.world.update(); + viewer.world.draw(); equal(handlerCount, 1, 'correct number of handlers called'); start(); }); - equal(viewer.world.needsUpdate(), false, 'needs no update at first'); + equal(viewer.world.needsDraw(), false, 'needs no draw at first'); viewer.open('/test/data/testpattern.dzi'); });