scaffold.libs_as.feathers.layout.HorizontalSpinnerLayout.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")]
/**
* For use with the SpinnerList
component, positions items from
* left to right in a single row and repeats infinitely.
*
* @see ../../../help/horizontal-spinner-layout.html How to use HorizontalSpinnerLayout with the Feathers SpinnerList component
*/
public class HorizontalSpinnerLayout extends EventDispatcher implements ISpinnerLayout, 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";
/**
* Constructor.
*/
public function HorizontalSpinnerLayout()
{
}
/**
* @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);
}
/**
* 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 #paddingBottom
*/
public function get padding():Number
{
return this._paddingTop;
}
/**
* @private
*/
public function set padding(value:Number):void
{
this.paddingTop = value;
this.paddingBottom = 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 _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 _verticalAlign:String = VerticalAlign.TOP;
[Inspectable(type="String",enumeration="top,middle,bottom,justify")]
/**
* The alignment of the items vertically, on the y-axis.
*
* @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 _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 _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 _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 _repeatItems:Boolean = true;
/**
* If set to true
, the layout will repeat the items
* infinitely, if there are enough items to allow this behavior. If the
* total width of the items is smaller than the width of the view port,
* the items cannot repeat.
*
* @default true
*/
public function get repeatItems():Boolean
{
return this._repeatItems;
}
/**
* @private
*/
public function set repeatItems(value:Boolean):void
{
if(this._repeatItems == value)
{
return;
}
this._repeatItems = value;
this.dispatchEventWith(Event.CHANGE);
}
/**
* @copy feathers.layout.ISpinnerLayout#snapInterval
*/
public function get snapInterval():Number
{
return this._typicalItem.width + this._gap;
}
/**
* @inheritDoc
*/
public function get requiresLayoutOnScroll():Boolean
{
return true;
}
/**
* @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._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, explicitWidth, explicitHeight - this._paddingTop - this._paddingBottom);
}
//this section prepares some variables needed for the following loop
var maxItemHeight:Number = this._useVirtualLayout ? calculatedTypicalItemHeight : 0;
var positionX:Number = boundsX;
var gap:Number = this._gap;
var itemCount:int = items.length;
var totalItemCount:int = itemCount;
if(this._useVirtualLayout)
{
//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 + gap));
}
//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;
//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(item)
{
//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;
item.width = calculatedTypicalItemWidth;
var itemHeight:Number = item.height;
//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++;
}
}
positionX += calculatedTypicalItemWidth + gap;
}
if(this._useVirtualLayout)
{
//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 + gap));
}
//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 - 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 = this._requestedColumnCount * (calculatedTypicalItemWidth + gap) - gap;
}
else
{
availableWidth = totalWidth;
}
if(availableWidth < minWidth)
{
availableWidth = minWidth;
}
else if(availableWidth > maxWidth)
{
availableWidth = maxWidth;
}
}
var canRepeatItems:Boolean = this._repeatItems && totalWidth > availableWidth;
if(canRepeatItems)
{
totalWidth += gap;
}
//in this section, we handle vertical alignment. the selected item
//needs to be centered vertically.
var horizontalAlignOffsetX:Number = Math.round((availableWidth - calculatedTypicalItemWidth) / 2);
if(!canRepeatItems)
{
totalWidth += 2 * horizontalAlignOffsetX;
}
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;
}
//if we're repeating items, then we may need to adjust the x
//position of some items so that they appear inside the viewport
if(canRepeatItems)
{
var adjustedScrollX:Number = scrollX - horizontalAlignOffsetX;
if(adjustedScrollX > 0)
{
item.x += totalWidth * int((adjustedScrollX + availableWidth) / totalWidth);
if(item.x >= (scrollX + availableWidth))
{
item.x -= totalWidth;
}
}
else if(adjustedScrollX < 0)
{
item.x += totalWidth * (int(adjustedScrollX / totalWidth) - 1);
if((item.x + item.width) < scrollX)
{
item.x += totalWidth;
}
}
}
//in this section, we handle vertical alignment
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
{
//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:
{
//round to the nearest pixel when dividing by 2 to
//align in the 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();
}
if(canRepeatItems)
{
result.contentX = Number.NEGATIVE_INFINITY;
result.contentWidth = Number.POSITIVE_INFINITY;
}
else
{
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 gap:Number = this._gap;
var positionX:Number = 0;
var maxItemHeight:Number = calculatedTypicalItemHeight;
positionX += ((calculatedTypicalItemWidth + gap) * itemCount);
positionX -= gap;
if(needsWidth)
{
if(this._requestedColumnCount > 0)
{
var resultWidth:Number = (calculatedTypicalItemWidth + gap) * this._requestedColumnCount - gap;
}
else
{
resultWidth = positionX;
}
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 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 gap:Number = this._gap;
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 + gap)) + 1;
var totalItemWidth:Number = itemCount * (calculatedTypicalItemWidth + gap) - gap;
scrollX -= Math.round((width - calculatedTypicalItemWidth) / 2);
var canRepeatItems:Boolean = this._repeatItems && totalItemWidth > width;
if(canRepeatItems)
{
//if we're repeating, then there's an extra gap
totalItemWidth += gap;
scrollX %= totalItemWidth;
if(scrollX < 0)
{
scrollX += totalItemWidth;
}
var minimum:int = scrollX / (calculatedTypicalItemWidth + gap);
var maximum:int = minimum + maxVisibleTypicalItemCount;
}
else
{
minimum = scrollX / (calculatedTypicalItemWidth + gap);
if(minimum < 0)
{
minimum = 0;
}
//if we're scrolling beyond the final item, we should keep the
//indices consistent so that items aren't destroyed and
//recreated unnecessarily
maximum = 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(!canRepeatItems || (i >= 0 && i < itemCount))
{
result[resultLastIndex] = i;
}
else if(i < 0)
{
result[resultLastIndex] = itemCount + i;
}
else if(i >= itemCount)
{
var loopedI:int = i - itemCount;
if(loopedI === minimum)
{
//we don't want to repeat items!
break;
}
result[resultLastIndex] = loopedI;
}
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
{
//normally, this isn't acceptable, but because the selection is
//based on the scroll position, it must work this way.
return this.getScrollPositionForIndex(index, items, x, y, width, height, result);
}
/**
* @inheritDoc
*/
public function getScrollPositionForIndex(index:int, items:Vector., x:Number, y:Number, width:Number, height:Number, result:Point = null):Point
{
this.prepareTypicalItem(height - this._paddingTop - this._paddingBottom);
var calculatedTypicalItemHeight:Number = this._typicalItem ? this._typicalItem.height : 0;
if(!result)
{
result = new Point();
}
result.x = 0;
result.y = calculatedTypicalItemHeight * index;
return result;
}
/**
* @private
*/
protected function validateItems(items:Vector., distributedWidth:Number, justifyHeight: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 mustSetJustifyHeight:Boolean = isJustified && justifyHeight === justifyHeight; //!isNaN
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(mustSetJustifyHeight)
{
item.height = justifyHeight;
}
else if(isJustified && item is IFeathersControl)
{
//the alignment is justified, but we don't yet have a height
//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 = NaN;
}
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;
}
if(this._verticalAlign == VerticalAlign.JUSTIFY &&
justifyHeight === justifyHeight) //!isNaN
{
this._typicalItem.height = justifyHeight;
}
else if(this._resetTypicalItemDimensionsOnMeasure)
{
this._typicalItem.height = this._typicalItemHeight;
}
if(this._typicalItem is IValidating)
{
IValidating(this._typicalItem).validate();
}
}
}
}