1////////////////////////////////////////////////////////////////////////////////
2//
3//  ADOBE SYSTEMS INCORPORATED
4//  Copyright 2008 Adobe Systems Incorporated
5//  All Rights Reserved.
6//
7//  NOTICE: Adobe permits you to use, modify, and distribute this file
8//  in accordance with the terms of the license agreement accompanying it.
9//
10////////////////////////////////////////////////////////////////////////////////
11
12package spark.layouts
13{
14import flash.geom.Point;
15import flash.geom.Rectangle;
16
17import mx.core.ILayoutElement;
18import mx.core.IVisualElement;
19import mx.core.mx_internal;
20import mx.events.PropertyChangeEvent;
21
22import spark.components.supportClasses.GroupBase;
23import spark.core.NavigationUnit;
24import spark.layouts.supportClasses.DropLocation;
25import spark.layouts.supportClasses.LayoutBase;
26
27use namespace mx_internal;
28
29/**
30 *  The TileLayout class arranges layout elements in columns and rows
31 *  of equally-sized cells.
32 *  The TileLayout class uses a number of properties that control orientation,
33 *  count, size, gap and justification of the columns and the rows
34 *  as well as element alignment within the cells.
35 *
36 *  <p>Per-element supported constraints are
37 *  <code>percentWidth</code> and <code>percentHeight</code>.
38 *  Element's minimum and maximum sizes are always be respected and
39 *  where possible, an element's size is limited to less then or equal
40 *  of the cell size.</p>
41 *
42 *  <p>When not explicitly set, the <code>columnWidth</code> property
43 *  is calculated as the maximum preferred bounds width of all elements
44 *  and the <code>columnHeight</code> property is calculated
45 *  as the maximum preferred bounds height of all elements.</p>
46 *
47 *  <p>When not explicitly set, the <code>columnCount</code> and
48 *  <code>rowCount</code> properties are calculated from
49 *  any explicit width and height settings for the layout target,
50 *  and <code>columnWidth</code> and <code>columnHeight</code>.
51 *  In case none is specified, the <code>columnCount</code> and <code>rowCount</code>
52 *  values are picked so that the resulting pixel area is as square as possible.</p>
53 *
54 * <p> The measured size is calculated from the <code>columnCount</code>, <code>rowCount</code>,
55 *  <code>columnWidth</code>, <code>rowHeight</code> properties and the gap sizes.</p>
56 *
57 *  <p>The default measured size, when no properties were explicitly set, is
58 *  as square as possible area and is large enough to fit all elements.</p>
59 *
60 *  <p>In other cases the measured size may not be big enough to fit all elements.
61 *  For example, when both <code>columnCount</code> and <code>rowCount</code> are explicitly set to values
62 *  such that <code>columnCount</code> &#42; <code>rowCount</code> &lt; element count.</p>
63 *
64 *  <p>The minimum measured size is calculated the same way as the measured size but
65 *  it's guaranteed to encompass enough rows and columns along the minor axis to fit
66 *  all elements.</p>
67 *
68 *  @mxml
69 *  <p>The <code>&lt;s:TileLayout&gt;</code> tag inherits all of the tag
70 *  attributes of its superclass and adds the following tag attributes:</p>
71 *
72 *  <pre>
73 *  &lt;s:TileLayout
74 *    <strong>Properties</strong>
75 *    columnAlign="left"
76 *    columnWidth="NaN"
77 *    horizontalAlign="justify"
78 *    horizontalGap="6"
79 *    orientation="rows"
80 *    requestedColumnCount="-1"
81 *    requestedRowCount="-1"
82 *    rowAlign="top"
83 *    rowCount="-1"
84 *    rowHeight="NaN"
85 *    verticalAlign="justify"
86 *    verticalGap="6"
87 *  /&gt;
88 *  </pre>
89 *
90 *  @langversion 3.0
91 *  @playerversion Flash 10
92 *  @playerversion AIR 1.5
93 *  @productversion Flex 4
94 */
95public class TileLayout extends LayoutBase
96{
97    include "../core/Version.as";
98
99    //--------------------------------------------------------------------------
100    //
101    //  Constructor
102    //
103    //--------------------------------------------------------------------------
104
105    /**
106     *  Constructor.
107     *
108     *  @langversion 3.0
109     *  @playerversion Flash 10
110     *  @playerversion AIR 1.5
111     *  @productversion Flex 4
112     */
113    public function TileLayout():void
114    {
115        super();
116    }
117
118    //--------------------------------------------------------------------------
119    //
120    //  Properties
121    //
122    //--------------------------------------------------------------------------
123
124    //----------------------------------
125    //  horizontalGap
126    //----------------------------------
127
128    private var explicitHorizontalGap:Number = 6;
129    private var _horizontalGap:Number = 6;
130
131    [Bindable("propertyChange")]
132    [Inspectable(category="General")]
133
134    /**
135     *  Horizontal space between columns, in pixels.
136     *
137     *  @see #verticalGap
138     *  @see #columnAlign
139     *  @default 6
140     *
141     *  @langversion 3.0
142     *  @playerversion Flash 10
143     *  @playerversion AIR 1.5
144     *  @productversion Flex 4
145     */
146    public function get horizontalGap():Number
147    {
148        return _horizontalGap;
149    }
150
151    /**
152     *  @private
153     */
154    public function set horizontalGap(value:Number):void
155    {
156        explicitHorizontalGap = value;
157        if (value == _horizontalGap)
158            return;
159
160        _horizontalGap = value;
161        invalidateTargetSizeAndDisplayList();
162    }
163
164    //----------------------------------
165    //  verticalGap
166    //----------------------------------
167
168    private var explicitVerticalGap:Number = 6;
169    private var _verticalGap:Number = 6;
170
171    [Bindable("propertyChange")]
172    [Inspectable(category="General")]
173
174    /**
175     *  Vertical space between rows, in pixels.
176     *
177     *  @see #horizontalGap
178     *  @see #rowAlign
179     *  @default 6
180     *
181     *  @langversion 3.0
182     *  @playerversion Flash 10
183     *  @playerversion AIR 1.5
184     *  @productversion Flex 4
185     */
186    public function get verticalGap():Number
187    {
188        return _verticalGap;
189    }
190
191    /**
192     *  @private
193     */
194    public function set verticalGap(value:Number):void
195    {
196        explicitVerticalGap = value;
197        if (value == _verticalGap)
198            return;
199
200        _verticalGap = value;
201        invalidateTargetSizeAndDisplayList();
202    }
203
204    //----------------------------------
205    //  columnCount
206    //----------------------------------
207
208    private var _columnCount:int = -1;
209
210    [Bindable("propertyChange")]
211    [Inspectable(category="General")]
212
213    /**
214     *  Contain the actual column count.
215     *
216     *  @see #rowCount
217     *  @see #columnAlign
218     *  @default -1
219     *
220     *  @langversion 3.0
221     *  @playerversion Flash 10
222     *  @playerversion AIR 1.5
223     *  @productversion Flex 4
224     */
225    public function get columnCount():int
226    {
227        return _columnCount;
228    }
229
230    //----------------------------------
231    //  requestedColumnCount
232    //----------------------------------
233
234    /**
235     *  @private
236     *  Storage for the requestedColumnCount property.
237     */
238    private var _requestedColumnCount:int = -1;
239
240    [Inspectable(category="General", minValue="-1")]
241
242    /**
243     *  Number of columns to be displayed.
244     *
245     *  <p>Set to -1 to allow the TileLayout to determine
246     *  the column count automatically.</p>
247     *
248     *  <p>If the <code>orientation</code> property is set to <code>TileOrientation.ROWS</code>,
249     *  then setting this property has no effect
250     *  In this case, the <code>rowCount</code> is explicitly set, and the
251     *  container width is explicitly set. </p>
252     *
253     *  @see #columnCount
254     *  @see #columnAlign
255     *  @default -1
256     *
257     *  @langversion 3.0
258     *  @playerversion Flash 10
259     *  @playerversion AIR 1.5
260     *  @productversion Flex 4
261     */
262    public function get requestedColumnCount():int
263    {
264        return _requestedColumnCount;
265    }
266
267    /**
268     *  @private
269     */
270    public function set requestedColumnCount(value:int):void
271    {
272        if (_requestedColumnCount == value)
273            return;
274
275        _requestedColumnCount = value;
276        _columnCount = value;
277        invalidateTargetSizeAndDisplayList();
278    }
279
280    //----------------------------------
281    //  rowCount
282    //----------------------------------
283
284    /**
285     *  @private
286     *  Storage for the rowCount property.
287     */
288    private var _rowCount:int = -1;
289
290    [Bindable("propertyChange")]
291    [Inspectable(category="General")]
292
293    /**
294     *  The row count.
295     *
296     *  @see #requestedRowCount
297     *  @see #columnCount
298     *  @default -1
299     *
300     *  @langversion 3.0
301     *  @playerversion Flash 10
302     *  @playerversion AIR 1.5
303     *  @productversion Flex 4
304     */
305    public function get rowCount():int
306    {
307        return _rowCount;
308    }
309
310    //----------------------------------
311    //  requestedRowCount
312    //----------------------------------
313
314    /**
315     *  @private
316     *  Storage for the requestedRowCount property.
317     */
318    private var _requestedRowCount:int = -1;
319
320    [Inspectable(category="General", minValue="-1")]
321
322    /**
323     *  Number of rows to be displayed.
324     *
325     *  <p>Set to -1 to remove explicit override and allow the TileLayout to determine
326     *  the row count automatically.</p>
327     *
328     *  <p>If the <code>orientation</code> property is set to
329     *  <code>TileOrientation.COLUMNS</code>, setting this property has no effect.
330     *  in this case, <code>columnCount</code> is explicitly set, and the
331     *  container height is explicitly set.</p>
332     *
333     *  @see #rowCount
334     *  @see #rowAlign
335     *  @default -1
336     *
337     *  @langversion 3.0
338     *  @playerversion Flash 10
339     *  @playerversion AIR 1.5
340     *  @productversion Flex 4
341     */
342    public function get requestedRowCount():int
343    {
344        return _requestedRowCount;
345    }
346
347    /**
348     *  @private
349     */
350    public function set requestedRowCount(value:int):void
351    {
352        if (_requestedRowCount == value)
353            return;
354
355        _requestedRowCount = value;
356        _rowCount = value;
357        invalidateTargetSizeAndDisplayList();
358    }
359
360    //----------------------------------
361    //  columnWidth
362    //----------------------------------
363
364    private var explicitColumnWidth:Number = NaN;
365    private var _columnWidth:Number = NaN;
366
367    [Bindable("propertyChange")]
368    [Inspectable(category="General", minValue="0.0")]
369
370    /**
371     *  Contain the actual column width, in pixels.
372     *
373     *  <p>If not explicitly set, the column width is
374     *  determined from the width of the widest element. </p>
375     *
376     *  <p>If the <code>columnAlign</code> property is set
377     *  to <code>"justifyUsingWidth"</code>,  the column width grows to the
378     *  container width to justify the fully-visible columns.</p>
379     *
380     *  @see #rowHeight
381     *  @see #columnAlign
382     *  @default NaN
383     *
384     *  @langversion 3.0
385     *  @playerversion Flash 10
386     *  @playerversion AIR 1.5
387     *  @productversion Flex 4
388     */
389    public function get columnWidth():Number
390    {
391        return _columnWidth;
392    }
393
394    /**
395     *  @private
396     */
397    public function set columnWidth(value:Number):void
398    {
399        explicitColumnWidth = value;
400        if (value == _columnWidth)
401            return;
402
403        _columnWidth = value;
404        invalidateTargetSizeAndDisplayList();
405    }
406
407    //----------------------------------
408    //  rowHeight
409    //----------------------------------
410
411    private var explicitRowHeight:Number = NaN;
412    private var _rowHeight:Number = NaN;
413
414    [Bindable("propertyChange")]
415    [Inspectable(category="General", minValue="0.0")]
416
417    /**
418     *  The row height, in pixels.
419     *
420     *  <p>If not explicitly set, the row height is
421     *  determined from the maximum of elements' height.</p>
422     *
423     *  If <code>rowAlign</code> is set to "justifyUsingHeight", the actual row height
424     *  increases to justify the fully-visible rows to the container height.
425     *
426     *  @see #columnWidth
427     *  @see #rowAlign
428     *  @default NaN
429     *
430     *  @langversion 3.0
431     *  @playerversion Flash 10
432     *  @playerversion AIR 1.5
433     *  @productversion Flex 4
434     */
435    public function get rowHeight():Number
436    {
437        return _rowHeight;
438    }
439
440    /**
441     *  @private
442     */
443    public function set rowHeight(value:Number):void
444    {
445        explicitRowHeight = value;
446        if (value == _rowHeight)
447            return;
448
449        _rowHeight = value;
450        invalidateTargetSizeAndDisplayList();
451    }
452
453    //----------------------------------
454    //  paddingLeft
455    //----------------------------------
456
457    private var _paddingLeft:Number = 0;
458
459    [Inspectable(category="General")]
460
461    /**
462     *  The minimum number of pixels between the container's left edge and
463     *  the left edge of the layout element.
464     *
465     *  @default 0
466     *
467     *  @langversion 3.0
468     *  @playerversion Flash 10
469     *  @playerversion AIR 1.5
470     *  @productversion Flex 4
471     */
472    public function get paddingLeft():Number
473    {
474        return _paddingLeft;
475    }
476
477    /**
478     *  @private
479     */
480    public function set paddingLeft(value:Number):void
481    {
482        if (_paddingLeft == value)
483            return;
484
485        _paddingLeft = value;
486        invalidateTargetSizeAndDisplayList();
487    }
488
489    //----------------------------------
490    //  paddingRight
491    //----------------------------------
492
493    private var _paddingRight:Number = 0;
494
495    [Inspectable(category="General")]
496
497    /**
498     *  The minimum number of pixels between the container's right edge and
499     *  the right edge of the layout element.
500     *
501     *  @default 0
502     *
503     *  @langversion 3.0
504     *  @playerversion Flash 10
505     *  @playerversion AIR 1.5
506     *  @productversion Flex 4
507     */
508    public function get paddingRight():Number
509    {
510        return _paddingRight;
511    }
512
513    /**
514     *  @private
515     */
516    public function set paddingRight(value:Number):void
517    {
518        if (_paddingRight == value)
519            return;
520
521        _paddingRight = value;
522        invalidateTargetSizeAndDisplayList();
523    }
524
525    //----------------------------------
526    //  paddingTop
527    //----------------------------------
528
529    private var _paddingTop:Number = 0;
530
531    [Inspectable(category="General")]
532
533    /**
534     *  Number of pixels between the container's top edge
535     *  and the top edge of the first layout element.
536     *
537     *  @default 0
538     *
539     *  @langversion 3.0
540     *  @playerversion Flash 10
541     *  @playerversion AIR 1.5
542     *  @productversion Flex 4
543     */
544    public function get paddingTop():Number
545    {
546        return _paddingTop;
547    }
548
549    /**
550     *  @private
551     */
552    public function set paddingTop(value:Number):void
553    {
554        if (_paddingTop == value)
555            return;
556
557        _paddingTop = value;
558        invalidateTargetSizeAndDisplayList();
559    }
560
561    //----------------------------------
562    //  paddingBottom
563    //----------------------------------
564
565    private var _paddingBottom:Number = 0;
566
567    [Inspectable(category="General")]
568
569    /**
570     *  Number of pixels between the container's bottom edge
571     *  and the bottom edge of the last layout element.
572     *
573     *  @default 0
574     *
575     *  @langversion 3.0
576     *  @playerversion Flash 10
577     *  @playerversion AIR 1.5
578     *  @productversion Flex 4
579     */
580    public function get paddingBottom():Number
581    {
582        return _paddingBottom;
583    }
584
585    /**
586     *  @private
587     */
588    public function set paddingBottom(value:Number):void
589    {
590        if (_paddingBottom == value)
591            return;
592
593        _paddingBottom = value;
594        invalidateTargetSizeAndDisplayList();
595    }
596
597    //----------------------------------
598    //  horizontalAlign
599    //----------------------------------
600
601    private var _horizontalAlign:String = HorizontalAlign.JUSTIFY;
602
603    [Inspectable(category="General", enumeration="left,right,center,justify", defaultValue="justify")]
604
605    /**
606     *  Specifies how to align the elements within the cells in the horizontal direction.
607     *  Supported values are
608     *  <code>HorizontalAlign.LEFT</code>,
609     *  <code>HorizontalAlign.CENTER</code>,
610     *  <code>HorizontalAlign.RIGHT</code>,
611     *  <code>HorizontalAlign.JUSTIFY</code>.
612     *
613     *  <p>When set to <code>HorizontalAlign.JUSTIFY</code> the width of each
614     *  element is set to the <code>columnWidth</code>.</p>
615     *
616     *  @default <code>HorizontalAlign.JUSTIFY</code>
617     *
618     *  @langversion 3.0
619     *  @playerversion Flash 10
620     *  @playerversion AIR 1.5
621     *  @productversion Flex 4
622     */
623    public function get horizontalAlign():String
624    {
625        return _horizontalAlign;
626    }
627
628    /**
629     *  @private
630     */
631    public function set horizontalAlign(value:String):void
632    {
633        if (_horizontalAlign == value)
634            return;
635
636        _horizontalAlign = value;
637        invalidateTargetSizeAndDisplayList();
638    }
639
640    //----------------------------------
641    //  verticalAlign
642    //----------------------------------
643
644    private var _verticalAlign:String = VerticalAlign.JUSTIFY;
645
646    [Inspectable(category="General", enumeration="top,bottom,middle,justify", defaultValue="justify")]
647
648    /**
649     *  Specifies how to align the elements within the cells in the vertical direction.
650     *  Supported values are
651     *  <code>VerticalAlign.TOP</code>,
652     *  <code>VerticalAlign.MIDDLE</code>,
653     *  <code>VerticalAlign.BOTTOM</code>,
654     *  <code>VerticalAlign.JUSTIFY</code>.
655     *
656     *  <p>When set to <code>VerticalAlign.JUSTIFY</code>, the height of each
657     *  element is set to <code>rowHeight</code>.</p>
658     *
659     *  @default <code>VerticalAlign.JUSTIFY</code>
660     *
661     *  @langversion 3.0
662     *  @playerversion Flash 10
663     *  @playerversion AIR 1.5
664     *  @productversion Flex 4
665     */
666    public function get verticalAlign():String
667    {
668        return _verticalAlign;
669    }
670
671    /**
672     *  @private
673     */
674    public function set verticalAlign(value:String):void
675    {
676        if (_verticalAlign == value)
677            return;
678
679        _verticalAlign = value;
680        invalidateTargetSizeAndDisplayList();
681    }
682
683    //----------------------------------
684    //  columnAlign
685    //----------------------------------
686
687    private var _columnAlign:String = ColumnAlign.LEFT;
688
689    [Inspectable(category="General", enumeration="left,justifyUsingGap,justifyUsingWidth", defaultValue="left")]
690
691    /**
692     *  Specifies how to justify the fully visible columns to the container width.
693     *  ActionScript values can be <code>ColumnAlign.LEFT</code>, <code>ColumnAlign.JUSTIFY_USING_GAP</code>
694     *  and <code>ColumnAlign.JUSTIFY_USING_WIDTH</code>.
695     *  MXML values can be <code>"left"</code>, <code>"justifyUsingGap"</code> and <code>"justifyUsingWidth"</code>.
696     *
697     *  <p>When set to <code>ColumnAlign.LEFT</code> it turns column justification off.
698     *  There may be partially visible columns or whitespace between the last column and
699     *  the right edge of the container.  This is the default value.</p>
700     *
701     *  <p>When set to <code>ColumnAlign.JUSTIFY_USING_GAP</code> the <code>horizontalGap</code>
702     *  actual value increases so that
703     *  the last fully visible column right edge aligns with the container's right edge.
704     *  In case there is only a single fully visible column, the <code>horizontalGap</code> actual value
705     *  increases so that it pushes any partially visible column beyond the right edge
706     *  of the container.
707     *  Note that explicitly setting the <code>horizontalGap</code> property does not turn off
708     *  justification. It only determines the initial gap value.
709     *  Justification may increases it.</p>
710     *
711     *  <p>When set to <code>ColumnAlign.JUSTIFY_USING_WIDTH</code> the <code>columnWidth</code>
712     *  actual value increases so that
713     *  the last fully visible column right edge aligns with the container's right edge.
714     *  Note that explicitly setting the <code>columnWidth</code> property does not turn off justification.
715     *  It only determines the initial column width value.
716     *  Justification may increases it.</p>
717     *
718     *  @see #horizontalGap
719     *  @see #columnWidth
720     *  @see #rowAlign
721     *  @default ColumnAlign.LEFT
722     *
723     *  @langversion 3.0
724     *  @playerversion Flash 10
725     *  @playerversion AIR 1.5
726     *  @productversion Flex 4
727     */
728    public function get columnAlign():String
729    {
730        return _columnAlign;
731    }
732
733    /**
734     *  @private
735     */
736    public function set columnAlign(value:String):void
737    {
738        if (_columnAlign == value)
739            return;
740
741        _columnAlign = value;
742        invalidateTargetSizeAndDisplayList();
743    }
744
745    //----------------------------------
746    //  rowAlign
747    //----------------------------------
748
749    private var _rowAlign:String = RowAlign.TOP;
750
751    [Inspectable(category="General", enumeration="top,justifyUsingGap,justifyUsingHeight", defaultValue="top")]
752
753    /**
754     *  Specifies how to justify the fully visible rows to the container height.
755     *  ActionScript values can be <code>RowAlign.TOP</code>, <code>RowAlign.JUSTIFY_USING_GAP</code>
756     *  and <code>RowAlign.JUSTIFY_USING_HEIGHT</code>.
757     *  MXML values can be <code>"top"</code>, <code>"justifyUsingGap"</code> and <code>"justifyUsingHeight"</code>.
758     *
759     *  <p>When set to <code>RowAlign.TOP</code> it turns column justification off.
760     *  There might be partially visible rows or whitespace between the last row and
761     *  the bottom edge of the container.  This is the default value.</p>
762     *
763     *  <p>When set to <code>RowAlign.JUSTIFY_USING_GAP</code> the <code>verticalGap</code>
764     *  actual value increases so that
765     *  the last fully visible row bottom edge aligns with the container's bottom edge.
766     *  In case there is only a single fully visible row, the value of <code>verticalGap</code>
767     *  increases so that it pushes any partially visible row beyond the bottom edge
768     *  of the container.  Note that explicitly setting the <code>verticalGap</code> does not turn off
769     *  justification, but just determines the initial gap value.
770     *  Justification can then increases it.</p>
771     *
772     *  <p>When set to <code>RowAlign.JUSTIFY_USING_HEIGHT</code> the <code>rowHeight</code>
773     *  actual value increases so that
774     *  the last fully visible row bottom edge aligns with the container's bottom edge.  Note that
775     *  explicitly setting the <code>rowHeight</code> does not turn off justification, but
776     *  determines the initial row height value.
777     *  Justification can then increase it.</p>
778     *
779     *  @see #verticalGap
780     *  @see #rowHeight
781     *  @see #columnAlign
782     *  @default RowAlign.TOP
783     *
784     *  @langversion 3.0
785     *  @playerversion Flash 10
786     *  @playerversion AIR 1.5
787     *  @productversion Flex 4
788     */
789    public function get rowAlign():String
790    {
791        return _rowAlign;
792    }
793
794    /**
795     *  @private
796     */
797    public function set rowAlign(value:String):void
798    {
799        if (_rowAlign == value)
800            return;
801
802        _rowAlign = value;
803        invalidateTargetSizeAndDisplayList();
804    }
805
806    //----------------------------------
807    //  orientation
808    //----------------------------------
809
810    private var _orientation:String = TileOrientation.ROWS;
811
812    [Inspectable(category="General", enumeration="rows,columns", defaultValue="rows")]
813
814    /**
815     *  Specifies whether elements are arranged row by row or
816     *  column by column.
817     *  ActionScript values can be <code>TileOrientation.ROWS</code> and
818     *  <code>TileOrientation.COLUMNS</code>.
819     *  MXML values can be <code>"rows"</code> and <code>"columns"</code>.
820     *
821     *  @default TileOrientation.ROWS
822     *
823     *  @langversion 3.0
824     *  @playerversion Flash 10
825     *  @playerversion AIR 1.5
826     *  @productversion Flex 4
827     */
828    public function get orientation():String
829    {
830        return _orientation;
831    }
832
833    /**
834     *  @private
835     */
836    public function set orientation(value:String):void
837    {
838        if (_orientation == value)
839            return;
840
841        _orientation = value;
842        _tileWidthCached = _tileHeightCached = NaN;
843        invalidateTargetSizeAndDisplayList();
844    }
845
846    //----------------------------------
847    //  useVirtualLayout
848    //----------------------------------
849
850    /**
851     *  @private
852     */
853    override public function set useVirtualLayout(value:Boolean):void
854    {
855        if (useVirtualLayout == value)
856            return;
857
858        super.useVirtualLayout = value;
859
860        // Reset the state that virtual depends on.  If the layout has already
861        // run with useVirtualLayout=false, the visibleStartEndIndex variables
862        // will have been set to 0, dataProvider.length.
863        if (value)
864        {
865            visibleStartIndex = -1;
866            visibleEndIndex = -1;
867            visibleStartX = 0;
868            visibleStartY = 0;
869        }
870    }
871
872    //--------------------------------------------------------------------------
873    //
874    //  Variables
875    //
876    //--------------------------------------------------------------------------
877
878    /**
879     * @private
880     */
881    override public function clearVirtualLayoutCache():void
882    {
883        _tileWidthCached = _tileHeightCached = NaN;
884    }
885
886    /**
887     *  @private
888     *  storage for old property values, in order to dispatch change events.
889     */
890    private var oldColumnWidth:Number = NaN;
891    private var oldRowHeight:Number = NaN;
892    private var oldColumnCount:int = -1;
893    private var oldRowCount:int = -1;
894    private var oldHorizontalGap:Number = NaN;
895    private var oldVerticalGap:Number = NaN;
896
897    // Cache storage to avoid repeating work from measure() in updateDisplayList().
898    // These are set the first time the value is calculated and are reset at the end
899    // of updateDisplayList().
900    private var _tileWidthCached:Number = NaN;
901    private var _tileHeightCached:Number = NaN;
902    private var _numElementsCached:int = -1;
903
904    /**
905     *  @private
906     *  The following variables are used by updateDisplayList() and set by
907     *  calculateDisplayParameters().   If virtualLayout=true they're based
908     *  on the current scrollRect.
909     */
910    private var visibleStartIndex:int = -1;   // dataProvider/layout element index
911    private var visibleEndIndex:int = -1;     // ...
912    private var visibleStartX:Number = 0;     // first tile/cell origin
913    private var visibleStartY:Number = 0;     // ...
914
915    //--------------------------------------------------------------------------
916    //
917    //  Class methods
918    //
919    //--------------------------------------------------------------------------
920
921    /**
922     *  Dispatches events if Actual values have changed since the last call.
923     *  Checks columnWidth, rowHeight, columnCount, rowCount, horizontalGap, verticalGap.
924     *  This method is called from within updateDisplayList()
925     *
926     *  @langversion 3.0
927     *  @playerversion Flash 10
928     *  @playerversion AIR 1.5
929     *  @productversion Flex 4
930     */
931    private function dispatchEventsForActualValueChanges():void
932    {
933        if (hasEventListener(PropertyChangeEvent.PROPERTY_CHANGE))
934        {
935            if (oldColumnWidth != _columnWidth)
936                dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "columnWidth", oldColumnWidth, _columnWidth));
937            if (oldRowHeight != _rowHeight)
938                dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "rowHeight", oldRowHeight, _rowHeight));
939            if (oldColumnCount != _columnCount)
940                dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "columnCount", oldColumnCount, _columnCount));
941            if (oldRowCount != _rowCount)
942                dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "rowCount", oldRowCount, _rowCount));
943            if (oldHorizontalGap != _horizontalGap)
944                dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "horizontalGap", oldHorizontalGap, _horizontalGap));
945            if (oldVerticalGap != _verticalGap)
946                dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "verticalGap", oldVerticalGap, _verticalGap));
947        }
948
949        oldColumnWidth   = _columnWidth;
950        oldRowHeight     = _rowHeight;
951        oldColumnCount   = _columnCount;
952        oldRowCount      = _rowCount;
953        oldHorizontalGap = _horizontalGap;
954        oldVerticalGap   = _verticalGap;
955    }
956
957    /**
958     *  This method is called from measure() and updateDisplayList() to calculate the
959     *  actual values for columnWidth, rowHeight, columnCount, rowCount, horizontalGap and verticalGap.
960     *  The width and height should include padding because the padding is accounted for in
961     *  the calculations.
962     *
963     *  @param width - the width during measure() is the layout target explicitWidth or NaN
964     *  and during updateDisplayList() is the unscaledWidth.
965     *  @param height - the height during measure() is the layout target explicitHeight or NaN
966     *  and during updateDisplayList() is the unscaledHeight.
967     *
968     *  @langversion 3.0
969     *  @playerversion Flash 10
970     *  @playerversion AIR 1.5
971     *  @productversion Flex 4
972     */
973    private function updateActualValues(width:Number, height:Number):void
974    {
975        var widthMinusPadding:Number = width - paddingLeft - paddingRight;
976        var heightMinusPadding:Number = height - paddingTop - paddingBottom;
977
978        // First, figure the tile size
979        calculateTileSize();
980
981        // Second, figure out number of rows/columns
982        var elementCount:int = calculateElementCount();
983        calculateColumnAndRowCount(widthMinusPadding, heightMinusPadding, elementCount);
984
985        // Third, adjust the gaps and column and row sizes based on justification settings
986        _horizontalGap = explicitHorizontalGap;
987        _verticalGap = explicitVerticalGap;
988
989        // Justify
990		if (!isNaN(width))
991		{
992            switch (columnAlign)
993            {
994                case ColumnAlign.JUSTIFY_USING_GAP:
995                    _horizontalGap = justifyByGapSize(widthMinusPadding, _columnWidth, _horizontalGap, _columnCount);
996                    break;
997                case ColumnAlign.JUSTIFY_USING_WIDTH:
998                    _columnWidth = justifyByElementSize(widthMinusPadding, _columnWidth, _horizontalGap, _columnCount);
999                    break;
1000	        }
1001		}
1002
1003		if (!isNaN(height))
1004		{
1005            switch (rowAlign)
1006            {
1007                case RowAlign.JUSTIFY_USING_GAP:
1008                    _verticalGap = justifyByGapSize(heightMinusPadding, _rowHeight, _verticalGap, _rowCount);
1009                    break;
1010                case RowAlign.JUSTIFY_USING_HEIGHT:
1011                    _rowHeight = justifyByElementSize(heightMinusPadding, _rowHeight, _verticalGap, _rowCount);
1012                    break;
1013	        }
1014		}
1015
1016        // Last, if we have explicit overrides for both rowCount and columnCount, then
1017        // make sure that column/row count along the minor axis reflects the actual count.
1018        if (-1 != _requestedColumnCount && -1 != _requestedRowCount)
1019        {
1020            if (orientation == TileOrientation.ROWS)
1021                _rowCount = Math.max(1, Math.ceil(elementCount / Math.max(1, _requestedColumnCount)));
1022            else
1023                _columnCount = Math.max(1, Math.ceil(elementCount / Math.max(1, _requestedRowCount)));
1024        }
1025    }
1026
1027    /**
1028     *  @private
1029     *  Returns true, if the dimensions (colCount1, rowCount1) are more square than (colCount2, rowCount2).
1030     *  Squareness is the difference between width and height of a tile layout
1031     *  with the specified number of columns and rows.
1032     */
1033    private function closerToSquare(colCount1:int, rowCount1:int, colCount2:int, rowCount2:int):Boolean
1034    {
1035        var difference1:Number = Math.abs(colCount1 * (_columnWidth + _horizontalGap) - _horizontalGap -
1036                                          rowCount1 * (_rowHeight   +   _verticalGap) + _verticalGap);
1037        var difference2:Number = Math.abs(colCount2 * (_columnWidth + _horizontalGap) - _horizontalGap -
1038                                          rowCount2 * (_rowHeight   +   _verticalGap) + _verticalGap);
1039
1040        return difference1 < difference2 || (difference1 == difference2 && rowCount1 <= rowCount2);
1041    }
1042
1043    /**
1044     *  @private
1045     *  Calculates _columnCount and _rowCount based on width, height,
1046     *  orientation, _requestedColumnCount, _requestedRowCount, _columnWidth, _rowHeight.
1047     *  _columnWidth and _rowHeight must be valid before calling.
1048     *
1049     *  The width and height should not include padding.
1050     */
1051    private function calculateColumnAndRowCount(width:Number, height:Number, elementCount:int):void
1052    {
1053        _columnCount = _rowCount = -1;
1054
1055        if (-1 != _requestedColumnCount || -1 != _requestedRowCount)
1056        {
1057            if (-1 != _requestedRowCount)
1058                _rowCount = Math.max(1, _requestedRowCount);
1059
1060            if (-1 != _requestedColumnCount)
1061                _columnCount = Math.max(1, _requestedColumnCount);
1062        }
1063        // Figure out number of columns or rows based on the explicit size along one of the axes
1064        else if (!isNaN(width) && (orientation == TileOrientation.ROWS || isNaN(height)))
1065        {
1066            if (_columnWidth + explicitHorizontalGap > 0)
1067                _columnCount = Math.max(1, Math.floor((width + explicitHorizontalGap) / (_columnWidth + explicitHorizontalGap)));
1068            else
1069                _columnCount = 1;
1070        }
1071        else if (!isNaN(height) && (orientation == TileOrientation.COLUMNS || isNaN(width)))
1072        {
1073            if (_rowHeight + explicitVerticalGap > 0)
1074                _rowCount = Math.max(1, Math.floor((height + explicitVerticalGap) / (_rowHeight + explicitVerticalGap)));
1075            else
1076                _rowCount = 1;
1077        }
1078        else // Figure out the number of columns and rows so that pixels area occupied is as square as possible
1079        {
1080            // Calculate number of rows and columns so that
1081            // pixel area is as square as possible
1082            var hGap:Number = explicitHorizontalGap;
1083            var vGap:Number = explicitVerticalGap;
1084
1085            // 1. columnCount * (columnWidth + hGap) - hGap == rowCount * (rowHeight + vGap) - vGap
1086            // 1. columnCount * (columnWidth + hGap) == rowCount * (rowHeight + vGap) + hGap - vGap
1087            // 1. columnCount == (rowCount * (rowHeight + vGap) + hGap - vGap) / (columnWidth + hGap)
1088            // 2. columnCount * rowCount == elementCount
1089            // substitute 1. in 2.
1090            // rowCount * rowCount + (hGap - vGap) * rowCount - elementCount * (columnWidth + hGap ) == 0
1091
1092            var a:Number = Math.max(0, (rowHeight + vGap));
1093            var b:Number = (hGap - vGap);
1094            var c:Number = -elementCount * (_columnWidth + hGap);
1095            var d:Number = b * b - 4 * a * c; // Always guaranteed to be greater than zero, since c <= 0
1096            d = Math.sqrt(d);
1097
1098            // We are guaranteed that we have only one positive root, since d >= b:
1099            var rowCount:Number = (a != 0) ? (b + d) / (2 * a) : elementCount;
1100
1101            // To get integer count for the columns/rows we round up and down so
1102            // we get four possible solutions. Then we pick the best one.
1103            var row1:int = Math.max(1, Math.floor(rowCount));
1104            var col1:int = Math.max(1, Math.ceil(elementCount / row1));
1105            row1 = Math.max(1, Math.ceil(elementCount / col1));
1106
1107            var row2:int = Math.max(1, Math.ceil(rowCount));
1108            var col2:int = Math.max(1, Math.ceil(elementCount / row2));
1109            row2 = Math.max(1, Math.ceil(elementCount / col2));
1110
1111            var col3:int = Math.max(1, Math.floor(elementCount / rowCount));
1112            var row3:int = Math.max(1, Math.ceil(elementCount / col3));
1113            col3 = Math.max(1, Math.ceil(elementCount / row3));
1114
1115            var col4:int = Math.max(1, Math.ceil(elementCount / rowCount));
1116            var row4:int = Math.max(1, Math.ceil(elementCount / col4));
1117            col4 = Math.max(1, Math.ceil(elementCount / row4));
1118
1119            if (closerToSquare(col3, row3, col1, row1))
1120            {
1121                col1 = col3;
1122                row1 = row3;
1123            }
1124
1125            if (closerToSquare(col4, row4, col2, row2))
1126            {
1127                col2 = col4;
1128                row2 = row4;
1129            }
1130
1131            if (closerToSquare(col1, row1, col2, row2))
1132            {
1133                _columnCount = col1;
1134                _rowCount = row1;
1135            }
1136            else
1137            {
1138                _columnCount = col2;
1139                _rowCount = row2;
1140            }
1141        }
1142
1143        // In case we determined only columns or rows (from explicit overrides or explicit width/height)
1144        // calculate the other from the number of elements
1145        if (-1 == _rowCount)
1146            _rowCount = Math.max(1, Math.ceil(elementCount / _columnCount));
1147        if (-1 == _columnCount)
1148            _columnCount = Math.max(1, Math.ceil(elementCount / _rowCount));
1149    }
1150
1151    /**
1152     *  @private
1153     *  Increases the gap so that elements are justified to exactly fit totalSize
1154     *  leaving no partially visible elements in view.
1155     *  @return Returs the new gap size.
1156     */
1157    private function justifyByGapSize(totalSize:Number, elementSize:Number,
1158                                      gap:Number, elementCount:int):Number
1159    {
1160        // If element + gap collapses to zero, then don't adjust the gap.
1161        if (elementSize + gap <= 0)
1162            return gap;
1163
1164        // Find the number of fully visible elements
1165        var visibleCount:int =
1166            Math.min(elementCount, Math.floor((totalSize + gap) / (elementSize + gap)));
1167
1168        // If there isn't even a singel fully visible element, don't adjust the gap
1169        if (visibleCount < 1)
1170            return gap;
1171
1172        // Special case: if there's a singe fully visible element and a partially
1173        // visible element, then make the gap big enough to push out the partially
1174        // visible element out of view.
1175        if (visibleCount == 1)
1176            return elementCount > 1 ? Math.max(gap, totalSize - elementSize) : gap;
1177
1178        // Now calculate the gap such that the fully visible elements and gaps
1179        // add up exactly to totalSize:
1180        // <==> totalSize == visibleCount * elementSize + (visibleCount - 1) * gap
1181        // <==> totalSize - visibleCount * elementSize == (visibleCount - 1) * gap
1182        // <==> (totalSize - visibleCount * elementSize) / (visibleCount - 1) == gap
1183        return (totalSize - visibleCount * elementSize) / (visibleCount - 1);
1184    }
1185
1186    /**
1187     *  @private
1188     *  Increases the element size so that elements are justified to exactly fit
1189     *  totalSize leaving no partially visible elements in view.
1190     *  @return Returns the the new element size.
1191     */
1192    private function justifyByElementSize(totalSize:Number, elementSize:Number,
1193                                          gap:Number, elementCount:int):Number
1194    {
1195        var elementAndGapSize:Number = elementSize + gap;
1196        var visibleCount:int = 0;
1197        // Find the number of fully visible elements
1198        if (elementAndGapSize == 0)
1199            visibleCount = elementCount;
1200        else
1201            visibleCount = Math.min(elementCount, Math.floor((totalSize + gap) / elementAndGapSize));
1202
1203        // If there isn't event a single fully visible element, don't adjust
1204        if (visibleCount < 1)
1205            return elementSize;
1206
1207        // Now calculate the elementSize such that the fully visible elements and gaps
1208        // add up exactly to totalSize:
1209        // <==> totalSize == visibleCount * elementSize + (visibleCount - 1) * gap
1210        // <==> totalSize - (visibleCount - 1) * gap == visibleCount * elementSize
1211        // <==> (totalSize - (visibleCount - 1) * gap) / visibleCount == elementSize
1212        return (totalSize - (visibleCount - 1) * gap) / visibleCount;
1213    }
1214
1215    /**
1216     *  @private
1217     *  Update _tileWidth,Height to be the maximum of their current
1218     *  value and the element's preferred bounds.
1219     */
1220    private function updateVirtualTileSize(elt:ILayoutElement):void
1221    {
1222        if (!elt || !elt.includeInLayout)
1223            return;
1224        var w:Number = elt.getPreferredBoundsWidth();
1225        var h:Number = elt.getPreferredBoundsHeight();
1226        _tileWidthCached = isNaN(_tileWidthCached) ? w : Math.max(w, _tileWidthCached);
1227        _tileHeightCached = isNaN(_tileHeightCached) ? h : Math.max(h, _tileHeightCached);
1228    }
1229
1230    /**
1231     *  @private
1232     */
1233    private function calculateVirtualTileSize():void
1234    {
1235        // If both dimensions are explicitly set, we're done
1236        _columnWidth = explicitColumnWidth;
1237        _rowHeight = explicitRowHeight;
1238        if (!isNaN(_columnWidth) && !isNaN(_rowHeight))
1239        {
1240            _tileWidthCached = _columnWidth;
1241            _tileHeightCached = _rowHeight;
1242            return;
1243        }
1244
1245        // update _tileWidth,HeightCached based on the typicalElement
1246        updateVirtualTileSize(typicalLayoutElement);
1247
1248        // update _tileWidth,HeightCached based on visible elements
1249        if ((visibleStartIndex != -1) && (visibleEndIndex != -1))
1250        {
1251            for (var index:int = visibleStartIndex; index <= visibleEndIndex; index++)
1252                updateVirtualTileSize(target.getVirtualElementAt(index));
1253        }
1254
1255        // Make sure that we always have non-NaN values in the cache, even
1256        // when there are no elements.
1257        if (isNaN(_tileWidthCached))
1258            _tileWidthCached = 0;
1259        if (isNaN(_tileHeightCached))
1260            _tileHeightCached = 0;
1261
1262        if (isNaN(_columnWidth))
1263            _columnWidth = _tileWidthCached;
1264        if (isNaN(_rowHeight))
1265            _rowHeight = _tileHeightCached;
1266    }
1267
1268    /**
1269     *  @private
1270     *  Calculates _columnWidth and _rowHeight from maximum of
1271     *  elements preferred size and any explicit overrides.
1272     */
1273    private function calculateRealTileSize():void
1274    {
1275        _columnWidth = _tileWidthCached;
1276        _rowHeight = _tileHeightCached;
1277        if (!isNaN(_columnWidth) && !isNaN(_rowHeight))
1278            return;
1279
1280        // Are both dimensions explicitly set?
1281        _columnWidth = _tileWidthCached = explicitColumnWidth;
1282        _rowHeight = _tileHeightCached = explicitRowHeight;
1283        if (!isNaN(_columnWidth) && !isNaN(_rowHeight))
1284            return;
1285
1286        // Find the maxmimums of element's preferred sizes
1287        var columnWidth:Number = 0;
1288        var rowHeight:Number = 0;
1289
1290        var layoutTarget:GroupBase = target;
1291        var count:int = layoutTarget.numElements;
1292        // Remember the number of includeInLayout elements
1293        _numElementsCached = count;
1294        for (var i:int = 0; i < count; i++)
1295        {
1296            var el:ILayoutElement = layoutTarget.getElementAt(i);
1297            if (!el || !el.includeInLayout)
1298            {
1299                _numElementsCached--;
1300                continue;
1301            }
1302
1303            if (isNaN(_columnWidth))
1304                columnWidth = Math.max(columnWidth, el.getPreferredBoundsWidth());
1305            if (isNaN(_rowHeight))
1306                rowHeight = Math.max(rowHeight, el.getPreferredBoundsHeight());
1307        }
1308
1309        if (isNaN(_columnWidth))
1310            _columnWidth = _tileWidthCached = columnWidth;
1311        if (isNaN(_rowHeight))
1312            _rowHeight = _tileHeightCached = rowHeight;
1313    }
1314
1315    private function calculateTileSize():void
1316    {
1317        if (useVirtualLayout)
1318            calculateVirtualTileSize();
1319        else
1320            calculateRealTileSize();
1321    }
1322
1323    /**
1324     *  @private
1325     *  For normal layout return the number of non-null includeInLayout=true
1326     *  layout elements, for virtual layout just return the number of layout
1327     *  elements.
1328     */
1329    private function calculateElementCount():int
1330    {
1331        if (-1 != _numElementsCached)
1332            return _numElementsCached;
1333
1334        var layoutTarget:GroupBase = target;
1335        var count:int = layoutTarget.numElements;
1336        _numElementsCached = count;
1337
1338        if (useVirtualLayout)
1339            return _numElementsCached;
1340
1341        for (var i:int = 0; i < count; i++)
1342        {
1343            var el:ILayoutElement = layoutTarget.getElementAt(i);
1344            if (!el || !el.includeInLayout)
1345                _numElementsCached--;
1346        }
1347
1348        return _numElementsCached;
1349    }
1350
1351    /**
1352     *  @private
1353     *  This method computes values for visibleStartX,Y, visibleStartIndex, and
1354     *  visibleEndIndex based on the TileLayout geometry values, like _columnWidth
1355     *  and _rowHeight, computed by calculateActualValues().
1356     *
1357     *  If useVirtualLayout=false, then visibleStartX,Y=0 and visibleStartIndex=0
1358     *  and visibleEndIndex=layoutTarget.numElements-1.
1359     *
1360     *  If useVirtualLayout=true and orientation=ROWS then visibleStartIndex is the
1361     *  layout element index of the item at first visible row relative to the scrollRect,
1362     *  column 0.  Note that we're using column=0 instead of the first visible column
1363     *  to simplify the iteration logic in updateDisplayList().  This is optimal
1364     *  for the common case where the entire row is visible.   Optimally handling
1365     *  the case where orientation=ROWS and each row is only partially visible is
1366     *  doable but adds some complexity to the main loop.
1367     *
1368     *  The logic for useVirtualLayout=true and orientation=COLS is similar.
1369     */
1370    private function calculateDisplayParameters(unscaledWidth:int, unscaledHeight:int):void
1371    {
1372        updateActualValues(unscaledWidth, unscaledHeight);
1373
1374        var layoutTarget:GroupBase = target;
1375        var eltCount:int = layoutTarget.numElements;
1376        visibleStartX = paddingLeft;   // initial values for xPos,yPos in updateDisplayList
1377        visibleStartY = paddingTop;
1378        visibleStartIndex = 0;
1379        visibleEndIndex = eltCount - 1;
1380
1381        if (useVirtualLayout)
1382        {
1383            var hsp:Number = layoutTarget.horizontalScrollPosition - paddingLeft;
1384            var vsp:Number = layoutTarget.verticalScrollPosition - paddingTop;
1385            var cwg:Number = _columnWidth + _horizontalGap;
1386            var rwg:Number = _rowHeight + _verticalGap;
1387
1388            var visibleCol0:int = Math.max(0, Math.floor(hsp / cwg));
1389            var visibleRow0:int = Math.max(0, Math.floor(vsp / rwg));
1390            var visibleCol1:int = Math.min(_columnCount - 1, Math.floor((hsp + unscaledWidth) / cwg));
1391            var visibleRow1:int = Math.min(_rowCount - 1, Math.floor((vsp + unscaledHeight) / rwg));
1392
1393            if (orientation == TileOrientation.ROWS)
1394            {
1395                visibleStartIndex = (visibleRow0 * _columnCount);
1396                visibleEndIndex = Math.min(eltCount - 1, (visibleRow1 * _columnCount) + visibleCol1);
1397                visibleStartY = visibleRow0 * rwg + paddingTop;
1398            }
1399            else
1400            {
1401                visibleStartIndex = (visibleCol0 * _rowCount);
1402                visibleEndIndex = Math.min(eltCount - 1, (visibleCol1 * _rowCount) + visibleRow1);
1403                visibleStartX = visibleCol0 * cwg + paddingLeft;
1404            }
1405        }
1406    }
1407
1408    /**
1409     *  @private
1410     *  This method is called by updateDisplayList() after initial values for
1411     *  visibleStartIndex, visibleEndIndex have been calculated.  We
1412     *  re-calculateDisplayParameters() to account for the possibility that
1413     *  larger cells may have been exposed.  Since tileWidth,Height can only
1414     *  increase, the new visibleStart,EndIndex values will be greater than or
1415     *  equal to the old ones.
1416     */
1417     private function updateVirtualLayout(unscaledWidth:int, unscaledHeight:int):void
1418     {
1419        var oldVisibleStartIndex:int = visibleStartIndex;
1420        var oldVisibleEndIndex:int = visibleEndIndex;
1421        calculateDisplayParameters(unscaledWidth, unscaledHeight);  // compute new visibleStart,EndIndex values
1422
1423        // We're responsible for laying out *all* of the elements requested
1424        // with getVirtualElementAt(), even if they don't fall within the final
1425        // visible range.  Hide any extra ones.  On the next layout pass, they'll
1426        // be added to DataGroup::freeRenderers
1427
1428        var layoutTarget:GroupBase = target;
1429        for (var i:int = oldVisibleStartIndex; i <= oldVisibleEndIndex; i++)
1430        {
1431            if ((i >= visibleStartIndex) && (i <= visibleEndIndex)) // skip past the visible range
1432            {
1433                i = visibleEndIndex;
1434                continue;
1435            }
1436            var el:ILayoutElement = layoutTarget.getElementAt(i);
1437            if (!el)
1438                continue;
1439            if (el is IVisualElement)
1440                IVisualElement(el).visible = false;
1441        }
1442     }
1443
1444    /**
1445     *  Sets the size and the position of the specified layout element and cell bounds.
1446     *  @param element - the element to resize and position.
1447     *  @param cellX - the x coordinate of the cell.
1448     *  @param cellY - the y coordinate of the cell.
1449     *  @param cellWidth - the width of the cell.
1450     *  @param cellHeight - the height of the cell.
1451     *
1452     *  @langversion 3.0
1453     *  @playerversion Flash 10
1454     *  @playerversion AIR 1.5
1455     *  @productversion Flex 4
1456     */
1457    private function sizeAndPositionElement(element:ILayoutElement,
1458                                              cellX:int,
1459                                              cellY:int,
1460                                              cellWidth:int,
1461                                              cellHeight:int):void
1462    {
1463        var childWidth:Number = NaN;
1464        var childHeight:Number = NaN;
1465
1466        // Determine size of the element
1467        if (horizontalAlign == "justify")
1468            childWidth = cellWidth;
1469        else if (!isNaN(element.percentWidth))
1470            childWidth = Math.round(cellWidth * element.percentWidth * 0.01);
1471        else
1472            childWidth = element.getPreferredBoundsWidth();
1473
1474        if (verticalAlign == "justify")
1475            childHeight = cellHeight;
1476        else if (!isNaN(element.percentHeight))
1477            childHeight = Math.round(cellHeight * element.percentHeight * 0.01);
1478        else
1479            childHeight = element.getPreferredBoundsHeight();
1480
1481        // Enforce min and max limits
1482        var maxChildWidth:Number = Math.min(element.getMaxBoundsWidth(), cellWidth);
1483        var maxChildHeight:Number = Math.min(element.getMaxBoundsHeight(), cellHeight);
1484        // Make sure we enforce element's minimum last, since it has the highest priority
1485        childWidth = Math.max(element.getMinBoundsWidth(), Math.min(maxChildWidth, childWidth));
1486        childHeight = Math.max(element.getMinBoundsHeight(), Math.min(maxChildHeight, childHeight));
1487
1488        // Size the element
1489        element.setLayoutBoundsSize(childWidth, childHeight);
1490
1491        var x:Number = cellX;
1492        switch (horizontalAlign)
1493        {
1494            case "right":
1495                x += cellWidth - element.getLayoutBoundsWidth();
1496            break;
1497            case "center":
1498                // Make sure division result is integer - Math.floor() the result.
1499                x = cellX + Math.floor((cellWidth - element.getLayoutBoundsWidth()) / 2);
1500            break;
1501        }
1502
1503        var y:Number = cellY;
1504        switch (verticalAlign)
1505        {
1506            case "bottom":
1507                y += cellHeight - element.getLayoutBoundsHeight();
1508            break;
1509            case "middle":
1510                // Make sure division result is integer - Math.floor() the result.
1511                y += Math.floor((cellHeight - element.getLayoutBoundsHeight()) / 2);
1512            break;
1513        }
1514
1515        // Position the element
1516        element.setLayoutBoundsPosition(x, y);
1517    }
1518
1519    /**
1520     *  @private
1521     *  @return Returns the x coordinate of the left edge for the specified column.
1522     */
1523    final private function leftEdge(columnIndex:int):Number
1524    {
1525        if (columnIndex < 0)
1526            return 0;
1527
1528        return Math.max(0, columnIndex * (_columnWidth + _horizontalGap)) + paddingLeft;
1529    }
1530
1531    /**
1532     *  @private
1533     *  @return Returns the x coordinate of the right edge for the specified column.
1534     */
1535    final private function rightEdge(columnIndex:int):Number
1536    {
1537        if (columnIndex < 0)
1538            return 0;
1539
1540        return Math.min(target.contentWidth, columnIndex * (_columnWidth + _horizontalGap) + _columnWidth) + paddingLeft;
1541    }
1542
1543    /**
1544     *  @private
1545     *  @return Returns the y coordinate of the top edge for the specified row.
1546     */
1547    final private function topEdge(rowIndex:int):Number
1548    {
1549        if (rowIndex < 0)
1550            return 0;
1551
1552        return Math.max(0, rowIndex * (_rowHeight + _verticalGap)) + paddingTop;
1553    }
1554
1555    /**
1556     *  @private
1557     *  @return Returns the y coordinate of the bottom edge for the specified row.
1558     */
1559    final private function bottomEdge(rowIndex:int):Number
1560    {
1561        if (rowIndex < 0)
1562            return 0;
1563
1564        return Math.min(target.contentHeight, rowIndex * (_rowHeight + _verticalGap) + _rowHeight) + paddingTop;
1565    }
1566
1567    /**
1568     *  @private
1569     *  Convenience function for subclasses that invalidates the
1570     *  target's size and displayList so that both layout's <code>measure()</code>
1571     *  and <code>updateDisplayList</code> methods get called.
1572     *
1573     *  <p>Typically a layout invalidates the target's size and display list so that
1574     *  it gets a chance to recalculate the target's default size and also size and
1575     *  position the target's elements. For example changing the <code>gap</code>
1576     *  property on a <code>VerticalLayout</code> will internally call this method
1577     *  to ensure that the elements are re-arranged with the new setting and the
1578     *  target's default size is recomputed.</p>
1579     */
1580    private function invalidateTargetSizeAndDisplayList():void
1581    {
1582        var g:GroupBase = target;
1583        if (!g)
1584            return;
1585
1586        g.invalidateSize();
1587        g.invalidateDisplayList();
1588    }
1589
1590    //--------------------------------------------------------------------------
1591    //
1592    //  Overridden methods from LayoutBase
1593    //
1594    //--------------------------------------------------------------------------
1595
1596    /**
1597     *  @private
1598     */
1599    override protected function scrollPositionChanged():void
1600    {
1601        super.scrollPositionChanged();
1602
1603        var layoutTarget:GroupBase = target;
1604        if (!layoutTarget)
1605            return;
1606
1607        if (useVirtualLayout)
1608            layoutTarget.invalidateDisplayList();
1609    }
1610
1611    /**
1612     *  @private
1613     */
1614    override public function measure():void
1615    {
1616        // Save and restore these values so they're not modified
1617        // as a sideeffect of measure().
1618        var savedColumnCount:int = _columnCount;
1619        var savedRowCount:int = _rowCount;
1620        var savedHorizontalGap:int = _horizontalGap;
1621        var savedVerticalGap:int = _verticalGap;
1622        var savedColumnWidth:int = _columnWidth;
1623        var savedRowHeight:int = _rowHeight;
1624
1625        var layoutTarget:GroupBase = target;
1626        if (!layoutTarget)
1627            return;
1628
1629        updateActualValues(layoutTarget.explicitWidth, layoutTarget.explicitHeight);
1630
1631        // For measure, any explicit overrides for rowCount and columnCount take precedence
1632        var columnCount:int = _requestedColumnCount != -1 ? Math.max(1, _requestedColumnCount) : _columnCount;
1633        var rowCount:int = _requestedRowCount != -1 ? Math.max(1, _requestedRowCount) : _rowCount;
1634
1635        var measuredWidth:Number = 0;
1636        var measuredMinWidth:Number = 0;
1637        var measuredHeight:Number = 0;
1638        var measuredMinHeight:Number = 0;
1639
1640        if (columnCount > 0)
1641        {
1642            measuredWidth = Math.ceil(columnCount * (_columnWidth + _horizontalGap) - _horizontalGap)
1643            // measured min size is guaranteed to have enough columns to fit all elements
1644            measuredMinWidth = Math.ceil(_columnCount * (_columnWidth + _horizontalGap) - _horizontalGap);
1645        }
1646
1647        if (rowCount > 0)
1648        {
1649            measuredHeight = Math.ceil(rowCount * (_rowHeight + _verticalGap) - _verticalGap);
1650            // measured min size is guaranteed to have enough rows to fit all elements
1651            measuredMinHeight = Math.ceil(_rowCount * (_rowHeight + _verticalGap) - _verticalGap);
1652        }
1653		_numElementsCached = -1;
1654
1655        var hPadding:Number = paddingLeft + paddingRight;
1656        var vPadding:Number = paddingTop + paddingBottom;
1657
1658        layoutTarget.measuredWidth = measuredWidth + hPadding;
1659        layoutTarget.measuredMinWidth = measuredMinWidth + hPadding;
1660        layoutTarget.measuredHeight = measuredHeight + vPadding;
1661        layoutTarget.measuredMinHeight = measuredMinHeight + vPadding;
1662
1663        _columnCount = savedColumnCount;
1664        _rowCount = savedRowCount;
1665        _horizontalGap = savedHorizontalGap;
1666        _verticalGap = savedVerticalGap;
1667        _columnWidth = savedColumnWidth;
1668        _rowHeight = savedRowHeight;
1669    }
1670
1671    /**
1672     *  @private
1673     */
1674    override public function getNavigationDestinationIndex(currentIndex:int, navigationUnit:uint, arrowKeysWrapFocus:Boolean):int
1675    {
1676        if (!target || target.numElements < 1)
1677            return -1;
1678
1679        var maxIndex:int = target.numElements - 1;
1680
1681        // Special case when nothing was previously selected
1682        if (currentIndex == -1)
1683        {
1684            if (navigationUnit == NavigationUnit.UP || navigationUnit == NavigationUnit.LEFT)
1685                return arrowKeysWrapFocus ? maxIndex : -1;
1686
1687            if (navigationUnit == NavigationUnit.DOWN || navigationUnit == NavigationUnit.RIGHT)
1688                return 0;
1689        }
1690
1691        // Make sure currentIndex is within range
1692        var inRows:Boolean = orientation == TileOrientation.ROWS;
1693        currentIndex = Math.max(0, Math.min(maxIndex, currentIndex));
1694
1695        // Find the current column and row
1696        var currentRow:int;
1697        var currentColumn:int;
1698        if (inRows)
1699        {
1700            // Is the TileLayout initialized with valid values?
1701            if (columnCount == 0 || rowHeight + verticalGap == 0)
1702                return currentIndex;
1703
1704            currentRow = currentIndex / columnCount;
1705            currentColumn = currentIndex - currentRow * columnCount;
1706        }
1707        else
1708        {
1709            // Is the TileLayout initialized with valid values?
1710            if (rowCount == 0 || columnWidth + horizontalGap == 0)
1711                return currentIndex;
1712
1713            currentColumn = currentIndex / rowCount;
1714            currentRow = currentIndex - currentColumn * rowCount;
1715        }
1716
1717        var newRow:int = currentRow;
1718        var newColumn:int = currentColumn;
1719
1720        // Handle user input, almost all range checks are
1721        // performed after the calculations, at the end of the method.
1722        switch (navigationUnit)
1723        {
1724            case NavigationUnit.LEFT:
1725            {
1726                // If we are at the first column, can
1727                // we go to the previous element (last column, previous row)?
1728                if (newColumn == 0 && inRows && newRow > 0)
1729                {
1730                    newRow--;
1731                    newColumn = columnCount - 1;
1732                }
1733                else if (arrowKeysWrapFocus && newColumn == 0 && inRows && newRow == 0)
1734                {
1735                    newRow = rowCount - 1;
1736                    newColumn = columnCount - 1;
1737                }
1738                else
1739                    newColumn--;
1740                break;
1741            }
1742
1743            case NavigationUnit.RIGHT:
1744            {
1745                // If we are at the last column, can
1746                // we go to the next element (first column, next row)?
1747                if (newColumn == columnCount - 1 && inRows && newRow < rowCount - 1)
1748                {
1749                    newColumn = 0;
1750                    newRow++;
1751                }
1752                else if (arrowKeysWrapFocus && newColumn == columnCount - 1 && inRows && newRow == rowCount - 1)
1753                {
1754                    newColumn = 0;
1755                    newRow = 0;
1756                }
1757                else
1758                    newColumn++;
1759                break;
1760            }
1761
1762            case NavigationUnit.UP:
1763            {
1764                // If we are at the first row, can we
1765                // go to the previous element (previous column, last row)?
1766                if (newRow == 0 && !inRows && newColumn > 0)
1767                {
1768                    newColumn--;
1769                    newRow = rowCount - 1;
1770                }
1771                else if (arrowKeysWrapFocus && newRow == 0 && !inRows && newColumn == 0)
1772                {
1773                    newColumn = columnCount - 1;
1774                    newRow = rowCount - 1;
1775                }
1776                else
1777                    newRow--;
1778                break;
1779            }
1780
1781            case NavigationUnit.DOWN:
1782            {
1783                // If we are at the last row, can we
1784                // go to the next element (next column, first row)?
1785                if (newRow == rowCount - 1 && !inRows && newColumn < columnCount - 1)
1786                {
1787                    newColumn++;
1788                    newRow = 0;
1789                }
1790                else if (arrowKeysWrapFocus && newRow == rowCount - 1 && !inRows && newColumn == columnCount - 1)
1791                {
1792                    newColumn = 0;
1793                    newRow = 0;
1794                }
1795                else
1796                    newRow++;
1797                break;
1798            }
1799
1800            case NavigationUnit.PAGE_UP:
1801            case NavigationUnit.PAGE_DOWN:
1802            {
1803                // Ensure we have a valid scrollRect as we use it for calculations below
1804                var scrollRect:Rectangle = getScrollRect();
1805                if (!scrollRect)
1806                    scrollRect = new Rectangle(0, 0, target.contentWidth, target.contentHeight);
1807
1808                if (inRows)
1809                {
1810                    var firstVisibleRow:int = Math.ceil(scrollRect.top / (rowHeight + verticalGap));
1811                    var lastVisibleRow:int = Math.floor(scrollRect.bottom / (rowHeight + verticalGap));
1812
1813                    if (navigationUnit == NavigationUnit.PAGE_UP)
1814                    {
1815                        // Is the current row visible, somewhere in the middle of the scrollRect?
1816                        if (firstVisibleRow < currentRow && currentRow <= lastVisibleRow)
1817                            newRow = firstVisibleRow;
1818                        else
1819                            newRow = 2 * firstVisibleRow - lastVisibleRow;
1820                    }
1821                    else
1822                    {
1823                        // Is the current row visible, somewhere in the middle of the scrollRect?
1824                        if (firstVisibleRow <= currentRow && currentRow < lastVisibleRow)
1825                            newRow = lastVisibleRow;
1826                        else
1827                            newRow = 2 * lastVisibleRow - firstVisibleRow;
1828                    }
1829                }
1830                else
1831                {
1832                    var firstVisibleColumn:int = Math.ceil(scrollRect.left / (columnWidth + horizontalGap));
1833                    var lastVisibleColumn:int = Math.floor(scrollRect.right / (columnWidth + horizontalGap));
1834
1835                    if (navigationUnit == NavigationUnit.PAGE_UP)
1836                    {
1837                        // Is the current column visible, somewhere in the middle of the scrollRect?
1838                        if (firstVisibleColumn < currentColumn && currentColumn <= lastVisibleColumn)
1839                            newColumn = firstVisibleColumn;
1840                        else
1841                            newColumn = 2 * firstVisibleColumn - lastVisibleColumn;
1842                    }
1843                    else
1844                    {
1845                        // Is the current column visible, somewhere in the middle of the scrollRect?
1846                        if (firstVisibleColumn <= currentColumn && currentColumn < lastVisibleColumn)
1847                            newColumn = lastVisibleColumn;
1848                        else
1849                            newColumn = 2 * lastVisibleColumn - firstVisibleColumn;
1850                    }
1851                }
1852                break;
1853            }
1854            default: return super.getNavigationDestinationIndex(currentIndex, navigationUnit, arrowKeysWrapFocus);
1855        }
1856
1857        // Make sure rows and columns are within range
1858        newRow = Math.max(0, Math.min(rowCount - 1, newRow));
1859        newColumn = Math.max(0, Math.min(columnCount - 1, newColumn));
1860
1861        // Calculate the new index based on orientation
1862        if (inRows)
1863        {
1864            // Make sure we don't return an index for an empty space in the last row.
1865            // newRow is guaranteed to be greater than zero:
1866
1867            if (newRow == rowCount - 1)
1868            {
1869                // Step 1: We can end up at the empty space in the last row if we moved right from
1870                // the last item.
1871                if (currentIndex == maxIndex && newColumn > currentColumn)
1872                    newColumn = currentColumn;
1873
1874                // Step 2: We can end up at the empty space in the last row if we moved down from
1875                // the previous row.
1876                if (newColumn > maxIndex - columnCount * (rowCount - 1))
1877                    newRow--;
1878            }
1879
1880            return newRow * columnCount + newColumn;
1881        }
1882        else
1883        {
1884            // Make sure we don't return an index for an empty space in the last column.
1885            // newColumn is guaranteed to be greater than zero:
1886
1887            if (newColumn == columnCount - 1)
1888            {
1889                // Step 1: We can end up at the empty space in the last column if we moved down from
1890                // the last item.
1891                if (currentIndex == maxIndex && newRow > currentRow)
1892                    newRow = currentRow;
1893
1894                // Step 2: We can end up at the empty space in the last column if we moved right from
1895                // the previous column.
1896                if (newRow > maxIndex - rowCount * (columnCount - 1))
1897                    newColumn--;
1898            }
1899
1900            return newColumn * rowCount + newRow;
1901        }
1902    }
1903
1904    /**
1905     *  @private
1906     */
1907    override public function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
1908    {
1909        var layoutTarget:GroupBase = target;
1910        if (!layoutTarget)
1911            return;
1912
1913        calculateDisplayParameters(unscaledWidth, unscaledHeight);
1914        if (useVirtualLayout)
1915            updateVirtualLayout(unscaledWidth, unscaledHeight);  // re-calculateDisplayParameters()
1916
1917        // Upper right hand corner of first (visibleStartIndex) tile/cell
1918        var xPos:Number = visibleStartX;  // paddingLeft if useVirtualLayout=false
1919        var yPos:Number = visibleStartY;  // paddingTop if useVirtualLayout=false
1920
1921        // Use MajorDelta when moving along the major axis
1922        var xMajorDelta:Number;
1923        var yMajorDelta:Number;
1924
1925        // Use MinorDelta when moving along the minor axis
1926        var xMinorDelta:Number;
1927        var yMinorDelta:Number;
1928
1929        // Use counter and counterLimit to track when to move along the minor axis
1930        var counter:int = 0;
1931        var counterLimit:int;
1932
1933        // Setup counterLimit and deltas based on orientation
1934        if (orientation == TileOrientation.ROWS)
1935        {
1936            counterLimit = _columnCount;
1937            xMajorDelta = _columnWidth + _horizontalGap;
1938            xMinorDelta = 0;
1939            yMajorDelta = 0;
1940            yMinorDelta = _rowHeight + _verticalGap;
1941        }
1942        else
1943        {
1944            counterLimit = _rowCount;
1945            xMajorDelta = 0;
1946            xMinorDelta = _columnWidth + _horizontalGap;
1947            yMajorDelta = _rowHeight + _verticalGap;
1948            yMinorDelta = 0;
1949        }
1950
1951        for (var index:int = visibleStartIndex; index <= visibleEndIndex; index++)
1952        {
1953            var el:ILayoutElement = null;
1954            if (useVirtualLayout)
1955            {
1956                el = layoutTarget.getVirtualElementAt(index);
1957                if (el is IVisualElement)  // see updateVirtualLayout
1958                    IVisualElement(el).visible = true;
1959            }
1960            else
1961                el = layoutTarget.getElementAt(index);
1962
1963            if (!el || !el.includeInLayout)
1964                continue;
1965
1966            // To calculate the cell extents as integers, first calculate
1967            // the extents and then use Math.round()
1968            var cellX:int = Math.round(xPos);
1969            var cellY:int = Math.round(yPos);
1970            var cellWidth:int = Math.round(xPos + _columnWidth) - cellX;
1971            var cellHeight:int = Math.round(yPos + _rowHeight) - cellY;
1972
1973            sizeAndPositionElement(el, cellX, cellY, cellWidth, cellHeight);
1974
1975            // Move along the major axis
1976            xPos += xMajorDelta;
1977            yPos += yMajorDelta;
1978
1979            // Move along the minor axis
1980            if (++counter >= counterLimit)
1981            {
1982                counter = 0;
1983                if (orientation == TileOrientation.ROWS)
1984                {
1985                    xPos = paddingLeft;
1986                    yPos += yMinorDelta;
1987                }
1988                else
1989                {
1990                    xPos += xMinorDelta;
1991                    yPos = paddingTop;
1992                }
1993            }
1994        }
1995
1996        var hPadding:Number = paddingLeft + paddingRight;
1997        var vPadding:Number = paddingTop + paddingBottom;
1998
1999        // Make sure that if the content spans partially over a pixel to the right/bottom,
2000        // the content size includes the whole pixel.
2001        layoutTarget.setContentSize(Math.ceil(_columnCount * (_columnWidth + _horizontalGap) - _horizontalGap) + hPadding,
2002                                    Math.ceil(_rowCount * (_rowHeight + _verticalGap) - _verticalGap) + vPadding);
2003
2004        // Reset the cache
2005        if (!useVirtualLayout)
2006            _tileWidthCached = _tileHeightCached = NaN;
2007        _numElementsCached = -1;
2008
2009        // No getVirtualElementAt() during measure, see calculateVirtualTileSize()
2010        if (useVirtualLayout)
2011            visibleStartIndex = visibleEndIndex = -1;
2012
2013        // If actual values have chnaged, notify listeners
2014        dispatchEventsForActualValueChanges();
2015    }
2016
2017    /**
2018     *  @private
2019     */
2020    override public function getElementBounds(index:int):Rectangle
2021    {
2022        if (!useVirtualLayout)
2023            return super.getElementBounds(index);
2024
2025        var g:GroupBase = GroupBase(target);
2026        if (!g || (index < 0) || (index >= g.numElements))
2027            return null;
2028
2029        var col:int;
2030        var row:int;
2031        if (orientation == TileOrientation.ROWS)
2032        {
2033            col = index % _columnCount;
2034            row = index / _columnCount;
2035        }
2036        else
2037        {
2038            col = index / _rowCount;
2039            row = index % _rowCount
2040        }
2041        return new Rectangle(leftEdge(col), topEdge(row), _columnWidth, _rowHeight);
2042    }
2043
2044    /**
2045     *  @private
2046     */
2047    override protected function getElementBoundsLeftOfScrollRect(scrollRect:Rectangle):Rectangle
2048    {
2049        var bounds:Rectangle = new Rectangle();
2050        // Find the column that spans or is to the left of the scrollRect left edge.
2051        var column:int = Math.floor((scrollRect.left - 1 - paddingLeft) / (_columnWidth + _horizontalGap));
2052        bounds.left = leftEdge(column);
2053        bounds.right = rightEdge(column);
2054        return bounds;
2055    }
2056
2057    /**
2058     *  @private
2059     */
2060    override protected function getElementBoundsRightOfScrollRect(scrollRect:Rectangle):Rectangle
2061    {
2062        var bounds:Rectangle = new Rectangle();
2063        // Find the column that spans or is to the right of the scrollRect right edge.
2064        var column:int = Math.floor(((scrollRect.right + 1 + _horizontalGap) - paddingLeft) / (_columnWidth + _horizontalGap));
2065        bounds.left = leftEdge(column);
2066        bounds.right = rightEdge(column);
2067        return bounds;
2068    }
2069
2070    /**
2071     *  @private
2072     */
2073    override protected function getElementBoundsAboveScrollRect(scrollRect:Rectangle):Rectangle
2074    {
2075        var bounds:Rectangle = new Rectangle();
2076        // Find the row that spans or is above the scrollRect top edge
2077        var row:int = Math.floor((scrollRect.top - 1 - paddingTop) / (_rowHeight + _verticalGap));
2078        bounds.top = topEdge(row);
2079        bounds.bottom = bottomEdge(row);
2080        return bounds;
2081    }
2082
2083    /**
2084     *  @private
2085     */
2086    override protected function getElementBoundsBelowScrollRect(scrollRect:Rectangle):Rectangle
2087    {
2088        var bounds:Rectangle = new Rectangle();
2089        // Find the row that spans or is below the scrollRect bottom edge
2090        var row:int = Math.floor(((scrollRect.bottom + 1 + _verticalGap) - paddingTop) / (_rowHeight + _verticalGap));
2091        bounds.top = topEdge(row);
2092        bounds.bottom = bottomEdge(row);
2093        return bounds;
2094    }
2095
2096    /**
2097     *  @private
2098     */
2099    override public function elementAdded(index:int):void
2100    {
2101        invalidateTargetSizeAndDisplayList();
2102    }
2103
2104    /**
2105     *  @private
2106     */
2107    override public function elementRemoved(index:int):void
2108    {
2109        invalidateTargetSizeAndDisplayList();
2110    }
2111
2112    //--------------------------------------------------------------------------
2113    //
2114    //  Drop methods
2115    //
2116    //--------------------------------------------------------------------------
2117
2118    /**
2119     *  @private
2120     *  Calculates the column and row and returns the corresponding cell index.
2121     *  Index may be out of range if there's no element for the cell.
2122     */
2123    private function calculateDropCellIndex(x:Number, y:Number):Array
2124    {
2125        var xStart:Number = x - paddingLeft;
2126        var yStart:Number = y - paddingTop;
2127        var column:int = Math.floor(xStart / (_columnWidth + _horizontalGap));
2128        var row:int = Math.floor(yStart / (_rowHeight + _verticalGap));
2129
2130        // Check whether x is closer to left column or right column:
2131        var midColumnLine:Number;
2132        var midRowLine:Number
2133
2134        var rowOrientation:Boolean = orientation == TileOrientation.ROWS;
2135        if (rowOrientation)
2136        {
2137            // Mid-line is at the middle of the cell
2138            midColumnLine = (column + 1) * (_columnWidth + _horizontalGap) - _horizontalGap - _columnWidth / 2;
2139
2140            // Mid-line is at the middle of the gap between the rows
2141            midRowLine = (row + 1) * (_rowHeight + _verticalGap) - _verticalGap / 2;
2142        }
2143        else
2144        {
2145            // Mid-line is at the middle of the gap between the columns
2146            midColumnLine = (column + 1) * (_columnWidth + _horizontalGap) - _horizontalGap / 2;
2147
2148            // Mid-line is at the middle of the cell
2149            midRowLine = (row + 1) * (_rowHeight + _verticalGap) - _verticalGap - _rowHeight / 2;
2150        }
2151
2152        if (xStart > midColumnLine)
2153            column++;
2154        if (yStart > midRowLine)
2155            row++;
2156
2157        // Limit row and column, if any one is too far from the drop location
2158        // And there is white space
2159        if (column > _columnCount || row > _rowCount)
2160        {
2161            row = _rowCount;
2162            column = _columnCount;
2163        }
2164
2165        if (column < 0)
2166            column = 0;
2167        if (row < 0)
2168            row = 0;
2169
2170        if (rowOrientation)
2171        {
2172            if (row >= _rowCount)
2173                row = _rowCount - 1;
2174        }
2175        else
2176        {
2177            if (column >= _columnCount)
2178                column = _columnCount - 1;
2179        }
2180        return [row, column];
2181    }
2182
2183    /**
2184     *  @inheritDoc
2185     *
2186     *  @langversion 3.0
2187     *  @playerversion Flash 10
2188     *  @playerversion AIR 1.5
2189     *  @productversion Flex 4
2190     */
2191    override protected function calculateDropIndex(x:Number, y:Number):int
2192    {
2193        var result:Array = calculateDropCellIndex(x, y);
2194        var row:int = result[0];
2195        var column:int = result[1];
2196        var index:int;
2197
2198        if (orientation == TileOrientation.ROWS)
2199            index = row * _columnCount + column;
2200        else
2201            index = column * _rowCount + row;
2202
2203        var count:int = calculateElementCount();
2204        _numElementsCached = -1; // Reset the cache
2205
2206        if (index > count)
2207            index = count;
2208        return index;
2209    }
2210
2211    /**
2212     *  @inheritDoc
2213     *
2214     *  @langversion 3.0
2215     *  @playerversion Flash 10
2216     *  @playerversion AIR 1.5
2217     *  @productversion Flex 4
2218     */
2219    override protected function calculateDropIndicatorBounds(dropLocation:DropLocation):Rectangle
2220    {
2221        var result:Array = calculateDropCellIndex(dropLocation.dropPoint.x, dropLocation.dropPoint.y);
2222        var row:int = result[0];
2223        var column:int = result[1];
2224
2225        var rowOrientation:Boolean = orientation == TileOrientation.ROWS;
2226        var count:int = calculateElementCount();
2227        _numElementsCached = -1; // Reset the cache
2228
2229        if (rowOrientation)
2230        {
2231            // The last row may be only partially full,
2232            // don't drop beyond the last column.
2233            if (row * _columnCount + column > count)
2234                column = count - (_rowCount - 1) * _columnCount;
2235        }
2236        else
2237        {
2238            // The last column may be only partially full,
2239            // don't drop beyond the last row.
2240            if (column * _rowCount + row > count)
2241                row = count - (_columnCount - 1) * _rowCount;
2242        }
2243
2244        var x:Number;
2245        var y:Number;
2246        var dropIndicatorElement:IVisualElement;
2247        var emptySpace:Number; // empty space between the elements
2248
2249        // Start with the dropIndicator dimensions, in case it's not
2250        // an IVisualElement
2251        var width:Number = dropIndicator.width;
2252        var height:Number = dropIndicator.height;
2253
2254        if (rowOrientation)
2255        {
2256            emptySpace = (0 < _horizontalGap ) ? _horizontalGap : 0;
2257            var emptySpaceLeft:Number = column * (_columnWidth + _horizontalGap) - emptySpace;
2258
2259            // Special case - if we have negative gap and we're the last column,
2260            // adjust the emptySpaceLeft
2261            if (_horizontalGap < 0 && (column == _columnCount || count == dropLocation.dropIndex))
2262                emptySpaceLeft -= _horizontalGap;
2263
2264            width = emptySpace;
2265            height = _rowHeight;
2266            // Special case - if we have negative gap and we're not the last
2267            // row, adjust the height
2268            if (_verticalGap < 0 && row < _rowCount - 1)
2269                height += _verticalGap + 1;
2270
2271            if (dropIndicator is IVisualElement)
2272            {
2273                dropIndicatorElement = IVisualElement(dropIndicator);
2274                width = Math.max(Math.min(width,
2275                                          dropIndicatorElement.getMaxBoundsWidth(false)),
2276                                          dropIndicatorElement.getMinBoundsWidth(false));
2277            }
2278
2279            x = emptySpaceLeft + Math.round((emptySpace - width) / 2) + paddingLeft;
2280            // Allow 1 pixel overlap with container border
2281            x = Math.max(-1, Math.min(target.contentWidth - width + 1, x));
2282
2283            y = row * (_rowHeight + _verticalGap) + paddingTop;
2284        }
2285        else
2286        {
2287            emptySpace = (0 < _verticalGap ) ? _verticalGap : 0;
2288            var emptySpaceTop:Number = row * (_rowHeight + _verticalGap) - emptySpace;
2289
2290            // Special case - if we have negative gap and we're the last column,
2291            // adjust the emptySpaceLeft
2292            if (_verticalGap < 0 && (row == _rowCount || count == dropLocation.dropIndex))
2293                emptySpaceTop -= _verticalGap;
2294
2295            width = _columnWidth;
2296            height = emptySpace;
2297            // Special case - if we have negative gap and we're not the last
2298            // column, adjust the width
2299            if (_horizontalGap < 0 && column < _columnCount - 1)
2300                width += _horizontalGap + 1;
2301
2302            if (dropIndicator is IVisualElement)
2303            {
2304                dropIndicatorElement = IVisualElement(dropIndicator);
2305                height = Math.max(Math.min(emptySpace,
2306                                           dropIndicatorElement.getMaxBoundsWidth(false)),
2307                                           dropIndicatorElement.getMinBoundsWidth(false));
2308            }
2309
2310            x = column * (_columnWidth + _horizontalGap) + paddingLeft;
2311
2312            y = emptySpaceTop + Math.round((emptySpace - height) / 2) + paddingTop;
2313            // Allow 1 pixel overlap with container border
2314            y = Math.max(-1, Math.min(target.contentHeight - height + 1, y));
2315        }
2316
2317        return new Rectangle(x, y, width, height);
2318    }
2319
2320    /**
2321     *  @private
2322     *  Helper function to return the row and column of the cell
2323     *  containing the x/y point.  The associated index may be out
2324     *  of range if there's no element for the cell.
2325     */
2326    private function calculateCellIndex(x:Number, y:Number):Array
2327    {
2328        var xStart:Number = x - paddingLeft;
2329        var yStart:Number = y - paddingTop;
2330        var column:int = Math.floor(xStart / (_columnWidth + _horizontalGap));
2331        var row:int = Math.floor(yStart / (_rowHeight + _verticalGap));
2332
2333        // Make sure column and row numbers are valid
2334        if (column < 0)
2335            column = 0;
2336        if (column >= _columnCount)
2337            column = _columnCount - 1;
2338        if (row < 0)
2339            row = 0;
2340        if (row >= _rowCount)
2341            row = _rowCount - 1;
2342
2343        return [row, column];
2344    }
2345
2346
2347    /**
2348     *  @private
2349     *  Identifies the element which has its "compare point" located closest
2350     *  to the specified position.
2351     */
2352    override mx_internal function getElementNearestScrollPosition(
2353        position:Point,
2354        elementComparePoint:String = "center"):int
2355    {
2356        // Determine which cell contains the point
2357        var result:Array = calculateCellIndex(position.x, position.y);
2358        var row:int = result[0];
2359        var column:int = result[1];
2360
2361        var maxRow:int = _rowCount-1;
2362        var maxColumn:int = _columnCount-1;
2363
2364        // create a rectangle of the element bounds
2365        var bounds:Rectangle =
2366            new Rectangle(leftEdge(column), topEdge(row), _columnWidth + _horizontalGap, _rowHeight + _verticalGap);
2367
2368        const TOP_LEFT:int = 0;
2369        const TOP_RIGHT:int = 1;
2370        const BOTTOM_LEFT:int = 2;
2371        const BOTTOM_RIGHT:int = 3;
2372
2373        // Determine the quadrant of the element/cell which contains the position point.
2374        var quadrant:int = TOP_LEFT;
2375        if (position.x > bounds.left + bounds.width/2)
2376            quadrant += TOP_RIGHT;
2377        if (position.y > bounds.top + bounds.height/2)
2378            quadrant += BOTTOM_LEFT;
2379
2380        var index:int;
2381        if (orientation == TileOrientation.ROWS)
2382            index = row * _columnCount + column;
2383        else
2384            index = column * _rowCount + row;
2385
2386        var g:GroupBase = GroupBase(target);
2387        if (index >= g.numElements)
2388        {
2389            // TODO (eday): two-dimensional item snapping will require more sophisticated cell detection
2390            // if the position is beyond the content.  For now (while we only support one-dimensional
2391            // snapping), using the last element will work fine.
2392            return g.numElements - 1;
2393        }
2394
2395        // Depending on which point of the element is to be compared with, and on which
2396        // quadrant of the element contains the position, we may adjust row/column to
2397        // points to an adjacent cell.
2398        switch (elementComparePoint)
2399        {
2400            case "topLeft":
2401                if (quadrant == TOP_RIGHT && column < maxColumn)
2402                    column++;
2403                else if (quadrant == BOTTOM_LEFT && row < maxRow)
2404                    row++;
2405                else if (quadrant == BOTTOM_RIGHT && row < maxRow && column < maxColumn)
2406                {
2407                    row++;
2408                    column++;
2409                }
2410                break;
2411            case "topRight":
2412                if (quadrant == TOP_LEFT && column > 0)
2413                    column--;
2414                else if (quadrant == BOTTOM_LEFT && column > 0 && row < maxRow)
2415                {
2416                    column--;
2417                    row++;
2418                }
2419                else if (quadrant == BOTTOM_RIGHT && row < maxRow)
2420                    row++;
2421                break;
2422            case "bottomLeft":
2423                if (quadrant == TOP_LEFT && row > 0)
2424                    row--;
2425                else if (quadrant == TOP_RIGHT && row > 0 && column < maxColumn)
2426                {
2427                    row--;
2428                    column++;
2429                }
2430                else if (quadrant == BOTTOM_RIGHT && column < maxColumn)
2431                    column++;
2432                break;
2433            case "bottomRight":
2434                if (quadrant == TOP_LEFT && column > 0 && row > 0)
2435                {
2436                    column--;
2437                    row--;
2438                }
2439                else if (quadrant == TOP_RIGHT && row > 0)
2440                    row--;
2441                else if (quadrant == BOTTOM_LEFT && column > 0)
2442                    column--
2443                break;
2444            case "center":
2445                // for center snapping, "index" is already always the cell with
2446                // its center closest to the specified position
2447                break;
2448        }
2449
2450        var newIndex:int;
2451        if (orientation == TileOrientation.ROWS)
2452            newIndex = row * _columnCount + column;
2453        else
2454            newIndex = column * _rowCount + row;
2455        if (newIndex < g.numElements)
2456            index = newIndex;
2457
2458        return index;
2459    }
2460
2461}
2462}
2463