/* Copyright (c) 2009 Yahoo! Inc. All rights reserved. The copyrights embodied in the content of this file are licensed under the BSD (revised) open source license */ package com.yahoo.astra.layout.modes { import com.yahoo.astra.layout.events.LayoutEvent; import com.yahoo.astra.utils.DisplayObjectUtil; import flash.display.DisplayObject; import flash.display.DisplayObjectContainer; import flash.geom.Rectangle; /** * Arranges a DisplayObjectContainer's children in a single column or row. * * @example The following code configures a BoxLayout instance and passes it to a container: * * var box:BoxLayout = new BoxLayout(); * box.direction = "vertical"; * box.verticalGap = 4; * box.verticalAlign = VerticalAlignment.MIDDLE; * * var container:LayoutContainer = new LayoutContainer(); * container.layoutMode = box; * this.addChild( container ); * * *

Advanced Client Options:

*

Optional client configuration parameters allow a developer to specify * behaviors for individual children of the target container. To set these * advanced options, one must call addClient() on the BoxLayout * instance and pass the child to configure along with an object specifying * the configuration parameters. Several client parameters are available to * the BoxLayout algorithm:

* *
*
percentWidth : Number
*
The target's width will be updated based on a percentage of the width specified in the layout bounds.
*
percentHeight : Number
*
The target's width will be updated based on a percentage of the width specified in the layout bounds.
*
minWidth : Number
*
The minimum width value to allow when resizing. The default value is 0.
*
minHeight : Number
*
The minimum height value to allow when resizing. The default value is 0.
*
maxWidth : Number
*
The maximum width value to allow when resizing. The default value is 10000.
*
maxHeight : Number
*
The maximum height value to allow when resizing. The default value is 10000.
*
includeInLayout : Boolean
*
If false, the target will not be included in layout calculations. The default value is true.
*
* * @example The following code adds multiple clients to a BoxLayout instance: * * var box:BoxLayout = new BoxLayout(); * box.direction = "vertical"; * box.addClient( headerSprite, { percentWidth: 100 } ); * box.addClient( contentSprite, * { * percentWidth: 100, * percentHeight: 100, * minWidth: 640, * minHeight: 480 * }); * box.addClient( footerSprite, { percentWidth: 100 } ); * * var container:LayoutContainer = new LayoutContainer( box ); * container.width = 1024; * container.height = 768; * container.addChild( headerSprite ); * container.addChild( contentSprite ); * container.addChild( footerSprite ); * this.addChild( container ); * * * @author Josh Tynjala * @see com.yahoo.astra.layout.LayoutContainer */ public class BoxLayout extends BaseLayoutMode implements IAdvancedLayoutMode { //-------------------------------------- // Static Properties //-------------------------------------- /** * @private * The default maximum number of pixels to calculate width sizing. */ private static const DEFAULT_MAX_WIDTH:Number = 10000; /** * @private * The default maximum number of pixels to calculate height sizing. */ private static const DEFAULT_MAX_HEIGHT:Number = 10000; //-------------------------------------- // Constructor //-------------------------------------- /** * Constructor. */ public function BoxLayout() { } //-------------------------------------- // Properties //-------------------------------------- /** * @private * Storage for the direction property. */ private var _direction:String = "horizontal"; /** * The direction in which children of the target are laid out. Valid * direction values include "vertical" or "horizontal". */ public function get direction():String { return this._direction; } /** * @private */ public function set direction(value:String):void { this._direction = value; this.dispatchEvent(new LayoutEvent(LayoutEvent.LAYOUT_CHANGE)); } /** * @private * Storage for the verticalGap property. */ private var _verticalGap:Number = 0; /** * The number of pixels appearing between the target's children * vertically. */ public function get verticalGap():Number { return this._verticalGap; } /** * @private */ public function set verticalGap(value:Number):void { this._verticalGap = value; this.dispatchEvent(new LayoutEvent(LayoutEvent.LAYOUT_CHANGE)); } /** * @private * Storage for the horizontalGap property. */ private var _horizontalGap:Number = 0; /** * The number of pixels appearing between the target's children * horizontally. */ public function get horizontalGap():Number { return this._horizontalGap; } /** * @private */ public function set horizontalGap(value:Number):void { this._horizontalGap = value; this.dispatchEvent(new LayoutEvent(LayoutEvent.LAYOUT_CHANGE)); } /** * @private * Storage for the verticalAlign property. */ private var _verticalAlign:String = "top"; /** * The vertical alignment of children displayed in the target. * * @see VerticalAlignment */ public function get verticalAlign():String { return this._verticalAlign; } /** * @private */ public function set verticalAlign(value:String):void { this._verticalAlign = value; this.dispatchEvent(new LayoutEvent(LayoutEvent.LAYOUT_CHANGE)); } /** * @private * Storage for the horizontalAlign property. */ private var _horizontalAlign:String = "left"; /** * The horizontal alignment of children displayed in the target. * * @see HorizontalAlignment */ public function get horizontalAlign():String { return this._horizontalAlign; } /** * @private */ public function set horizontalAlign(value:String):void { this._horizontalAlign = value; this.dispatchEvent(new LayoutEvent(LayoutEvent.LAYOUT_CHANGE)); } /** * @private * The maximum width value from among the current target's children. */ protected var maxChildWidth:Number; /** * @private * The maximum height value from among the current target's children. */ protected var maxChildHeight:Number; //-------------------------------------- // Public Methods //-------------------------------------- /** * @inheritDoc */ override public function layoutObjects(displayObjects:Array, bounds:Rectangle):Rectangle { var childrenInLayout:Array = this.configureChildren(displayObjects); //determine the available horizontal space var hSpaceForChildren:Number = bounds.width - this.paddingLeft - this.paddingRight; if((hSpaceForChildren == Infinity)||(hSpaceForChildren >9000)) { hSpaceForChildren = DEFAULT_MAX_WIDTH; } //determine the available vertical space var vSpaceForChildren:Number = bounds.height - this.paddingTop - this.paddingBottom; if((vSpaceForChildren == Infinity)||(vSpaceForChildren >9000)) { vSpaceForChildren = DEFAULT_MAX_HEIGHT; } //resize the children based on the available space and the specified percentage width and height values. if(this.direction == "vertical") { vSpaceForChildren -= (this.verticalGap * (childrenInLayout.length - 1)); PercentageSizeUtil.flexChildHeightsProportionally(this.clients, this.configurations, hSpaceForChildren, vSpaceForChildren); } else { hSpaceForChildren -= (this.horizontalGap * (childrenInLayout.length - 1)); PercentageSizeUtil.flexChildWidthsProportionally(this.clients, this.configurations, hSpaceForChildren, vSpaceForChildren); } this.maxChildWidth = 0; this.maxChildHeight = 0; var childCount:int = childrenInLayout.length; for(var i:int = 0; i < childCount; i++) { var child:DisplayObject = DisplayObject(childrenInLayout[i]); //measure the child's width this.maxChildWidth = Math.max(this.maxChildWidth, child.width); this.maxChildHeight = Math.max(this.maxChildHeight, child.height); } if(this.direction == "vertical") { this.layoutChildrenVertically(childrenInLayout, bounds); } else { this.layoutChildrenHorizontally(childrenInLayout, bounds); } bounds = LayoutModeUtil.calculateChildBounds(childrenInLayout); bounds.width += this.paddingRight; bounds.height += this.paddingBottom; return bounds; } //-------------------------------------- // Protected Methods //-------------------------------------- /** * @private * Positions the children when direction is vertical. */ protected function layoutChildrenVertically(children:Array, bounds:Rectangle):void { var maxXPosition:Number = bounds.width; if(maxXPosition == Number.POSITIVE_INFINITY) { maxXPosition = this.maxChildWidth; } maxXPosition -= (this.paddingLeft + this.paddingRight); var xPosition:Number = bounds.x + this.paddingLeft; var yPosition:Number = bounds.y + this.paddingTop; var childCount:int = children.length; for(var i:int = 0; i < childCount; i++) { var child:DisplayObject = DisplayObject(children[i]); child.x = xPosition; child.y = yPosition; DisplayObjectUtil.align(child, new Rectangle(child.x, child.y, maxXPosition, child.height), this.horizontalAlign, null); yPosition += child.height + this.verticalGap; } //special case: if the combined height of the children //is less than the total height specified in the bounds, //then we can align vertically as well! var totalHeight:Number = yPosition - this.verticalGap - bounds.y + this.paddingBottom; if(totalHeight < bounds.height) { var middleStart:Number = (bounds.height - totalHeight) / 2; var rightStart:Number = bounds.height - totalHeight - bounds.y; rightStart = (rightStart == Infinity)?DEFAULT_MAX_HEIGHT:rightStart; for(i = 0; i < childCount; i++) { child = DisplayObject(children[i]); switch(this.verticalAlign) { case "middle": child.y += middleStart; break; case "bottom": child.y += rightStart; break; } } } } /** * @private * Positions the children when direction is horizontal. */ protected function layoutChildrenHorizontally(children:Array, bounds:Rectangle):void { var maxYPosition:Number = bounds.height; if(maxYPosition == Number.POSITIVE_INFINITY) { maxYPosition = this.maxChildHeight; } maxYPosition -= (this.paddingBottom + this.paddingTop); var xPosition:Number = bounds.x + this.paddingLeft; var yPosition:Number = bounds.y + this.paddingTop var childCount:int = children.length; for(var i:int = 0; i < childCount; i++) { var child:DisplayObject = DisplayObject(children[i]); child.x = xPosition; child.y = yPosition; DisplayObjectUtil.align(child, new Rectangle(child.x, child.y, child.width, maxYPosition), null, this.verticalAlign); xPosition += child.width + this.horizontalGap; } //special case: if the combined width of the children //is less than the total width specified in the bounds, //then we can align horizontally as well! var totalWidth:Number = xPosition - this.horizontalGap - bounds.x + this.paddingRight; if(totalWidth < bounds.width) { var middleStart:Number = (bounds.width - totalWidth) / 2; var rightStart:Number = bounds.width - totalWidth; rightStart = (rightStart == Infinity)?DEFAULT_MAX_WIDTH:rightStart; for(i = 0; i < childCount; i++) { child = DisplayObject(children[i]); switch(this.horizontalAlign) { case "center": child.x += middleStart; break; case "right": child.x += rightStart; break; } } } } /** * @private * Creates the default configuration for this layout mode. */ override protected function newConfiguration():Object { return { includeInLayout: true, minWidth: 0, maxWidth: 10000, minHeight: 0, maxHeight: 10000, percentWidth: NaN, percentHeight: NaN }; } } }