/** * @module UI * @namespace springroll * @requires Core */ (function(undefined) { // Class imports var ScaleItem = include('springroll.ScaleItem'), ScaleImage = include('springroll.ScaleImage'), Positioner = include('springroll.Positioner'), Application = include('springroll.Application'), Debug; /** * The UI scale is responsible for scaling UI components to help easy the burden of different * device aspect ratios. The UI can expand either vertically or horizontally to fill excess * space. * * @class ScaleManager * @constructor * @param {Object} [options] The options * @param {Object} [options.size] The dimensions of the Scaler * @param {Number} [options.size.width] The designed width * @param {Number} [options.size.height] The designed height * @param {Number} [options.size.maxwidth=size.width] The designed max width * @param {Number} [options.size.maxheight=size.height] The designed max height * @param {Object} [options.items] The items to load * @param {PIXI.DisplayObjectContainer|createjs.Container} [options.container] The container if * adding items * @param {Object} [options.display] The current display * @param {Boolean} [options.enabled=false] If the scaler is enabled */ var ScaleManager = function(options) { Debug = include('springroll.Debug', false); options = Object.merge( { enabled: false, size: null, items: null, display: null, container: null }, options); /** * The configuration for each items * @property {Array} _items * @private */ this._items = []; /** * The screen settings object, contains information about designed size * @property {object} _size * @private */ this._size = null; /** * The current overall scale of the game * @property {Number} _scale * @private * @default 1 */ this._scale = 1; /** * The adapter for universal scale, rotation size access * @property {Object} _adapter * @private */ this._adapter = null; /** * The internal enabled * @property {boolean} _enabled * @private */ this._enabled = options.enabled; if (DEBUG) { /** * If we should log verbose messages (unminified module only!) * @property {Boolean} verbose * @default false */ this.verbose = false; } // Set the designed size this.size = options.size; // Set the display so we can get an adapter this.display = options.display; if (options.items) { if (!options.container) { throw "ScaleManager requires container to add items"; } this.addItems(options.container, options.items); } // Setup the resize bind this._resize = this._resize.bind(this); // Set the enabled status this.enabled = this._enabled; }; // Reference to the prototype var p = extend(ScaleManager); /** * Vertically align to the top * @property {String} ALIGN_TOP * @static * @final * @readOnly * @default "top" */ var ALIGN_TOP = ScaleManager.ALIGN_TOP = "top"; /** * Vertically align to the bottom * @property {String} ALIGN_BOTTOM * @static * @final * @readOnly * @default "bottom" */ var ALIGN_BOTTOM = ScaleManager.ALIGN_BOTTOM = "bottom"; /** * Horizontally align to the left * @property {String} ALIGN_LEFT * @static * @final * @readOnly * @default "left" */ var ALIGN_LEFT = ScaleManager.ALIGN_LEFT = "left"; /** * Horizontally align to the right * @property {String} ALIGN_RIGHT * @static * @final * @readOnly * @default "right" */ var ALIGN_RIGHT = ScaleManager.ALIGN_RIGHT = "right"; /** * Vertically or horizontally align to the center * @property {String} ALIGN_CENTER * @static * @final * @readOnly * @default "center" */ var ALIGN_CENTER = ScaleManager.ALIGN_CENTER = "center"; /** * Get the adapter by display * @method _getAdapter * @private * @param {object} display The canvas renderer display */ ScaleManager._getAdapter = function(display) { if (!display) { display = Application.instance.display; } if (!display) return null; // Check for a displayadpater, doesn't work with generic display if (!display.adapter) { if (DEBUG) { throw "The display specified is incompatible with ScaleManager because it doesn't contain an adapter"; } else { throw "ScaleManager incompatible display"; } } return display.adapter; }; /** * Set the display * @property {springroll.AbstractDisplay} display */ Object.defineProperty(p, 'display', { set: function(display) { this._adapter = ScaleManager._getAdapter(display); } }); /** * The design sized of the application * @property {Object} size * @default null */ /** * The designed width of the application * @property {Number} size.width */ /** * The designed width of the application * @property {Number} size.height */ /** * The designed max width of the application * @property {Number} size.maxWidth * @default size.width */ /** * The designed maxHeight of the application * @property {Number} size.maxHeight * @default size.height */ Object.defineProperty(p, 'size', { set: function(size) { this._size = size; if (!size) return; if (!size.width || !size.height) { if (DEBUG && Debug) { Debug.error(size); throw "Designed size parameter must be a plain object with 'width' & 'height' properties"; } else { throw "Invalid design settings"; } } // Allow for responsive designs if they're a max width var options = Application.instance.options; if (size.maxWidth) { // Set the max width so that Application can limit the aspect ratio properly options.maxWidth = size.maxWidth; } if (size.maxHeight) { // Set the max height so that Application can limit the aspect ratio properly options.maxHeight = size.maxHeight; } }, get: function() { return this._size; } }); /** * Get the current scale of the screen relative to the designed screen size * @property {Number} scale * @readOnly */ Object.defineProperty(p, 'scale', { get: function() { return this._scale; } }); /** * The total number of items * @property {Number} numItems * @readOnly */ Object.defineProperty(p, 'numItems', { get: function() { return this._items.length; } }); /** * Whether the ScaleManager should listen to the stage resize. Setting to true * initialized a resize. * @property {boolean} enabled * @default true */ Object.defineProperty(p, 'enabled', { get: function() { return this._enabled; }, set: function(enabled) { this._enabled = enabled; var app = Application.instance; app.off('resize', this._resize); if (enabled) { app.on('resize', this._resize); // Refresh the resize event app.triggerResize(); } } }); /** * Remove all items where the item display is a the container or it contains items * @method removeItems * @param {*} parent The object which contains the items as live variables. * @param {Object} items The items that was passed to `addItems` * @return {springroll.ScaleManager} The ScaleManager for chaining */ p.removeItems = function(parent, items) { var children = []; if (items) { // Get the list of children to remove for (var name in items) { if (parent[name]) { children.push(parent[name]); } } } else { // @deprecated implementation if (DEBUG) { console.warn("ScaleManager.removeItems should have a second parameter which is the items dictionary e.g., removeItems(panel, items)"); } return this.removeItemsByContainer(parent); } // Remove the items by children's list if (children.length) { var _itemsCopy = this._items.slice(); var _items = this._items; _itemsCopy.forEach(function(item) { if (children.indexOf(item.display) > -1) { _items.splice(_items.indexOf(item), 1); } }); } return this; }; /** * Remove all items where the item display is a child of the container display * @method removeItemsByParent * @param {createjs.Container|PIXI.DisplayObjectContainer} container The container to remove items from * @return {springroll.ScaleManager} The ScaleManager for chaining */ p.removeItemsByContainer = function(container) { var adapter = this._adapter; this._items.forEach(function(item, i, items) { if (adapter.contains(container, item.display)) { items.splice(i, 1); } }); return this; }; /** * Remove a single item from the scaler * @method removeItem * @param {createjs.Bitmap|PIXI.Sprite|createjs.Container|PIXI.DisplayObjectContainer} display The object to remove * @return {springroll.ScaleManager} The ScaleManager for chaining */ p.removeItem = function(display) { var items = this._items; for (var i = 0, len = items.length; i < len; i++) { if (items[i].display === display) { items.splice(i, 1); break; } } return this; }; /** * Register a dictionary of items to the ScaleManager to control. * @method addItems * @param {*} parent The parent object that contains the items as variables. * @param {object} items The items object where the keys are the name of the property on the * parent and the value is an object with keys of "titleSafe", "minScale", * "maxScale", "centerHorizontally", "align", see ScaleManager.addItem for a * description of the different keys. * @return {springroll.ScaleManager} The instance of this ScaleManager for chaining */ p.addItems = function(parent, items) { // Temp variables var settings; var name; // Loop through all the items and register // Each dpending on the settings for (name in items) { settings = items[name]; if (!parent[name]) { if (DEBUG && Debug && this.verbose) { Debug.info("ScaleManager: could not find object '" + name + "'"); } continue; } this.addItem(parent[name], settings, false); } Application.instance.triggerResize(); return this; }; /** * Manually add an item * @method addItem * @param {createjs.DisplayObject|PIXI.DisplayObject} displayObject The display object item * @param {object|String} [settings="center"] The collection of settings or the align property * @param {String} [settings.align="center"] The vertical alignment ("top", "bottom", "center") * then horizontal alignment ("left", "right" and "center"). Or you can use the short- * handed versions: "center" = "center-center", "top" = "top-center", * "bottom" = "bottom-center", "left" = "center-left", "right" = "center-right". * @param {Boolean|String} [settings.titleSafe=false] If the item needs to be in the title safe * area. Acceptable values are false, "horizontal", "vertical", "all", and true. * The default is false, and true is the same as "all". * @param {Number} [settings.minScale=NaN] The minimum scale amount (default, scales the same * size as the stage) * @param {Number} [settings.maxScale=NaN] The maximum scale amount (default, scales the same * size as the stage) * @param {Boolean} [settings.centeredHorizontally=false] Makes sure that the center of the * object is directly in the center of the stage assuming origin point is in * the upper-left corner. * @param {Number} [settings.x] The initial X position of the item * @param {Number} [settings.y] The initial Y position of the item * @param {Object} [settings.scale] The initial scale * @param {Number} [settings.scale.x] The initial scale X value * @param {Number} [settings.scale.y] The initial scale Y value * @param {Object} [settings.pivot] The pivot point * @param {Number} [settings.pivot.x] The pivot point X location * @param {Number} [settings.pivot.y] The pivot point Y location * @param {Number} [settings.rotation] The initial rotation in degrees * @param {Object|Array} [settings.hitArea] An object which describes the hit area of the item * or an array of points. * @param {String} [settings.hitArea.type] If the hitArea is an object, the type of hit area, * "rect", "ellipse", "circle", etc * @return {springroll.ScaleManager} The instance of this ScaleManager for chaining */ /** * Add a bitmap to make be fullscreen * @method addItem * @param {PIXI.Sprite|createjs.Bitmap} bitmap The bitmap to scale * @param {String} settings Must be 'cover-image' * @return {springroll.ScaleManager} The instance of this ScaleManager for chaining */ p.addItem = function(displayObject, settings, doResize) { if (doResize === undefined) { doResize = true; } if (!settings) { settings = { align: ALIGN_CENTER }; } if (settings === "cover-image") { this._items.push(new ScaleImage(displayObject, this._size, this._adapter)); } else { if (typeof settings === "string") { settings = { align: settings }; } var align = settings.align || ALIGN_CENTER; // Interpret short handed versions switch (align) { case ALIGN_CENTER: { align = align + "-" + align; break; } case ALIGN_LEFT: case ALIGN_RIGHT: { align = ALIGN_CENTER + "-" + align; break; } case ALIGN_TOP: case ALIGN_BOTTOM: { align = align + "-" + ALIGN_CENTER; break; } } // Error check the alignment value input if (!/^(center|top|bottom)\-(left|right|center)$/.test(align)) { throw "Item align '" + align + "' is invalid for " + displayObject; } // Do the intial positioning of the display object Positioner.init(displayObject, settings, this._adapter); // Create the item settings var item = new ScaleItem(displayObject, align, this._size, this._adapter); item.titleSafe = settings.titleSafe == "all" ? true : settings.titleSafe; item.maxScale = settings.maxScale || NaN; item.minScale = settings.minScale || NaN; item.centeredHorizontally = !!settings.centeredHorizontally; this._items.push(item); } if (doResize) { Application.instance.triggerResize(); } return this; }; /** * Scale the UI items that have been registered to the current screen * @method _resize * @private * @param {Number} w The current width of the application * @param {Number} h The current height of the application */ p._resize = function(w, h) { var _size = this._size; // Size hasn't been setup yet if (!_size) { if (DEBUG && Debug) { Debug.warn("Unable to resize scaling because the scaling size hasn't been set."); } return; } var defaultRatio = _size.width / _size.height; var currentRatio = w / h; this._scale = currentRatio > defaultRatio ? h / _size.height : w / _size.width; // Resize all the items this._items.forEach(function(item) { item.resize(w, h); }); }; /** * Destroy the scaler object * @method destroy */ p.destroy = function() { this.enabled = false; this._items.forEach(function(item) { item.destroy(); }); this._adapter = null; this._size = null; this._items = null; }; // Assign to namespace namespace('springroll').ScaleManager = ScaleManager; }());