From f5fcea898c311bea5c9e5a34a28d6ea9039ecb61 Mon Sep 17 00:00:00 2001 From: Richard Davey Date: Wed, 19 Dec 2018 17:07:46 +0000 Subject: [PATCH] Updated Size component to handle fitting, ratios, locking and more. --- src/structs/Size.js | 358 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 303 insertions(+), 55 deletions(-) diff --git a/src/structs/Size.js b/src/structs/Size.js index 1a29fae73..ddba90efc 100644 --- a/src/structs/Size.js +++ b/src/structs/Size.js @@ -4,12 +4,16 @@ * @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} */ +var Clamp = require('../math/Clamp'); var Class = require('../utils/Class'); /** * @classdesc - * The Size component is a simple structure that helps you encapsulate a `width` and `height` and, if required, maintain - * the aspect ratios between them. + * The Size component allows you to set `width` and `height` properties and define the relationship between them. + * + * The component can automatically maintain the aspect ratios between the two values, and clamp them + * to a defined min-max range. You can also control the dominant axis. When dimensions are given to the Size component + * that would cause it to exceed its min-max range, the dimensions are adjusted based on the dominant axis. * * @class Size * @memberof Phaser.Structs @@ -18,17 +22,19 @@ var Class = require('../utils/Class'); * * @param {number} [width] - The width of the Size component. * @param {number} [height=width] - The height of the Size component. If not given, it will use the `width`. - * @param {boolean} [lockAspectRatio=false] - Should the aspect ratio between the `width` and `height` be locked? + * @param {boolean} [lockAspectRatio=false] - Should the aspect ratio be locked? It will be based on the given `width` and `height` arguments. + * @param {boolean} [lockWidth=true] - Set to `true` to make the `width` the dominant axis, or `false` to make `height` the dominant axis. */ var Size = new Class({ initialize: - function Size (width, height, lockAspectRatio) + function Size (width, height, lockAspectRatio, lockWidth) { if (width === undefined) { width = 0; } if (height === undefined) { height = width; } if (lockAspectRatio === undefined) { lockAspectRatio = false; } + if (lockWidth === undefined) { lockWidth = true; } /** * The width. @@ -61,7 +67,7 @@ var Size = new Class({ * @readonly * @since 3.16.0 */ - this.ratioH = (height === 0) ? 0 : width / height; + this.ratioH = (height === 0) ? 1 : width / height; /** * The proportional relationship between the height and width. @@ -74,18 +80,70 @@ var Size = new Class({ * @readonly * @since 3.16.0 */ - this.ratioV = (width === 0) ? 0 : height / width; + this.ratioV = (width === 0) ? 1 : height / width; /** - * Lock the aspect ratio to its current value? + * Set this property to lock the aspect ratios to their current values. * - * If enabled, changing the width or height properties will automatically adjust the other based on the aspect ratio. + * Once enabled, changing the `width` or `height` properties will automatically adjust the other based on the aspect ratio. * - * @name Phaser.Structs.Size#lock + * @name Phaser.Structs.Size#lockAspectRatio * @type {boolean} * @since 3.16.0 */ - this.lock = lockAspectRatio; + this.lockAspectRatio = lockAspectRatio; + + /** + * When scaling the Size based on the min-max range and the aspect ratio, this property controls the priority of + * the axis. If `true` (the default) the `width` will be the dominant axis, and the height will adjust to match it. If `false`, + * the `height` will be the dominant axis, and the `width` will adjust to match it. + * + * @name Phaser.Structs.Size#lockWidth + * @type {boolean} + * @default true + * @since 3.16.0 + */ + this.lockWidth = lockWidth; + + /** + * The minimum allowed width. + * + * @name Phaser.Structs.Size#_minWidth + * @type {number} + * @private + * @since 3.16.0 + */ + this._minWidth = 0; + + /** + * The minimum allowed height. + * + * @name Phaser.Structs.Size#_minHeight + * @type {number} + * @private + * @since 3.16.0 + */ + this._minHeight = 0; + + /** + * The maximum allowed width. + * + * @name Phaser.Structs.Size#_maxWidth + * @type {number} + * @private + * @since 3.16.0 + */ + this._maxWidth = Number.MAX_VALUE; + + /** + * The maximum allowed height. + * + * @name Phaser.Structs.Size#_maxHeight + * @type {number} + * @private + * @since 3.16.0 + */ + this._maxHeight = Number.MAX_VALUE; }, /** @@ -102,41 +160,247 @@ var Size = new Class({ */ setAspectRatioLock: function (value) { - this.lock = value; + this.lockAspectRatio = value; return this; }, /** - * Lock the aspect ratio to its current value? + * Set the minimum width and height values this Size component will allow. * - * If enabled, changing the `width` or `height` properties will automatically adjust the other based on the aspect ratio. + * If enabled, the properties will be clamped to the min-max range, including when locked to their aspect ratios. + * + * Setting this will automatically adjust both the `width` and `height` properties to ensure they are within range. * - * @method Phaser.Structs.Size#set + * @method Phaser.Structs.Size#setMin * @since 3.16.0 * - * @param {number} [width] - The width of the Size component. - * @param {number} [height=width] - The height of the Size component. If not given, it will use the `width`. - * @param {boolean} [lockAspectRatio=false] - Should the aspect ratio between the `width` and `height` be locked? + * @param {number} [width=0] - The minimum allowed width of the Size component. + * @param {number} [height=width] - The minimum allowed height of the Size component. If not given, it will use the `width`. * * @return {this} This Size instance. */ - set: function (width, height) + setMin: function (width, height) { if (width === undefined) { width = 0; } if (height === undefined) { height = width; } - if (this.lock) + this._minWidth = width; + this._minHeight = height; + + return this.update(); + }, + + /** + * Set the maximum width and height values this Size component will allow. + * + * If enabled, the properties will be clamped to the min-max range, including when locked to their aspect ratios. + * + * Setting this will automatically adjust both the `width` and `height` properties to ensure they are within range. + * + * @method Phaser.Structs.Size#setMax + * @since 3.16.0 + * + * @param {number} [width=Number.MAX_VALUE] - The maximum allowed width of the Size component. + * @param {number} [height=width] - The maximum allowed height of the Size component. If not given, it will use the `width`. + * + * @return {this} This Size instance. + */ + setMax: function (width, height) + { + if (width === undefined) { width = Number.MAX_VALUE; } + if (height === undefined) { height = width; } + + this._maxWidth = width; + this._maxHeight = height; + + return this.update(); + }, + + /** + * Calls `setSize` with the current width and height. + * This has the effect of applying min-max clamping and axis locking to the current values. + * + * @method Phaser.Structs.Size#update + * @since 3.16.0 + * + * @return {this} This Size instance. + */ + update: function () + { + return this.setSize(this._width, this._height); + }, + + /** + * Updates the `ratioH` and `ratioV` properties based on the current width and height. + * + * They are only updated if `lockAspectRatio` is `false`. + * + * @method Phaser.Structs.Size#updateRatios + * @since 3.16.0 + * + * @return {this} This Size instance. + */ + updateRatios: function () + { + if (!this.lockAspectRatio) { - this._width = width * this.ratioH; - this._height = height * this.ratioV; + this.ratioH = (this._height === 0) ? 1 : this._width / this._height; + this.ratioV = (this._width === 0) ? 1 : this._height / this._width; } - else + + return this; + }, + + /** + * Sets a new width for this Size component. + * + * The new width is clamped to the min-max range automatically. + * + * Additionally, if the aspect ratio is locked, the height will also be adjusted based on the new width. + * + * @method Phaser.Structs.Size#updateWidth + * @since 3.16.0 + * + * @param {number} width - The new width of the Size component. + * + * @return {this} This Size instance. + */ + updateWidth: function (width) + { + width = Clamp(width, this._minWidth, this._maxWidth); + + if (this.lockAspectRatio) { - this._width = width; - this.height = height; + // What's the new height? + var height = width * this.ratioV; + + // height takes priority + if (!this.lockWidth) + { + if (height < this._minHeight) + { + height = this._minHeight; + } + else if (height > this._maxHeight) + { + height = this._maxHeight; + } + + // Re-adjust the width based on the dominant height + width = height * this.ratioH; + } } + this._width = width; + this._height = height; + + return this.updateRatios(); + }, + + /** + * Sets a new height for this Size component. + * + * The new height is clamped to the min-max range automatically. + * + * Additionally, if the aspect ratio is locked, the width will also be adjusted based on the new height. + * + * @method Phaser.Structs.Size#updateHeight + * @since 3.16.0 + * + * @param {number} height - The new height of the Size component. + * + * @return {this} This Size instance. + */ + updateHeight: function (height) + { + height = Clamp(height, this._minHeight, this._maxHeight); + + if (this.lockAspectRatio) + { + // What's the new width? + var width = height * this.ratioH; + + // width takes priority + if (this.lockWidth) + { + if (width < this._minWidth) + { + width = this._minWidth; + } + else if (width > this._maxWidth) + { + width = this._maxWidth; + } + + // Re-adjust the height based on the dominant width + height = width * this.ratioV; + } + } + + this._width = width; + this._height = height; + + return this.updateRatios(); + }, + + /** + * Set the width and height of this Size component, adjusting for the aspect ratio, if locked. + * + * @method Phaser.Structs.Size#setSize + * @since 3.16.0 + * + * @param {number} [width] - The width of the Size component. + * @param {number} [height=width] - The height of the Size component. If not given, it will use the `width`. + * + * @return {this} This Size instance. + */ + setSize: function (width, height) + { + if (width === undefined) { width = 0; } + if (height === undefined) { height = width; } + + return (this.lockWidth) ? this.updateWidth(width) : this.updateHeight(height); + }, + + /** + * The `width` and `height` are adjusted to fit inside the given dimensions, while keeping the current aspect ratio. + * + * There may be some space inside the parent area which is not covered if its aspect ratio differs. + * + * @method Phaser.Structs.Size#fitTo + * @since 3.16.0 + * + * @param {number} width - The width of the Size component. + * @param {number} height - The height of the Size component. + * + * @return {this} This Size instance. + */ + fitTo: function (width, height) + { + // Get the aspect ratios in case we need to expand or shrink to fit + var newRatio = (height === 0) ? 1 : width / height; + + var newWidth = width; + var newHeight = height; + + // Get the larger aspect ratio of the two. + // If aspect ratio is 1 then no adjustment needed + if (this.ratioH > newRatio) + { + newHeight = width / this.ratioH; + } + else if (this.ratioH < newRatio) + { + newWidth = height * this.ratioH; + } + + this._width = newWidth; + this._height = newHeight; + + this.ratioH = (this._height === 0) ? 1 : this._width / this._height; + this.ratioV = (this._width === 0) ? 1 : this._height / this._width; + return this; }, @@ -154,9 +418,7 @@ var Size = new Class({ */ setWidth: function (value) { - this.width = value; - - return this; + return this.updateWidth(value); }, /** @@ -186,9 +448,7 @@ var Size = new Class({ */ setHeight: function (value) { - this.height = value; - - return this; + return this.updateHeight(value); }, /** @@ -214,11 +474,15 @@ var Size = new Class({ */ toString: function () { - return '[{ Size (width=' + this._width + ' height=' + this._height + ' ratioH=' + this.ratioH + ' ratioV=' + this.ratioV + ' lock=' + this.lock + ') }]'; + return '[{ Size (width=' + this._width + ' height=' + this._height + ' ratioH=' + this.ratioH + ' ratioV=' + this.ratioV + ' lockAspectRatio=' + this.lockAspectRatio + ') }]'; }, /** - * The width. + * The width of this Size component. + * + * This value is clamped to the range specified by `minWidth` and `maxWidth`, if enabled. + * + * A width can never be less than zero. * * Changing this value will automatically update the `height` if the aspect ratio lock is enabled. * You can also use the `setWidth` and `getWidth` methods. @@ -236,23 +500,17 @@ var Size = new Class({ set: function (value) { - this._width = value; - - if (this.lock) - { - this._height = value * this.ratioV; - } - else - { - this.ratioH = (this._height === 0) ? 0 : value / this._height; - this.ratioV = (this._width === 0) ? 0 : this._height / value; - } + this.updateWidth(value); } }, /** - * The height. + * The height of this Size component. + * + * This value is clamped to the range specified by `minHeight` and `maxHeight`, if enabled. + * + * A height can never be less than zero. * * Changing this value will automatically update the `width` if the aspect ratio lock is enabled. * You can also use the `setHeight` and `getHeight` methods. @@ -270,17 +528,7 @@ var Size = new Class({ set: function (value) { - this._height = value; - - if (this.lock) - { - this._width = value * this.ratioH; - } - else - { - this.ratioH = (this._height === 0) ? 0 : this._width / value; - this.ratioV = (this._width === 0) ? 0 : value / this._width; - } + this.updateHeight(value); } }