| //////////////////////////////////////////////////////////////////////////////// |
| // |
| // 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 VerticalLayout class arranges the layout elements in a vertical sequence, |
| * top to bottom, with optional gaps between the elements and optional padding |
| * around the sequence of elements. |
| * |
| * <p>The vertical position of the elements is determined by arranging them |
| * in a vertical sequence, top to bottom, taking into account the padding |
| * before the first element and the gaps between the elements.</p> |
| * |
| * <p>The horizontal position of the elements is determined by the layout's |
| * <code>horizontalAlign</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 <code>requestedRowCount</code> is set, 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 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 <code>variableRowHeight</code> is <code>false</code>, |
| * then set the element's height to the |
| * value of the <code>rowHeight</code> property.</li> |
| * |
| * <li>If the element's <code>percentHeight</code> is set, then calculate the element's |
| * height by distributing the available container height between all |
| * elements with a <code>percentHeight</code> setting. |
| * The available container height |
| * is equal to the container height minus the gaps, the padding and the |
| * space occupied by the rest of the elements. The element's <code>precentHeight</code> |
| * property is ignored when the layout is virtualized.</li> |
| * |
| * <li>Set the element's height to its preferred height.</li> |
| * </ul> |
| * |
| * <p>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>horizontalAlign</code> is <code>"justify"</code>, |
| * then set the element's width to the container width.</li> |
| * |
| * <li>If <code>horizontalAlign</code> is <code>"contentJustify"</code>, |
| * then set the element's width to the maximum between the container's width |
| * and all elements' preferred width.</li> |
| * |
| * <li>If the element's <code>percentWidth</code> is set, then calculate the element's |
| * width as a percentage of the container's width.</li> |
| * |
| * <li>Set the element's width to its preferred width.</li> |
| * </ul> |
| * |
| * @mxml |
| * <p>The <code><s:VerticalLayout></code> tag inherits all of the tag |
| * attributes of its superclass and adds the following tag attributes:</p> |
| * |
| * <pre> |
| * <s:VerticalLayout |
| * <strong>Properties</strong> |
| * gap="6" |
| * horizontalAlign="left" |
| * paddingBottom="0" |
| * paddingLeft="0" |
| * paddingRight="0" |
| * paddingTop="0" |
| * requestedMaxRowCount="-1" |
| * requestedMinRowCount="-1" |
| * requestedRowCount="-1" |
| * rowHeight="<i>calculated</i>" |
| * variableRowHeight="true" |
| * verticalAlign="top" |
| * /> |
| * </pre> |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public class VerticalLayout extends LayoutBase |
| { |
| include "../core/Version.as"; |
| |
| /** |
| * @private |
| * Cached row heights, max column width for virtual layout. Not used unless |
| * useVirtualLayout=true. See updateLLV(), resetCachedVirtualLayoutState(), |
| * etc. |
| */ |
| private var llv:LinearLayoutVector; |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Class methods |
| // |
| //-------------------------------------------------------------------------- |
| |
| private static function calculatePercentWidth(layoutElement:ILayoutElement, width:Number):Number |
| { |
| var percentWidth:Number; |
| if (FlexVersion.compatibilityVersion < FlexVersion.VERSION_4_6) |
| { |
| percentWidth = LayoutElementHelper.pinBetween(Math.round(layoutElement.percentWidth * 0.01 * width), |
| layoutElement.getMinBoundsWidth(), |
| layoutElement.getMaxBoundsWidth() ); |
| return percentWidth < width ? percentWidth : width; |
| } |
| else |
| { |
| percentWidth = LayoutElementHelper.pinBetween(Math.min(Math.round(layoutElement.percentWidth * 0.01 * width), width), |
| layoutElement.getMinBoundsWidth(), |
| layoutElement.getMaxBoundsWidth() ); |
| return percentWidth; |
| } |
| } |
| |
| private static function sizeLayoutElement(layoutElement:ILayoutElement, |
| width:Number, |
| horizontalAlign:String, |
| restrictedWidth:Number, |
| height:Number, |
| variableRowHeight:Boolean, |
| rowHeight:Number):void |
| { |
| var newWidth:Number = NaN; |
| |
| // if horizontalAlign is "justify" or "contentJustify", |
| // restrict the width to restrictedWidth. Otherwise, |
| // size it normally |
| if (horizontalAlign == HorizontalAlign.JUSTIFY || |
| horizontalAlign == HorizontalAlign.CONTENT_JUSTIFY) |
| { |
| newWidth = restrictedWidth; |
| } |
| else |
| { |
| if (!isNaN(layoutElement.percentWidth)) |
| newWidth = calculatePercentWidth(layoutElement, width); |
| } |
| |
| if (variableRowHeight) |
| layoutElement.setLayoutBoundsSize(newWidth, height); |
| else |
| layoutElement.setLayoutBoundsSize(newWidth, rowHeight); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Constructor |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * Constructor. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function VerticalLayout():void |
| { |
| super(); |
| |
| // Don't drag-scroll in the horizontal direction |
| dragScrollRegionSizeHorizontal = 0; |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Properties |
| // |
| //-------------------------------------------------------------------------- |
| |
| //---------------------------------- |
| // gap |
| //---------------------------------- |
| |
| private var _gap:int = 6; |
| |
| [Inspectable(category="General")] |
| |
| /** |
| * The vertical 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(); |
| } |
| |
| //---------------------------------- |
| // rowCount |
| //---------------------------------- |
| |
| private var _rowCount:int = -1; |
| |
| [Bindable("propertyChange")] |
| [Inspectable(category="General")] |
| |
| /** |
| * The current number of visible elements. |
| * |
| * @default -1 |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get rowCount():int |
| { |
| return _rowCount; |
| } |
| |
| /** |
| * @private |
| * |
| * Sets the <code>rowCount</code> property and dispatches a |
| * PropertyChangeEvent. |
| */ |
| mx_internal function setRowCount(value:int):void |
| { |
| if (_rowCount == value) |
| return; |
| var oldValue:int = _rowCount; |
| _rowCount = value; |
| dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "rowCount", oldValue, value)); |
| } |
| |
| //---------------------------------- |
| // horizontalAlign |
| //---------------------------------- |
| |
| /** |
| * @private |
| */ |
| private var _horizontalAlign:String = HorizontalAlign.LEFT; |
| |
| [Inspectable(category="General", enumeration="left,right,center,justify,contentJustify", defaultValue="left")] |
| |
| /** |
| * The horizontal alignment of layout elements. |
| * 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>If the value is <code>"contentJustify"</code>, then the layout element's actual |
| * width is set to the <code>contentWidth</code> of the container. |
| * The <code>contentWidth</code> of the container is the width of the largest layout element. |
| * If all layout elements are smaller than the width of the container, |
| * then set the width of all layout elements to the width of the container.</p> |
| * |
| * <p>If the value is <code>"justify"</code> then the layout element's actual width |
| * is set to 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", defaultValue="top")] |
| |
| /** |
| * The vertical alignment of the content relative to the container's height. |
| * |
| * <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>This property has no effect when <code>clipAndEnableScrolling</code> is true |
| * and the <code>contentHeight</code> is greater than the container's height.</p> |
| * |
| * <p>This property does not affect the layout's measured size.</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; |
| |
| _verticalAlign = value; |
| |
| var layoutTarget:GroupBase = target; |
| if (layoutTarget) |
| layoutTarget.invalidateDisplayList(); |
| } |
| |
| //---------------------------------- |
| // paddingLeft |
| //---------------------------------- |
| |
| private var _paddingLeft:Number = 0; |
| |
| [Inspectable(category="General")] |
| |
| /** |
| * The minimum number of pixels between the container's left edge and |
| * the left edge of the 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")] |
| |
| /** |
| * The minimum number of pixels between the container's right edge and |
| * the right edge of the 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")] |
| |
| /** |
| * Number of pixels between the container's top edge |
| * and the top edge of the first layout element. |
| * |
| * @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")] |
| |
| /** |
| * Number of pixels between the container's bottom edge |
| * and the bottom edge of the last layout element. |
| * |
| * @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(); |
| } |
| |
| //---------------------------------- |
| // requestedMaxRowCount |
| //---------------------------------- |
| |
| private var _requestedMaxRowCount:int = -1; |
| |
| [Inspectable(category="General", minValue="-1")] |
| |
| /** |
| * The measured height of this layout is large enough to display |
| * at most <code>requestedMaxRowCount</code> layout elements. |
| * |
| * <p>If <code>requestedRowCount</code> is set, then |
| * this property has no effect.</p> |
| * |
| * <p>If the actual size of the container has been explicitly set, |
| * then this property has no effect.</p> |
| * |
| * @default -1 |
| * @see #requestedRowCount |
| * @see #requestedMinRowCount |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 2.5 |
| * @productversion Flex 4.5 |
| */ |
| public function get requestedMaxRowCount():int |
| { |
| return _requestedMaxRowCount; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set requestedMaxRowCount(value:int):void |
| { |
| if (_requestedMaxRowCount == value) |
| return; |
| |
| _requestedMaxRowCount = value; |
| |
| if (target) |
| target.invalidateSize(); |
| } |
| |
| //---------------------------------- |
| // requestedMinRowCount |
| //---------------------------------- |
| |
| private var _requestedMinRowCount:int = -1; |
| |
| [Inspectable(category="General", minValue="-1")] |
| |
| /** |
| * The measured height of this layout is large enough to display |
| * at least <code>requestedMinRowCount</code> layout elements. |
| * |
| * <p>If <code>requestedRowCount</code> is set, then |
| * this property has no effect.</p> |
| * |
| * <p>If the actual size of the container has been explicitly set, |
| * then this property has no effect.</p> |
| * |
| * @default -1 |
| * @see #requestedRowCount |
| * @see #requestedMaxRowCount |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 2.5 |
| * @productversion Flex 4.5 |
| */ |
| public function get requestedMinRowCount():int |
| { |
| return _requestedMinRowCount; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set requestedMinRowCount(value:int):void |
| { |
| if (_requestedMinRowCount == value) |
| return; |
| |
| _requestedMinRowCount = value; |
| |
| if (target) |
| target.invalidateSize(); |
| } |
| |
| //---------------------------------- |
| // requestedRowCount |
| //---------------------------------- |
| |
| private var _requestedRowCount:int = -1; |
| |
| [Inspectable(category="General", minValue="-1")] |
| |
| /** |
| * The measured size of this layout is tall enough to display |
| * the first <code>requestedRowCount</code> layout elements. |
| * |
| * <p>If <code>requestedRowCount</code> is -1, then the measured |
| * size will be big enough for all of the layout elements.</p> |
| * |
| * <p>If the actual size of the container has been explicitly set, |
| * then this property has no effect.</p> |
| * |
| * @default -1 |
| * @see #requestedMinRowCount |
| * @see #requestedMaxRowCount |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get requestedRowCount():int |
| { |
| return _requestedRowCount; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set requestedRowCount(value:int):void |
| { |
| if (_requestedRowCount == value) |
| return; |
| |
| _requestedRowCount = value; |
| |
| if (target) |
| target.invalidateSize(); |
| } |
| |
| //---------------------------------- |
| // rowHeight |
| //---------------------------------- |
| |
| private var _rowHeight:Number; |
| |
| [Inspectable(category="General", minValue="0.0")] |
| |
| /** |
| * If <code>variableRowHeight</code> is <code>false</code>, then |
| * this property specifies the actual height of each child, in pixels. |
| * |
| * <p>If <code>variableRowHeight</code> is <code>true</code>, |
| * the default, then this property has no effect.</p> |
| * |
| * <p>The default value of this property is the preferred height |
| * of the <code>typicalLayoutElement</code>.</p> |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get rowHeight():Number |
| { |
| if (!isNaN(_rowHeight)) |
| return _rowHeight; |
| else |
| { |
| var elt:ILayoutElement = typicalLayoutElement |
| return (elt) ? elt.getPreferredBoundsHeight() : 0; |
| } |
| } |
| |
| /** |
| * @private |
| */ |
| public function set rowHeight(value:Number):void |
| { |
| if (_rowHeight == value) |
| return; |
| |
| _rowHeight = value; |
| invalidateTargetSizeAndDisplayList(); |
| } |
| |
| //---------------------------------- |
| // variableRowHeight |
| //---------------------------------- |
| |
| /** |
| * @private |
| */ |
| private var _variableRowHeight:Boolean = true; |
| |
| [Inspectable(category="General", enumeration="true,false")] |
| |
| /** |
| * Specifies whether layout elements are allocated their |
| * preferred height. |
| * Setting this property to <code>false</code> specifies fixed height rows. |
| * |
| * <p>If <code>false</code>, the actual height of each layout element is |
| * the value of <code>rowHeight</code>. |
| * Setting this property to <code>false</code> causes the layout to ignore |
| * the layout elements' <code>percentHeight</code> property.</p> |
| * |
| * @default true |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get variableRowHeight():Boolean |
| { |
| return _variableRowHeight; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set variableRowHeight(value:Boolean):void |
| { |
| if (value == _variableRowHeight) |
| return; |
| |
| _variableRowHeight = value; |
| invalidateTargetSizeAndDisplayList(); |
| } |
| |
| //---------------------------------- |
| // firstIndexInView |
| //---------------------------------- |
| |
| /** |
| * @private |
| */ |
| private var _firstIndexInView:int = -1; |
| |
| [Inspectable(category="General")] |
| [Bindable("indexInViewChanged")] |
| |
| /** |
| * The index of the first layout element that is part of the |
| * layout and within the layout target's scroll rectangle, or -1 |
| * if nothing has been displayed yet. |
| * |
| * <p>"Part of the layout" means that the element is non-null |
| * and that its <code>includeInLayout</code> property is <code>true</code>.</p> |
| * |
| * <p>Note that the layout element may only be partially in view.</p> |
| * |
| * @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 row that's part of the layout and within |
| * the container's scroll rectangle, or -1 if nothing has been displayed yet. |
| * |
| * <p>"Part of the layout" means that the child is non-null |
| * and that its <code>includeInLayout</code> property is <code>true</code>.</p> |
| * |
| * <p>Note that the row may only be partially in view.</p> |
| * |
| * @see firstIndexInView |
| * @see fractionOfElementInView |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get lastIndexInView():int |
| { |
| return _lastIndexInView; |
| } |
| |
| /** |
| * 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 |
| */ |
| mx_internal 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 vAlign:String = verticalAlign; |
| if (vAlign == VerticalAlign.MIDDLE || vAlign == VerticalAlign.BOTTOM) |
| { |
| 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 vertical 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 row. |
| * |
| * @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 = 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; |
| |
| // outside the visible index 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 Y and Height |
| var eltY:Number; |
| var eltHeight:Number; |
| if (useVirtualLayout) |
| { |
| if (!llv) |
| return 0.0; |
| eltY = llv.start(index); |
| eltHeight = llv.getMajorSize(index); |
| } |
| else |
| { |
| var elt:ILayoutElement = g.getElementAt(index); |
| if (!elt || !elt.includeInLayout) |
| return 0.0; |
| eltY = elt.getLayoutBoundsY(); |
| eltHeight = elt.getLayoutBoundsHeight(); |
| } |
| |
| // So, index is either the first or last row in the scrollRect |
| // and potentially partially visible. |
| // y0,y1 - scrollRect top,bottom edges |
| // iy0, iy1 - layout element top,bottom edges |
| var y0:Number = g.verticalScrollPosition; |
| var y1:Number = y0 + g.height; |
| var iy0:Number = eltY; |
| var iy1:Number = iy0 + eltHeight; |
| if (iy0 >= iy1) // element has 0 or negative height |
| return 1.0; |
| if ((iy0 >= y0) && (iy1 <= y1)) |
| return 1.0; |
| return (Math.min(y1, iy1) - Math.max(y0, iy0)) / (iy1 - iy0); |
| } |
| |
| /** |
| * @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 y, or -1. |
| */ |
| private static function findIndexAt(y:Number, gap:int, g:GroupBase, i0:int, i1:int):int |
| { |
| var index:int = (i0 + i1) / 2; |
| var element:ILayoutElement = g.getElementAt(index); |
| var elementY:Number = element.getLayoutBoundsY(); |
| var elementHeight:Number = element.getLayoutBoundsHeight(); |
| // TBD: deal with null element, includeInLayout false. |
| if ((y >= elementY) && (y < elementY + elementHeight + gap)) |
| return index; |
| else if (i0 == i1) |
| return -1; |
| else if (y < elementY) |
| return findIndexAt(y, gap, g, i0, Math.max(i0, index-1)); |
| else |
| return findIndexAt(y, 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 top and bottom edges of the scrollRect. |
| // Values that are exactly equal to scrollRect.bottom aren't actually |
| // rendered, since the top,bottom interval is only half open. |
| // To account for that we back away from the bottom edge by a |
| // hopefully infinitesimal amount. |
| |
| var y0:Number = scrollR.top; |
| var y1:Number = scrollR.bottom - .0001; |
| if (y1 <= y0) |
| { |
| setIndexInView(-1, -1); |
| return; |
| } |
| |
| if (useVirtualLayout && !llv) |
| { |
| setIndexInView(-1, -1); |
| return; |
| } |
| |
| var i0:int; |
| var i1:int; |
| if (useVirtualLayout) |
| { |
| i0 = llv.indexOf(y0); |
| i1 = llv.indexOf(y1); |
| } |
| else |
| { |
| i0 = findIndexAt(y0 + gap, gap, g, 0, n); |
| i1 = findIndexAt(y1, gap, g, 0, n); |
| } |
| |
| // Special case: no element overlaps y0, is index 0 visible? |
| if (i0 == -1) |
| { |
| var index0:int = findLayoutElementIndex(g, 0, +1); |
| if (index0 != -1) |
| { |
| var element0:ILayoutElement = g.getElementAt(index0); |
| var element0Y:Number = element0.getLayoutBoundsY(); |
| var elementHeight:Number = element0.getLayoutBoundsHeight(); |
| if ((element0Y < y1) && ((element0Y + elementHeight) > y0)) |
| 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 element1Y:Number = element1.getLayoutBoundsY(); |
| var element1Height:Number = element1.getLayoutBoundsHeight(); |
| if ((element1Y < y1) && ((element1Y + element1Height) > y0)) |
| 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.top < firstElement.getLayoutBoundsY() || |
| scrollRect.bottom >= (lastElement.getLayoutBoundsY() + lastElement.getLayoutBoundsHeight())) |
| { |
| 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, 0, paddingTop); |
| if (i >= n) |
| return new Rectangle(0, getElementBounds(n-1).bottom, 0, paddingBottom); |
| } |
| |
| while((i >= 0) && (i < n)) |
| { |
| var elementR:Rectangle = getElementBounds(i); |
| // Special case: if the scrollRect r _only_ contains |
| // elementR, then if we're searching up (dir == -1), |
| // and elementR's top edge is visible, then try again |
| // with i-1. Likewise for dir == +1. |
| if (elementR) |
| { |
| var overlapsTop:Boolean = (dir == -1) && (elementR.top == r.top) && (elementR.bottom >= r.bottom); |
| var overlapsBottom:Boolean = (dir == +1) && (elementR.bottom == r.bottom) && (elementR.top <= r.top); |
| if (!(overlapsTop || overlapsBottom)) |
| return elementR; |
| } |
| i += dir; |
| } |
| return null; |
| } |
| |
| /** |
| * @private |
| */ |
| override protected function getElementBoundsAboveScrollRect(scrollRect:Rectangle):Rectangle |
| { |
| return findLayoutElementBounds(target, firstIndexInView, -1, scrollRect); |
| } |
| |
| /** |
| * @private |
| */ |
| override protected function getElementBoundsBelowScrollRect(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, justify:Boolean, result:SizesAndLimit):void |
| { |
| // Calculate preferred width first, as it's being used to calculate min width |
| var elementPreferredWidth:Number = Math.ceil(element.getPreferredBoundsWidth()); |
| |
| // Calculate min width |
| var flexibleWidth:Boolean = !isNaN(element.percentWidth) || justify; |
| 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, fixedRowHeight:Number, result:SizesAndLimit):void |
| { |
| // Calculate preferred height first, as it's being used to calculate min height below |
| var elementPreferredHeight:Number = isNaN(fixedRowHeight) ? Math.ceil(element.getPreferredBoundsHeight()) : |
| fixedRowHeight; |
| // Calculate min height |
| var flexibleHeight:Boolean = !isNaN(element.percentHeight); |
| var elementMinHeight:Number = flexibleHeight ? Math.ceil(element.getMinBoundsHeight()) : |
| elementPreferredHeight; |
| result.preferredSize = elementPreferredHeight; |
| result.minSize = elementMinHeight; |
| } |
| |
| /** |
| * @private |
| * @return rows to measure based on elements in layout and any requested/min/max rowCount settings. |
| */ |
| mx_internal function getRowsToMeasure(numElementsInLayout:int):int |
| { |
| var rowsToMeasure:int; |
| if (requestedRowCount != -1) |
| rowsToMeasure = requestedRowCount; |
| else |
| { |
| rowsToMeasure = numElementsInLayout; |
| if (requestedMaxRowCount != -1) |
| rowsToMeasure = Math.min(requestedMaxRowCount, rowsToMeasure); |
| if (requestedMinRowCount != -1) |
| rowsToMeasure = Math.max(requestedMinRowCount, rowsToMeasure); |
| } |
| return rowsToMeasure; |
| } |
| |
| /** |
| * @private |
| * |
| * Compute exact values for measuredWidth,Height and measuredMinWidth,Height. |
| * |
| * Measure each of the layout elements. If requestedRowCount >= 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 width of the elements remaining. |
| * |
| * If requestedRowCount is -1, we measure all of the layout elements. |
| */ |
| private function measureReal(layoutTarget:GroupBase):void |
| { |
| var size:SizesAndLimit = new SizesAndLimit(); |
| var justify:Boolean = horizontalAlign == HorizontalAlign.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 requestedRowCount:int = this.requestedRowCount; |
| var rowsMeasured:int = 0; // How many rows 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 fixedRowHeight:Number = NaN; |
| if (!variableRowHeight) |
| fixedRowHeight = rowHeight; // may query typicalLayoutElement, elt at index=0 |
| |
| // Get the numElementsInLayout clamped to requested min/max |
| var rowsToMeasure:int = getRowsToMeasure(numElementsInLayout); |
| var element:ILayoutElement; |
| for (var i:int = 0; i < numElements; i++) |
| { |
| element = layoutTarget.getElementAt(i); |
| if (!element || !element.includeInLayout) |
| { |
| numElementsInLayout--; |
| continue; |
| } |
| |
| // Can we measure this row height? |
| if (rowsMeasured < rowsToMeasure) |
| { |
| getElementHeight(element, fixedRowHeight, size); |
| preferredHeight += size.preferredSize; |
| minHeight += size.minSize; |
| rowsMeasured++; |
| } |
| |
| // Consider the width of each element, inclusive of those outside |
| // the requestedRowCount range. |
| getElementWidth(element, justify, size); |
| preferredWidth = Math.max(preferredWidth, size.preferredSize); |
| minWidth = Math.max(minWidth, size.minSize); |
| } |
| |
| // Calculate the total number of rows to measure again, since numElementsInLayout may have changed |
| rowsToMeasure = getRowsToMeasure(numElementsInLayout); |
| |
| // Do we need to measure more rows? |
| if (rowsMeasured < rowsToMeasure) |
| { |
| // Use the typical element |
| element = typicalLayoutElement; |
| if (element) |
| { |
| // Height |
| getElementHeight(element, fixedRowHeight, size); |
| preferredHeight += size.preferredSize * (rowsToMeasure - rowsMeasured); |
| minHeight += size.minSize * (rowsToMeasure - rowsMeasured); |
| |
| // Width |
| getElementWidth(element, justify, size); |
| preferredWidth = Math.max(preferredWidth, size.preferredSize); |
| minWidth = Math.max(minWidth, size.minSize); |
| rowsMeasured = rowsToMeasure; |
| } |
| } |
| |
| // Add gaps |
| if (rowsMeasured > 1) |
| { |
| var vgap:Number = gap * (rowsMeasured - 1); |
| preferredHeight += vgap; |
| minHeight += vgap; |
| } |
| |
| 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(); |
| // 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 = 71; |
| llv.defaultMajorSize = 22; |
| } |
| |
| var typicalElt:ILayoutElement = typicalLayoutElement; |
| if (typicalElt) |
| { |
| var typicalWidth:Number = typicalElt.getPreferredBoundsWidth(); |
| var typicalHeight:Number = typicalElt.getPreferredBoundsHeight(); |
| llv.defaultMinorSize = typicalWidth; |
| llv.defaultMajorSize = typicalHeight; |
| } |
| |
| if (!isNaN(_rowHeight)) |
| llv.defaultMajorSize = _rowHeight; |
| |
| if (layoutTarget) |
| llv.length = layoutTarget.numElements; |
| |
| llv.gap = gap; |
| llv.majorAxisOffset = paddingTop; |
| } |
| |
| /** |
| * @private |
| */ |
| override public function elementAdded(index:int):void |
| { |
| if (llv && (index >= 0) && useVirtualLayout) |
| llv.insert(index); // insert index parameter is uint |
| } |
| |
| /** |
| * @private |
| */ |
| override public function elementRemoved(index:int):void |
| { |
| if (llv && (index >= 0) && useVirtualLayout) |
| 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 variableRowHeight="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 = getRowsToMeasure(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 (variableRowHeight) |
| { |
| // Special case: fewer elements than requestedRowCount, so temporarily |
| // make llv.length == requestedRowCount. |
| var oldLength:int = -1; |
| if (measuredEltCount > llv.length) |
| { |
| oldLength = llv.length; |
| llv.length = measuredEltCount; |
| } |
| |
| // paddingTop is already taken into account as the majorAxisOffset of the llv |
| // Measured size according to the cached actual size: |
| var measuredHeight:Number = llv.end(measuredEltCount - 1) + paddingBottom; |
| |
| // 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) |
| { |
| measuredHeight -= llv.getMajorSize(i); |
| measuredHeight += element.getPreferredBoundsHeight(); |
| } |
| } |
| } |
| |
| layoutTarget.measuredHeight = measuredHeight; |
| |
| if (oldLength != -1) |
| llv.length = oldLength; |
| } |
| else |
| { |
| var vgap:Number = (measuredEltCount > 1) ? (measuredEltCount - 1) * gap : 0; |
| layoutTarget.measuredHeight = (measuredEltCount * rowHeight) + vgap + vPadding; |
| } |
| layoutTarget.measuredWidth = llv.minorSize + hPadding; |
| |
| layoutTarget.measuredMinWidth = (horizontalAlign == HorizontalAlign.JUSTIFY) ? |
| llv.minMinorSize + hPadding : layoutTarget.measuredWidth; |
| layoutTarget.measuredMinHeight = layoutTarget.measuredHeight; |
| } |
| |
| /** |
| * @private |
| * |
| * If requestedRowCount is specified then as many layout elements |
| * or "rows" are measured, starting with element 0, otherwise all of the |
| * layout elements are measured. |
| * |
| * If requestedRowCount is specified and is greater than the |
| * number of layout elements, then the typicalLayoutElement is used |
| * in place of the missing layout elements. |
| * |
| * If variableRowHeight="true", then the layoutTarget's measuredHeight |
| * is the sum of preferred heights of the layout elements, plus the sum of the |
| * gaps between elements, and its measuredWidth is the max of the elements' |
| * preferred widths. |
| * |
| * If variableRowHeight="false", then the layoutTarget's measuredHeight |
| * is rowHeight multiplied by the number or layout elements, plus the |
| * sum of the gaps between elements. |
| * |
| * The layoutTarget's measuredMinHeight is the sum of the minHeights of |
| * layout elements that have specified a value for the percentHeight |
| * property, and the preferredHeight of the elements that have not, |
| * plus the sum of the gaps between elements. |
| * |
| * The difference reflects the fact that elements which specify |
| * percentHeight are considered to be "flexible" and updateDisplayList |
| * will give flexible components at least their minHeight. |
| * |
| * Layout elements that aren't flexible always get their preferred height. |
| * |
| * The layoutTarget's measuredMinWidth is the max of the minWidths for |
| * elements that have specified percentWidth (that are "flexible") and the |
| * preferredWidth of the elements that have not. |
| * |
| * As before the difference is due to the fact that flexible items are only |
| * guaranteed their minWidth. |
| */ |
| 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.UP) |
| return arrowKeysWrapFocus ? maxIndex : -1; |
| if (navigationUnit == NavigationUnit.DOWN) |
| return 0; |
| } |
| |
| // Make sure currentIndex is within range |
| currentIndex = Math.max(0, Math.min(maxIndex, currentIndex)); |
| |
| var newIndex:int; |
| var bounds:Rectangle; |
| var y:Number; |
| |
| switch (navigationUnit) |
| { |
| case NavigationUnit.UP: |
| { |
| if (arrowKeysWrapFocus && currentIndex == 0) |
| newIndex = maxIndex; |
| else |
| newIndex = currentIndex - 1; |
| break; |
| } |
| |
| case NavigationUnit.DOWN: |
| { |
| if (arrowKeysWrapFocus && currentIndex == maxIndex) |
| newIndex = 0; |
| else |
| newIndex = currentIndex + 1; |
| break; |
| } |
| |
| case NavigationUnit.PAGE_UP: |
| { |
| // 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 up |
| if (currentIndex == firstFullyVisible || currentIndex == firstVisible) |
| { |
| // currentIndex is visible, we can calculate where the scrollRect top |
| // would end up if we scroll by a page |
| y = getVerticalScrollPositionDelta(NavigationUnit.PAGE_UP) + getScrollRect().top; |
| } |
| else |
| { |
| // currentIndex is not visible, just find an element a page up from currentIndex |
| y = getElementBounds(currentIndex).bottom - getScrollRect().height; |
| } |
| |
| // Find the element after the last element that spans above the y position |
| newIndex = currentIndex - 1; |
| while (0 <= newIndex) |
| { |
| bounds = getElementBounds(newIndex); |
| if (bounds && bounds.top < y) |
| { |
| // 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: |
| { |
| // 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 down |
| if (currentIndex == lastFullyVisible || currentIndex == lastVisible) |
| { |
| // currentIndex is visible, we can calculate where the scrollRect bottom |
| // would end up if we scroll by a page |
| y = getVerticalScrollPositionDelta(NavigationUnit.PAGE_DOWN) + getScrollRect().bottom; |
| } |
| else |
| { |
| // currentIndex is not visible, just find an element a page down from currentIndex |
| y = getElementBounds(currentIndex).top + getScrollRect().height; |
| } |
| |
| // Find the element before the first element that spans below the y position |
| newIndex = currentIndex + 1; |
| while (newIndex <= maxIndex) |
| { |
| bounds = getElementBounds(newIndex); |
| if (bounds && bounds.bottom > y) |
| { |
| // 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 calculateElementWidth(elt:ILayoutElement, targetWidth:Number, containerWidth:Number):Number |
| { |
| // If percentWidth is specified then the element's width is the percentage |
| // of targetWidth clipped to min/maxWidth and to (upper limit) targetWidth. |
| var percentWidth:Number = elt.percentWidth; |
| if (!isNaN(percentWidth)) |
| { |
| var width:Number = percentWidth * 0.01 * targetWidth; |
| return Math.min(targetWidth, Math.min(elt.getMaxBoundsWidth(), Math.max(elt.getMinBoundsWidth(), width))); |
| } |
| switch(horizontalAlign) |
| { |
| case HorizontalAlign.JUSTIFY: |
| return targetWidth; |
| case HorizontalAlign.CONTENT_JUSTIFY: |
| return Math.max(elt.getPreferredBoundsWidth(), containerWidth); |
| } |
| return NaN; // not constrained |
| } |
| |
| /** |
| * @private |
| * |
| * Used only for virtual layout. |
| */ |
| private function calculateElementX(elt:ILayoutElement, eltWidth:Number, containerWidth:Number):Number |
| { |
| switch(horizontalAlign) |
| { |
| case HorizontalAlign.CENTER: |
| return Math.round((containerWidth - eltWidth) * 0.5); |
| case HorizontalAlign.RIGHT: |
| return containerWidth - eltWidth; |
| } |
| return 0; // HorizontalAlign.LEFT |
| } |
| |
| |
| /** |
| * @private |
| * |
| * Update the layout of the virtualized elements that overlap |
| * the scrollRect's vertical extent. |
| * |
| * The height of each layout element will be its preferred height, and its |
| * y will be the bottom of the previous item, plus the gap. |
| * |
| * No support for percentHeight, includeInLayout=false, or null layoutElements, |
| * |
| * The width of each layout element will be set to its preferred width, unless |
| * one of the following is true: |
| * |
| * - If percentWidth is specified for this element, then its width will be the |
| * specified percentage of the target's actual (unscaled) width, clipped |
| * the layout element's minimum and maximum width. |
| * |
| * - If horizontalAlign is "justify", then the element's width will |
| * be set to the target's actual (unscaled) width. |
| * |
| * - If horizontalAlign is "contentJustify", then the element's width |
| * will be set to the larger of the target's width and its content width. |
| * |
| * The X coordinate of each layout element will be set to 0 unless one of the |
| * following is true: |
| * |
| * - If horizontalAlign is "center" then x is set so that the element's preferred |
| * width is centered within the larger of the contentWidth, target width: |
| * x = (Math.max(contentWidth, target.width) - layoutElementWidth) * 0.5 |
| * |
| * - If horizontalAlign is "right" the x is set so that the element's right |
| * edge is aligned with the the right edge of the content: |
| * x = (Math.max(contentWidth, target.width) - layoutElementWidth) |
| * |
| * Implementation note: unless horizontalAlign is either "justify" or |
| * "left", the layout elements' x or width depends on the contentWidth. |
| * The contentWidth 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 contentWidth |
| * for the initial layout and then, if it has changed, we loop through |
| * the layout items again and fix up the x/width values. |
| */ |
| private function updateDisplayListVirtual():void |
| { |
| var layoutTarget:GroupBase = target; |
| var eltCount:int = layoutTarget.numElements; |
| var targetWidth:Number = Math.max(0, layoutTarget.width - paddingLeft - paddingRight); |
| var minVisibleY:Number = layoutTarget.verticalScrollPosition; |
| var maxVisibleY:Number = minVisibleY + layoutTarget.height; |
| |
| var contentHeight:Number; |
| var paddedContentHeight: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 minVisibleY - SDK-22497. |
| var startIndex:int = llv.indexOf(Math.max(0, minVisibleY + gap)); |
| if (startIndex == -1) |
| { |
| // No items are visible. Just set the content size. |
| contentHeight = llv.end(llv.length - 1) - paddingTop; |
| paddedContentHeight = Math.ceil(contentHeight + paddingTop + paddingBottom); |
| layoutTarget.setContentSize(layoutTarget.contentWidth, paddedContentHeight); |
| return; |
| } |
| |
| var fixedRowHeight:Number = NaN; |
| if (!variableRowHeight) |
| fixedRowHeight = rowHeight; // may query typicalLayoutElement, elt at index=0 |
| |
| var justifyWidths:Boolean = horizontalAlign == HorizontalAlign.JUSTIFY; |
| var eltWidth:Number = (justifyWidths) ? targetWidth : NaN; |
| var eltHeight:Number = NaN; |
| var contentWidth:Number = (justifyWidths) ? Math.max(llv.minMinorSize, targetWidth) : llv.minorSize; |
| var containerWidth:Number = Math.max(contentWidth, targetWidth); |
| var y:Number = llv.start(startIndex); |
| var index:int = startIndex; |
| var x0:Number = paddingLeft; |
| |
| // First pass: compute element x,y,width,height based on |
| // current contentWidth; cache computed widths/heights in llv. |
| for (; (y < maxVisibleY) && (index < eltCount); index++) |
| { |
| var elt:ILayoutElement = layoutTarget.getVirtualElementAt(index, eltWidth, eltHeight); |
| var w:Number = calculateElementWidth(elt, targetWidth, containerWidth); // can be NaN |
| var h:Number = fixedRowHeight; // NaN for variable height rows |
| elt.setLayoutBoundsSize(w, h); |
| w = elt.getLayoutBoundsWidth(); |
| h = elt.getLayoutBoundsHeight(); |
| var x:Number = x0 + calculateElementX(elt, w, containerWidth); |
| elt.setLayoutBoundsPosition(x, y); |
| llv.cacheDimensions(index, elt); |
| y += h + gap; |
| } |
| var endIndex:int = index - 1; |
| |
| // Second pass: if neccessary, fix up x and width values based |
| // on the updated contentWidth |
| if (!justifyWidths && (llv.minorSize != contentWidth)) |
| { |
| contentWidth = llv.minorSize; |
| containerWidth = Math.max(contentWidth, targetWidth); |
| if (horizontalAlign != HorizontalAlign.LEFT) |
| { |
| for (index = startIndex; index <= endIndex; index++) |
| { |
| elt = layoutTarget.getElementAt(index); |
| w = calculateElementWidth(elt, targetWidth, containerWidth); // can be NaN |
| elt.setLayoutBoundsSize(w, elt.getLayoutBoundsHeight()); |
| w = elt.getLayoutBoundsWidth(); |
| x = x0 + calculateElementX(elt, w, containerWidth); |
| elt.setLayoutBoundsPosition(x, elt.getLayoutBoundsY()); |
| } |
| } |
| } |
| |
| // Third pass: if neccessary, fix up y based on updated contentHeight |
| contentHeight = llv.end(llv.length - 1) - paddingTop; |
| var targetHeight:Number = Math.max(0, layoutTarget.height - paddingTop - paddingBottom); |
| if (contentHeight < targetHeight) |
| { |
| var excessHeight:Number = targetHeight - contentHeight; |
| var dy:Number = 0; |
| var vAlign:String = verticalAlign; |
| if (vAlign == VerticalAlign.MIDDLE) |
| { |
| dy = Math.round(excessHeight / 2); |
| } |
| else if (vAlign == VerticalAlign.BOTTOM) |
| { |
| dy = excessHeight; |
| } |
| if (dy > 0) |
| { |
| for (index = startIndex; index <= endIndex; index++) |
| { |
| elt = layoutTarget.getElementAt(index); |
| elt.setLayoutBoundsPosition(elt.getLayoutBoundsX(), dy + elt.getLayoutBoundsY()); |
| } |
| contentHeight += dy; |
| } |
| } |
| |
| setRowCount(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. |
| var paddedContentWidth:Number = Math.ceil(contentWidth + paddingLeft + paddingRight); |
| paddedContentHeight = 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 horizontalAlign is left, we don't need to figure out the contentWidth |
| // Otherwise the contentWidth is used to position the element and even size |
| // the element if it's "contentJustify" or "justify". |
| var containerWidth:Number = targetWidth; |
| if (horizontalAlign == HorizontalAlign.CONTENT_JUSTIFY || |
| (clipAndEnableScrolling && (horizontalAlign == HorizontalAlign.CENTER || |
| horizontalAlign == HorizontalAlign.RIGHT))) |
| { |
| for (var i:int = 0; i < count; i++) |
| { |
| layoutElement = layoutTarget.getElementAt(i); |
| if (!layoutElement || !layoutElement.includeInLayout) |
| continue; |
| |
| var layoutElementWidth:Number; |
| if (!isNaN(layoutElement.percentWidth)) |
| layoutElementWidth = calculatePercentWidth(layoutElement, targetWidth); |
| else |
| layoutElementWidth = layoutElement.getPreferredBoundsWidth(); |
| |
| containerWidth = Math.max(containerWidth, Math.ceil(layoutElementWidth)); |
| } |
| } |
| |
| var excessHeight:Number = distributeHeight(targetWidth, targetHeight, containerWidth); |
| |
| // default to left (0) |
| var hAlign:Number = 0; |
| if (horizontalAlign == HorizontalAlign.CENTER) |
| hAlign = .5; |
| else if (horizontalAlign == HorizontalAlign.RIGHT) |
| hAlign = 1; |
| |
| // As the layoutElements are positioned, we'll count how many rows |
| // fall within the layoutTarget's scrollRect |
| var visibleRows:uint = 0; |
| var minVisibleY:Number = layoutTarget.verticalScrollPosition; |
| var maxVisibleY:Number = minVisibleY + targetHeight; |
| |
| // Finally, position the layoutElements and find the first/last |
| // visible indices, the content size, and the number of |
| // visible elements. |
| var x0:Number = paddingLeft; |
| var y:Number = paddingTop; |
| var maxX:Number = paddingLeft; |
| var maxY:Number = paddingTop; |
| var firstRowInView:int = -1; |
| var lastRowInView:int = -1; |
| |
| // Take verticalAlign into account |
| if (excessHeight > 0 || !clipAndEnableScrolling) |
| { |
| var vAlign:String = verticalAlign; |
| if (vAlign == VerticalAlign.MIDDLE) |
| { |
| y = paddingTop + Math.round(excessHeight / 2); |
| } |
| else if (vAlign == VerticalAlign.BOTTOM) |
| { |
| y = paddingTop + excessHeight; |
| } |
| } |
| |
| 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 x:Number = x0 + (containerWidth - dx) * hAlign; |
| // In case we have HorizontalAlign.CENTER we have to round |
| if (hAlign == 0.5) |
| x = Math.round(x); |
| layoutElement.setLayoutBoundsPosition(x, y); |
| |
| // Update maxX,Y, first,lastVisibleIndex, and y |
| maxX = Math.max(maxX, x + dx); |
| maxY = Math.max(maxY, y + dy); |
| if (!clipAndEnableScrolling || |
| ((y < maxVisibleY) && ((y + dy) > minVisibleY)) || |
| ((dy <= 0) && ((y == maxVisibleY) || (y == minVisibleY)))) |
| { |
| visibleRows += 1; |
| if (firstRowInView == -1) |
| firstRowInView = lastRowInView = index; |
| else |
| lastRowInView = index; |
| } |
| y += dy + gap; |
| } |
| |
| setRowCount(visibleRows); |
| setIndexInView(firstRowInView, lastRowInView); |
| |
| // 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 height of each child |
| * so that the heights add up to <code>height</code>. |
| * Each child is set to its preferred height |
| * if its percentHeight is zero. |
| * If its percentHeight 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 maxHeight. |
| */ |
| private function distributeHeight(width:Number, |
| height:Number, |
| restrictedWidth:Number):Number |
| { |
| var spaceToDistribute:Number = height; |
| var totalPercentHeight:Number = 0; |
| var childInfoArray:Array = []; |
| var childInfo:LayoutElementFlexChildInfo; |
| var layoutElement:ILayoutElement; |
| |
| // rowHeight can be expensive to compute |
| var rh:Number = (variableRowHeight) ? 0 : Math.ceil(rowHeight); |
| 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.percentHeight) && variableRowHeight) |
| { |
| totalPercentHeight += layoutElement.percentHeight; |
| |
| childInfo = new LayoutElementFlexChildInfo(); |
| childInfo.layoutElement = layoutElement; |
| childInfo.percent = layoutElement.percentHeight; |
| childInfo.min = layoutElement.getMinBoundsHeight(); |
| childInfo.max = layoutElement.getMaxBoundsHeight(); |
| |
| childInfoArray.push(childInfo); |
| } |
| else |
| { |
| sizeLayoutElement(layoutElement, width, horizontalAlign, |
| restrictedWidth, NaN, variableRowHeight, rh); |
| |
| spaceToDistribute -= Math.ceil(layoutElement.getLayoutBoundsHeight()); |
| } |
| } |
| |
| if (totalCount > 1) |
| spaceToDistribute -= (totalCount-1) * gap; |
| |
| // Distribute the extra space among the flexible children |
| if (totalPercentHeight) |
| { |
| Flex.flexChildrenProportionally(height, |
| spaceToDistribute, |
| totalPercentHeight, |
| 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, width, horizontalAlign, |
| restrictedWidth, childSize, |
| variableRowHeight, rh); |
| 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)) |
| { |
| setRowCount(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.top <= y && y <= elementBounds.bottom) |
| { |
| var centerY:Number = elementBounds.y + elementBounds.height / 2; |
| return (y < centerY) ? i : i + 1; |
| } |
| |
| var curDistance:Number = Math.min(Math.abs(y - elementBounds.top), |
| Math.abs(y - elementBounds.bottom)); |
| if (curDistance < minDistance) |
| { |
| minDistance = curDistance; |
| bestIndex = (y < elementBounds.top) ? 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).y < y ? 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 emptySpaceTop:Number = 0; |
| if (target.numElements > 0) |
| { |
| emptySpaceTop = (dropIndex < count) ? getElementBounds(dropIndex).top - emptySpace : |
| getElementBounds(dropIndex - 1).bottom + gap - emptySpace; |
| } |
| |
| // Calculate the size of the bounds, take minium and maximum into account |
| var width:Number = Math.max(target.width, target.contentWidth) - paddingLeft - paddingRight; |
| var height:Number = emptySpace; |
| if (dropIndicator is IVisualElement) |
| { |
| var element:IVisualElement = IVisualElement(dropIndicator); |
| height = Math.max(Math.min(height, element.getMaxBoundsHeight(false)), element.getMinBoundsHeight(false)); |
| } |
| |
| var x:Number = paddingLeft; |
| |
| var y:Number = emptySpaceTop + Math.round((emptySpace - height)/2); |
| // Allow 1 pixel overlap with container border |
| y = Math.max(-1, Math.min(target.contentHeight - height + 1, y)); |
| 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 horizontal direction |
| if (delta) |
| delta.x = 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.y); |
| |
| // 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.y < 0 ? 0 : g.numElements - 1; |
| |
| var bounds:Rectangle = llv.getBounds(index); |
| var adjacentBounds:Rectangle; |
| |
| // If we're comparing with a bottom-edge point, check both the current element and the element |
| // at index-1 to see which is closest. |
| if ((elementComparePoint == "bottomLeft" || 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 top-edge point, check both the current element and the element |
| // at index+1 to see which is closest. |
| if ((elementComparePoint == "topLeft" || elementComparePoint == "topRight") && 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 LayoutElementFlexChildInfo extends FlexChildInfo |
| { |
| public var layoutElement:ILayoutElement; |
| } |
| |
| class SizesAndLimit |
| { |
| public var preferredSize:Number; |
| public var minSize:Number; |
| } |
| |
| |
| |