366 lines
10 KiB
ActionScript
Executable File
366 lines
10 KiB
ActionScript
Executable File
/*
|
|
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 using a flow algorithm. When
|
|
* a child is too large for a row or column, a new row or column is created.
|
|
* Similar to the flow of text in a document.
|
|
*
|
|
* @example The following code configures a FlowLayout instance and passes it to a container:
|
|
* <listing version="3.0">
|
|
* var flow:FlowLayout = new FlowLayout();
|
|
* flow.direction = "horizontal";
|
|
* flow.horizontalGap = 1;
|
|
* flow.verticalGap = 4;
|
|
* flow.verticalAlign = VerticalAlignment.BOTTOM;
|
|
*
|
|
* var container:LayoutContainer = new LayoutContainer();
|
|
* container.layoutMode = flow;
|
|
* this.addChild( container );
|
|
* </listing>
|
|
*
|
|
* <p><strong>Advanced Client Options:</strong></p>
|
|
* <p>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 <code>addClient()</code> on the FlowLayout
|
|
* instance and pass the child to configure along with an object specifying
|
|
* the configuration parameters. The following client parameters are available to
|
|
* the FlowLayout algorithm:</p>
|
|
*
|
|
* <dl>
|
|
* <dt><strong><code>includeInLayout</code></strong> : Boolean</dt>
|
|
* <dd>If <code>false</code>, the target will not be included in layout calculations. The default value is <code>true</code>.</dd>
|
|
* </dl>
|
|
*
|
|
* @author Josh Tynjala
|
|
* @see com.yahoo.astra.layout.LayoutContainer
|
|
*/
|
|
public class FlowLayout extends BaseLayoutMode implements IAdvancedLayoutMode
|
|
{
|
|
|
|
//--------------------------------------
|
|
// Constructor
|
|
//--------------------------------------
|
|
|
|
/**
|
|
* Constructor.
|
|
*/
|
|
public function FlowLayout()
|
|
{
|
|
super();
|
|
}
|
|
|
|
//--------------------------------------
|
|
// Properties
|
|
//--------------------------------------
|
|
|
|
/**
|
|
* @private
|
|
* Storage for the direction property.
|
|
*/
|
|
private var _direction:String = "horizontal";
|
|
|
|
/**
|
|
* The direction in which children of the target are laid out. Once
|
|
* the edge of the container is reached, the children will begin
|
|
* appearing on the next row or column. Valid direction values include
|
|
* <code>"vertical"</code> or <code>"horizontal"</code>.
|
|
*/
|
|
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));
|
|
}
|
|
|
|
//--------------------------------------
|
|
// Public Methods
|
|
//--------------------------------------
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
override public function layoutObjects(displayObjects:Array, bounds:Rectangle):Rectangle
|
|
{
|
|
var childrenInLayout:Array = this.configureChildren(displayObjects);
|
|
|
|
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
|
|
{
|
|
const START_Y:Number = bounds.y + this.paddingTop;
|
|
var xPosition:Number = bounds.x + this.paddingLeft;
|
|
var yPosition:Number = START_Y;
|
|
var maxChildWidth:Number = 0;
|
|
var column:Array = [];;
|
|
|
|
var childCount:int = children.length;
|
|
for(var i:int = 0; i < childCount; i++)
|
|
{
|
|
var child:DisplayObject = DisplayObject(children[i]);
|
|
|
|
//next column if we're over the height, but not if we're at yposition == bounds.y
|
|
var endOfColumn:Number = yPosition + child.height + this.paddingBottom;
|
|
if(endOfColumn - bounds.y >= bounds.height && yPosition != START_Y)
|
|
{
|
|
//update alignment
|
|
this.alignColumn(column, maxChildWidth, bounds);
|
|
|
|
xPosition += maxChildWidth + this.horizontalGap;
|
|
yPosition = START_Y;
|
|
maxChildWidth = 0;
|
|
column = [];
|
|
}
|
|
|
|
child.x = xPosition;
|
|
child.y = yPosition;
|
|
column.push(child);
|
|
maxChildWidth = Math.max(maxChildWidth, child.width);
|
|
yPosition += child.height + this.verticalGap;
|
|
}
|
|
this.alignColumn(column, maxChildWidth, bounds);
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
* Positions the children when direction is horizontal.
|
|
*/
|
|
protected function layoutChildrenHorizontally(children:Array, bounds:Rectangle):void
|
|
{
|
|
const START_X:Number = bounds.x + this.paddingLeft;
|
|
var xPosition:Number = START_X;
|
|
var yPosition:Number = bounds.y + this.paddingTop;
|
|
var maxChildHeight:Number = 0;
|
|
var row:Array = [];
|
|
|
|
var childCount:int = children.length;
|
|
for(var i:int = 0; i < childCount; i++)
|
|
{
|
|
var child:DisplayObject = DisplayObject(children[i]);
|
|
var childWidth:Number = child.width;
|
|
var childHeight:Number = child.height;
|
|
|
|
//next row if we're over the width, but not if we're at xposition == bounds.x
|
|
var endOfRow:Number = xPosition + child.width + this.paddingRight;
|
|
if(endOfRow - bounds.x >= bounds.width && xPosition != START_X)
|
|
{
|
|
//update alignment
|
|
this.alignRow(row, maxChildHeight, bounds);
|
|
|
|
xPosition = START_X;
|
|
yPosition += maxChildHeight + this.verticalGap;
|
|
maxChildHeight = 0;
|
|
row = [];
|
|
}
|
|
child.x = xPosition;
|
|
child.y = yPosition;
|
|
row.push(child);
|
|
maxChildHeight = Math.max(maxChildHeight, childHeight);
|
|
xPosition += child.width + this.horizontalGap;
|
|
}
|
|
this.alignRow(row, maxChildHeight, bounds);
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
* Repositions a column of children based on the alignment values.
|
|
*/
|
|
protected function alignColumn(column:Array, maxChildWidth:Number, bounds:Rectangle):void
|
|
{
|
|
if(column.length == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var lastChild:DisplayObject = DisplayObject(column[column.length - 1]);
|
|
var columnHeight:Number = (lastChild.y + lastChild.height) - bounds.y + this.paddingBottom;
|
|
var difference:Number = bounds.height - columnHeight;
|
|
|
|
var columnCount:int = column.length;
|
|
for(var i:int = 0; i < columnCount; i++)
|
|
{
|
|
var child:DisplayObject = DisplayObject(column[i]);
|
|
DisplayObjectUtil.align(child, new Rectangle(child.x, child.y, maxChildWidth, child.height), this.horizontalAlign, null);
|
|
|
|
switch(this.verticalAlign)
|
|
{
|
|
case "middle":
|
|
child.y += difference / 2;
|
|
break;
|
|
case "bottom":
|
|
child.y += difference;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
* Repositions a row of children based on the alignment values.
|
|
*/
|
|
protected function alignRow(row:Array, maxChildHeight:Number, bounds:Rectangle):void
|
|
{
|
|
if(row.length == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var lastChild:DisplayObject = DisplayObject(row[row.length - 1]);
|
|
var rowWidth:Number = (lastChild.x + lastChild.width) - bounds.x + this.paddingRight;
|
|
var difference:Number = bounds.width - rowWidth;
|
|
|
|
var rowCount:int = row.length;
|
|
for(var i:int = 0; i < rowCount; i++)
|
|
{
|
|
var child:DisplayObject = DisplayObject(row[i]);
|
|
DisplayObjectUtil.align(child, new Rectangle(child.x, child.y, child.width, maxChildHeight), null, this.verticalAlign);
|
|
|
|
switch(this.horizontalAlign)
|
|
{
|
|
case "center":
|
|
child.x += difference / 2;
|
|
break;
|
|
case "right":
|
|
child.x += difference;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} |