diff --git a/build.properties b/build.properties
index af817a35..fdaf90fb 100644
--- a/build.properties
+++ b/build.properties
@@ -1,12 +1,12 @@
# OpenSeadragon build.properties
# TODO: how do you auto-increment build_id's with every commit?
-# TRY: continuos integration
+# TRY: continuous integration
# TRY: git-hooks
PROJECT: openseadragon
BUILD_MAJOR: 0
BUILD_MINOR: 9
-BUILD_ID: 18
+BUILD_ID: 21
BUILD: ${PROJECT}.${BUILD_MAJOR}.${BUILD_MINOR}.${BUILD_ID}
VERSION: ${BUILD_MAJOR}.${BUILD_MINOR}.${BUILD_ID}
diff --git a/build.xml b/build.xml
index c7ae2277..0ec07da6 100644
--- a/build.xml
+++ b/build.xml
@@ -24,6 +24,7 @@
+
diff --git a/openseadragon.js b/openseadragon.js
index 7ec55431..31c3099e 100644
--- a/openseadragon.js
+++ b/openseadragon.js
@@ -1,5 +1,5 @@
/**
- * @version OpenSeadragon 0.9.18
+ * @version OpenSeadragon 0.9.21
*
* @fileOverview
*
@@ -109,17 +109,28 @@
* floated on top of the image the user is viewing.
*
* @param {Boolean} [options.immediateRender=false]
+ * Render the best closest level first, ignoring the lowering levels which
+ * provide the effect of very blurry to sharp. It is recommended to change
+ * setting to true for mobile devices.
*
* @param {Boolean} [options.wrapHorizontal=false]
- * Should the image wrap horizontally within the viewport. Useful for
- * maps or images representing the surface of a sphere or cylinder.
+ * Set to true to force the image to wrap horizontally within the viewport.
+ * Useful for maps or images representing the surface of a sphere or cylinder.
*
* @param {Boolean} [options.wrapVertical=false]
- * Should the image wrap vertically within the viewport. Useful for
- * maps or images representing the surface of a sphere or cylinder.
+ * Set to true to force the image to wrap vertically within the viewport.
+ * Useful for maps or images representing the surface of a sphere or cylinder.
*
* @param {Number} [options.minZoomImageRatio=0.8]
+ * The minimum percentage ( expressed as a number between 0 and 1 ) of
+ * the viewport height or width at which the zoom out will be constrained.
+ * Setting it to 0, for example will allow you to zoom out infinitly.
+ *
* @param {Number} [options.maxZoomPixelRatio=2]
+ * The maximum ratio to allow a zoom-in to affect the highest level pixel
+ * ratio. This can be set to Infinity to allow 'infinite' zooming into the
+ * image though it is less effective visually if the HTML5 Canvas is not
+ * availble on the viewing device.
*
* @param {Number} [options.visibilityRatio=0.5]
* The percentage ( as a number from 0 to 1 ) of the source image which
@@ -129,14 +140,31 @@
* true will provide the effect of an infinitely scrolling viewport.
*
* @param {Number} [options.springStiffness=5.0]
+ *
* @param {Number} [options.imageLoaderLimit=0]
+ * The maximum number of image requests to make concurrently. By default
+ * it is set to 0 allowing the browser to make the maximum number of
+ * image requests in parallel as allowed by the browsers policy.
+ *
* @param {Number} [options.clickTimeThreshold=200]
+ * If multiple mouse clicks occurs within less than this number of
+ * milliseconds, treat them as a single click.
+ *
* @param {Number} [options.clickDistThreshold=5]
+ * If a mouse or touch drag occurs and the distance to the starting drag
+ * point is less than this many pixels, ignore the drag event.
+ *
* @param {Number} [options.zoomPerClick=2.0]
+ * The "zoom distance" per mouse click or touch tap.
+ *
* @param {Number} [options.zoomPerScroll=1.2]
+ * The "zoom distance" per mouse scroll or touch pinch.
+ *
* @param {Number} [options.zoomPerSecond=2.0]
+ * The number of seconds to animate a single zoom event over.
*
* @param {Boolean} [options.showNavigationControl=true]
+ * Set to false to prevent the appearance of the default navigation controls.
*
* @param {Number} [options.controlsFadeDelay=2000]
* The number of milliseconds to wait once the user has stopped interacting
@@ -421,6 +449,12 @@ OpenSeadragon = window.OpenSeadragon || function( options ){
zoomPerScroll: 1.2,
zoomPerSecond: 2.0,
showNavigationControl: true,
+
+ showNavigator: false,
+ navigatorElement: null,
+ navigatorHeight: null,
+ navigatorWidth: null,
+ navigatorPosition: null,
//These two were referenced but never defined
controlsFadeDelay: 2000,
@@ -1076,6 +1110,78 @@ OpenSeadragon = window.OpenSeadragon || function( options ){
},
+ /**
+ * Taken from jQuery 1.6.1
+ * @param {Object} options
+ * @param {String} options.url
+ * @param {Function} options.callback
+ * @param {String} [options.param='callback'] The name of the url parameter
+ * to request the jsonp provider with.
+ * @param {String} [options.callbackName=] The name of the callback to
+ * request the jsonp provider with.
+ */
+ jsonp: function( options ){
+ var script,
+ url = options.url,
+ head = document.head ||
+ document.getElementsByTagName( "head" )[ 0 ] ||
+ document.documentElement,
+ jsonpCallback = options.callbackName || 'openseadragon' + (+new Date()),
+ previous = window[ jsonpCallback ],
+ replace = "$1" + jsonpCallback + "$2",
+ callbackParam = options.param || 'callback',
+ callback = options.callback;
+
+ url = url.replace( /(\=)\?(&|$)|\?\?/i, replace );
+ // Add callback manually
+ url += (/\?/.test( url ) ? "&" : "?") + callbackParam + "=" + jsonpCallback;
+
+ // Install callback
+ window[ jsonpCallback ] = function( response ) {
+ if ( !previous ){
+ delete window[ jsonpCallback ];
+ } else {
+ window[ jsonpCallback ] = previous;
+ }
+ if( callback && $.isFunction( callback ) ){
+ callback( response );
+ }
+ };
+
+ script = document.createElement( "script" );
+
+ script.async = "async";
+
+ if ( options.scriptCharset ) {
+ script.charset = options.scriptCharset;
+ }
+
+ script.src = url;
+
+ // Attach handlers for all browsers
+ script.onload = script.onreadystatechange = function( _, isAbort ) {
+
+ if ( isAbort || !script.readyState || /loaded|complete/.test( script.readyState ) ) {
+
+ // Handle memory leak in IE
+ script.onload = script.onreadystatechange = null;
+
+ // Remove the script
+ if ( head && script.parentNode ) {
+ head.removeChild( script );
+ }
+
+ // Dereference the script
+ script = undefined;
+ }
+ };
+ // Use insertBefore instead of appendChild to circumvent an IE6 bug.
+ // This arises when a base node is used (#2709 and #4378).
+ head.insertBefore( script, head.firstChild );
+
+ },
+
+
/**
* Loads a Deep Zoom Image description from a url or XML string and
* provides a callback hook for the resulting Document
@@ -2758,11 +2864,12 @@ $.Viewer = function( options ) {
source: null,
drawer: null,
viewport: null,
+ navigator: null,
profiler: null
}, $.DEFAULT_SETTINGS, options );
- this.element = document.getElementById( this.id );
+ this.element = this.element || document.getElementById( this.id );
this.container = $.makeNeutralElement( "div" );
this.canvas = $.makeNeutralElement( "div" );
@@ -2792,7 +2899,7 @@ $.Viewer = function( options ) {
dragHandler: $.delegate( this, onCanvasDrag ),
releaseHandler: $.delegate( this, onCanvasRelease ),
scrollHandler: $.delegate( this, onCanvasScroll )
- }).setTracking( true ); // default state
+ }).setTracking( this.mouseNavEnabled ? true : false ); // default state
this.outerTracker = new $.MouseTracker({
element: this.container,
@@ -2801,7 +2908,7 @@ $.Viewer = function( options ) {
enterHandler: $.delegate( this, onContainerEnter ),
exitHandler: $.delegate( this, onContainerExit ),
releaseHandler: $.delegate( this, onContainerRelease )
- }).setTracking( true ); // always tracking
+ }).setTracking( this.mouseNavEnabled ? true : false ); // always tracking
(function( canvas ){
canvas.width = "100%";
@@ -2934,6 +3041,23 @@ $.Viewer = function( options ) {
this.addControl( this.navControl, $.ControlAnchor.BOTTOM_RIGHT );
}
+ if ( this.showNavigator ){
+ this.navigator = new $.Navigator({
+ viewerId: this.id,
+ id: this.navigatorElement,
+ position: this.navigatorPosition,
+ height: this.navigatorHeight,
+ width: this.navigatorWidth,
+ tileSources: this.tileSources,
+ prefixUrl: this.prefixUrl
+ });
+ this.addControl(
+ this.navigator.element,
+ $.ControlAnchor.TOP_RIGHT
+ );
+ }
+
+
for ( i = 0; i < this.customControls.length; i++ ) {
this.addControl(
this.customControls[ i ].id,
@@ -3628,9 +3752,15 @@ function updateOnce( viewer ) {
if ( animated ) {
viewer.drawer.update();
+ if( viewer.navigator ){
+ viewer.navigator.update( viewer.viewport );
+ }
viewer.raiseEvent( "animation" );
} else if ( THIS[ viewer.hash ].forceRedraw || viewer.drawer.needsUpdate() ) {
viewer.drawer.update();
+ if( viewer.navigator ){
+ viewer.navigator.update( viewer.viewport );
+ }
THIS[ viewer.hash ].forceRedraw = false;
}
@@ -3736,7 +3866,102 @@ function onFullPage() {
};
}( OpenSeadragon ));
+(function( $ ){
+
+/**
+ * @param {Object} options
+ * @param {String} options.viewerId
+ */
+$.Navigator = function( options ){
+ var _this = this,
+ viewer = $.getElement( options.viewerId ),
+ viewerSize = $.getElementSize( viewer );
+
+ //We may need to create a new element and id if they did not
+ //provide the id for the existing element
+ if( !options.id ){
+ options.id = 'navigator-' + (+new Date());
+ this.element = $.makeNeutralElement( "div" );
+ this.element.id = options.id;
+ }
+
+ options = $.extend( true, options, {
+ element: this.element,
+ //These need to be overridden to prevent recursion since
+ //the navigator is a viewer and a viewer has a navigator
+ showNavigator: false,
+ mouseNavEnabled: false,
+ showNavigationControl: false
+ });
+
+ (function( style ){
+ style.marginTop = '0px';
+ style.marginRight = '0px';
+ style.marginBottom = '0px';
+ style.marginLeft = '0px';
+ style.border = '2px solid #555';
+ style.background = '#000';
+ style.opacity = 0.8;
+ style.overflow = 'hidden';
+ }( this.element.style ));
+
+ this.displayRegion = $.makeNeutralElement( "div" );
+ this.displayRegion.id = this.element.id + '-displayregion';
+
+ (function( style ){
+ style.position = 'relative';
+ style.top = '0px';
+ style.left = '0px';
+ style.border = '1px solid red';
+ style.background = 'transparent';
+ style.float = 'left';
+ style.zIndex = 999999999;
+ style.opacity = 0.8;
+ }( this.displayRegion.style ));
+
+ this.element.appendChild( this.displayRegion );
+
+ $.Viewer.apply( this, [ options ] );
+
+ if( options.width ){
+ this.element.style.width = options.width + 'px';
+ } else {
+ this.element.style.width = ( viewerSize.x / 4 ) + 'px';
+ }
+ if( options.height ){
+ this.element.style.height = options.height + 'px';
+ } else {
+ this.element.style.height = ( viewerSize.y / 4 ) + 'px';
+ }
+
+};
+
+$.extend( $.Navigator.prototype, $.EventHandler.prototype, $.Viewer.prototype, {
+
+ update: function( viewport ){
+
+
+ var bounds = viewport.getBounds( true ),
+ topleft = this.viewport.pixelFromPoint( bounds.getTopLeft() )
+ bottomright = this.viewport.pixelFromPoint( bounds.getBottomRight() );
+
+ //update style for navigator-box
+ (function(style){
+
+ style.top = topleft.y + 'px';
+ style.left = topleft.x + 'px';
+ style.width = ( Math.abs( topleft.x - bottomright.x ) - 2 ) + 'px';
+ style.height = ( Math.abs( topleft.y - bottomright.y ) - 2 ) + 'px';
+
+ }( this.displayRegion.style ));
+
+ }
+
+});
+
+
+}( OpenSeadragon ));
(function( $ ){
//TODO: I guess this is where the i18n needs to be reimplemented. I'll look
@@ -6390,8 +6615,8 @@ $.Viewport.prototype = {
height = width / this.getAspectRatio();
return new $.Rect(
- center.x - width / 2.0,
- center.y - height / 2.0,
+ center.x - ( width / 2.0 ),
+ center.y - ( height / 2.0 ),
width,
height
);
diff --git a/src/navigator.js b/src/navigator.js
new file mode 100644
index 00000000..e75f0675
--- /dev/null
+++ b/src/navigator.js
@@ -0,0 +1,96 @@
+(function( $ ){
+
+/**
+ * @param {Object} options
+ * @param {String} options.viewerId
+ */
+$.Navigator = function( options ){
+
+ var _this = this,
+ viewer = $.getElement( options.viewerId ),
+ viewerSize = $.getElementSize( viewer );
+
+ //We may need to create a new element and id if they did not
+ //provide the id for the existing element
+ if( !options.id ){
+ options.id = 'navigator-' + (+new Date());
+ this.element = $.makeNeutralElement( "div" );
+ this.element.id = options.id;
+ }
+
+ options = $.extend( true, options, {
+ element: this.element,
+ //These need to be overridden to prevent recursion since
+ //the navigator is a viewer and a viewer has a navigator
+ showNavigator: false,
+ mouseNavEnabled: false,
+ showNavigationControl: false
+ });
+
+ (function( style ){
+ style.marginTop = '0px';
+ style.marginRight = '0px';
+ style.marginBottom = '0px';
+ style.marginLeft = '0px';
+ style.border = '2px solid #555';
+ style.background = '#000';
+ style.opacity = 0.8;
+ style.overflow = 'hidden';
+ }( this.element.style ));
+
+ this.displayRegion = $.makeNeutralElement( "div" );
+ this.displayRegion.id = this.element.id + '-displayregion';
+
+ (function( style ){
+ style.position = 'relative';
+ style.top = '0px';
+ style.left = '0px';
+ style.border = '1px solid red';
+ style.background = 'transparent';
+ style.float = 'left';
+ style.zIndex = 999999999;
+ style.opacity = 0.8;
+ }( this.displayRegion.style ));
+
+ this.element.appendChild( this.displayRegion );
+
+ $.Viewer.apply( this, [ options ] );
+
+ if( options.width ){
+ this.element.style.width = options.width + 'px';
+ } else {
+ this.element.style.width = ( viewerSize.x / 4 ) + 'px';
+ }
+ if( options.height ){
+ this.element.style.height = options.height + 'px';
+ } else {
+ this.element.style.height = ( viewerSize.y / 4 ) + 'px';
+ }
+
+};
+
+$.extend( $.Navigator.prototype, $.EventHandler.prototype, $.Viewer.prototype, {
+
+ update: function( viewport ){
+
+
+ var bounds = viewport.getBounds( true ),
+ topleft = this.viewport.pixelFromPoint( bounds.getTopLeft() )
+ bottomright = this.viewport.pixelFromPoint( bounds.getBottomRight() );
+
+ //update style for navigator-box
+ (function(style){
+
+ style.top = topleft.y + 'px';
+ style.left = topleft.x + 'px';
+ style.width = ( Math.abs( topleft.x - bottomright.x ) - 2 ) + 'px';
+ style.height = ( Math.abs( topleft.y - bottomright.y ) - 2 ) + 'px';
+
+ }( this.displayRegion.style ));
+
+ }
+
+});
+
+
+}( OpenSeadragon ));
\ No newline at end of file
diff --git a/src/openseadragon.js b/src/openseadragon.js
index 99c51611..0c8f585c 100644
--- a/src/openseadragon.js
+++ b/src/openseadragon.js
@@ -109,17 +109,28 @@
* floated on top of the image the user is viewing.
*
* @param {Boolean} [options.immediateRender=false]
+ * Render the best closest level first, ignoring the lowering levels which
+ * provide the effect of very blurry to sharp. It is recommended to change
+ * setting to true for mobile devices.
*
* @param {Boolean} [options.wrapHorizontal=false]
- * Should the image wrap horizontally within the viewport. Useful for
- * maps or images representing the surface of a sphere or cylinder.
+ * Set to true to force the image to wrap horizontally within the viewport.
+ * Useful for maps or images representing the surface of a sphere or cylinder.
*
* @param {Boolean} [options.wrapVertical=false]
- * Should the image wrap vertically within the viewport. Useful for
- * maps or images representing the surface of a sphere or cylinder.
+ * Set to true to force the image to wrap vertically within the viewport.
+ * Useful for maps or images representing the surface of a sphere or cylinder.
*
* @param {Number} [options.minZoomImageRatio=0.8]
+ * The minimum percentage ( expressed as a number between 0 and 1 ) of
+ * the viewport height or width at which the zoom out will be constrained.
+ * Setting it to 0, for example will allow you to zoom out infinitly.
+ *
* @param {Number} [options.maxZoomPixelRatio=2]
+ * The maximum ratio to allow a zoom-in to affect the highest level pixel
+ * ratio. This can be set to Infinity to allow 'infinite' zooming into the
+ * image though it is less effective visually if the HTML5 Canvas is not
+ * availble on the viewing device.
*
* @param {Number} [options.visibilityRatio=0.5]
* The percentage ( as a number from 0 to 1 ) of the source image which
@@ -129,14 +140,31 @@
* true will provide the effect of an infinitely scrolling viewport.
*
* @param {Number} [options.springStiffness=5.0]
+ *
* @param {Number} [options.imageLoaderLimit=0]
+ * The maximum number of image requests to make concurrently. By default
+ * it is set to 0 allowing the browser to make the maximum number of
+ * image requests in parallel as allowed by the browsers policy.
+ *
* @param {Number} [options.clickTimeThreshold=200]
+ * If multiple mouse clicks occurs within less than this number of
+ * milliseconds, treat them as a single click.
+ *
* @param {Number} [options.clickDistThreshold=5]
+ * If a mouse or touch drag occurs and the distance to the starting drag
+ * point is less than this many pixels, ignore the drag event.
+ *
* @param {Number} [options.zoomPerClick=2.0]
+ * The "zoom distance" per mouse click or touch tap.
+ *
* @param {Number} [options.zoomPerScroll=1.2]
+ * The "zoom distance" per mouse scroll or touch pinch.
+ *
* @param {Number} [options.zoomPerSecond=2.0]
+ * The number of seconds to animate a single zoom event over.
*
* @param {Boolean} [options.showNavigationControl=true]
+ * Set to false to prevent the appearance of the default navigation controls.
*
* @param {Number} [options.controlsFadeDelay=2000]
* The number of milliseconds to wait once the user has stopped interacting
@@ -421,6 +449,12 @@ OpenSeadragon = window.OpenSeadragon || function( options ){
zoomPerScroll: 1.2,
zoomPerSecond: 2.0,
showNavigationControl: true,
+
+ showNavigator: false,
+ navigatorElement: null,
+ navigatorHeight: null,
+ navigatorWidth: null,
+ navigatorPosition: null,
//These two were referenced but never defined
controlsFadeDelay: 2000,
@@ -1076,6 +1110,78 @@ OpenSeadragon = window.OpenSeadragon || function( options ){
},
+ /**
+ * Taken from jQuery 1.6.1
+ * @param {Object} options
+ * @param {String} options.url
+ * @param {Function} options.callback
+ * @param {String} [options.param='callback'] The name of the url parameter
+ * to request the jsonp provider with.
+ * @param {String} [options.callbackName=] The name of the callback to
+ * request the jsonp provider with.
+ */
+ jsonp: function( options ){
+ var script,
+ url = options.url,
+ head = document.head ||
+ document.getElementsByTagName( "head" )[ 0 ] ||
+ document.documentElement,
+ jsonpCallback = options.callbackName || 'openseadragon' + (+new Date()),
+ previous = window[ jsonpCallback ],
+ replace = "$1" + jsonpCallback + "$2",
+ callbackParam = options.param || 'callback',
+ callback = options.callback;
+
+ url = url.replace( /(\=)\?(&|$)|\?\?/i, replace );
+ // Add callback manually
+ url += (/\?/.test( url ) ? "&" : "?") + callbackParam + "=" + jsonpCallback;
+
+ // Install callback
+ window[ jsonpCallback ] = function( response ) {
+ if ( !previous ){
+ delete window[ jsonpCallback ];
+ } else {
+ window[ jsonpCallback ] = previous;
+ }
+ if( callback && $.isFunction( callback ) ){
+ callback( response );
+ }
+ };
+
+ script = document.createElement( "script" );
+
+ script.async = "async";
+
+ if ( options.scriptCharset ) {
+ script.charset = options.scriptCharset;
+ }
+
+ script.src = url;
+
+ // Attach handlers for all browsers
+ script.onload = script.onreadystatechange = function( _, isAbort ) {
+
+ if ( isAbort || !script.readyState || /loaded|complete/.test( script.readyState ) ) {
+
+ // Handle memory leak in IE
+ script.onload = script.onreadystatechange = null;
+
+ // Remove the script
+ if ( head && script.parentNode ) {
+ head.removeChild( script );
+ }
+
+ // Dereference the script
+ script = undefined;
+ }
+ };
+ // Use insertBefore instead of appendChild to circumvent an IE6 bug.
+ // This arises when a base node is used (#2709 and #4378).
+ head.insertBefore( script, head.firstChild );
+
+ },
+
+
/**
* Loads a Deep Zoom Image description from a url or XML string and
* provides a callback hook for the resulting Document
diff --git a/src/viewer.js b/src/viewer.js
index 61b0494f..8d989b5d 100644
--- a/src/viewer.js
+++ b/src/viewer.js
@@ -81,11 +81,12 @@ $.Viewer = function( options ) {
source: null,
drawer: null,
viewport: null,
+ navigator: null,
profiler: null
}, $.DEFAULT_SETTINGS, options );
- this.element = document.getElementById( this.id );
+ this.element = this.element || document.getElementById( this.id );
this.container = $.makeNeutralElement( "div" );
this.canvas = $.makeNeutralElement( "div" );
@@ -115,7 +116,7 @@ $.Viewer = function( options ) {
dragHandler: $.delegate( this, onCanvasDrag ),
releaseHandler: $.delegate( this, onCanvasRelease ),
scrollHandler: $.delegate( this, onCanvasScroll )
- }).setTracking( true ); // default state
+ }).setTracking( this.mouseNavEnabled ? true : false ); // default state
this.outerTracker = new $.MouseTracker({
element: this.container,
@@ -124,7 +125,7 @@ $.Viewer = function( options ) {
enterHandler: $.delegate( this, onContainerEnter ),
exitHandler: $.delegate( this, onContainerExit ),
releaseHandler: $.delegate( this, onContainerRelease )
- }).setTracking( true ); // always tracking
+ }).setTracking( this.mouseNavEnabled ? true : false ); // always tracking
(function( canvas ){
canvas.width = "100%";
@@ -257,6 +258,23 @@ $.Viewer = function( options ) {
this.addControl( this.navControl, $.ControlAnchor.BOTTOM_RIGHT );
}
+ if ( this.showNavigator ){
+ this.navigator = new $.Navigator({
+ viewerId: this.id,
+ id: this.navigatorElement,
+ position: this.navigatorPosition,
+ height: this.navigatorHeight,
+ width: this.navigatorWidth,
+ tileSources: this.tileSources,
+ prefixUrl: this.prefixUrl
+ });
+ this.addControl(
+ this.navigator.element,
+ $.ControlAnchor.TOP_RIGHT
+ );
+ }
+
+
for ( i = 0; i < this.customControls.length; i++ ) {
this.addControl(
this.customControls[ i ].id,
@@ -951,9 +969,15 @@ function updateOnce( viewer ) {
if ( animated ) {
viewer.drawer.update();
+ if( viewer.navigator ){
+ viewer.navigator.update( viewer.viewport );
+ }
viewer.raiseEvent( "animation" );
} else if ( THIS[ viewer.hash ].forceRedraw || viewer.drawer.needsUpdate() ) {
viewer.drawer.update();
+ if( viewer.navigator ){
+ viewer.navigator.update( viewer.viewport );
+ }
THIS[ viewer.hash ].forceRedraw = false;
}
diff --git a/src/viewport.js b/src/viewport.js
index b00e1093..b6807edd 100644
--- a/src/viewport.js
+++ b/src/viewport.js
@@ -129,8 +129,8 @@ $.Viewport.prototype = {
height = width / this.getAspectRatio();
return new $.Rect(
- center.x - width / 2.0,
- center.y - height / 2.0,
+ center.x - ( width / 2.0 ),
+ center.y - ( height / 2.0 ),
width,
height
);