1package com.yahoo.astra.layout.modes
2{
3	import com.yahoo.astra.layout.events.LayoutEvent;
4	import com.yahoo.astra.utils.DisplayObjectUtil;
5
6	import flash.display.DisplayObject;
7	import flash.geom.Rectangle;
8
9	/**
10	 * Arranges a DisplayObjectContainer's children using a tiling algorithm.
11	 * All tiles are the same size and tile dimensions are determined from the
12	 * maximum width or height values of the available children.
13	 *
14	 * @example The following code configures a TileLayout instance and passes it to a container:
15	 * <listing version="3.0">
16	 * var tile:TileLayout = new TileLayout();
17	 * tile.direction = "horizontal";
18	 * tile.horizontalGap = 1;
19	 * tile.verticalGap = 4;
20	 * tile.horizontalAlign = HorizontalAlignment.CENTER;
21	 * tile.verticalAlign = VerticalAlignment.MIDDLE;
22	 *
23	 * var container:LayoutContainer = new LayoutContainer();
24	 * container.layoutMode = tile;
25	 * this.addChild( container );
26	 * </listing>
27	 *
28	 * <p><strong>Advanced Client Options:</strong></p>
29	 * <p>Optional client configuration parameters allow a developer to specify
30	 * behaviors for individual children of the target container. To set these
31	 * advanced options, one must call <code>addClient()</code> on the TileLayout
32	 * instance and pass the child to configure along with an object specifying
33	 * the configuration parameters. The following client parameters are available to
34	 * the TileLayout algorithm:</p>
35	 *
36	 * <dl>
37	 * 	<dt><strong><code>includeInLayout</code></strong> : Boolean</dt>
38	 * 		<dd>If <code>false</code>, the target will not be included in layout calculations. The default value is <code>true</code>.</dd>
39	 * </dl>
40	 *
41	 * @author Josh Tynjala
42	 * @see com.yahoo.astra.layout.LayoutContainer
43	 */
44	public class TileLayout extends BaseLayoutMode implements IAdvancedLayoutMode
45	{
46
47	//--------------------------------------
48	//  Constructor
49	//--------------------------------------
50
51		/**
52		 * Constructor.
53		 */
54		public function TileLayout()
55		{
56		}
57
58	//--------------------------------------
59	//  Properties
60	//--------------------------------------
61
62		/**
63		 * @private
64		 * Storage for the direction property.
65		 */
66		private var _direction:String = "horizontal";
67
68		/**
69		 * The direction in which children of the target are laid out. Once
70		 * the edge of the container is reached, the children will begin
71		 * appearing on the next row or column. Valid direction values include
72		 * <code>"vertical"</code> or <code>"horizontal"</code>.
73		 */
74		public function get direction():String
75		{
76			return this._direction;
77		}
78
79		/**
80		 * @private
81		 */
82		public function set direction(value:String):void
83		{
84			this._direction = value;
85			this.dispatchEvent(new LayoutEvent(LayoutEvent.LAYOUT_CHANGE));
86		}
87
88		/**
89		 * @private
90		 * Storage for the verticalGap property.
91		 */
92		private var _verticalGap:Number = 0;
93
94		/**
95		 * The number of pixels appearing between the target's children
96		 * vertically.
97		 */
98		public function get verticalGap():Number
99		{
100			return this._verticalGap;
101		}
102
103		/**
104		 * @private
105		 */
106		public function set verticalGap(value:Number):void
107		{
108			this._verticalGap = value;
109			this.dispatchEvent(new LayoutEvent(LayoutEvent.LAYOUT_CHANGE));
110		}
111
112		/**
113		 * @private
114		 * Storage for the horizontalGap property.
115		 */
116		private var _horizontalGap:Number = 0;
117
118		/**
119		 * The number of pixels appearing between the target's children
120		 * horizontally.
121		 */
122		public function get horizontalGap():Number
123		{
124			return this._horizontalGap;
125		}
126
127		/**
128		 * @private
129		 */
130		public function set horizontalGap(value:Number):void
131		{
132			this._horizontalGap = value;
133			this.dispatchEvent(new LayoutEvent(LayoutEvent.LAYOUT_CHANGE));
134		}
135
136		/**
137		 * @private
138		 * Storage for the verticalAlign property.
139		 */
140		private var _verticalAlign:String = "top";
141
142		/**
143		 * The children of the target may be aligned vertically within their
144		 * respective tiles.
145		 *
146		 * @see VerticalAlignment
147		 */
148		public function get verticalAlign():String
149		{
150			return this._verticalAlign;
151		}
152
153		/**
154		 * @private
155		 */
156		public function set verticalAlign(value:String):void
157		{
158			this._verticalAlign = value;
159			this.dispatchEvent(new LayoutEvent(LayoutEvent.LAYOUT_CHANGE));
160		}
161
162		/**
163		 * @private
164		 * Storage for the horizontalAlign property.
165		 */
166		private var _horizontalAlign:String = "left";
167
168		/**
169		 * The children of the target may be aligned horizontally within their
170		 * respective tiles.
171		 *
172		 * @see HorizontalAlignment
173		 */
174		public function get horizontalAlign():String
175		{
176			return this._horizontalAlign;
177		}
178
179		/**
180		 * @private
181		 */
182		public function set horizontalAlign(value:String):void
183		{
184			this._horizontalAlign = value;
185			this.dispatchEvent(new LayoutEvent(LayoutEvent.LAYOUT_CHANGE));
186		}
187
188		/**
189		 * @private
190		 * Storage for the tileWidth property.
191		 */
192		private var _tileWidth:Number = NaN;
193
194		/**
195		 * The width of tiles displayed in the target. If NaN, the tile width
196		 * will be calculated based on the maximum width among the target's children.
197		 */
198		public function get tileWidth():Number
199		{
200			return this._tileWidth;
201		}
202
203		/**
204		 * @private
205		 */
206		public function set tileWidth(value:Number):void
207		{
208			this._tileWidth = value;
209			this.dispatchEvent(new LayoutEvent(LayoutEvent.LAYOUT_CHANGE));
210		}
211
212		/**
213		 * @private
214		 * Storage for the tileHeight property.
215		 */
216		private var _tileHeight:Number = NaN;
217
218		/**
219		 * The height of tiles displayed in the target. If NaN, the tile height
220		 * will be calculated based on the maximum height among the target's children.
221		 */
222		public function get tileHeight():Number
223		{
224			return this._tileHeight;
225		}
226
227		/**
228		 * @private
229		 */
230		public function set tileHeight(value:Number):void
231		{
232			this._tileHeight = value;
233			this.dispatchEvent(new LayoutEvent(LayoutEvent.LAYOUT_CHANGE));
234		}
235
236		/**
237		 * @private
238		 * The maximum width value from among the current target's children.
239		 */
240		protected var maxChildWidth:Number;
241
242		/**
243		 * @private
244		 * The maximum height value from among the current target's children.
245		 */
246		protected var maxChildHeight:Number;
247
248	//--------------------------------------
249	//  Public Methods
250	//--------------------------------------
251
252		/**
253		 * @inheritDoc
254		 */
255		override public function layoutObjects(displayObjects:Array, bounds:Rectangle):Rectangle
256		{
257			var childrenInLayout:Array = this.configureChildren(displayObjects);
258
259			this.maxChildWidth = this.maxChildHeight = 0;
260			var childCount:int = displayObjects.length;
261			for(var i:int = 0; i < childCount; i++)
262			{
263				var child:DisplayObject = DisplayObject(displayObjects[i]);
264				this.maxChildWidth = Math.max(this.maxChildWidth, child.width);
265				this.maxChildHeight = Math.max(this.maxChildHeight, child.height);
266			}
267
268			if(!isNaN(this.tileWidth))
269			{
270				this.maxChildWidth = this.tileWidth;
271			}
272
273			if(!isNaN(this.tileHeight))
274			{
275				this.maxChildHeight = this.tileHeight;
276			}
277
278			if(this.direction == "vertical")
279			{
280				this.layoutChildrenVertically(childrenInLayout, bounds);
281			}
282			else
283			{
284				this.layoutChildrenHorizontally(childrenInLayout, bounds);
285			}
286
287			var bounds:Rectangle = LayoutModeUtil.calculateChildBounds(childrenInLayout);
288			bounds.width += this.paddingRight;
289			bounds.height += this.paddingBottom;
290			return bounds;
291		}
292
293	//--------------------------------------
294	//  Protected Methods
295	//--------------------------------------
296
297		/**
298		 * @private
299		 * Positions the children when direction is vertical.
300		 */
301		protected function layoutChildrenVertically(children:Array, bounds:Rectangle):void
302		{
303			const START_Y:Number = bounds.y + this.paddingTop;
304			var xPosition:Number = bounds.x + this.paddingLeft;
305			var yPosition:Number = START_Y;
306
307			var childCount:int = children.length;
308			for(var i:int = 0; i < childCount; i++)
309			{
310				var child:DisplayObject = DisplayObject(children[i]);
311
312				var endOfColumn:Number = yPosition + this.maxChildHeight + this.paddingBottom;
313				if(endOfColumn - bounds.y >= bounds.height && yPosition != START_Y)
314				{
315					//next column if we're over the height,
316					//but not if we're at yposition == START_Y
317					yPosition = START_Y;
318					xPosition += this.maxChildWidth + this.horizontalGap;
319				}
320
321				DisplayObjectUtil.align(child, new Rectangle(xPosition, yPosition, this.maxChildWidth, this.maxChildHeight), this.horizontalAlign, this.verticalAlign);
322
323				yPosition += this.maxChildHeight + this.verticalGap;
324			}
325		}
326
327		/**
328		 * @private
329		 * Positions the children when direction is horizontal.
330		 */
331		protected function layoutChildrenHorizontally(children:Array, bounds:Rectangle):void
332		{
333			const START_X:Number = bounds.x + this.paddingLeft;
334			var xPosition:Number = START_X;
335			var yPosition:Number = bounds.y + this.paddingTop;
336
337			var childCount:int = children.length;
338			for(var i:int = 0; i < childCount; i++)
339			{
340				var child:DisplayObject = DisplayObject(children[i]);
341
342				var endOfRow:Number = xPosition + this.maxChildWidth + this.paddingRight;
343				if(endOfRow - bounds.x >= bounds.width && xPosition != START_X)
344				{
345					//next row if we're over the width,
346					//but not if we're at xposition == START_X
347					xPosition = START_X;
348					yPosition += this.maxChildHeight + this.verticalGap;
349				}
350
351				DisplayObjectUtil.align(child, new Rectangle(xPosition, yPosition, this.maxChildWidth, this.maxChildHeight), this.horizontalAlign, this.verticalAlign);
352
353				xPosition += this.maxChildWidth + this.horizontalGap;
354			}
355		}
356
357	}
358}