scaffold.libs_as.feathers.layout.HorizontalLayout.as Maven / Gradle / Ivy
/*
Feathers
Copyright 2012-2015 Bowler Hat LLC. All Rights Reserved.
This program is free software. You can redistribute and/or modify it in
accordance with the terms of the accompanying license agreement.
*/
package feathers.layout
{
import feathers.core.IFeathersControl;
import feathers.core.IValidating;
import flash.errors.IllegalOperationError;
import flash.geom.Point;
import starling.display.DisplayObject;
import starling.events.Event;
import starling.events.EventDispatcher;
/**
* Dispatched when a property of the layout changes, indicating that a
* redraw is probably needed.
*
* The properties of the event object have the following values:
*
* Property Value
* bubbles
false
* currentTarget
The Object that defines the
* event listener that handles the event. For example, if you use
* myButton.addEventListener()
to register an event listener,
* myButton is the value of the currentTarget
.
* data
null
* target
The Object that dispatched the event;
* it is not always the Object listening for the event. Use the
* currentTarget
property to always access the Object
* listening for the event.
*
*
* @eventType starling.events.Event.CHANGE
*/
[Event(name="change",type="starling.events.Event")]
/**
* Dispatched when the layout would like to adjust the container's scroll
* position. Typically, this is used when the virtual dimensions of an item
* differ from its real dimensions. This event allows the container to
* adjust scrolling so that it appears smooth, without jarring jumps or
* shifts when an item resizes.
*
* The properties of the event object have the following values:
*
* Property Value
* bubbles
false
* currentTarget
The Object that defines the
* event listener that handles the event. For example, if you use
* myButton.addEventListener()
to register an event listener,
* myButton is the value of the currentTarget
.
* data
A flash.geom.Point
object
* representing how much the scroll position should be adjusted in both
* horizontal and vertical directions. Measured in pixels.
* target
The Object that dispatched the event;
* it is not always the Object listening for the event. Use the
* currentTarget
property to always access the Object
* listening for the event.
*
*
* @eventType starling.events.Event.SCROLL
*/
[Event(name="scroll",type="starling.events.Event")]
/**
* Positions items from left to right in a single row.
*
* @see ../../../help/horizontal-layout.html How to use HorizontalLayout with Feathers containers
*/
public class HorizontalLayout extends EventDispatcher implements IVariableVirtualLayout, ITrimmedVirtualLayout
{
/**
* @private
* DEPRECATED: Replaced by feathers.layout.VerticalAlign.TOP
.
*
* DEPRECATION WARNING: This constant is deprecated
* starting with Feathers 3.0. It will be removed in a future version of
* Feathers according to the standard
* Feathers deprecation policy.
*/
public static const VERTICAL_ALIGN_TOP:String = "top";
/**
* @private
* DEPRECATED: Replaced by feathers.layout.VerticalAlign.MIDDLE
.
*
* DEPRECATION WARNING: This constant is deprecated
* starting with Feathers 3.0. It will be removed in a future version of
* Feathers according to the standard
* Feathers deprecation policy.
*/
public static const VERTICAL_ALIGN_MIDDLE:String = "middle";
/**
* @private
* DEPRECATED: Replaced by feathers.layout.VerticalAlign.BOTTOM
.
*
* DEPRECATION WARNING: This constant is deprecated
* starting with Feathers 3.0. It will be removed in a future version of
* Feathers according to the standard
* Feathers deprecation policy.
*/
public static const VERTICAL_ALIGN_BOTTOM:String = "bottom";
/**
* @private
* DEPRECATED: Replaced by feathers.layout.VerticalAlign.JUSTIFY
.
*
* DEPRECATION WARNING: This constant is deprecated
* starting with Feathers 3.0. It will be removed in a future version of
* Feathers according to the standard
* Feathers deprecation policy.
*/
public static const VERTICAL_ALIGN_JUSTIFY:String = "justify";
/**
* @private
* DEPRECATED: Replaced by feathers.layout.HorizontalAlign.LEFT
.
*
* DEPRECATION WARNING: This constant is deprecated
* starting with Feathers 3.0. It will be removed in a future version of
* Feathers according to the standard
* Feathers deprecation policy.
*/
public static const HORIZONTAL_ALIGN_LEFT:String = "left";
/**
* @private
* DEPRECATED: Replaced by feathers.layout.HorizontalAlign.CENTER
.
*
* DEPRECATION WARNING: This constant is deprecated
* starting with Feathers 3.0. It will be removed in a future version of
* Feathers according to the standard
* Feathers deprecation policy.
*/
public static const HORIZONTAL_ALIGN_CENTER:String = "center";
/**
* @private
* DEPRECATED: Replaced by feathers.layout.HorizontalAlign.RIGHT
.
*
* DEPRECATION WARNING: This constant is deprecated
* starting with Feathers 3.0. It will be removed in a future version of
* Feathers according to the standard
* Feathers deprecation policy.
*/
public static const HORIZONTAL_ALIGN_RIGHT:String = "right";
/**
* Constructor.
*/
public function HorizontalLayout()
{
}
/**
* @private
*/
protected var _widthCache:Array = [];
/**
* @private
*/
protected var _discoveredItemsCache:Vector. = new [];
/**
* @private
*/
protected var _gap:Number = 0;
/**
* The space, in pixels, between items.
*
* @default 0
*/
public function get gap():Number
{
return this._gap;
}
/**
* @private
*/
public function set gap(value:Number):void
{
if(this._gap == value)
{
return;
}
this._gap = value;
this.dispatchEventWith(Event.CHANGE);
}
/**
* @private
*/
protected var _firstGap:Number = NaN;
/**
* The space, in pixels, between the first and second items. If the
* value of firstGap
is NaN
, the value of the
* gap
property will be used instead.
*
* @default NaN
*/
public function get firstGap():Number
{
return this._firstGap;
}
/**
* @private
*/
public function set firstGap(value:Number):void
{
if(this._firstGap == value)
{
return;
}
this._firstGap = value;
this.dispatchEventWith(Event.CHANGE);
}
/**
* @private
*/
protected var _lastGap:Number = NaN;
/**
* The space, in pixels, between the last and second to last items. If
* the value of lastGap
is NaN
, the value of
* the gap
property will be used instead.
*
* @default NaN
*/
public function get lastGap():Number
{
return this._lastGap;
}
/**
* @private
*/
public function set lastGap(value:Number):void
{
if(this._lastGap == value)
{
return;
}
this._lastGap = value;
this.dispatchEventWith(Event.CHANGE);
}
/**
* Quickly sets all padding properties to the same value. The
* padding
getter always returns the value of
* paddingTop
, but the other padding values may be
* different.
*
* @default 0
*
* @see #paddingTop
* @see #paddingRight
* @see #paddingBottom
* @see #paddingLeft
*/
public function get padding():Number
{
return this._paddingTop;
}
/**
* @private
*/
public function set padding(value:Number):void
{
this.paddingTop = value;
this.paddingRight = value;
this.paddingBottom = value;
this.paddingLeft = value;
}
/**
* @private
*/
protected var _paddingTop:Number = 0;
/**
* The minimum space, in pixels, above the items.
*
* @default 0
*/
public function get paddingTop():Number
{
return this._paddingTop;
}
/**
* @private
*/
public function set paddingTop(value:Number):void
{
if(this._paddingTop == value)
{
return;
}
this._paddingTop = value;
this.dispatchEventWith(Event.CHANGE);
}
/**
* @private
*/
protected var _paddingRight:Number = 0;
/**
* The space, in pixels, that appears to the right, after the last item.
*
* @default 0
*/
public function get paddingRight():Number
{
return this._paddingRight;
}
/**
* @private
*/
public function set paddingRight(value:Number):void
{
if(this._paddingRight == value)
{
return;
}
this._paddingRight = value;
this.dispatchEventWith(Event.CHANGE);
}
/**
* @private
*/
protected var _paddingBottom:Number = 0;
/**
* The minimum space, in pixels, above the items.
*
* @default 0
*/
public function get paddingBottom():Number
{
return this._paddingBottom;
}
/**
* @private
*/
public function set paddingBottom(value:Number):void
{
if(this._paddingBottom == value)
{
return;
}
this._paddingBottom = value;
this.dispatchEventWith(Event.CHANGE);
}
/**
* @private
*/
protected var _paddingLeft:Number = 0;
/**
* The space, in pixels, that appears to the left, before the first
* item.
*
* @default 0
*/
public function get paddingLeft():Number
{
return this._paddingLeft;
}
/**
* @private
*/
public function set paddingLeft(value:Number):void
{
if(this._paddingLeft == value)
{
return;
}
this._paddingLeft = value;
this.dispatchEventWith(Event.CHANGE);
}
/**
* @private
*/
protected var _verticalAlign:String = VerticalAlign.TOP;
[Inspectable(type="String",enumeration="top,middle,bottom,justify")]
/**
* The alignment of the items vertically, on the y-axis.
*
* If the verticalAlign
property is set to
* VerticalAlign.JUSTIFY
, the
* height
, minHeight
, and
* maxHeight
properties of the items may be changed, and
* their original values ignored by the layout. In this situation, if
* the height needs to be constrained, the height
,
* minHeight
, or maxHeight
properties should
* instead be set on the parent container that is using this layout.
*
* @default feathers.layout.VerticalAlign.TOP
*
* @see feathers.layout.VerticalAlign#TOP
* @see feathers.layout.VerticalAlign#MIDDLE
* @see feathers.layout.VerticalAlign#BOTTOM
* @see feathers.layout.VerticalAlign#JUSTIFY
*/
public function get verticalAlign():String
{
return this._verticalAlign;
}
/**
* @private
*/
public function set verticalAlign(value:String):void
{
if(this._verticalAlign == value)
{
return;
}
this._verticalAlign = value;
this.dispatchEventWith(Event.CHANGE);
}
/**
* @private
*/
protected var _horizontalAlign:String = HorizontalAlign.LEFT;
[Inspectable(type="String",enumeration="left,center,right")]
/**
* If the total item width is less than the bounds, the positions of
* the items can be aligned horizontally.
*
* @default feathers.layout.HorizontalAlign.LEFT
*
* @see feathers.layout.HorizontalAlign#LEFT
* @see feathers.layout.HorizontalAlign#CENTER
* @see feathers.layout.HorizontalAlign#RIGHT
*/
public function get horizontalAlign():String
{
return this._horizontalAlign;
}
/**
* @private
*/
public function set horizontalAlign(value:String):void
{
if(this._horizontalAlign == value)
{
return;
}
this._horizontalAlign = value;
this.dispatchEventWith(Event.CHANGE);
}
/**
* @private
*/
protected var _useVirtualLayout:Boolean = true;
/**
* @inheritDoc
*
* @default true
*/
public function get useVirtualLayout():Boolean
{
return this._useVirtualLayout;
}
/**
* @private
*/
public function set useVirtualLayout(value:Boolean):void
{
if(this._useVirtualLayout == value)
{
return;
}
this._useVirtualLayout = value;
this.dispatchEventWith(Event.CHANGE);
}
/**
* @private
*/
protected var _hasVariableItemDimensions:Boolean = false;
/**
* When the layout is virtualized, and this value is true, the items may
* have variable width values. If false, the items will all share the
* same width value with the typical item.
*
* @default false
*/
public function get hasVariableItemDimensions():Boolean
{
return this._hasVariableItemDimensions;
}
/**
* @private
*/
public function set hasVariableItemDimensions(value:Boolean):void
{
if(this._hasVariableItemDimensions == value)
{
return;
}
this._hasVariableItemDimensions = value;
this.dispatchEventWith(Event.CHANGE);
}
/**
* @private
*/
protected var _requestedColumnCount:int = 0;
/**
* Requests that the layout set the view port dimensions to display a
* specific number of columns (plus gaps and padding), if possible. If
* the explicit width of the view port is set, then this value will be
* ignored. If the view port's minimum and/or maximum width are set,
* the actual number of visible columns may be adjusted to meet those
* requirements. Set this value to 0
to display as many
* columns as possible.
*
* @default 0
*/
public function get requestedColumnCount():int
{
return this._requestedColumnCount;
}
/**
* @private
*/
public function set requestedColumnCount(value:int):void
{
if(value < 0)
{
throw RangeError("requestedColumnCount requires a value >= 0");
}
if(this._requestedColumnCount == value)
{
return;
}
this._requestedColumnCount = value;
this.dispatchEventWith(Event.CHANGE);
}
/**
* @private
*/
protected var _distributeWidths:Boolean = false;
/**
* Distributes the width of the view port equally to each item. If the
* view port width needs to be measured, the largest item's width will
* be used for all items, subject to any specified minimum and maximum
* width values.
*
* @default false
*/
public function get distributeWidths():Boolean
{
return this._distributeWidths;
}
/**
* @private
*/
public function set distributeWidths(value:Boolean):void
{
if(this._distributeWidths == value)
{
return;
}
this._distributeWidths = value;
this.dispatchEventWith(Event.CHANGE);
}
/**
* @private
*/
protected var _beforeVirtualizedItemCount:int = 0;
/**
* @inheritDoc
*/
public function get beforeVirtualizedItemCount():int
{
return this._beforeVirtualizedItemCount;
}
/**
* @private
*/
public function set beforeVirtualizedItemCount(value:int):void
{
if(this._beforeVirtualizedItemCount == value)
{
return;
}
this._beforeVirtualizedItemCount = value;
this.dispatchEventWith(Event.CHANGE);
}
/**
* @private
*/
protected var _afterVirtualizedItemCount:int = 0;
/**
* @inheritDoc
*/
public function get afterVirtualizedItemCount():int
{
return this._afterVirtualizedItemCount;
}
/**
* @private
*/
public function set afterVirtualizedItemCount(value:int):void
{
if(this._afterVirtualizedItemCount == value)
{
return;
}
this._afterVirtualizedItemCount = value;
this.dispatchEventWith(Event.CHANGE);
}
/**
* @private
*/
protected var _typicalItem:DisplayObject;
/**
* @inheritDoc
*
* @see #resetTypicalItemDimensionsOnMeasure
* @see #typicalItemWidth
* @see #typicalItemHeight
*/
public function get typicalItem():DisplayObject
{
return this._typicalItem;
}
/**
* @private
*/
public function set typicalItem(value:DisplayObject):void
{
if(this._typicalItem == value)
{
return;
}
this._typicalItem = value;
this.dispatchEventWith(Event.CHANGE);
}
/**
* @private
*/
protected var _resetTypicalItemDimensionsOnMeasure:Boolean = false;
/**
* If set to true
, the width and height of the
* typicalItem
will be reset to typicalItemWidth
* and typicalItemHeight
, respectively, whenever the
* typical item needs to be measured. The measured dimensions of the
* typical item are used to fill in the blanks of a virtualized layout
* for virtual items that don't have their own display objects to
* measure yet.
*
* @default false
*
* @see #typicalItemWidth
* @see #typicalItemHeight
* @see #typicalItem
*/
public function get resetTypicalItemDimensionsOnMeasure():Boolean
{
return this._resetTypicalItemDimensionsOnMeasure;
}
/**
* @private
*/
public function set resetTypicalItemDimensionsOnMeasure(value:Boolean):void
{
if(this._resetTypicalItemDimensionsOnMeasure == value)
{
return;
}
this._resetTypicalItemDimensionsOnMeasure = value;
this.dispatchEventWith(Event.CHANGE);
}
/**
* @private
*/
protected var _typicalItemWidth:Number = NaN;
/**
* Used to reset the width, in pixels, of the typicalItem
* for measurement. The measured dimensions of the typical item are used
* to fill in the blanks of a virtualized layout for virtual items that
* don't have their own display objects to measure yet.
*
* This value is only used when resetTypicalItemDimensionsOnMeasure
* is set to true
. If resetTypicalItemDimensionsOnMeasure
* is set to false
, this value will be ignored and the
* typicalItem
dimensions will not be reset before
* measurement.
*
* If typicalItemWidth
is set to NaN
, the
* typical item will auto-size itself to its preferred width. If you
* pass a valid Number
value, the typical item's width will
* be set to a fixed size. May be used in combination with
* typicalItemHeight
.
*
* @default NaN
*
* @see #resetTypicalItemDimensionsOnMeasure
* @see #typicalItemHeight
* @see #typicalItem
*/
public function get typicalItemWidth():Number
{
return this._typicalItemWidth;
}
/**
* @private
*/
public function set typicalItemWidth(value:Number):void
{
if(this._typicalItemWidth == value)
{
return;
}
this._typicalItemWidth = value;
this.dispatchEventWith(Event.CHANGE);
}
/**
* @private
*/
protected var _typicalItemHeight:Number = NaN;
/**
* Used to reset the height, in pixels, of the typicalItem
* for measurement. The measured dimensions of the typical item are used
* to fill in the blanks of a virtualized layout for virtual items that
* don't have their own display objects to measure yet.
*
* This value is only used when resetTypicalItemDimensionsOnMeasure
* is set to true
. If resetTypicalItemDimensionsOnMeasure
* is set to false
, this value will be ignored and the
* typicalItem
dimensions will not be reset before
* measurement.
*
* If typicalItemHeight
is set to NaN
, the
* typical item will auto-size itself to its preferred height. If you
* pass a valid Number
value, the typical item's height will
* be set to a fixed size. May be used in combination with
* typicalItemWidth
.
*
* @default NaN
*
* @see #resetTypicalItemDimensionsOnMeasure
* @see #typicalItemWidth
* @see #typicalItem
*/
public function get typicalItemHeight():Number
{
return this._typicalItemHeight;
}
/**
* @private
*/
public function set typicalItemHeight(value:Number):void
{
if(this._typicalItemHeight == value)
{
return;
}
this._typicalItemHeight = value;
this.dispatchEventWith(Event.CHANGE);
}
/**
* @private
*/
protected var _scrollPositionHorizontalAlign:String = HorizontalAlign.CENTER;
[Inspectable(type="String",enumeration="left,center,right")]
/**
* When the scroll position is calculated for an item, an attempt will
* be made to align the item to this position.
*
* @default feathers.layout.HorizontalAlign.CENTER
*
* @see feathers.layout.HorizontalAlign#LEFT
* @see feathers.layout.HorizontalAlign#CENTER
* @see feathers.layout.HorizontalAlign#RIGHT
*/
public function get scrollPositionHorizontalAlign():String
{
return this._scrollPositionHorizontalAlign;
}
/**
* @private
*/
public function set scrollPositionHorizontalAlign(value:String):void
{
this._scrollPositionHorizontalAlign = value;
}
/**
* @inheritDoc
*/
public function get requiresLayoutOnScroll():Boolean
{
return this._useVirtualLayout;
}
/**
* @inheritDoc
*/
public function layout(items:Vector., viewPortBounds:ViewPortBounds = null, result:LayoutBoundsResult = null):LayoutBoundsResult
{
//this function is very long because it may be called every frame,
//in some situations. testing revealed that splitting this function
//into separate, smaller functions affected performance.
//since the SWC compiler cannot inline functions, we can't use that
//feature either.
//since viewPortBounds can be null, we may need to provide some defaults
var scrollX:Number = viewPortBounds ? viewPortBounds.scrollX : 0;
var scrollY:Number = viewPortBounds ? viewPortBounds.scrollY : 0;
var boundsX:Number = viewPortBounds ? viewPortBounds.x : 0;
var boundsY:Number = viewPortBounds ? viewPortBounds.y : 0;
var minWidth:Number = viewPortBounds ? viewPortBounds.minWidth : 0;
var minHeight:Number = viewPortBounds ? viewPortBounds.minHeight : 0;
var maxWidth:Number = viewPortBounds ? viewPortBounds.maxWidth : Number.POSITIVE_INFINITY;
var maxHeight:Number = viewPortBounds ? viewPortBounds.maxHeight : Number.POSITIVE_INFINITY;
var explicitWidth:Number = viewPortBounds ? viewPortBounds.explicitWidth : NaN;
var explicitHeight:Number = viewPortBounds ? viewPortBounds.explicitHeight : NaN;
if(this._useVirtualLayout)
{
//if the layout is virtualized, we'll need the dimensions of the
//typical item so that we have fallback values when an item is null
this.prepareTypicalItem(explicitHeight - this._paddingTop - this._paddingBottom);
var calculatedTypicalItemWidth:Number = this._typicalItem ? this._typicalItem.width : 0;
var calculatedTypicalItemHeight:Number = this._typicalItem ? this._typicalItem.height : 0;
}
if(!this._useVirtualLayout || this._hasVariableItemDimensions || this._distributeWidths ||
this._verticalAlign != VerticalAlign.JUSTIFY ||
explicitHeight !== explicitHeight) //isNaN
{
//in some cases, we may need to validate all of the items so
//that we can use their dimensions below.
this.validateItems(items, explicitHeight - this._paddingTop - this._paddingBottom,
minHeight - this._paddingTop - this._paddingBottom,
maxHeight - this._paddingTop - this._paddingBottom,
explicitWidth);
}
if(!this._useVirtualLayout)
{
//handle the percentWidth property from HorizontalLayoutData,
//if available.
this.applyPercentWidths(items, explicitWidth, minWidth, maxWidth);
}
var distributedWidth:Number;
if(this._distributeWidths)
{
//distribute the width evenly among all items
distributedWidth = this.calculateDistributedWidth(items, explicitWidth, minWidth, maxWidth);
}
var hasDistributedWidth:Boolean = distributedWidth === distributedWidth; //!isNaN
//this section prepares some variables needed for the following loop
var hasFirstGap:Boolean = this._firstGap === this._firstGap; //!isNaN
var hasLastGap:Boolean = this._lastGap === this._lastGap; //!isNaN
var maxItemHeight:Number = this._useVirtualLayout ? calculatedTypicalItemHeight : 0;
var positionX:Number = boundsX + this._paddingLeft;
var itemCount:int = items.length;
var totalItemCount:int = itemCount;
if(this._useVirtualLayout && !this._hasVariableItemDimensions)
{
//if the layout is virtualized, and the items all have the same
//width, we can make our loops smaller by skipping some items
//at the beginning and end. this improves performance.
totalItemCount += this._beforeVirtualizedItemCount + this._afterVirtualizedItemCount;
positionX += (this._beforeVirtualizedItemCount * (calculatedTypicalItemWidth + this._gap));
if(hasFirstGap && this._beforeVirtualizedItemCount > 0)
{
positionX = positionX - this._gap + this._firstGap;
}
}
var secondToLastIndex:int = totalItemCount - 2;
//this cache is used to save non-null items in virtual layouts. by
//using a smaller array, we can improve performance by spending less
//time in the upcoming loops.
this._discoveredItemsCache.length = 0;
var discoveredItemsCacheLastIndex:int = 0;
//if there are no items in layout, then we don't want to subtract
//any gap when calculating the total width, so default to 0.
var gap:Number = 0;
//this first loop sets the x position of items, and it calculates
//the total width of all items
for(var i:int = 0; i < itemCount; i++)
{
var item:DisplayObject = items[i];
//if we're trimming some items at the beginning, we need to
//adjust i to account for the missing items in the array
var iNormalized:int = i + this._beforeVirtualizedItemCount;
//pick the gap that will follow this item. the first and second
//to last items may have different gaps.
gap = this._gap;
if(hasFirstGap && iNormalized == 0)
{
gap = this._firstGap;
}
else if(hasLastGap && iNormalized > 0 && iNormalized == secondToLastIndex)
{
gap = this._lastGap;
}
if(this._useVirtualLayout && this._hasVariableItemDimensions)
{
var cachedWidth:Number = this._widthCache[iNormalized];
}
if(this._useVirtualLayout && !item)
{
//the item is null, and the layout is virtualized, so we
//need to estimate the width of the item.
if(!this._hasVariableItemDimensions ||
cachedWidth !== cachedWidth) //isNaN
{
//if all items must have the same width, we will
//use the width of the typical item (calculatedTypicalItemWidth).
//if items may have different widths, we first check
//the cache for a width value. if there isn't one, then
//we'll use calculatedTypicalItemWidth as a fallback.
positionX += calculatedTypicalItemWidth + gap;
}
else
{
//if we have variable item widths, we should use a
//cached width when there's one available. it will be
//more accurate than the typical item's width.
positionX += cachedWidth + gap;
}
}
else
{
//we get here if the item isn't null. it is never null if
//the layout isn't virtualized.
if(item is ILayoutDisplayObject && !ILayoutDisplayObject(item).includeInLayout)
{
continue;
}
item.x = item.pivotX + positionX;
var itemWidth:Number;
if(hasDistributedWidth)
{
item.width = itemWidth = distributedWidth;
}
else
{
itemWidth = item.width;
}
var itemHeight:Number = item.height;
if(this._useVirtualLayout)
{
if(this._hasVariableItemDimensions)
{
if(itemWidth != cachedWidth)
{
//update the cache if needed. this will notify
//the container that the virtualized layout has
//changed, and it the view port may need to be
//re-measured.
this._widthCache[iNormalized] = itemWidth;
//attempt to adjust the scroll position so that
//it looks like we're scrolling smoothly after
//this item resizes.
if(positionX < scrollX &&
cachedWidth !== cachedWidth && //isNaN
itemWidth != calculatedTypicalItemWidth)
{
this.dispatchEventWith(Event.SCROLL, false, new Point(itemWidth - calculatedTypicalItemWidth, 0));
}
this.dispatchEventWith(Event.CHANGE);
}
}
else if(calculatedTypicalItemWidth >= 0)
{
//if all items must have the same width, we will
//use the width of the typical item (calculatedTypicalItemWidth).
item.width = itemWidth = calculatedTypicalItemWidth;
}
}
positionX += itemWidth + gap;
//we compare with > instead of Math.max() because the rest
//arguments on Math.max() cause extra garbage collection and
//hurt performance
if(itemHeight > maxItemHeight)
{
//we need to know the maximum height of the items in the
//case where the height of the view port needs to be
//calculated by the layout.
maxItemHeight = itemHeight;
}
if(this._useVirtualLayout)
{
this._discoveredItemsCache[discoveredItemsCacheLastIndex] = item;
discoveredItemsCacheLastIndex++;
}
}
}
if(this._useVirtualLayout && !this._hasVariableItemDimensions)
{
//finish the final calculation of the x position so that it can
//be used for the total width of all items
positionX += (this._afterVirtualizedItemCount * (calculatedTypicalItemWidth + this._gap));
if(hasLastGap && this._afterVirtualizedItemCount > 0)
{
positionX = positionX - this._gap + this._lastGap;
}
}
//this array will contain all items that are not null. see the
//comment above where the discoveredItemsCache is initialized for
//details about why this is important.
var discoveredItems:Vector. = this._useVirtualLayout ? this._discoveredItemsCache : items;
var discoveredItemCount:int = discoveredItems.length;
var totalHeight:Number = maxItemHeight + this._paddingTop + this._paddingBottom;
//the available height is the height of the viewport. if the explicit
//height is NaN, we need to calculate the viewport height ourselves
//based on the total height of all items.
var availableHeight:Number = explicitHeight;
if(availableHeight !== availableHeight) //isNaN
{
availableHeight = totalHeight;
if(availableHeight < minHeight)
{
availableHeight = minHeight;
}
else if(availableHeight > maxHeight)
{
availableHeight = maxHeight;
}
}
//this is the total width of all items
var totalWidth:Number = positionX - gap + this._paddingRight - boundsX;
//the available width is the width of the viewport. if the explicit
//width is NaN, we need to calculate the viewport width ourselves
//based on the total width of all items.
var availableWidth:Number = explicitWidth;
if(availableWidth !== availableWidth) //isNaN
{
if(this._requestedColumnCount > 0)
{
availableWidth = (calculatedTypicalItemWidth + this._gap) * this._requestedColumnCount - this._gap + this._paddingLeft + this._paddingRight
}
else
{
availableWidth = totalWidth;
}
if(availableWidth < minWidth)
{
availableWidth = minWidth;
}
else if(availableWidth > maxWidth)
{
availableWidth = maxWidth;
}
}
//in this section, we handle horizontal alignment. items will be
//aligned horizontally if the total width of all items is less than
//the available width of the view port.
if(totalWidth < availableWidth)
{
var horizontalAlignOffsetX:Number = 0;
if(this._horizontalAlign == HorizontalAlign.RIGHT)
{
horizontalAlignOffsetX = availableWidth - totalWidth;
}
else if(this._horizontalAlign == HorizontalAlign.CENTER)
{
horizontalAlignOffsetX = Math.round((availableWidth - totalWidth) / 2);
}
if(horizontalAlignOffsetX != 0)
{
for(i = 0; i < discoveredItemCount; i++)
{
item = discoveredItems[i];
if(item is ILayoutDisplayObject && !ILayoutDisplayObject(item).includeInLayout)
{
continue;
}
item.x += horizontalAlignOffsetX;
}
}
}
for(i = 0; i < discoveredItemCount; i++)
{
item = discoveredItems[i];
var layoutItem:ILayoutDisplayObject = item as ILayoutDisplayObject;
if(layoutItem && !layoutItem.includeInLayout)
{
continue;
}
//in this section, we handle vertical alignment and percent
//height from HorizontalLayoutData
if(this._verticalAlign == VerticalAlign.JUSTIFY)
{
//if we justify items vertically, we can skip percent height
item.y = item.pivotY + boundsY + this._paddingTop;
item.height = availableHeight - this._paddingTop - this._paddingBottom;
}
else
{
if(layoutItem)
{
var layoutData:HorizontalLayoutData = layoutItem.layoutData as HorizontalLayoutData;
if(layoutData)
{
//in this section, we handle percentage width if
//VerticalLayoutData is available.
var percentHeight:Number = layoutData.percentHeight;
if(percentHeight === percentHeight) //!isNaN
{
if(percentHeight < 0)
{
percentHeight = 0;
}
if(percentHeight > 100)
{
percentHeight = 100;
}
itemHeight = percentHeight * (availableHeight - this._paddingTop - this._paddingBottom) / 100;
if(item is IFeathersControl)
{
var feathersItem:IFeathersControl = IFeathersControl(item);
var itemMinHeight:Number = feathersItem.minHeight;
if(itemHeight < itemMinHeight)
{
itemHeight = itemMinHeight;
}
else
{
var itemMaxHeight:Number = feathersItem.maxHeight;
if(itemHeight > itemMaxHeight)
{
itemHeight = itemMaxHeight;
}
}
}
item.height = itemHeight;
}
}
}
//handle all other vertical alignment values (we handled
//justify already). the y position of all items is set here.
var verticalAlignHeight:Number = availableHeight;
if(totalHeight > verticalAlignHeight)
{
verticalAlignHeight = totalHeight;
}
switch(this._verticalAlign)
{
case VerticalAlign.BOTTOM:
{
item.y = item.pivotY + boundsY + verticalAlignHeight - this._paddingBottom - item.height;
break;
}
case VerticalAlign.MIDDLE:
{
item.y = item.pivotY + boundsY + this._paddingTop + Math.round((verticalAlignHeight - this._paddingTop - this._paddingBottom - item.height) / 2);
break;
}
default: //top
{
item.y = item.pivotY + boundsY + this._paddingTop;
}
}
}
}
//we don't want to keep a reference to any of the items, so clear
//this cache
this._discoveredItemsCache.length = 0;
//finally, we want to calculate the result so that the container
//can use it to adjust its viewport and determine the minimum and
//maximum scroll positions (if needed)
if(!result)
{
result = new LayoutBoundsResult();
}
result.contentX = 0;
result.contentWidth = totalWidth;
result.contentY = 0;
result.contentHeight = this._verticalAlign == VerticalAlign.JUSTIFY ? availableHeight : totalHeight;
result.viewPortWidth = availableWidth;
result.viewPortHeight = availableHeight;
return result;
}
/**
* @inheritDoc
*/
public function measureViewPort(itemCount:int, viewPortBounds:ViewPortBounds = null, result:Point = null):Point
{
if(!result)
{
result = new Point();
}
if(!this._useVirtualLayout)
{
throw new IllegalOperationError("measureViewPort() may be called only if useVirtualLayout is true.")
}
var explicitWidth:Number = viewPortBounds ? viewPortBounds.explicitWidth : NaN;
var explicitHeight:Number = viewPortBounds ? viewPortBounds.explicitHeight : NaN;
var needsWidth:Boolean = explicitWidth !== explicitWidth; //isNaN
var needsHeight:Boolean = explicitHeight !== explicitHeight; //isNaN
if(!needsWidth && !needsHeight)
{
result.x = explicitWidth;
result.y = explicitHeight;
return result;
}
var minWidth:Number = viewPortBounds ? viewPortBounds.minWidth : 0;
var minHeight:Number = viewPortBounds ? viewPortBounds.minHeight : 0;
var maxWidth:Number = viewPortBounds ? viewPortBounds.maxWidth : Number.POSITIVE_INFINITY;
var maxHeight:Number = viewPortBounds ? viewPortBounds.maxHeight : Number.POSITIVE_INFINITY;
this.prepareTypicalItem(explicitHeight - this._paddingTop - this._paddingBottom);
var calculatedTypicalItemWidth:Number = this._typicalItem ? this._typicalItem.width : 0;
var calculatedTypicalItemHeight:Number = this._typicalItem ? this._typicalItem.height : 0;
var hasFirstGap:Boolean = this._firstGap === this._firstGap; //!isNaN
var hasLastGap:Boolean = this._lastGap === this._lastGap; //!isNaN
var positionX:Number;
if(this._distributeWidths)
{
positionX = (calculatedTypicalItemWidth + this._gap) * itemCount;
}
else
{
positionX = 0;
var maxItemHeight:Number = calculatedTypicalItemHeight;
if(!this._hasVariableItemDimensions)
{
positionX += ((calculatedTypicalItemWidth + this._gap) * itemCount);
}
else
{
for(var i:int = 0; i < itemCount; i++)
{
var cachedWidth:Number = this._widthCache[i];
if(cachedWidth !== cachedWidth) //isNaN
{
positionX += calculatedTypicalItemWidth + this._gap;
}
else
{
positionX += cachedWidth + this._gap;
}
}
}
}
positionX -= this._gap;
if(hasFirstGap && itemCount > 1)
{
positionX = positionX - this._gap + this._firstGap;
}
if(hasLastGap && itemCount > 2)
{
positionX = positionX - this._gap + this._lastGap;
}
if(needsWidth)
{
if(this._requestedColumnCount > 0)
{
var resultWidth:Number = (calculatedTypicalItemWidth + this._gap) * this._requestedColumnCount - this._gap + this._paddingLeft + this._paddingRight
}
else
{
resultWidth = positionX + this._paddingLeft + this._paddingRight;
}
if(resultWidth < minWidth)
{
resultWidth = minWidth;
}
else if(resultWidth > maxWidth)
{
resultWidth = maxWidth;
}
result.x = resultWidth;
}
else
{
result.x = explicitWidth;
}
if(needsHeight)
{
var resultHeight:Number = maxItemHeight + this._paddingTop + this._paddingBottom;
if(resultHeight < minHeight)
{
resultHeight = minHeight;
}
else if(resultHeight > maxHeight)
{
resultHeight = maxHeight;
}
result.y = resultHeight;
}
else
{
result.y = explicitHeight;
}
return result;
}
/**
* @inheritDoc
*/
public function resetVariableVirtualCache():void
{
this._widthCache.length = 0;
}
/**
* @inheritDoc
*/
public function resetVariableVirtualCacheAtIndex(index:int, item:DisplayObject = null):void
{
delete this._widthCache[index];
if(item)
{
this._widthCache[index] = item.width;
this.dispatchEventWith(Event.CHANGE);
}
}
/**
* @inheritDoc
*/
public function addToVariableVirtualCacheAtIndex(index:int, item:DisplayObject = null):void
{
var widthValue:* = item ? item.width : undefined;
this._widthCache.insertAt(index, widthValue);
}
/**
* @inheritDoc
*/
public function removeFromVariableVirtualCacheAtIndex(index:int):void
{
this._widthCache.removeAt(index);
}
/**
* @inheritDoc
*/
public function getVisibleIndicesAtScrollPosition(scrollX:Number, scrollY:Number, width:Number, height:Number, itemCount:int, result:Vector. = null):Vector.
{
if(result)
{
result.length = 0;
}
else
{
result = new [];
}
if(!this._useVirtualLayout)
{
throw new IllegalOperationError("getVisibleIndicesAtScrollPosition() may be called only if useVirtualLayout is true.")
}
this.prepareTypicalItem(height - this._paddingTop - this._paddingBottom);
var calculatedTypicalItemWidth:Number = this._typicalItem ? this._typicalItem.width : 0;
var calculatedTypicalItemHeight:Number = this._typicalItem ? this._typicalItem.height : 0;
var hasFirstGap:Boolean = this._firstGap === this._firstGap; //!isNaN
var hasLastGap:Boolean = this._lastGap === this._lastGap; //!isNaN
var resultLastIndex:int = 0;
//we add one extra here because the first item renderer in view may
//be partially obscured, which would reveal an extra item renderer.
var maxVisibleTypicalItemCount:int = Math.ceil(width / (calculatedTypicalItemWidth + this._gap)) + 1;
if(!this._hasVariableItemDimensions)
{
//this case can be optimized because we know that every item has
//the same width
var totalItemWidth:Number = itemCount * (calculatedTypicalItemWidth + this._gap) - this._gap;
if(hasFirstGap && itemCount > 1)
{
totalItemWidth = totalItemWidth - this._gap + this._firstGap;
}
if(hasLastGap && itemCount > 2)
{
totalItemWidth = totalItemWidth - this._gap + this._lastGap;
}
var indexOffset:int = 0;
if(totalItemWidth < width)
{
if(this._horizontalAlign == HorizontalAlign.RIGHT)
{
indexOffset = Math.ceil((width - totalItemWidth) / (calculatedTypicalItemWidth + this._gap));
}
else if(this._horizontalAlign == HorizontalAlign.CENTER)
{
indexOffset = Math.ceil(((width - totalItemWidth) / (calculatedTypicalItemWidth + this._gap)) / 2);
}
}
var minimum:int = (scrollX - this._paddingLeft) / (calculatedTypicalItemWidth + this._gap);
if(minimum < 0)
{
minimum = 0;
}
minimum -= indexOffset;
//if we're scrolling beyond the final item, we should keep the
//indices consistent so that items aren't destroyed and
//recreated unnecessarily
var maximum:int = minimum + maxVisibleTypicalItemCount;
if(maximum >= itemCount)
{
maximum = itemCount - 1;
}
minimum = maximum - maxVisibleTypicalItemCount;
if(minimum < 0)
{
minimum = 0;
}
for(var i:int = minimum; i <= maximum; i++)
{
if(i >= 0 && i < itemCount)
{
result[resultLastIndex] = i;
}
else if(i < 0)
{
result[resultLastIndex] = itemCount + i;
}
else if(i >= itemCount)
{
result[resultLastIndex] = i - itemCount;
}
resultLastIndex++;
}
return result;
}
var secondToLastIndex:int = itemCount - 2;
var maxPositionX:Number = scrollX + width;
var positionX:Number = this._paddingLeft;
for(i = 0; i < itemCount; i++)
{
var gap:Number = this._gap;
if(hasFirstGap && i == 0)
{
gap = this._firstGap;
}
else if(hasLastGap && i > 0 && i == secondToLastIndex)
{
gap = this._lastGap;
}
var cachedWidth:Number = this._widthCache[i];
if(cachedWidth !== cachedWidth) //isNaN
{
var itemWidth:Number = calculatedTypicalItemWidth;
}
else
{
itemWidth = cachedWidth;
}
var oldPositionX:Number = positionX;
positionX += itemWidth + gap;
if(positionX > scrollX && oldPositionX < maxPositionX)
{
result[resultLastIndex] = i;
resultLastIndex++;
}
if(positionX >= maxPositionX)
{
break;
}
}
//similar to above, in order to avoid costly destruction and
//creation of item renderers, we're going to fill in some extra
//indices
var resultLength:int = result.length;
var visibleItemCountDifference:int = maxVisibleTypicalItemCount - resultLength;
if(visibleItemCountDifference > 0 && resultLength > 0)
{
//add extra items before the first index
var firstExistingIndex:int = result[0];
var lastIndexToAdd:int = firstExistingIndex - visibleItemCountDifference;
if(lastIndexToAdd < 0)
{
lastIndexToAdd = 0;
}
for(i = firstExistingIndex - 1; i >= lastIndexToAdd; i--)
{
result.insertAt(0, i);
}
}
resultLength = result.length;
resultLastIndex = resultLength;
visibleItemCountDifference = maxVisibleTypicalItemCount - resultLength;
if(visibleItemCountDifference > 0)
{
//add extra items after the last index
var startIndex:int = resultLength > 0 ? (result[resultLength - 1] + 1) : 0;
var endIndex:int = startIndex + visibleItemCountDifference;
if(endIndex > itemCount)
{
endIndex = itemCount;
}
for(i = startIndex; i < endIndex; i++)
{
result[resultLastIndex] = i;
resultLastIndex++;
}
}
return result;
}
/**
* @inheritDoc
*/
public function getNearestScrollPositionForIndex(index:int, scrollX:Number, scrollY:Number, items:Vector.,
x:Number, y:Number, width:Number, height:Number, result:Point = null):Point
{
var maxScrollX:Number = this.calculateMaxScrollXOfIndex(index, items, x, y, width, height);
if(this._useVirtualLayout)
{
if(this._hasVariableItemDimensions)
{
var itemWidth:Number = this._widthCache[index];
if(itemWidth !== itemWidth)
{
itemWidth = this._typicalItem.width;
}
}
else
{
itemWidth = this._typicalItem.width;
}
}
else
{
itemWidth = items[index].width;
}
if(!result)
{
result = new Point();
}
var rightPosition:Number = maxScrollX - (width - itemWidth);
if(scrollX >= rightPosition && scrollX <= maxScrollX)
{
//keep the current scroll position because the item is already
//fully visible
result.x = scrollX;
}
else
{
var leftDifference:Number = Math.abs(maxScrollX - scrollX);
var rightDifference:Number = Math.abs(rightPosition - scrollX);
if(rightDifference < leftDifference)
{
result.x = rightPosition;
}
else
{
result.x = maxScrollX;
}
}
result.y = 0;
return result;
}
/**
* @inheritDoc
*/
public function getScrollPositionForIndex(index:int, items:Vector., x:Number, y:Number, width:Number, height:Number, result:Point = null):Point
{
var maxScrollX:Number = this.calculateMaxScrollXOfIndex(index, items, x, y, width, height);
if(this._useVirtualLayout)
{
if(this._hasVariableItemDimensions)
{
var itemWidth:Number = this._widthCache[index];
if(itemWidth !== itemWidth)
{
itemWidth = this._typicalItem.width;
}
}
else
{
itemWidth = this._typicalItem.width;
}
}
else
{
itemWidth = items[index].width;
}
if(this._scrollPositionHorizontalAlign == HorizontalAlign.CENTER)
{
maxScrollX -= Math.round((width - itemWidth) / 2);
}
else if(this._scrollPositionHorizontalAlign == HorizontalAlign.RIGHT)
{
maxScrollX -= (width - itemWidth);
}
result.x = maxScrollX;
result.y = 0;
return result;
}
/**
* @private
*/
protected function validateItems(items:Vector., explicitHeight:Number,
minHeight:Number, maxHeight:Number, distributedWidth:Number):void
{
//if the alignment is justified, then we want to set the height of
//each item before validating because setting one dimension may
//cause the other dimension to change, and that will invalidate the
//layout if it happens after validation, causing more invalidation
var isJustified:Boolean = this._verticalAlign == VerticalAlign.JUSTIFY;
var itemCount:int = items.length;
for(var i:int = 0; i < itemCount; i++)
{
var item:DisplayObject = items[i];
if(!item || (item is ILayoutDisplayObject && !ILayoutDisplayObject(item).includeInLayout))
{
continue;
}
if(this._distributeWidths)
{
item.width = distributedWidth;
}
if(isJustified)
{
//the alignment is justified, but we don't yet have a width
//to use, so we need to ensure that we accurately measure
//the items instead of using an old justified height that
//may be wrong now!
item.height = explicitHeight;
if(item is IFeathersControl)
{
var feathersItem:IFeathersControl = IFeathersControl(item);
feathersItem.minHeight = minHeight;
feathersItem.maxHeight = maxHeight;
}
}
if(item is IValidating)
{
IValidating(item).validate()
}
}
}
/**
* @private
*/
protected function prepareTypicalItem(justifyHeight:Number):void
{
if(!this._typicalItem)
{
return;
}
if(this._resetTypicalItemDimensionsOnMeasure)
{
this._typicalItem.width = this._typicalItemWidth;
}
var hasSetHeight:Boolean = false;
if(this._verticalAlign == VerticalAlign.JUSTIFY &&
justifyHeight === justifyHeight) //!isNaN
{
hasSetHeight = true;
this._typicalItem.height = justifyHeight;
}
else if(this._typicalItem is ILayoutDisplayObject)
{
var layoutItem:ILayoutDisplayObject = ILayoutDisplayObject(this._typicalItem);
var layoutData:VerticalLayoutData = layoutItem.layoutData as VerticalLayoutData;
if(layoutData && layoutData.percentHeight === layoutData.percentHeight)
{
hasSetHeight = true;
this._typicalItem.height = justifyHeight * layoutData.percentHeight / 100;
}
}
if(!hasSetHeight && this._resetTypicalItemDimensionsOnMeasure)
{
this._typicalItem.height = this._typicalItemHeight;
}
if(this._typicalItem is IValidating)
{
IValidating(this._typicalItem).validate();
}
}
/**
* @private
*/
protected function calculateDistributedWidth(items:Vector., explicitWidth:Number, minWidth:Number, maxWidth:Number):Number
{
var itemCount:int = items.length;
if(explicitWidth !== explicitWidth) //isNaN
{
var maxItemWidth:Number = 0;
for(var i:int = 0; i < itemCount; i++)
{
var item:DisplayObject = items[i];
var itemWidth:Number = item.width;
if(itemWidth > maxItemWidth)
{
maxItemWidth = itemWidth;
}
}
explicitWidth = maxItemWidth * itemCount + this._paddingLeft + this._paddingRight + this._gap * (itemCount - 1);
var needsRecalculation:Boolean = false;
if(explicitWidth > maxWidth)
{
explicitWidth = maxWidth;
needsRecalculation = true;
}
else if(explicitWidth < minWidth)
{
explicitWidth = minWidth;
needsRecalculation = true;
}
if(!needsRecalculation)
{
return maxItemWidth;
}
}
var availableSpace:Number = explicitWidth - this._paddingLeft - this._paddingRight - this._gap * (itemCount - 1);
if(itemCount > 1 && this._firstGap === this._firstGap) //!isNaN
{
availableSpace += this._gap - this._firstGap;
}
if(itemCount > 2 && this._lastGap === this._lastGap) //!isNaN
{
availableSpace += this._gap - this._lastGap;
}
return availableSpace / itemCount;
}
/**
* @private
*/
protected function applyPercentWidths(items:Vector., explicitWidth:Number, minWidth:Number, maxWidth:Number):void
{
var remainingWidth:Number = explicitWidth;
this._discoveredItemsCache.length = 0;
var totalExplicitWidth:Number = 0;
var totalMinWidth:Number = 0;
var totalPercentWidth:Number = 0;
var itemCount:int = items.length;
var pushIndex:int = 0;
for(var i:int = 0; i < itemCount; i++)
{
var item:DisplayObject = items[i];
if(item is ILayoutDisplayObject)
{
var layoutItem:ILayoutDisplayObject = ILayoutDisplayObject(item);
if(!layoutItem.includeInLayout)
{
continue;
}
var layoutData:HorizontalLayoutData = layoutItem.layoutData as HorizontalLayoutData;
if(layoutData)
{
var percentWidth:Number = layoutData.percentWidth;
if(percentWidth === percentWidth) //!isNaN
{
if(layoutItem is IFeathersControl)
{
var feathersItem:IFeathersControl = IFeathersControl(layoutItem);
totalMinWidth += feathersItem.minWidth;
}
totalPercentWidth += percentWidth;
this._discoveredItemsCache[pushIndex] = item;
pushIndex++;
continue;
}
}
}
totalExplicitWidth += item.width;
}
totalExplicitWidth += this._gap * (itemCount - 1);
if(this._firstGap === this._firstGap && itemCount > 1)
{
totalExplicitWidth += (this._firstGap - this._gap);
}
else if(this._lastGap === this._lastGap && itemCount > 2)
{
totalExplicitWidth += (this._lastGap - this._gap);
}
totalExplicitWidth += this._paddingLeft + this._paddingRight;
if(totalPercentWidth < 100)
{
totalPercentWidth = 100;
}
if(remainingWidth !== remainingWidth) //isNaN
{
remainingWidth = totalExplicitWidth + totalMinWidth;
if(remainingWidth < minWidth)
{
remainingWidth = minWidth;
}
else if(remainingWidth > maxWidth)
{
remainingWidth = maxWidth;
}
}
remainingWidth -= totalExplicitWidth;
if(remainingWidth < 0)
{
remainingWidth = 0;
}
do
{
var needsAnotherPass:Boolean = false;
var percentToPixels:Number = remainingWidth / totalPercentWidth;
for(i = 0; i < pushIndex; i++)
{
layoutItem = ILayoutDisplayObject(this._discoveredItemsCache[i]);
if(!layoutItem)
{
continue;
}
layoutData = HorizontalLayoutData(layoutItem.layoutData);
percentWidth = layoutData.percentWidth;
var itemWidth:Number = percentToPixels * percentWidth;
if(layoutItem is IFeathersControl)
{
feathersItem = IFeathersControl(layoutItem);
var itemMinWidth:Number = feathersItem.minWidth;
if(itemWidth < itemMinWidth)
{
itemWidth = itemMinWidth;
remainingWidth -= itemWidth;
totalPercentWidth -= percentWidth;
this._discoveredItemsCache[i] = null;
needsAnotherPass = true;
}
else
{
var itemMaxWidth:Number = feathersItem.maxWidth;
if(itemWidth > itemMaxWidth)
{
itemWidth = itemMaxWidth;
remainingWidth -= itemWidth;
totalPercentWidth -= percentWidth;
this._discoveredItemsCache[i] = null;
needsAnotherPass = true;
}
}
}
layoutItem.width = itemWidth;
if(layoutItem is IValidating)
{
//changing the width of the item may cause its height
//to change, so we need to validate. the height is
//needed for measurement.
IValidating(layoutItem).validate();
}
}
}
while(needsAnotherPass)
this._discoveredItemsCache.length = 0;
}
/**
* @private
*/
protected function calculateMaxScrollXOfIndex(index:int, items:Vector., x:Number, y:Number, width:Number, height:Number):Number
{
if(this._useVirtualLayout)
{
this.prepareTypicalItem(height - this._paddingTop - this._paddingBottom);
var calculatedTypicalItemWidth:Number = this._typicalItem ? this._typicalItem.width : 0;
var calculatedTypicalItemHeight:Number = this._typicalItem ? this._typicalItem.height : 0;
}
var hasFirstGap:Boolean = this._firstGap === this._firstGap; //!isNaN
var hasLastGap:Boolean = this._lastGap === this._lastGap; //!isNaN
var positionX:Number = x + this._paddingLeft;
var lastWidth:Number = 0;
var gap:Number = this._gap;
var startIndexOffset:int = 0;
var endIndexOffset:Number = 0;
var itemCount:int = items.length;
var totalItemCount:int = itemCount;
if(this._useVirtualLayout && !this._hasVariableItemDimensions)
{
totalItemCount += this._beforeVirtualizedItemCount + this._afterVirtualizedItemCount;
if(index < this._beforeVirtualizedItemCount)
{
//this makes it skip the loop below
startIndexOffset = index + 1;
lastWidth = calculatedTypicalItemWidth;
gap = this._gap;
}
else
{
startIndexOffset = this._beforeVirtualizedItemCount;
endIndexOffset = index - items.length - this._beforeVirtualizedItemCount + 1;
if(endIndexOffset < 0)
{
endIndexOffset = 0;
}
positionX += (endIndexOffset * (calculatedTypicalItemWidth + this._gap));
}
positionX += (startIndexOffset * (calculatedTypicalItemWidth + this._gap));
}
index -= (startIndexOffset + endIndexOffset);
var secondToLastIndex:int = totalItemCount - 2;
for(var i:int = 0; i <= index; i++)
{
var item:DisplayObject = items[i];
var iNormalized:int = i + startIndexOffset;
if(hasFirstGap && iNormalized == 0)
{
gap = this._firstGap;
}
else if(hasLastGap && iNormalized > 0 && iNormalized == secondToLastIndex)
{
gap = this._lastGap;
}
else
{
gap = this._gap;
}
if(this._useVirtualLayout && this._hasVariableItemDimensions)
{
var cachedWidth:Number = this._widthCache[iNormalized];
}
if(this._useVirtualLayout && !item)
{
if(!this._hasVariableItemDimensions ||
cachedWidth !== cachedWidth) //isNaN
{
lastWidth = calculatedTypicalItemWidth;
}
else
{
lastWidth = cachedWidth;
}
}
else
{
var itemWidth:Number = item.width;
if(this._useVirtualLayout)
{
if(this._hasVariableItemDimensions)
{
if(itemWidth != cachedWidth)
{
this._widthCache[iNormalized] = itemWidth;
this.dispatchEventWith(Event.CHANGE);
}
}
else if(calculatedTypicalItemWidth >= 0)
{
item.width = itemWidth = calculatedTypicalItemWidth;
}
}
lastWidth = itemWidth;
}
positionX += lastWidth + gap;
}
positionX -= (lastWidth + gap);
return positionX;
}
}
}