diff --git a/src/viewport.js b/src/viewport.js index 8a2ba22d..3c08c594 100644 --- a/src/viewport.js +++ b/src/viewport.js @@ -555,40 +555,27 @@ $.Viewport.prototype = { }, /** + * Enforces the minZoom, maxZoom and visibilityRatio constraints by + * zooming and panning to the closest acceptable zoom and location. * @function + * @param {Boolean} [immediately=false] * @return {OpenSeadragon.Viewport} Chainable. * @fires OpenSeadragon.Viewer.event:constrain */ - applyConstraints: function( immediately ) { - var actualZoom = this.getZoom(), - constrainedZoom = Math.max( - Math.min( actualZoom, this.getMaxZoom() ), - this.getMinZoom() - ), - bounds, - constrainedBounds; - - if ( actualZoom != constrainedZoom ) { - this.zoomTo( constrainedZoom, this.zoomPoint, immediately ); - } - - bounds = this.getBoundsNoRotate(); - - constrainedBounds = this._applyBoundaryConstraints( bounds, immediately ); - - if ( bounds.x !== constrainedBounds.x || bounds.y !== constrainedBounds.y || immediately ){ - this.fitBounds( constrainedBounds, immediately ); - } - + applyConstraints: function(immediately) { + this.fitBoundsWithConstraints(this.getBounds(), immediately); return this; }, /** + * Equivalent to {@link OpenSeadragon.Viewport#applyConstraints} * @function - * @param {Boolean} immediately + * @param {Boolean} [immediately=false] + * @return {OpenSeadragon.Viewport} Chainable. + * @fires OpenSeadragon.Viewer.event:constrain */ - ensureVisible: function( immediately ) { - return this.applyConstraints( immediately ); + ensureVisible: function(immediately) { + return this.applyConstraints(immediately); }, /** @@ -670,29 +657,41 @@ $.Viewport.prototype = { }, /** + * Makes the viewport zoom and pan so that the specified bounds take + * as much space as possible in the viewport. + * Note: this method ignores the constraints (minZoom, maxZoom and + * visibilityRatio). + * Use {@link OpenSeadragon.Viewport#fitBoundsWithConstraints} to enforce + * them. * @function * @param {OpenSeadragon.Rect} bounds - * @param {Boolean} immediately + * @param {Boolean} [immediately=false] * @return {OpenSeadragon.Viewport} Chainable. */ - fitBounds: function( bounds, immediately ) { - return this._fitBounds( bounds, { + fitBounds: function(bounds, immediately) { + return this._fitBounds(bounds, { immediately: immediately, constraints: false - } ); + }); }, /** + * Makes the viewport zoom and pan so that the specified bounds take + * as much space as possible in the viewport while enforcing the constraints + * (minZoom, maxZoom and visibilityRatio). + * Note: because this method enforces the constraints, part of the + * provided bounds may end up outside of the viewport. + * Use {@link OpenSeadragon.Viewport#fitBounds} to ignore them. * @function * @param {OpenSeadragon.Rect} bounds - * @param {Boolean} immediately + * @param {Boolean} [immediately=false] * @return {OpenSeadragon.Viewport} Chainable. */ - fitBoundsWithConstraints: function( bounds, immediately ) { - return this._fitBounds( bounds, { + fitBoundsWithConstraints: function(bounds, immediately) { + return this._fitBounds(bounds, { immediately: immediately, constraints: true - } ); + }); }, /** diff --git a/test/modules/events.js b/test/modules/events.js index 18f50c37..2d435fc0 100644 --- a/test/modules/events.js +++ b/test/modules/events.js @@ -678,45 +678,20 @@ } ); // ---------- - asyncTest( 'Viewer: preventDefaultAction', function () { - var $canvas = $( viewer.element ).find( '.openseadragon-canvas' ).not( '.navigator .openseadragon-canvas' ), - tracker = viewer.innerTracker, - origClickHandler, - origDragHandler, - dragCount = 10, - originalZoom = 0, - originalBounds = null; - - var onOpen = function ( event ) { - viewer.removeHandler( 'open', onOpen ); - - // Hook viewer events to set preventDefaultAction - origClickHandler = tracker.clickHandler; - tracker.clickHandler = function ( event ) { - event.preventDefaultAction = true; - return origClickHandler( event ); - }; - origDragHandler = tracker.dragHandler; - tracker.dragHandler = function ( event ) { - event.preventDefaultAction = true; - return origDragHandler( event ); - }; - - originalZoom = viewer.viewport.getZoom(); - originalBounds = viewer.viewport.getBounds(); - - var event = { - clientX:1, - clientY:1 - }; + asyncTest('Viewer: preventDefaultAction', function() { + var $canvas = $(viewer.element).find('.openseadragon-canvas') + .not('.navigator .openseadragon-canvas'); + var tracker = viewer.innerTracker; + var epsilon = 0.0000001; + function simulateClickAndDrag() { $canvas.simulate( 'focus', event ); // Drag to pan Util.simulateViewerClickWithDrag( { viewer: viewer, widthFactor: 0.25, heightFactor: 0.25, - dragCount: dragCount, + dragCount: 10, dragDx: 1, dragDy: 1 } ); @@ -730,20 +705,57 @@ dragDy: 0 } ); $canvas.simulate( 'blur', event ); + } - var zoom = viewer.viewport.getZoom(), - bounds = viewer.viewport.getBounds(); + var onOpen = function() { + viewer.removeHandler('open', onOpen); - equal( zoom, originalZoom, "Zoom prevented" ); - ok( bounds.x == originalBounds.x && bounds.y == originalBounds.y, 'Pan prevented' ); + // Hook viewer events to set preventDefaultAction + var origClickHandler = tracker.clickHandler; + tracker.clickHandler = function(event) { + event.preventDefaultAction = true; + return origClickHandler(event); + }; + var origDragHandler = tracker.dragHandler; + tracker.dragHandler = function(event) { + event.preventDefaultAction = true; + return origDragHandler(event); + }; + + var originalZoom = viewer.viewport.getZoom(); + var originalBounds = viewer.viewport.getBounds(); + + simulateClickAndDrag(); + + var zoom = viewer.viewport.getZoom(); + var bounds = viewer.viewport.getBounds(); + Util.assessNumericValue(zoom, originalZoom, epsilon, + "Zoom should be prevented"); + Util.assertRectangleEquals(bounds, originalBounds, epsilon, + 'Pan should be prevented'); + + tracker.clickHandler = origClickHandler; + tracker.dragHandler = origDragHandler; + + simulateClickAndDrag(); + + var zoom = viewer.viewport.getZoom(); + var bounds = viewer.viewport.getBounds(); + Util.assessNumericValue(zoom, 0.002, epsilon, + "Zoom should not be prevented"); + Util.assertRectangleEquals( + bounds, + new OpenSeadragon.Rect(-250, -0.25, 500, 0.5), + epsilon, + 'Pan should not be prevented'); viewer.close(); start(); }; - viewer.addHandler( 'open', onOpen ); - viewer.open( '/test/data/testpattern.dzi' ); - } ); + viewer.addHandler('open', onOpen); + viewer.open('/test/data/testpattern.dzi'); + }); // ---------- asyncTest( 'EventSource/MouseTracker/Viewer: event.originalEvent event.userData canvas-drag canvas-drag-end canvas-release canvas-click', function () { diff --git a/test/modules/viewport.js b/test/modules/viewport.js index 0b06b93c..8412a007 100644 --- a/test/modules/viewport.js +++ b/test/modules/viewport.js @@ -409,7 +409,7 @@ viewer.open(DZI_PATH); }); - asyncTest('ensureVisible', function(){ + asyncTest('ensureVisible', function() { var openHandler = function(event) { viewer.removeHandler('open', openHandler); var viewport = viewer.viewport; @@ -427,6 +427,45 @@ viewer.open(DZI_PATH); }); + asyncTest('applyConstraints', function() { + var openHandler = function() { + viewer.removeHandler('open', openHandler); + var viewport = viewer.viewport; + + viewport.fitBounds(new OpenSeadragon.Rect(1, 1, 1, 1), true); + viewport.visibilityRatio = 0.3; + viewport.applyConstraints(true); + var bounds = viewport.getBounds(); + Util.assertRectangleEquals( + bounds, + new OpenSeadragon.Rect(0.7, 0.7, 1, 1), + EPSILON, + "Viewport.applyConstraints should move viewport."); + start(); + }; + viewer.addHandler('open', openHandler); + viewer.open(DZI_PATH); + }); + + asyncTest('applyConstraints with rotation', function() { + var openHandler = function() { + viewer.removeHandler('open', openHandler); + var viewport = viewer.viewport; + viewport.setRotation(45); + viewport.fitBounds(new OpenSeadragon.Rect(1, 1, 1, 1), true); + viewport.applyConstraints(true); + var bounds = viewport.getBounds(); + Util.assertRectangleEquals( + bounds, + new OpenSeadragon.Rect(1, 0, Math.sqrt(2), Math.sqrt(2), 45), + EPSILON, + "Viewport.applyConstraints with rotation should move viewport."); + start(); + }; + viewer.addHandler('open', openHandler); + viewer.open(DZI_PATH); + }); + // Fit bounds tests var testRectsFitBounds = [ new OpenSeadragon.Rect(0, -0.75, 0.5, 1),