/* 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: * * 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 ); * * *

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 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:

* *
*
includeInLayout : Boolean
*
If false, the target will not be included in layout calculations. The default value is true.
*
* * @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 * "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)); } //-------------------------------------- // 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; } } } } }