| //////////////////////////////////////////////////////////////////////////////// |
| // |
| // Licensed to the Apache Software Foundation (ASF) under one or more |
| // contributor license agreements. See the NOTICE file distributed with |
| // this work for additional information regarding copyright ownership. |
| // The ASF licenses this file to You under the Apache License, Version 2.0 |
| // (the "License"); you may not use this file except in compliance with |
| // the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| // |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| package spark.layouts |
| { |
| import flash.events.Event; |
| import flash.geom.Point; |
| import flash.geom.Rectangle; |
| |
| import mx.containers.utilityClasses.Flex; |
| import mx.core.FlexVersion; |
| import mx.core.ILayoutElement; |
| import mx.core.IVisualElement; |
| import mx.core.mx_internal; |
| import mx.events.PropertyChangeEvent; |
| |
| import spark.components.DataGroup; |
| import spark.components.supportClasses.GroupBase; |
| import spark.core.NavigationUnit; |
| import spark.layouts.supportClasses.DropLocation; |
| import spark.layouts.supportClasses.LayoutBase; |
| import spark.layouts.supportClasses.LayoutElementHelper; |
| import spark.layouts.supportClasses.LinearLayoutVector; |
| |
| use namespace mx_internal; |
| |
| /** |
| * The HorizontalLayout class arranges the layout elements in a horizontal sequence, |
| * left to right, with optional gaps between the elements and optional padding |
| * around the elements. |
| * |
| * <p>The horizontal position of the elements is determined by arranging them |
| * in a horizontal sequence, left to right, taking into account the padding |
| * before the first element and the gaps between the elements.</p> |
| * |
| * <p>The vertical position of the elements is determined by the layout's |
| * <code>verticalAlign</code> property.</p> |
| * |
| * <p>During the execution of the <code>measure()</code> method, |
| * the default size of the container is calculated by |
| * accumulating the preferred sizes of the elements, including gaps and padding. |
| * When the <code>requestedColumnCount</code> property is set to a value other than -1, |
| * only the space for that many elements |
| * is measured, starting from the first element.</p> |
| * |
| * <p>During the execution of the <code>updateDisplayList()</code> method, |
| * the width of each element is calculated |
| * according to the following rules, listed in their respective order of |
| * precedence (element's minimum width and maximum width are always respected):</p> |
| * <ul> |
| * <li>If <code>variableColumnWidth</code> is <code>false</code>, |
| * then set the element's width to the |
| * value of the <code>columnWidth</code> property.</li> |
| * |
| * <li>If the element's <code>percentWidth</code> is set, then calculate the element's |
| * width by distributing the available container width between all |
| * elements with <code>percentWidth</code> setting. |
| * The available container width |
| * is equal to the container width minus the gaps, the padding and the |
| * space occupied by the rest of the elements. The element's <code>precentWidth</code> |
| * property is ignored when the layout is virtualized.</li> |
| * |
| * <li>Set the element's width to its preferred width.</li> |
| * </ul> |
| * |
| * <p>The height of each element is calculated according to the following rules, |
| * listed in their respective order of precedence (element's minimum height and |
| * maximum height are always respected):</p> |
| * <ul> |
| * <li>If the <code>verticalAlign</code> property is <code>"justify"</code>, |
| * then set the element's height to the container height.</li> |
| * |
| * <li>If the <code>verticalAlign</code> property is <code>"contentJustify"</code>, |
| * then set the element's height to the maximum between the container's height |
| * and all elements' preferred height.</li> |
| * |
| * <li>If the element's <code>percentHeight</code> property is set, |
| * then calculate the element's height as a percentage of the container's height.</li> |
| * |
| * <li>Set the element's height to its preferred height.</li> |
| * </ul> |
| * |
| * @mxml |
| * <p>The <code><s:HorizontalLayout></code> tag inherits all of the tag |
| * attributes of its superclass and adds the following tag attributes:</p> |
| * |
| * <pre> |
| * <s:HorizontalLayout |
| * <strong>Properties</strong> |
| * columnWidth="<i>calculated</i>" |
| * gap="6" |
| * paddingBottom="0" |
| * paddingLeft="0" |
| * paddingRight="0" |
| * paddingTop="0" |
| * requestedColumnCount="-1" |
| * requestedMaxColumnCount="-1" |
| * requestedMinColumnCount="-1" |
| * variableColumnWidth="true" |
| * verticalAlign="top" |
| * /> |
| * </pre> |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public class HorizontalLayout extends LayoutBase |
| { |
| include "../core/Version.as"; |
| |
| /** |
| * @private |
| * Cached column widths, max row height for virtual layout. Not used unless |
| * useVirtualLayout=true. See updateLLV(), resetCachedVirtualLayoutState(), |
| * etc. |
| */ |
| private var llv:LinearLayoutVector; |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Class methods |
| // |
| //-------------------------------------------------------------------------- |
| |
| private static function calculatePercentHeight(layoutElement:ILayoutElement, height:Number):Number |
| { |
| var percentHeight:Number; |
| if (FlexVersion.compatibilityVersion < FlexVersion.VERSION_4_6) |
| { |
| percentHeight = LayoutElementHelper.pinBetween(Math.round(layoutElement.percentHeight * 0.01 * height), |
| layoutElement.getMinBoundsHeight(), |
| layoutElement.getMaxBoundsHeight() ); |
| return percentHeight < height ? percentHeight : height; |
| } |
| else |
| { |
| percentHeight = LayoutElementHelper.pinBetween(Math.min(Math.round(layoutElement.percentHeight * 0.01 * height), height), |
| layoutElement.getMinBoundsHeight(), |
| layoutElement.getMaxBoundsHeight() ); |
| return percentHeight; |
| } |
| } |
| |
| private static function sizeLayoutElement(layoutElement:ILayoutElement, height:Number, |
| verticalAlign:String, restrictedHeight:Number, |
| width:Number, variableColumnWidth:Boolean, |
| columnWidth:Number):void |
| { |
| var newHeight:Number = NaN; |
| |
| // if verticalAlign is "justify" or "contentJustify", |
| // restrict the height to restrictedHeight. Otherwise, |
| // size it normally |
| if (verticalAlign == VerticalAlign.JUSTIFY || |
| verticalAlign == VerticalAlign.CONTENT_JUSTIFY) |
| { |
| newHeight = restrictedHeight; |
| } |
| else |
| { |
| if (!isNaN(layoutElement.percentHeight)) |
| newHeight = calculatePercentHeight(layoutElement, height); |
| } |
| |
| if (variableColumnWidth) |
| layoutElement.setLayoutBoundsSize(width, newHeight); |
| else |
| layoutElement.setLayoutBoundsSize(columnWidth, newHeight); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Constructor |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * Constructor. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function HorizontalLayout():void |
| { |
| super(); |
| |
| // Don't drag-scroll in the vertical direction |
| dragScrollRegionSizeVertical = 0; |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Properties |
| // |
| //-------------------------------------------------------------------------- |
| |
| //---------------------------------- |
| // alignmentBaseline |
| //---------------------------------- |
| |
| /** |
| * @private |
| * Storage for the alignmentBaseline property. |
| */ |
| private var _alignmentBaseline:Object = "maxAscent:0"; |
| |
| /** |
| * @private |
| * |
| * The base line of the layout, in pixels. |
| * |
| * The base line is the virtual horizontal line to which layout elements' text is aligned, |
| * it is relative to the top edge of the container plus any <code>paddingTop</code>. |
| * |
| * The base line can be specified as either an explicit number, or as a numberical offset from |
| * the computed maximum of the elements' text ascent by using the "maxAscent:offset" syntax. |
| * |
| * Note that <code>alignmentBaseline</code> has an effect only when <code>verticalAlign</code> |
| * is set to "baseline". |
| * |
| * @default "maxAscent:0" |
| * @see #verticalAlign |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 2.5 |
| * @productversion Flex 4.5 |
| */ |
| mx_internal function get alignmentBaseline():Object |
| { |
| return _alignmentBaseline; |
| } |
| |
| /** |
| * @private |
| */ |
| mx_internal function set alignmentBaseline(value:Object):void |
| { |
| if (_alignmentBaseline == value) |
| return; |
| |
| _alignmentBaseline = value; |
| invalidateTargetSizeAndDisplayList(); |
| } |
| |
| //---------------------------------- |
| // gap |
| //---------------------------------- |
| |
| private var _gap:int = 6; |
| |
| [Inspectable(category="General")] |
| |
| /** |
| * The horizontal space between layout elements, in pixels. |
| * |
| * Note that the gap is only applied between layout elements, so if there's |
| * just one element, the gap has no effect on the layout. |
| * |
| * @default 6 |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get gap():int |
| { |
| return _gap; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set gap(value:int):void |
| { |
| if (_gap == value) |
| return; |
| |
| _gap = value; |
| invalidateTargetSizeAndDisplayList(); |
| } |
| |
| //---------------------------------- |
| // columnCount |
| //---------------------------------- |
| |
| private var _columnCount:int = -1; |
| |
| [Bindable("propertyChange")] |
| [Inspectable(category="General")] |
| |
| /** |
| * Returns the current number of elements in view. |
| * |
| * @default -1 |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get columnCount():int |
| { |
| return _columnCount; |
| } |
| |
| /** |
| * @private |
| * |
| * Sets the <code>columnCount</code> property and dispatches |
| * a PropertyChangeEvent. |
| */ |
| private function setColumnCount(value:int):void |
| { |
| if (_columnCount == value) |
| return; |
| var oldValue:int = _columnCount; |
| _columnCount = value; |
| dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "columnCount", oldValue, value)); |
| } |
| |
| //---------------------------------- |
| // paddingLeft |
| //---------------------------------- |
| |
| private var _paddingLeft:Number = 0; |
| |
| [Inspectable(category="General")] |
| |
| /** |
| * Number of pixels between the container's left edge |
| * and the left edge of the first layout element. |
| * |
| * @default 0 |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get paddingLeft():Number |
| { |
| return _paddingLeft; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set paddingLeft(value:Number):void |
| { |
| if (_paddingLeft == value) |
| return; |
| |
| _paddingLeft = value; |
| invalidateTargetSizeAndDisplayList(); |
| } |
| |
| //---------------------------------- |
| // paddingRight |
| //---------------------------------- |
| |
| private var _paddingRight:Number = 0; |
| |
| [Inspectable(category="General")] |
| |
| /** |
| * Number of pixels between the container's right edge |
| * and the right edge of the last layout element. |
| * |
| * @default 0 |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get paddingRight():Number |
| { |
| return _paddingRight; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set paddingRight(value:Number):void |
| { |
| if (_paddingRight == value) |
| return; |
| |
| _paddingRight = value; |
| invalidateTargetSizeAndDisplayList(); |
| } |
| |
| //---------------------------------- |
| // paddingTop |
| //---------------------------------- |
| |
| private var _paddingTop:Number = 0; |
| |
| [Inspectable(category="General")] |
| |
| /** |
| * The minimum number of pixels between the container's top edge and |
| * the top of all the container's layout elements. |
| * |
| * @default 0 |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get paddingTop():Number |
| { |
| return _paddingTop; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set paddingTop(value:Number):void |
| { |
| if (_paddingTop == value) |
| return; |
| |
| _paddingTop = value; |
| invalidateTargetSizeAndDisplayList(); |
| } |
| |
| //---------------------------------- |
| // paddingBottom |
| //---------------------------------- |
| |
| private var _paddingBottom:Number = 0; |
| |
| [Inspectable(category="General")] |
| |
| /** |
| * The minimum number of pixels between the container's bottom edge and |
| * the bottom of all the container's layout elements. |
| * |
| * @default 0 |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get paddingBottom():Number |
| { |
| return _paddingBottom; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set paddingBottom(value:Number):void |
| { |
| if (_paddingBottom == value) |
| return; |
| |
| _paddingBottom = value; |
| invalidateTargetSizeAndDisplayList(); |
| } |
| |
| //---------------------------------- |
| // requestedMaxColumnCount |
| //---------------------------------- |
| |
| private var _requestedMaxColumnCount:int = -1; |
| |
| [Inspectable(category="General", minValue="-1")] |
| |
| /** |
| * The measured width of this layout is large enough to display |
| * at most <code>requestedMaxColumnCount</code> layout elements. |
| * |
| * <p>If <code>requestedColumnCount</code> is set, then |
| * this property has no effect.</p> |
| * |
| * <p>If the actual size of the container using this layout has been explicitly set, |
| * then this property has no effect.</p> |
| * |
| * @default -1 |
| * @see #requestedColumnCount |
| * @see #requestedMinColumnCount |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 2.5 |
| * @productversion Flex 4.5 |
| */ |
| public function get requestedMaxColumnCount():int |
| { |
| return _requestedMaxColumnCount; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set requestedMaxColumnCount(value:int):void |
| { |
| if (_requestedMaxColumnCount == value) |
| return; |
| |
| _requestedMaxColumnCount = value; |
| |
| if (target) |
| target.invalidateSize(); |
| } |
| |
| //---------------------------------- |
| // requestedMinColumnCount |
| //---------------------------------- |
| |
| private var _requestedMinColumnCount:int = -1; |
| |
| [Inspectable(category="General", minValue="-1")] |
| |
| /** |
| * The measured width of this layout is large enough to display |
| * at least <code>requestedMinColumnCount</code> layout elements. |
| * |
| * <p>If <code>requestedColumnCount</code> is set, then |
| * this property has no effect.</p> |
| * |
| * <p>If the actual size of the container using this layout has been explicitly set, |
| * then this property has no effect.</p> |
| * |
| * @default -1 |
| * @see #requestedColumnCount |
| * @see #requestedMaxColumnCount |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 2.5 |
| * @productversion Flex 4.5 |
| */ |
| public function get requestedMinColumnCount():int |
| { |
| return _requestedMinColumnCount; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set requestedMinColumnCount(value:int):void |
| { |
| if (_requestedMinColumnCount == value) |
| return; |
| |
| _requestedMinColumnCount = value; |
| |
| if (target) |
| target.invalidateSize(); |
| } |
| |
| //---------------------------------- |
| // requestedColumnCount |
| //---------------------------------- |
| |
| private var _requestedColumnCount:int = -1; |
| |
| [Inspectable(category="General", minValue="-1")] |
| |
| /** |
| * The measured size of this layout is wide enough to display |
| * the first <code>requestedColumnCount</code> layout elements. |
| * If <code>requestedColumnCount</code> is -1, then the measured |
| * size will be big enough for all of the layout elements. |
| * |
| * <p>If the actual size of the container using this layout has been explicitly set, |
| * then this property has no effect.</p> |
| * |
| * @default -1 |
| * @see #requestedMinColumnCount |
| * @see #requestedMaxColumnCount |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get requestedColumnCount():int |
| { |
| return _requestedColumnCount; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set requestedColumnCount(value:int):void |
| { |
| if (_requestedColumnCount == value) |
| return; |
| |
| _requestedColumnCount = value; |
| |
| if (target) |
| target.invalidateSize(); |
| } |
| |
| //---------------------------------- |
| // columnWidth |
| //---------------------------------- |
| |
| private var _columnWidth:Number; |
| |
| [Inspectable(category="General", minValue="0.0")] |
| |
| /** |
| * If the <code>variableColumnWidth</code> property is <code>false</code>, |
| * then this property specifies the actual width of each layout element, in pixels. |
| * |
| * <p>If the <code>variableColumnWidth</code> property is <code>true</code>, |
| * the default, then this property has no effect.</p> |
| * |
| * <p>The default value of this property is the preferred width |
| * of the item specified by the <code>typicalLayoutElement</code> property.</p> |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get columnWidth():Number |
| { |
| if (!isNaN(_columnWidth)) |
| return _columnWidth; |
| else |
| { |
| var elt:ILayoutElement = typicalLayoutElement |
| return (elt) ? elt.getPreferredBoundsWidth() : 0; |
| } |
| } |
| |
| /** |
| * @private |
| */ |
| public function set columnWidth(value:Number):void |
| { |
| if (_columnWidth == value) |
| return; |
| |
| _columnWidth = value; |
| invalidateTargetSizeAndDisplayList(); |
| } |
| |
| //---------------------------------- |
| // variableColumnWidth |
| //---------------------------------- |
| |
| /** |
| * @private |
| */ |
| private var _variableColumnWidth:Boolean = true; |
| |
| [Inspectable(category="General", enumeration="true,false")] |
| |
| /** |
| * If <code>true</code>, specifies that layout elements are to be allocated their |
| * preferred width. |
| * |
| * <p>Setting this property to <code>false</code> specifies fixed width columns. |
| * The actual width of each layout element is |
| * the value of the <code>columnWidth</code> property, and the layout ignores |
| * a layout elements' <code>percentWidth</code> property.</p> |
| * |
| * @default true |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get variableColumnWidth():Boolean |
| { |
| return _variableColumnWidth; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set variableColumnWidth(value:Boolean):void |
| { |
| if (value == _variableColumnWidth) return; |
| |
| _variableColumnWidth = value; |
| invalidateTargetSizeAndDisplayList(); |
| } |
| |
| //---------------------------------- |
| // firstIndexInView |
| //---------------------------------- |
| |
| /** |
| * @private |
| */ |
| private var _firstIndexInView:int = -1; |
| |
| [Inspectable(category="General")] |
| [Bindable("indexInViewChanged")] |
| |
| /** |
| * The index of the first column that is part of the layout and within |
| * the layout target's scroll rectangle, or -1 if nothing has been displayed yet. |
| * |
| * Note that the column may only be partially in view. |
| * |
| * @see lastIndexInView |
| * @see fractionOfElementInView |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get firstIndexInView():int |
| { |
| return _firstIndexInView; |
| } |
| |
| |
| //---------------------------------- |
| // lastIndexInView |
| //---------------------------------- |
| |
| /** |
| * @private |
| */ |
| private var _lastIndexInView:int = -1; |
| |
| [Inspectable(category="General")] |
| [Bindable("indexInViewChanged")] |
| |
| /** |
| * The index of the last column that is part of the layout and within |
| * the layout target's scroll rectangle, or -1 if nothing has been displayed yet. |
| * |
| * Note that the column may only be partially in view. |
| * |
| * @see firstIndexInView |
| * @see fractionOfElementInView |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get lastIndexInView():int |
| { |
| return _lastIndexInView; |
| } |
| |
| //---------------------------------- |
| // horizontalAlign |
| //---------------------------------- |
| |
| /** |
| * @private |
| */ |
| private var _horizontalAlign:String = HorizontalAlign.LEFT; |
| |
| [Inspectable(category="General", enumeration="left,right,center", defaultValue="left")] |
| |
| /** |
| * The horizontal alignment of the content relative to the container's width. |
| * If the value is <code>"left"</code>, <code>"right"</code>, or <code>"center"</code> then the |
| * layout element is aligned relative to the container's <code>contentWidth</code> property. |
| * |
| * <p>This property has no effect when <code>clipAndEnableScrolling</code> is true |
| * and the <code>contentWidth</code> is greater than the container's width.</p> |
| * |
| * <p>This property does not affect the layout's measured size.</p> |
| * |
| * @default "left" |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get horizontalAlign():String |
| { |
| return _horizontalAlign; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set horizontalAlign(value:String):void |
| { |
| if (value == _horizontalAlign) |
| return; |
| |
| _horizontalAlign = value; |
| |
| var layoutTarget:GroupBase = target; |
| if (layoutTarget) |
| layoutTarget.invalidateDisplayList(); |
| } |
| |
| //---------------------------------- |
| // verticalAlign |
| //---------------------------------- |
| |
| /** |
| * @private |
| */ |
| private var _verticalAlign:String = VerticalAlign.TOP; |
| |
| [Inspectable(category="General", enumeration="top,bottom,middle,justify,contentJustify,baseline", defaultValue="top")] |
| |
| /** |
| * The vertical alignment of layout elements. |
| * |
| * <p>If the value is <code>"bottom"</code>, <code>"middle"</code>, |
| * or <code>"top"</code> then the layout elements are aligned relative |
| * to the container's <code>contentHeight</code> property.</p> |
| * |
| * <p>If the value is <code>"contentJustify"</code> then the actual |
| * height of the layout element is set to |
| * the container's <code>contentHeight</code> property. |
| * The content height of the container is the height of the largest layout element. |
| * If all layout elements are smaller than the height of the container, |
| * then set the height of all the layout elements to the height of the container.</p> |
| * |
| * <p>If the value is <code>"justify"</code> then the actual height |
| * of the layout elements is set to the container's height.</p> |
| * |
| * <p>If the value is <code>"baseline"</code> then the elements are positioned |
| * such that their text is aligned to the maximum of the elements' text ascent.</p> |
| * |
| * @default "top" |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get verticalAlign():String |
| { |
| return _verticalAlign; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set verticalAlign(value:String):void |
| { |
| if (value == _verticalAlign) |
| return; |
| |
| var oldValue:String = _verticalAlign; |
| _verticalAlign = value; |
| |
| // "baseline" affects the measured size |
| if (oldValue == VerticalAlign.BASELINE || |
| value == VerticalAlign.BASELINE) |
| { |
| invalidateTargetSizeAndDisplayList(); |
| } |
| else |
| { |
| var layoutTarget:GroupBase = target; |
| if (layoutTarget) |
| layoutTarget.invalidateDisplayList(); |
| } |
| } |
| |
| /** |
| * @private |
| * |
| * Sets the <code>firstIndexInView</code> and <code>lastIndexInView</code> |
| * properties and dispatches a <code>"indexInViewChanged"</code> |
| * event. |
| * |
| * @param firstIndex The new value for firstIndexInView. |
| * @param lastIndex The new value for lastIndexInView. |
| * |
| * @see firstIndexInView |
| * @see lastIndexInview |
| */ |
| private function setIndexInView(firstIndex:int, lastIndex:int):void |
| { |
| if ((_firstIndexInView == firstIndex) && (_lastIndexInView == lastIndex)) |
| return; |
| |
| _firstIndexInView = firstIndex; |
| _lastIndexInView = lastIndex; |
| dispatchEvent(new Event("indexInViewChanged")); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Methods |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| */ |
| override public function set clipAndEnableScrolling(value:Boolean):void |
| { |
| super.clipAndEnableScrolling = value; |
| var hAlign:String = horizontalAlign; |
| if (hAlign == HorizontalAlign.CENTER || hAlign == HorizontalAlign.RIGHT) |
| { |
| var g:GroupBase = target; |
| if (g) |
| g.invalidateDisplayList(); |
| } |
| } |
| |
| /** |
| * @private |
| */ |
| override public function clearVirtualLayoutCache():void |
| { |
| llv = null; |
| |
| var g:GroupBase = GroupBase(target); |
| if (!g) |
| return; |
| |
| target.invalidateSize(); |
| target.invalidateDisplayList(); |
| } |
| |
| /** |
| * @private |
| */ |
| override public function getElementBounds(index:int):Rectangle |
| { |
| if (!useVirtualLayout) |
| return super.getElementBounds(index); |
| |
| var g:GroupBase = GroupBase(target); |
| if (!g || (index < 0) || (index >= g.numElements) || !llv) |
| return null; |
| |
| // We need a valid LLV for this function |
| updateLLV(g); |
| |
| return llv.getBounds(index); |
| } |
| |
| /** |
| * Returns 1.0 if the specified index is completely in view, 0.0 if |
| * it's not, or a value between 0.0 and 1.0 that represents the percentage |
| * of the if the index that is partially in view. |
| * |
| * <p>An index is "in view" if the corresponding non-null layout element is |
| * within the horizontal limits of the container's <code>scrollRect</code> |
| * and included in the layout.</p> |
| * |
| * <p>If the specified index is partially within the view, the |
| * returned value is the percentage of the corresponding |
| * layout element that's visible.</p> |
| * |
| * @param index The index of the column. |
| * |
| * @return The percentage of the specified element that's in view. |
| * Returns 0.0 if the specified index is invalid or if it corresponds to |
| * null element, or a ILayoutElement for which |
| * the <code>includeInLayout</code> property is <code>false</code>. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function fractionOfElementInView(index:int):Number |
| { |
| var g:GroupBase = target; |
| if (!g) |
| return 0.0; |
| |
| if ((index < 0) || (index >= g.numElements)) |
| return 0.0; |
| |
| if (!clipAndEnableScrolling) |
| return 1.0; |
| |
| var r0:int = firstIndexInView; |
| var r1:int = lastIndexInView; |
| |
| // index is outside the "in view" or visible range |
| if ((r0 == -1) || (r1 == -1) || (index < r0) || (index > r1)) |
| return 0.0; |
| |
| // within the visible index range, but not first or last |
| if ((index > r0) && (index < r1)) |
| return 1.0; |
| |
| // get the layout element's X and Width |
| var eltX:Number; |
| var eltWidth:Number; |
| if (useVirtualLayout) |
| { |
| if (!llv) |
| return 0.0; |
| eltX = llv.start(index); |
| eltWidth = llv.getMajorSize(index); |
| } |
| else |
| { |
| var elt:ILayoutElement = g.getElementAt(index); |
| if (!elt || !elt.includeInLayout) |
| return 0.0; |
| eltX = elt.getLayoutBoundsX(); |
| eltWidth = elt.getLayoutBoundsWidth(); |
| } |
| |
| // index is either the first or last column in the scrollRect |
| // and potentially partially visible. |
| // x0,x1 - scrollRect left,right edges |
| // ix0, ix1 - layout element left,right edges |
| var x0:Number = g.horizontalScrollPosition; |
| var x1:Number = x0 + g.width; |
| var ix0:Number = eltX; |
| var ix1:Number = ix0 + eltWidth; |
| if (ix0 >= ix1) // element has 0 or negative height |
| return 1.0; |
| if ((ix0 >= x0) && (ix1 <= x1)) |
| return 1.0; |
| return (Math.min(x1, ix1) - Math.max(x0, ix0)) / (ix1 - ix0); |
| } |
| |
| /** |
| * @private |
| * |
| * Binary search for the first layout element that contains y. |
| * |
| * This function considers both the element's actual bounds and |
| * the gap that follows it to be part of the element. The search |
| * covers index i0 through i1 (inclusive). |
| * |
| * This function is intended for variable height elements. |
| * |
| * Returns the index of the element that contains x, or -1. |
| */ |
| private static function findIndexAt(x:Number, gap:int, g:GroupBase, i0:int, i1:int):int |
| { |
| var index:int = (i0 + i1) / 2; |
| var element:ILayoutElement = g.getElementAt(index); |
| var elementX:Number = element.getLayoutBoundsX(); |
| // TBD: deal with null element, includeInLayout false. |
| if ((x >= elementX) && (x < elementX + element.getLayoutBoundsWidth() + gap)) |
| return index; |
| else if (i0 == i1) |
| return -1; |
| else if (x < elementX) |
| return findIndexAt(x, gap, g, i0, Math.max(i0, index-1)); |
| else |
| return findIndexAt(x, gap, g, Math.min(index+1, i1), i1); |
| } |
| |
| /** |
| * @private |
| * |
| * Returns the index of the first non-null includeInLayout element, |
| * beginning with the element at index i. |
| * |
| * Returns -1 if no such element can be found. |
| */ |
| private static function findLayoutElementIndex(g:GroupBase, i:int, dir:int):int |
| { |
| var n:int = g.numElements; |
| while((i >= 0) && (i < n)) |
| { |
| var element:ILayoutElement = g.getElementAt(i); |
| if (element && element.includeInLayout) |
| { |
| return i; |
| } |
| i += dir; |
| } |
| return -1; |
| } |
| |
| /** |
| * @private |
| * |
| * Updates the first,lastIndexInView properties per the new |
| * scroll position. |
| * |
| * @see setIndexInView |
| */ |
| override protected function scrollPositionChanged():void |
| { |
| super.scrollPositionChanged(); |
| |
| var g:GroupBase = target; |
| if (!g) |
| return; |
| |
| var n:int = g.numElements - 1; |
| if (n < 0) |
| { |
| setIndexInView(-1, -1); |
| return; |
| } |
| |
| var scrollR:Rectangle = getScrollRect(); |
| if (!scrollR) |
| { |
| setIndexInView(0, n); |
| return; |
| } |
| |
| // We're going to use findIndexAt to find the index of |
| // the elements that overlap the left and right edges of the scrollRect. |
| // Values that are exactly equal to scrollRect.right aren't actually |
| // rendered, since the left,right interval is only half open. |
| // To account for that we back away from the right edge by a |
| // hopefully infinitesimal amount. |
| |
| var x0:Number = scrollR.left; |
| var x1:Number = scrollR.right - .0001; |
| if (x1 <= x0) |
| { |
| setIndexInView(-1, -1); |
| return; |
| } |
| |
| if (useVirtualLayout && !llv) |
| { |
| setIndexInView(-1, -1); |
| return; |
| } |
| |
| var i0:int; |
| var i1:int; |
| if (useVirtualLayout) |
| { |
| i0 = llv.indexOf(x0); |
| i1 = llv.indexOf(x1); |
| } |
| else |
| { |
| i0 = findIndexAt(x0 + gap, gap, g, 0, n); |
| i1 = findIndexAt(x1, gap, g, 0, n); |
| } |
| |
| // Special case: no element overlaps x0, is index 0 visible? |
| if (i0 == -1) |
| { |
| var index0:int = findLayoutElementIndex(g, 0, +1); |
| if (index0 != -1) |
| { |
| var element0:ILayoutElement = g.getElementAt(index0); |
| var element0X:Number = element0.getLayoutBoundsX(); |
| var element0Width:Number = element0.getLayoutBoundsWidth(); |
| if ((element0X < x1) && ((element0X + element0Width) > x0)) |
| i0 = index0; |
| } |
| } |
| |
| // Special case: no element overlaps y1, is index n visible? |
| if (i1 == -1) |
| { |
| var index1:int = findLayoutElementIndex(g, n, -1); |
| if (index1 != -1) |
| { |
| var element1:ILayoutElement = g.getElementAt(index1); |
| var element1X:Number = element1.getLayoutBoundsX(); |
| var element1Width:Number = element1.getLayoutBoundsWidth(); |
| if ((element1X < x1) && ((element1X + element1Width) > x0)) |
| i1 = index1; |
| } |
| } |
| |
| if (useVirtualLayout) |
| { |
| var firstElement:ILayoutElement = g.getElementAt(_firstIndexInView); |
| var lastElement:ILayoutElement = g.getElementAt(_lastIndexInView); |
| var scrollRect:Rectangle = getScrollRect(); |
| |
| /* If the scrollRect is within the bounds of the elements, we do |
| not need to call invalidateDisplayList(). This considerably speeds |
| up small scrolls. */ |
| if (!firstElement || !lastElement || |
| scrollRect.left < firstElement.getLayoutBoundsX() || |
| scrollRect.right >= (lastElement.getLayoutBoundsX() + lastElement.getLayoutBoundsWidth())) |
| { |
| g.invalidateDisplayList(); |
| } |
| } |
| |
| setIndexInView(i0, i1); |
| } |
| |
| /** |
| * @private |
| * |
| * Returns the actual position/size Rectangle of the first partially |
| * visible or not-visible, non-null includeInLayout element, beginning |
| * with the element at index i, searching in direction dir (dir must |
| * be +1 or -1). The last argument is the GroupBase scrollRect, it's |
| * guaranteed to be non-null. |
| * |
| * Returns null if no such element can be found. |
| */ |
| private function findLayoutElementBounds(g:GroupBase, i:int, dir:int, r:Rectangle):Rectangle |
| { |
| var n:int = g.numElements; |
| |
| if (fractionOfElementInView(i) >= 1) |
| { |
| // Special case: if we hit the first/last element, |
| // then return the area of the padding so that we |
| // can scroll all the way to the start/end. |
| i += dir; |
| if (i < 0) |
| return new Rectangle(0, 0, paddingLeft, 0); |
| if (i >= n) |
| return new Rectangle(getElementBounds(n-1).right, 0, paddingRight, 0); |
| } |
| |
| while((i >= 0) && (i < n)) |
| { |
| var elementR:Rectangle = getElementBounds(i); |
| // Special case: if the scrollRect r _only_ contains |
| // elementR, then if we're searching left (dir == -1), |
| // and elementR's left edge is visible, then try again |
| // with i-1. Likewise for dir == +1. |
| if (elementR) |
| { |
| var overlapsLeft:Boolean = (dir == -1) && (elementR.left == r.left) && (elementR.right >= r.right); |
| var overlapsRight:Boolean = (dir == +1) && (elementR.right == r.right) && (elementR.left <= r.left); |
| if (!(overlapsLeft || overlapsRight)) |
| return elementR; |
| } |
| i += dir; |
| } |
| return null; |
| } |
| |
| /** |
| * @private |
| */ |
| override protected function getElementBoundsLeftOfScrollRect(scrollRect:Rectangle):Rectangle |
| { |
| return findLayoutElementBounds(target, firstIndexInView, -1, scrollRect); |
| } |
| |
| /** |
| * @private |
| */ |
| override protected function getElementBoundsRightOfScrollRect(scrollRect:Rectangle):Rectangle |
| { |
| return findLayoutElementBounds(target, lastIndexInView, +1, scrollRect); |
| } |
| |
| /** |
| * @private |
| * Fills in the result with preferred and min sizes of the element. |
| */ |
| private function getElementWidth(element:ILayoutElement, fixedColumnWidth:Number, result:SizesAndLimit):void |
| { |
| // Calculate preferred width first, as it's being used to calculate min width |
| var elementPreferredWidth:Number = isNaN(fixedColumnWidth) ? Math.ceil(element.getPreferredBoundsWidth()) : |
| fixedColumnWidth; |
| // Calculate min width |
| var flexibleWidth:Boolean = !isNaN(element.percentWidth); |
| var elementMinWidth:Number = flexibleWidth ? Math.ceil(element.getMinBoundsWidth()) : |
| elementPreferredWidth; |
| result.preferredSize = elementPreferredWidth; |
| result.minSize = elementMinWidth; |
| } |
| |
| /** |
| * @private |
| * Fills in the result with preferred and min sizes of the element. |
| */ |
| private function getElementHeight(element:ILayoutElement, justify:Boolean, result:SizesAndLimit):void |
| { |
| // Calculate preferred height first, as it's being used to calculate min height below |
| var elementPreferredHeight:Number = Math.ceil(element.getPreferredBoundsHeight()); |
| |
| // Calculate min height |
| var flexibleHeight:Boolean = !isNaN(element.percentHeight) || justify; |
| var elementMinHeight:Number = flexibleHeight ? Math.ceil(element.getMinBoundsHeight()) : |
| elementPreferredHeight; |
| result.preferredSize = elementPreferredHeight; |
| result.minSize = elementMinHeight; |
| } |
| |
| /** |
| * @private |
| * Returns [baselineTop, baselineBottom, baselineBottomMin], |
| * where baselineTop is the portion of the elements above the common baseline |
| * and the baselineBottom is the portion of the elements below the common baseline. |
| */ |
| private function calculateBaselineTopBottom(calculateBottom:Boolean):Array |
| { |
| var baselineOffset:Number = 0; |
| var baselineTop:Number = 0; |
| var baselineBottom:Number = 0; |
| var baselineBottomMin:Number = 0; |
| |
| var calculateTop:Boolean; |
| var temp:Array = LayoutElementHelper.parseConstraintExp(alignmentBaseline); |
| if (temp.length == 2 && temp[1] == "maxAscent") |
| { |
| baselineOffset = Number(temp[0]); |
| calculateTop = true; |
| } |
| else |
| { |
| calculateTop = false; |
| baselineTop = Number(temp[0]); |
| |
| // If someone sets the explicit baseline to NaN, then |
| // still calculate from the elements |
| if (isNaN(baselineTop)) |
| { |
| baselineTop = 0; |
| calculateTop = true; |
| } |
| } |
| |
| var count:int = target.numElements; |
| for (var i:int = 0; i < count; i++) |
| { |
| var element:ILayoutElement = target.getElementAt(i); |
| if (!element || !element.includeInLayout) |
| continue; |
| |
| var elementBaseline:Number = element.baseline as Number; |
| if (isNaN(elementBaseline)) |
| elementBaseline = 0; |
| |
| var baselinePosition:Number = element.baselinePosition; |
| |
| // The portion of the current element that's above the baseline |
| var elementBaselineTop:Number = baselinePosition - elementBaseline; |
| |
| if (calculateTop) |
| baselineTop = Math.max(elementBaselineTop, baselineTop); |
| |
| if (calculateBottom) |
| { |
| // The portion of the current element that's below the baseline |
| var elementHeight:Number = element.getPreferredBoundsHeight(); |
| var elementBaselineBottom:Number = elementHeight - elementBaselineTop; |
| |
| // Calculate bottom based on min height if the element has flexible height |
| var elementBaselineBottomMin:Number = elementBaselineBottom; |
| if (!isNaN(element.percentHeight)) |
| elementBaselineBottomMin = element.getMinBoundsHeight() - elementBaselineTop; |
| |
| baselineBottom = Math.max(elementBaselineBottom, baselineBottom); |
| } |
| } |
| |
| // If ascent was specified, add the offset |
| if (calculateTop) |
| baselineTop += baselineOffset; |
| |
| return [baselineTop, baselineBottom, baselineBottomMin]; |
| } |
| |
| /** |
| * @private |
| * @return columns to measure based on elements in layout and any requested/min/max rowCount settings. |
| */ |
| private function getColumsToMeasure(numElementsInLayout:int):int |
| { |
| var columnsToMeasure:int; |
| if (requestedColumnCount != -1) |
| columnsToMeasure = requestedColumnCount; |
| else |
| { |
| columnsToMeasure = numElementsInLayout; |
| if (requestedMaxColumnCount != -1) |
| columnsToMeasure = Math.min(requestedMaxColumnCount, columnsToMeasure); |
| if (requestedMinColumnCount != -1) |
| columnsToMeasure = Math.max(requestedMinColumnCount, columnsToMeasure); |
| } |
| return columnsToMeasure; |
| } |
| |
| /** |
| * @private |
| * |
| * Compute exact values for measuredWidth,Height and measuredMinWidth,Height. |
| * |
| * Measure each of the layout elements. If requestedColumnCount >= 0 we |
| * consider the height and width of as many layout elements, padding with |
| * typicalLayoutElement if needed, starting with index 0. We then only |
| * consider the height of the elements remaining. |
| * |
| * If requestedColumnCount is -1, we consider width/height of each element. |
| */ |
| private function measureReal(layoutTarget:GroupBase):void |
| { |
| var size:SizesAndLimit = new SizesAndLimit(); |
| var alignToBaseline:Boolean = verticalAlign == VerticalAlign.BASELINE; |
| var justify:Boolean = verticalAlign == VerticalAlign.JUSTIFY; |
| var numElements:int = layoutTarget.numElements; // How many elements there are in the target |
| var numElementsInLayout:int = numElements; // How many elements have includeInLayout == true, start off with numElements. |
| var requestedColumnCount:int = this.requestedColumnCount; |
| var columnsMeasured:int = 0; // How many columns have been measured |
| |
| var preferredHeight:Number = 0; // sum of the elt preferred heights |
| var preferredWidth:Number = 0; // max of the elt preferred widths |
| var minHeight:Number = 0; // sum of the elt minimum heights |
| var minWidth:Number = 0; // max of the elt minimum widths |
| |
| var fixedColumnWidth:Number = NaN; |
| if (!variableColumnWidth) |
| fixedColumnWidth = columnWidth; // may query typicalLayoutElement, elt at index=0 |
| |
| // Get the numElementsInLayout clamped to requested min/max |
| var columnsToMeasure:int = getColumsToMeasure(numElementsInLayout); |
| var element:ILayoutElement; |
| for (var i:int = 0; i < numElements; i++) |
| { |
| element = layoutTarget.getElementAt(i); |
| if (!element || !element.includeInLayout) |
| { |
| numElementsInLayout--; |
| continue; |
| } |
| |
| if (!alignToBaseline) |
| { |
| // Consider the height of each element, inclusive of those outside |
| // the requestedColumnCount range. |
| getElementHeight(element, justify, size); |
| preferredHeight = Math.max(preferredHeight, size.preferredSize); |
| minHeight = Math.max(minHeight, size.minSize); |
| } |
| |
| // Can we measure the width of this column? |
| if (columnsMeasured < columnsToMeasure) |
| { |
| getElementWidth(element, fixedColumnWidth, size); |
| preferredWidth += size.preferredSize; |
| minWidth += size.minSize; |
| columnsMeasured++; |
| } |
| } |
| |
| // Calculate the total number of columns to measure again, since numElementsInLayout may have changed |
| columnsToMeasure = getColumsToMeasure(numElementsInLayout); |
| |
| // Do we need to measure more columns? |
| if (columnsMeasured < columnsToMeasure) |
| { |
| // Use the typical element |
| element = typicalLayoutElement; |
| if (element) |
| { |
| if (!alignToBaseline) |
| { |
| // Height |
| getElementHeight(element, justify, size); |
| preferredHeight = Math.max(preferredHeight, size.preferredSize); |
| minHeight = Math.max(minHeight, size.minSize); |
| } |
| |
| // Width |
| getElementWidth(element, fixedColumnWidth, size); |
| preferredWidth += size.preferredSize * (columnsToMeasure - columnsMeasured); |
| minWidth += size.minSize * (columnsToMeasure - columnsMeasured); |
| columnsMeasured = columnsToMeasure; |
| } |
| } |
| |
| if (alignToBaseline) |
| { |
| var result:Array = calculateBaselineTopBottom(true /*calculateBottom*/); |
| var top:Number = result[0]; |
| var bottom:Number = result[1]; |
| var bottomMin:Number = result[2]; |
| |
| preferredHeight = Math.ceil(top + bottom); |
| minHeight = Math.ceil(top + bottomMin); |
| } |
| |
| // Add gaps |
| if (columnsMeasured > 1) |
| { |
| var hgap:Number = gap * (columnsMeasured - 1); |
| preferredWidth += hgap; |
| minWidth += hgap; |
| } |
| |
| var hPadding:Number = paddingLeft + paddingRight; |
| var vPadding:Number = paddingTop + paddingBottom; |
| |
| layoutTarget.measuredHeight = preferredHeight + vPadding; |
| layoutTarget.measuredWidth = preferredWidth + hPadding; |
| layoutTarget.measuredMinHeight = minHeight + vPadding; |
| layoutTarget.measuredMinWidth = minWidth + hPadding; |
| } |
| |
| /** |
| * @private |
| * |
| * Syncs the LinearLayoutVector llv with typicalLayoutElement and |
| * the target's numElements. Calling this function accounts |
| * for the possibility that the typicalLayoutElement has changed, or |
| * something that its preferred size depends on has changed. |
| */ |
| private function updateLLV(layoutTarget:GroupBase):void |
| { |
| if (!llv) |
| { |
| llv = new LinearLayoutVector(LinearLayoutVector.HORIZONTAL) |
| // Virtualization defaults for cases |
| // where there are no items and no typical item. |
| // The llv defaults are the width/height of a Spark Button skin. |
| llv.defaultMinorSize = 22; |
| llv.defaultMajorSize = 71; |
| } |
| |
| var typicalElt:ILayoutElement = typicalLayoutElement; |
| if (typicalElt) |
| { |
| var typicalWidth:Number = typicalElt.getPreferredBoundsWidth(); |
| var typicalHeight:Number = typicalElt.getPreferredBoundsHeight(); |
| llv.defaultMinorSize = typicalHeight; |
| llv.defaultMajorSize = typicalWidth; |
| } |
| |
| if (!isNaN(_columnWidth)) |
| llv.defaultMajorSize = _columnWidth; |
| |
| if (layoutTarget) |
| llv.length = layoutTarget.numElements; |
| |
| llv.gap = gap; |
| llv.majorAxisOffset = paddingLeft; |
| } |
| |
| /** |
| * @private |
| */ |
| override public function elementAdded(index:int):void |
| { |
| if ((index >= 0) && useVirtualLayout && llv) |
| llv.insert(index); // insert index parameter is uint |
| } |
| |
| /** |
| * @private |
| */ |
| override public function elementRemoved(index:int):void |
| { |
| if ((index >= 0) && useVirtualLayout && llv) |
| llv.remove(index); // remove index parameter is uint |
| } |
| |
| /** |
| * @private |
| * |
| * Compute potentially approximate values for measuredWidth,Height and |
| * measuredMinWidth,Height. |
| * |
| * This method does not get layout elements from the target except |
| * as a side effect of calling typicalLayoutElement. |
| * |
| * If variableColumnWidth="false" then all dimensions are based on |
| * typicalLayoutElement and the sizes already cached in llv. The |
| * llv's defaultMajorSize, minorSize, and minMinorSize |
| * are based on typicalLayoutElement. |
| */ |
| private function measureVirtual(layoutTarget:GroupBase):void |
| { |
| var eltCount:int = layoutTarget.numElements; |
| var measuredEltCount:int = getColumsToMeasure(eltCount); |
| |
| var hPadding:Number = paddingLeft + paddingRight; |
| var vPadding:Number = paddingTop + paddingBottom; |
| |
| if (measuredEltCount <= 0) |
| { |
| layoutTarget.measuredWidth = layoutTarget.measuredMinWidth = hPadding; |
| layoutTarget.measuredHeight = layoutTarget.measuredMinHeight = vPadding; |
| return; |
| } |
| |
| updateLLV(layoutTarget); |
| if (variableColumnWidth) |
| { |
| // Special case: fewer elements than requestedColumnCount, so temporarily |
| // make llv.length == requestedColumnCount. |
| var oldLength:int = -1; |
| if (measuredEltCount > llv.length) |
| { |
| oldLength = llv.length; |
| llv.length = measuredEltCount; |
| } |
| |
| // paddingRight is already taken into account as the majorAxisOffset of the llv |
| // Measured size according to the cached actual size: |
| var measuredWidth:Number = llv.end(measuredEltCount - 1) + paddingRight; |
| |
| // For the live ItemRenderers use the preferred size |
| // instead of the cached actual size: |
| var dataGroupTarget:DataGroup = layoutTarget as DataGroup; |
| if (dataGroupTarget) |
| { |
| var indices:Vector.<int> = dataGroupTarget.getItemIndicesInView(); |
| for each (var i:int in indices) |
| { |
| var element:ILayoutElement = dataGroupTarget.getElementAt(i); |
| if (element) |
| { |
| measuredWidth -= llv.getMajorSize(i); |
| measuredWidth += element.getPreferredBoundsWidth(); |
| } |
| } |
| } |
| |
| layoutTarget.measuredWidth = measuredWidth; |
| |
| if (oldLength != -1) |
| llv.length = oldLength; |
| } |
| else |
| { |
| var hgap:Number = (measuredEltCount > 1) ? (measuredEltCount - 1) * gap : 0; |
| layoutTarget.measuredWidth = (measuredEltCount * columnWidth) + hgap + hPadding; |
| } |
| layoutTarget.measuredHeight = llv.minorSize + vPadding; |
| |
| layoutTarget.measuredMinWidth = layoutTarget.measuredWidth; |
| layoutTarget.measuredMinHeight = (verticalAlign == VerticalAlign.JUSTIFY) ? |
| llv.minMinorSize + vPadding : layoutTarget.measuredHeight; |
| } |
| |
| /** |
| * @private |
| * |
| * If requestedColumnCount is specified then as many layout elements |
| * or "columns" are measured, starting with element 0, otherwise all of the |
| * layout elements are measured. |
| * |
| * If requestedColumnCount is specified and is greater than the |
| * number of layout elements, then the typicalLayoutElement is used |
| * in place of the missing layout elements. |
| * |
| * If variableColumnWidth="true", then the layoutTarget's measuredWidth |
| * is the sum of preferred widths of the layout elements, plus the sum of the |
| * gaps between elements, and its measuredHeight is the max of the elements' |
| * preferred heights. |
| * |
| * If variableColumnWidth="false", then the layoutTarget's measuredWidth |
| * is columnWidth multiplied by the number or layout elements, plus the |
| * sum of the gaps between elements. |
| * |
| * The layoutTarget's measuredMinWidth is the sum of the minWidths of |
| * layout elements that have specified a value for the percentWidth |
| * property, and the preferredWidth of the elements that have not, |
| * plus the sum of the gaps between elements. |
| * |
| * The difference reflects the fact that elements which specify |
| * percentWidth are considered to be "flexible" and updateDisplayList |
| * will give flexible components at least their minWidth. |
| * |
| * Layout elements that aren't flexible always get their preferred width. |
| * |
| * The layoutTarget's measuredMinHeight is the max of the minHeights for |
| * elements that have specified percentHeight (that are "flexible") and the |
| * preferredHeight of the elements that have not. |
| * |
| * As before the difference is due to the fact that flexible items are only |
| * guaranteed their minHeight. |
| */ |
| override public function measure():void |
| { |
| var layoutTarget:GroupBase = target; |
| if (!layoutTarget) |
| return; |
| |
| if (useVirtualLayout) |
| measureVirtual(layoutTarget); |
| else |
| measureReal(layoutTarget); |
| |
| // Use Math.ceil() to make sure that if the content partially occupies |
| // the last pixel, we'll count it as if the whole pixel is occupied. |
| layoutTarget.measuredWidth = Math.ceil(layoutTarget.measuredWidth); |
| layoutTarget.measuredHeight = Math.ceil(layoutTarget.measuredHeight); |
| layoutTarget.measuredMinWidth = Math.ceil(layoutTarget.measuredMinWidth); |
| layoutTarget.measuredMinHeight = Math.ceil(layoutTarget.measuredMinHeight); |
| } |
| |
| /** |
| * @private |
| */ |
| override public function getNavigationDestinationIndex(currentIndex:int, navigationUnit:uint, arrowKeysWrapFocus:Boolean):int |
| { |
| if (!target || target.numElements < 1) |
| return -1; |
| |
| var maxIndex:int = target.numElements - 1; |
| |
| // Special case when nothing was previously selected |
| if (currentIndex == -1) |
| { |
| if (navigationUnit == NavigationUnit.LEFT) |
| return arrowKeysWrapFocus ? maxIndex : -1; |
| |
| if (navigationUnit == NavigationUnit.RIGHT) |
| return 0; |
| } |
| |
| // Make sure currentIndex is within range |
| currentIndex = Math.max(0, Math.min(maxIndex, currentIndex)); |
| |
| var newIndex:int; |
| var bounds:Rectangle; |
| var x:Number; |
| |
| switch (navigationUnit) |
| { |
| case NavigationUnit.LEFT: |
| { |
| if (arrowKeysWrapFocus && currentIndex == 0) |
| newIndex = maxIndex; |
| else |
| newIndex = currentIndex - 1; |
| break; |
| } |
| |
| case NavigationUnit.RIGHT: |
| { |
| if (arrowKeysWrapFocus && currentIndex == maxIndex) |
| newIndex = 0; |
| else |
| newIndex = currentIndex + 1; |
| break; |
| } |
| |
| case NavigationUnit.PAGE_UP: |
| case NavigationUnit.PAGE_LEFT: |
| { |
| // Find the first fully visible element |
| var firstVisible:int = firstIndexInView; |
| var firstFullyVisible:int = firstVisible; |
| if (fractionOfElementInView(firstFullyVisible) < 1) |
| firstFullyVisible += 1; |
| |
| // Is the current element in the middle of the viewport? |
| if (firstFullyVisible < currentIndex && currentIndex <= lastIndexInView) |
| newIndex = firstFullyVisible; |
| else |
| { |
| // Find an element that's one page left |
| if (currentIndex == firstFullyVisible || currentIndex == firstVisible) |
| { |
| // currentIndex is visible, we can calculate where the scrollRect top |
| // would end up if we scroll by a page |
| x = getHorizontalScrollPositionDelta(NavigationUnit.PAGE_LEFT) + getScrollRect().left; |
| } |
| else |
| { |
| // currentIndex is not visible, just find an element a page left from currentIndex |
| x = getElementBounds(currentIndex).right - getScrollRect().width; |
| } |
| |
| // Find the element after the last element that spans left of the x position |
| newIndex = currentIndex - 1; |
| while (0 <= newIndex) |
| { |
| bounds = getElementBounds(newIndex); |
| if (bounds && bounds.left < x) |
| { |
| // This element spans the y position, so return the next one |
| newIndex = Math.min(currentIndex - 1, newIndex + 1); |
| break; |
| } |
| newIndex--; |
| } |
| } |
| break; |
| } |
| |
| case NavigationUnit.PAGE_DOWN: |
| case NavigationUnit.PAGE_RIGHT: |
| { |
| // Find the last fully visible element: |
| var lastVisible:int = lastIndexInView; |
| var lastFullyVisible:int = lastVisible; |
| if (fractionOfElementInView(lastFullyVisible) < 1) |
| lastFullyVisible -= 1; |
| |
| // Is the current element in the middle of the viewport? |
| if (firstIndexInView <= currentIndex && currentIndex < lastFullyVisible) |
| newIndex = lastFullyVisible; |
| else |
| { |
| // Find an element that's one page right |
| if (currentIndex == lastFullyVisible || currentIndex == lastVisible) |
| { |
| // currentIndex is visible, we can calculate where the scrollRect bottom |
| // would end up if we scroll by a page |
| x = getHorizontalScrollPositionDelta(NavigationUnit.PAGE_RIGHT) + getScrollRect().right; |
| } |
| else |
| { |
| // currentIndex is not visible, just find an element a page right from currentIndex |
| x = getElementBounds(currentIndex).left + getScrollRect().width; |
| } |
| |
| // Find the element before the first element that spans right of the y position |
| newIndex = currentIndex + 1; |
| while (newIndex <= maxIndex) |
| { |
| bounds = getElementBounds(newIndex); |
| if (bounds && bounds.right > x) |
| { |
| // This element spans the y position, so return the previous one |
| newIndex = Math.max(currentIndex + 1, newIndex - 1); |
| break; |
| } |
| newIndex++; |
| } |
| } |
| break; |
| } |
| |
| default: return super.getNavigationDestinationIndex(currentIndex, navigationUnit, arrowKeysWrapFocus); |
| } |
| return Math.max(0, Math.min(maxIndex, newIndex)); |
| } |
| |
| /** |
| * @private |
| * |
| * Used only for virtual layout. |
| */ |
| private function calculateElementHeight(elt:ILayoutElement, targetHeight:Number, containerHeight:Number):Number |
| { |
| // If percentHeight is specified then the element's height is the percentage |
| // of targetHeight clipped to min/maxHeight and to (upper limit) targetHeight. |
| var percentHeight:Number = elt.percentHeight; |
| if (!isNaN(percentHeight)) |
| { |
| var height:Number = percentHeight * 0.01 * targetHeight; |
| return Math.min(targetHeight, Math.min(elt.getMaxBoundsHeight(), Math.max(elt.getMinBoundsHeight(), height))); |
| } |
| switch(verticalAlign) |
| { |
| case VerticalAlign.JUSTIFY: |
| return targetHeight; |
| case VerticalAlign.CONTENT_JUSTIFY: |
| return Math.max(elt.getPreferredBoundsHeight(), containerHeight); |
| } |
| return NaN; // not constrained |
| } |
| |
| /** |
| * @private |
| * |
| * Used only for virtual layout. |
| */ |
| private function calculateElementY(elt:ILayoutElement, eltHeight:Number, containerHeight:Number):Number |
| { |
| switch(verticalAlign) |
| { |
| case VerticalAlign.MIDDLE: |
| return Math.round((containerHeight - eltHeight) * 0.5); |
| case VerticalAlign.BOTTOM: |
| return containerHeight - eltHeight; |
| } |
| return 0; // VerticalAlign.TOP |
| } |
| |
| /** |
| * @private |
| * |
| * Update the layout of the virtualized elements that overlap |
| * the scrollRect's horizontal extent. |
| * |
| * The width of each layout element will be its preferred width, and its |
| * x will be the right edge of the previous item, plus the gap. |
| * |
| * No support for percentWidth, includeInLayout=false, or null layoutElements, |
| * |
| * The height of each layout element will be set to its preferred height, unless |
| * one of the following is true: |
| * |
| * - If percentHeight is specified for this element, then its height will be the |
| * specified percentage of the target's actual (unscaled) height, clipped |
| * the layout element's minimum and maximum height. |
| * |
| * - If verticalAlign is "justify", then the element's height will |
| * be set to the target's actual (unscaled) height. |
| * |
| * - If verticalAlign is "contentJustify", then the element's height |
| * will be set to the target's content height. |
| * |
| * The Y coordinate of each layout element will be set to 0 unless one of the |
| * following is true: |
| * |
| * - If verticalAlign is "middle" then y is set so that the element's preferred |
| * height is centered within the larget of the contentHeight and the target's height: |
| * y = (Math.max(contentHeight, target.height) - layoutElementHeight) * 0.5 |
| * |
| * - If verticalAlign is "bottom" the y is set so that the element's bottom |
| * edge is aligned with the the bottom edge of the content: |
| * y = (Math.max(contentHeight, target.height) - layoutElementHeight) |
| * |
| * Implementation note: unless verticalAlign is either "justify" or |
| * "top", the layout elements' y or height depends on the contentHeight. |
| * The contentHeight is a maximum and although it may be updated to |
| * different value after all (viewable) elements have been laid out, it |
| * often does not change. For that reason we use the current contentHeight |
| * for the initial layout and then, if it has changed, we loop through |
| * the layout items again and fix up the y/height values. |
| */ |
| private function updateDisplayListVirtual():void |
| { |
| var layoutTarget:GroupBase = target; |
| var eltCount:int = layoutTarget.numElements; |
| var targetHeight:Number = Math.max(0, layoutTarget.height - paddingTop - paddingBottom); |
| var minVisibleX:Number = layoutTarget.horizontalScrollPosition; |
| var maxVisibleX:Number = minVisibleX + layoutTarget.width; |
| |
| var contentWidth:Number; |
| var paddedContentWidth:Number; |
| |
| updateLLV(layoutTarget); |
| |
| // Find the index of the first visible item. Since the item's bounds includes the gap |
| // that follows it, we want to avoid looking at an item that has only a portion of |
| // its gap intersecting with the visible region. |
| // We have to also be careful, as gap could be negative and in that case, we should |
| // simply start from minVisibleX - SDK-22497. |
| var startIndex:int = llv.indexOf(Math.max(0, minVisibleX + gap)); |
| if (startIndex == -1) |
| { |
| // No items are visible. Just set the content size. |
| contentWidth = llv.end(llv.length - 1) - paddingLeft; |
| paddedContentWidth = Math.ceil(contentWidth + paddingLeft + paddingRight); |
| layoutTarget.setContentSize(paddedContentWidth, layoutTarget.contentHeight); |
| return; |
| } |
| |
| var fixedColumnWidth:Number = NaN; |
| if (!variableColumnWidth) |
| fixedColumnWidth = columnWidth; // may query typicalLayoutElement, elt at index=0 |
| |
| var justifyHeights:Boolean = verticalAlign == VerticalAlign.JUSTIFY; |
| var eltWidth:Number = NaN; |
| var eltHeight:Number = (justifyHeights) ? Math.max(llv.minMinorSize, targetHeight) : llv.minorSize; |
| var contentHeight:Number = (justifyHeights) ? Math.max(llv.minMinorSize, targetHeight) : llv.minorSize; |
| var containerHeight:Number = Math.max(contentHeight, targetHeight); |
| var x:Number = llv.start(startIndex); |
| var index:int = startIndex; |
| var y0:Number = paddingTop; |
| |
| // First pass: compute element x,y,width,height based on |
| // current contentHeight; cache computed widths/heights in llv. |
| for (; (x < maxVisibleX) && (index < eltCount); index++) |
| { |
| // TODO (hmuller): should pass in eltWidth, eltHeight |
| var elt:ILayoutElement = layoutTarget.getVirtualElementAt(index); |
| var w:Number = fixedColumnWidth; // NaN for variable width columns |
| var h:Number = calculateElementHeight(elt, targetHeight, containerHeight); // can be NaN |
| elt.setLayoutBoundsSize(w, h); |
| w = elt.getLayoutBoundsWidth(); |
| h = elt.getLayoutBoundsHeight(); |
| var y:Number = y0 + calculateElementY(elt, h, containerHeight); |
| elt.setLayoutBoundsPosition(x, y); |
| llv.cacheDimensions(index, elt); |
| x += w + gap; |
| } |
| var endIndex:int = index - 1; |
| |
| // Second pass: if neccessary, fix up y and height values based |
| // on the updated contentHeight |
| if (!justifyHeights && (llv.minorSize != contentHeight)) |
| { |
| contentHeight = llv.minorSize; |
| containerHeight = Math.max(contentHeight, targetHeight); |
| if ((verticalAlign != VerticalAlign.TOP) && (verticalAlign != VerticalAlign.JUSTIFY)) |
| { |
| for (index = startIndex; index <= endIndex; index++) |
| { |
| elt = layoutTarget.getElementAt(index); |
| h = calculateElementHeight(elt, targetHeight, containerHeight); // can be NaN |
| elt.setLayoutBoundsSize(elt.getLayoutBoundsWidth(), h); |
| h = elt.getLayoutBoundsHeight(); |
| y = y0 + calculateElementY(elt, h, containerHeight); |
| elt.setLayoutBoundsPosition(elt.getLayoutBoundsX(), y); |
| } |
| } |
| } |
| |
| // Third pass: if neccessary, fix up x based on updated contentWidth |
| contentWidth = llv.end(llv.length - 1) - paddingLeft; |
| var targetWidth:Number = Math.max(0, layoutTarget.width - paddingLeft - paddingRight); |
| if (contentWidth < targetWidth) |
| { |
| var excessWidth:Number = targetWidth - contentWidth; |
| var dx:Number = 0; |
| var hAlign:String = horizontalAlign; |
| if (hAlign == HorizontalAlign.CENTER) |
| { |
| dx = Math.round(excessWidth / 2); |
| } |
| else if (hAlign == HorizontalAlign.RIGHT) |
| { |
| dx = excessWidth; |
| } |
| if (dx > 0) |
| { |
| for (index = startIndex; index <= endIndex; index++) |
| { |
| elt = layoutTarget.getElementAt(index); |
| elt.setLayoutBoundsPosition(dx + elt.getLayoutBoundsX(), elt.getLayoutBoundsY()); |
| } |
| contentWidth += dx; |
| } |
| } |
| |
| setColumnCount(index - startIndex); |
| setIndexInView(startIndex, endIndex); |
| |
| // Make sure that if the content spans partially over a pixel to the right/bottom, |
| // the content size includes the whole pixel. |
| paddedContentWidth = Math.ceil(contentWidth + paddingLeft + paddingRight); |
| var paddedContentHeight:Number = Math.ceil(contentHeight + paddingTop + paddingBottom); |
| layoutTarget.setContentSize(paddedContentWidth, paddedContentHeight); |
| } |
| |
| /** |
| * @private |
| */ |
| private function updateDisplayListReal():void |
| { |
| var layoutTarget:GroupBase = target; |
| var targetWidth:Number = Math.max(0, layoutTarget.width - paddingLeft - paddingRight); |
| var targetHeight:Number = Math.max(0, layoutTarget.height - paddingTop - paddingBottom); |
| |
| var layoutElement:ILayoutElement; |
| var count:int = layoutTarget.numElements; |
| |
| // If verticalAlign is top, we don't need to figure out the contentHeight. |
| // Otherwise the contentHeight is used to position the element and even size |
| // the element if it's "contentJustify" or "justify". |
| var containerHeight:Number = targetHeight; |
| if (verticalAlign == VerticalAlign.CONTENT_JUSTIFY || |
| (clipAndEnableScrolling && (verticalAlign == VerticalAlign.MIDDLE || |
| verticalAlign == VerticalAlign.BOTTOM))) |
| { |
| for (var i:int = 0; i < count; i++) |
| { |
| layoutElement = layoutTarget.getElementAt(i); |
| if (!layoutElement || !layoutElement.includeInLayout) |
| continue; |
| |
| var layoutElementHeight:Number; |
| if (!isNaN(layoutElement.percentHeight)) |
| layoutElementHeight = calculatePercentHeight(layoutElement, targetHeight); |
| else |
| layoutElementHeight = layoutElement.getPreferredBoundsHeight(); |
| |
| containerHeight = Math.max(containerHeight, Math.ceil(layoutElementHeight)); |
| } |
| } |
| |
| var excessWidth:Number = distributeWidth(targetWidth, targetHeight, containerHeight); |
| |
| // default to top (0) |
| var vAlign:Number = 0; |
| if (verticalAlign == VerticalAlign.MIDDLE) |
| vAlign = .5; |
| else if (verticalAlign == VerticalAlign.BOTTOM) |
| vAlign = 1; |
| |
| var actualBaseline:Number = 0; |
| var alignToBaseline:Boolean = verticalAlign == VerticalAlign.BASELINE; |
| if (alignToBaseline) |
| { |
| var result:Array = calculateBaselineTopBottom(false /*calculateBottom*/); |
| actualBaseline = result[0]; |
| } |
| |
| // If columnCount wasn't set, then as the LayoutElements are positioned |
| // we'll count how many columns fall within the layoutTarget's scrollRect |
| var visibleColumns:uint = 0; |
| var minVisibleX:Number = layoutTarget.horizontalScrollPosition; |
| var maxVisibleX:Number = minVisibleX + targetWidth |
| |
| // Finally, position the LayoutElements and find the first/last |
| // visible indices, the content size, and the number of |
| // visible elements. |
| var x:Number = paddingLeft; |
| var y0:Number = paddingTop; |
| var maxX:Number = paddingLeft; |
| var maxY:Number = paddingTop; |
| var firstColInView:int = -1; |
| var lastColInView:int = -1; |
| |
| // Take horizontalAlign into account |
| if (excessWidth > 0 || !clipAndEnableScrolling) |
| { |
| var hAlign:String = horizontalAlign; |
| if (hAlign == HorizontalAlign.CENTER) |
| { |
| x = paddingLeft + Math.round(excessWidth / 2); |
| } |
| else if (hAlign == HorizontalAlign.RIGHT) |
| { |
| x = paddingLeft + excessWidth; |
| } |
| } |
| |
| for (var index:int = 0; index < count; index++) |
| { |
| layoutElement = layoutTarget.getElementAt(index); |
| if (!layoutElement || !layoutElement.includeInLayout) |
| continue; |
| |
| // Set the layout element's position |
| var dx:Number = Math.ceil(layoutElement.getLayoutBoundsWidth()); |
| var dy:Number = Math.ceil(layoutElement.getLayoutBoundsHeight()); |
| |
| var y:Number; |
| if (alignToBaseline) |
| { |
| var elementBaseline:Number = layoutElement.baseline as Number; |
| if (isNaN(elementBaseline)) |
| elementBaseline = 0; |
| |
| // Note: don't round the position. Rounding will case the text line to shift by |
| // a pixel and won't look aligned with the other element's text. |
| var baselinePosition:Number = layoutElement.baselinePosition; |
| y = y0 + actualBaseline + elementBaseline - baselinePosition; |
| } |
| else |
| { |
| y = y0 + (containerHeight - dy) * vAlign; |
| // In case we have VerticalAlign.MIDDLE we have to round |
| if (vAlign == 0.5) |
| y = Math.round(y); |
| } |
| |
| layoutElement.setLayoutBoundsPosition(x, y); |
| |
| // Update maxX,Y, first,lastVisibleIndex, and x |
| maxX = Math.max(maxX, x + dx); |
| maxY = Math.max(maxY, y + dy); |
| if (!clipAndEnableScrolling || |
| ((x < maxVisibleX) && ((x + dx) > minVisibleX)) || |
| ((dx <= 0) && ((x == maxVisibleX) || (x == minVisibleX)))) |
| { |
| visibleColumns += 1; |
| if (firstColInView == -1) |
| firstColInView = lastColInView = index; |
| else |
| lastColInView = index; |
| } |
| x += dx + gap; |
| } |
| |
| setColumnCount(visibleColumns); |
| setIndexInView(firstColInView, lastColInView); |
| |
| // Make sure that if the content spans partially over a pixel to the right/bottom, |
| // the content size includes the whole pixel. |
| layoutTarget.setContentSize(Math.ceil(maxX + paddingRight), |
| Math.ceil(maxY + paddingBottom)); |
| } |
| |
| |
| /** |
| * @private |
| * |
| * This function sets the width of each child |
| * so that the widths add up to <code>width</code>. |
| * Each child is set to its preferred width |
| * if its percentWidth is zero. |
| * If its percentWidth is a positive number, |
| * the child grows (or shrinks) to consume its |
| * share of extra space. |
| * |
| * The return value is any extra space that's left over |
| * after growing all children to their maxWidth. |
| */ |
| private function distributeWidth(width:Number, |
| height:Number, |
| restrictedHeight:Number):Number |
| { |
| var spaceToDistribute:Number = width; |
| var totalPercentWidth:Number = 0; |
| var childInfoArray:Array = []; |
| var childInfo:HLayoutElementFlexChildInfo; |
| var newHeight:Number; |
| var layoutElement:ILayoutElement; |
| |
| // columnWidth can be expensive to compute |
| var cw:Number = (variableColumnWidth) ? 0 : Math.ceil(columnWidth); |
| var count:int = target.numElements; |
| var totalCount:int = count; // number of elements to use in gap calculation |
| |
| // If the child is flexible, store information about it in the |
| // childInfoArray. For non-flexible children, just set the child's |
| // width and height immediately. |
| for (var index:int = 0; index < count; index++) |
| { |
| layoutElement = target.getElementAt(index); |
| if (!layoutElement || !layoutElement.includeInLayout) |
| { |
| totalCount--; |
| continue; |
| } |
| |
| if (!isNaN(layoutElement.percentWidth) && variableColumnWidth) |
| { |
| totalPercentWidth += layoutElement.percentWidth; |
| |
| childInfo = new HLayoutElementFlexChildInfo(); |
| childInfo.layoutElement = layoutElement; |
| childInfo.percent = layoutElement.percentWidth; |
| childInfo.min = layoutElement.getMinBoundsWidth(); |
| childInfo.max = layoutElement.getMaxBoundsWidth(); |
| |
| childInfoArray.push(childInfo); |
| } |
| else |
| { |
| sizeLayoutElement(layoutElement, height, verticalAlign, |
| restrictedHeight, NaN, variableColumnWidth, cw); |
| |
| spaceToDistribute -= Math.ceil(layoutElement.getLayoutBoundsWidth()); |
| } |
| } |
| |
| if (totalCount > 1) |
| spaceToDistribute -= (totalCount-1) * gap; |
| |
| // Distribute the extra space among the flexible children |
| if (totalPercentWidth) |
| { |
| Flex.flexChildrenProportionally(width, |
| spaceToDistribute, |
| totalPercentWidth, |
| childInfoArray); |
| var roundOff:Number = 0; |
| for each (childInfo in childInfoArray) |
| { |
| // Make sure the calculated percentages are rounded to pixel boundaries |
| var childSize:int = Math.round(childInfo.size + roundOff); |
| roundOff += childInfo.size - childSize; |
| |
| sizeLayoutElement(childInfo.layoutElement, height, verticalAlign, |
| restrictedHeight, childSize, |
| variableColumnWidth, cw); |
| spaceToDistribute -= childSize; |
| } |
| } |
| return spaceToDistribute; |
| } |
| |
| /** |
| * @private |
| */ |
| override public function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void |
| { |
| super.updateDisplayList(unscaledWidth, unscaledHeight); |
| |
| var layoutTarget:GroupBase = target; |
| if (!layoutTarget) |
| return; |
| |
| if ((layoutTarget.numElements == 0) || (unscaledWidth == 0) || (unscaledHeight == 0)) |
| { |
| setColumnCount(0); |
| setIndexInView(-1, -1); |
| if (layoutTarget.numElements == 0) |
| layoutTarget.setContentSize(Math.ceil(paddingLeft + paddingRight), |
| Math.ceil(paddingTop + paddingBottom)); |
| return; |
| } |
| |
| if (useVirtualLayout) |
| updateDisplayListVirtual(); |
| else |
| updateDisplayListReal(); |
| } |
| |
| /** |
| * @private |
| * Convenience function for subclasses that invalidates the |
| * target's size and displayList so that both layout's <code>measure()</code> |
| * and <code>updateDisplayList</code> methods get called. |
| * |
| * <p>Typically a layout invalidates the target's size and display list so that |
| * it gets a chance to recalculate the target's default size and also size and |
| * position the target's elements. For example changing the <code>gap</code> |
| * property on a <code>VerticalLayout</code> will internally call this method |
| * to ensure that the elements are re-arranged with the new setting and the |
| * target's default size is recomputed.</p> |
| */ |
| private function invalidateTargetSizeAndDisplayList():void |
| { |
| var g:GroupBase = target; |
| if (!g) |
| return; |
| |
| g.invalidateSize(); |
| g.invalidateDisplayList(); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Drop methods |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| */ |
| override protected function calculateDropIndex(x:Number, y:Number):int |
| { |
| // Iterate over the visible elements |
| var layoutTarget:GroupBase = target; |
| var count:int = layoutTarget.numElements; |
| |
| // If there are no items, insert at index 0 |
| if (count == 0) |
| return 0; |
| |
| // Go through the visible elements |
| var minDistance:Number = Number.MAX_VALUE; |
| var bestIndex:int = -1; |
| var start:int = this.firstIndexInView; |
| var end:int = this.lastIndexInView; |
| |
| for (var i:int = start; i <= end; i++) |
| { |
| var elementBounds:Rectangle = this.getElementBounds(i); |
| if (!elementBounds) |
| continue; |
| |
| if (elementBounds.left <= x && x <= elementBounds.right) |
| { |
| var centerX:Number = elementBounds.x + elementBounds.width / 2; |
| return (x < centerX) ? i : i + 1; |
| } |
| |
| var curDistance:Number = Math.min(Math.abs(x - elementBounds.left), |
| Math.abs(x - elementBounds.right)); |
| if (curDistance < minDistance) |
| { |
| minDistance = curDistance; |
| bestIndex = (x < elementBounds.left) ? i : i + 1; |
| } |
| } |
| |
| // If there are no visible elements, either pick to drop at the beginning or at the end |
| if (bestIndex == -1) |
| bestIndex = getElementBounds(0).x < x ? count : 0; |
| |
| return bestIndex; |
| } |
| |
| /** |
| * @private |
| */ |
| override protected function calculateDropIndicatorBounds(dropLocation:DropLocation):Rectangle |
| { |
| var dropIndex:int = dropLocation.dropIndex; |
| var count:int = target.numElements; |
| var gap:Number = this.gap; |
| |
| // Special case, if we insert at the end, and the gap is negative, consider it to be zero |
| if (gap < 0 && dropIndex == count) |
| gap = 0; |
| |
| var emptySpace:Number = (0 < gap ) ? gap : 0; |
| var emptySpaceLeft:Number = 0; |
| if (target.numElements > 0) |
| { |
| emptySpaceLeft = (dropIndex < count) ? getElementBounds(dropIndex).left - emptySpace : |
| getElementBounds(dropIndex - 1).right + gap - emptySpace; |
| } |
| |
| // Calculate the size of the bounds, take minium and maximum into account |
| var width:Number = emptySpace; |
| var height:Number = Math.max(target.height, target.contentHeight) - paddingTop - paddingBottom; |
| if (dropIndicator is IVisualElement) |
| { |
| var element:IVisualElement = IVisualElement(dropIndicator); |
| width = Math.max(Math.min(width, element.getMaxBoundsWidth(false)), element.getMinBoundsWidth(false)); |
| } |
| |
| var x:Number = emptySpaceLeft + Math.round((emptySpace - width)/2); |
| // Allow 1 pixel overlap with container border |
| x = Math.max(-Math.ceil(width / 2), Math.min(target.contentWidth - Math.ceil(width/2), x)); |
| |
| var y:Number = paddingTop; |
| return new Rectangle(x, y, width, height); |
| } |
| |
| /** |
| * @private |
| */ |
| override protected function calculateDragScrollDelta(dropLocation:DropLocation, |
| elapsedTime:Number):Point |
| { |
| var delta:Point = super.calculateDragScrollDelta(dropLocation, elapsedTime); |
| // Don't scroll in the vertical direction |
| if (delta) |
| delta.y = 0; |
| return delta; |
| } |
| |
| /** |
| * @private |
| * Identifies the element which has its "compare point" located closest |
| * to the specified position. |
| */ |
| override mx_internal function getElementNearestScrollPosition( |
| position:Point, |
| elementComparePoint:String = "center"):int |
| { |
| if (!useVirtualLayout) |
| return super.getElementNearestScrollPosition(position, elementComparePoint); |
| |
| var g:GroupBase = GroupBase(target); |
| if (!g) |
| return -1; |
| |
| // We need a valid LLV for this function |
| updateLLV(g); |
| |
| // Find the element which overlaps with the position |
| var index:int = llv.indexOf(position.x); |
| |
| // Invalid index indicates that the position is past either end of the |
| // laid-out elements. In this case, choose either the first or last one. |
| if (index == -1) |
| index = position.x < 0 ? 0 : g.numElements - 1; |
| |
| var bounds:Rectangle = llv.getBounds(index); |
| var adjacentBounds:Rectangle; |
| |
| // If we're comparing with a right-edge point, check both the current element and the element |
| // at index-1 to see which is closest. |
| if ((elementComparePoint == "topRight" || elementComparePoint == "bottomRight") && index > 0) |
| { |
| adjacentBounds = llv.getBounds(index - 1); |
| if (Point.distance(position, adjacentBounds.bottomRight) < Point.distance(position, bounds.bottomRight)) |
| index--; |
| } |
| |
| // If we're comparing with a left-edge point, check both the current element and the element |
| // at index+1 to see which is closest. |
| if ((elementComparePoint == "topLeft" || elementComparePoint == "bottomLeft") && index < g.numElements-1) |
| { |
| adjacentBounds = llv.getBounds(index + 1); |
| if (Point.distance(position, adjacentBounds.topLeft) < Point.distance(position, bounds.topLeft)) |
| index++; |
| } |
| return index; |
| } |
| } |
| } |
| |
| import mx.containers.utilityClasses.FlexChildInfo; |
| import mx.core.ILayoutElement; |
| class HLayoutElementFlexChildInfo extends FlexChildInfo |
| { |
| public var layoutElement:ILayoutElement; |
| } |
| |
| class SizesAndLimit |
| { |
| public var preferredSize:Number; |
| public var minSize:Number; |
| } |