/* 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: *
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
: NumberpercentHeight
: NumberminWidth
: Number0
.minHeight
: Number0
.maxWidth
: Number10000
.maxHeight
: Number10000
.includeInLayout
: Booleanfalse
, the target will not be included in layout calculations. The default value is true
."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
};
}
}
}