scaffold.libs_as.feathers.controls.PageIndicator.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.controls
{
import feathers.core.FeathersControl;
import feathers.core.IValidating;
import feathers.layout.Direction;
import feathers.layout.HorizontalAlign;
import feathers.layout.HorizontalLayout;
import feathers.layout.ILayout;
import feathers.layout.IVirtualLayout;
import feathers.layout.LayoutBoundsResult;
import feathers.layout.VerticalAlign;
import feathers.layout.VerticalLayout;
import feathers.layout.ViewPortBounds;
import feathers.skins.IStyleProvider;
import flash.geom.Point;
import starling.display.DisplayObject;
import starling.display.Quad;
import starling.events.Event;
import starling.events.Touch;
import starling.events.TouchEvent;
import starling.events.TouchPhase;
/**
* Dispatched when the selected item changes.
*
* 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")]
/**
* Displays a selected index, usually corresponding to a page index in
* another UI control, using a highlighted symbol.
*
* @see ../../../help/page-indicator.html How to use the Feathers PageIndicator component
*/
public class PageIndicator extends FeathersControl
{
/**
* @private
*/
private static const LAYOUT_RESULT:LayoutBoundsResult = new LayoutBoundsResult();
/**
* @private
*/
private static const SUGGESTED_BOUNDS:ViewPortBounds = new ViewPortBounds();
/**
* @private
*/
private static const HELPER_POINT:Point = new Point();
/**
* @private
* DEPRECATED: Replaced by feathers.layout.Direction.HORIZONTAL
.
*
* 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 DIRECTION_VERTICAL:String = "vertical";
/**
* @private
* DEPRECATED: Replaced by feathers.layout.Direction.VERTICAL
.
*
* 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 DIRECTION_HORIZONTAL:String = "horizontal";
/**
* @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.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";
/**
* Touching the page indicator on the left of the selected symbol will
* select the previous index and to the right of the selected symbol
* will select the next index.
*
* @see #interactionMode
*/
public static const INTERACTION_MODE_PREVIOUS_NEXT:String = "previousNext";
/**
* Touching the page indicator on a symbol will select that symbol's
* exact index.
*
* @see #interactionMode
*/
public static const INTERACTION_MODE_PRECISE:String = "precise";
/**
* The default IStyleProvider
for all PageIndicator
* components.
*
* @default null
* @see feathers.core.FeathersControl#styleProvider
*/
public static var globalStyleProvider:IStyleProvider;
/**
* @private
*/
protected static function defaultSelectedSymbolFactory():Quad
{
return new Quad(25, 25, 0xffffff);
}
/**
* @private
*/
protected static function defaultNormalSymbolFactory():Quad
{
return new Quad(25, 25, 0xcccccc);
}
/**
* Constructor.
*/
public function PageIndicator()
{
super();
this.isQuickHitAreaEnabled = true;
this.addEventListener(TouchEvent.TOUCH, touchHandler);
}
/**
* @private
*/
protected var selectedSymbol:DisplayObject;
/**
* @private
*/
protected var cache:Vector. = new [];
/**
* @private
*/
protected var unselectedSymbols:Vector. = new [];
/**
* @private
*/
protected var symbols:Vector. = new [];
/**
* @private
*/
protected var touchPointID:int = -1;
/**
* @private
*/
override protected function get defaultStyleProvider():IStyleProvider
{
return PageIndicator.globalStyleProvider;
}
/**
* @private
*/
protected var _pageCount:int = 1;
/**
* The number of available pages.
*
* In the following example, the page count is changed:
*
*
* pages.pageCount = 5;
*
* @default 1
*/
public function get pageCount():int
{
return this._pageCount;
}
/**
* @private
*/
public function set pageCount(value:int):void
{
if(this._pageCount == value)
{
return;
}
this._pageCount = value;
this.invalidate(INVALIDATION_FLAG_DATA);
}
/**
* @private
*/
protected var _selectedIndex:int = 0;
/**
* The currently selected index.
*
* In the following example, the page indicator's selected index is
* changed:
*
*
* pages.selectedIndex = 2;
*
* The following example listens for when selection changes and
* requests the selected index:
*
*
* function pages_changeHandler( event:Event ):void
* {
* var pages:PageIndicator = PageIndicator( event.currentTarget );
* var index:int = pages.selectedIndex;
*
* }
* pages.addEventListener( Event.CHANGE, pages_changeHandler );
*
* @default 0
*/
public function get selectedIndex():int
{
return this._selectedIndex;
}
/**
* @private
*/
public function set selectedIndex(value:int):void
{
value = Math.max(0, Math.min(value, this._pageCount - 1));
if(this._selectedIndex == value)
{
return;
}
this._selectedIndex = value;
this.invalidate(INVALIDATION_FLAG_SELECTED);
this.dispatchEventWith(Event.CHANGE);
}
/**
* @private
*/
protected var _interactionMode:String = INTERACTION_MODE_PREVIOUS_NEXT;
[Inspectable(type="String",enumeration="previousNext,precise")]
/**
* Determines how the selected index changes on touch.
*
* In the following example, the interaction mode is changed to precise:
*
*
* pages.direction = PageIndicator.INTERACTION_MODE_PRECISE;
*
* @default PageIndicator.INTERACTION_MODE_PREVIOUS_NEXT
*
* @see #INTERACTION_MODE_PREVIOUS_NEXT
* @see #INTERACTION_MODE_PRECISE
*/
public function get interactionMode():String
{
return this._interactionMode;
}
/**
* @private
*/
public function set interactionMode(value:String):void
{
this._interactionMode = value;
}
/**
* @private
*/
protected var _layout:ILayout;
/**
* @private
*/
protected var _direction:String = Direction.HORIZONTAL;
[Inspectable(type="String",enumeration="horizontal,vertical")]
/**
* The symbols may be positioned vertically or horizontally.
*
* In the following example, the direction is changed to vertical:
*
*
* pages.direction = Direction.VERTICAL;
*
* @default feathers.layout.Direction.HORIZONTAL
*
* @see feathers.layout.Direction#HORIZONTAL
* @see feathers.layout.Direction#VERTICAL
*/
public function get direction():String
{
return this._direction;
}
/**
* @private
*/
public function set direction(value:String):void
{
if(this._direction == value)
{
return;
}
this._direction = value;
this.invalidate(INVALIDATION_FLAG_LAYOUT);
}
/**
* @private
*/
protected var _horizontalAlign:String = HorizontalAlign.CENTER;
[Inspectable(type="String",enumeration="left,center,right")]
/**
* The alignment of the symbols on the horizontal axis.
*
* In the following example, the symbols are horizontally aligned to
* the right:
*
*
* pages.horizontalAlign = HorizontalAlign.RIGHT;
*
* @default feathers.layout.HorizontalAlign.CENTER
*
* @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.invalidate(INVALIDATION_FLAG_LAYOUT);
}
/**
* @private
*/
protected var _verticalAlign:String = VerticalAlign.MIDDLE;
[Inspectable(type="String",enumeration="top,middle,bottom")]
/**
* The alignment of the symbols on the vertical axis.
*
* In the following example, the symbols are vertically aligned to
* the bottom:
*
*
* pages.verticalAlign = VerticalAlign.BOTTOM;
*
* @default feathers.layout.VerticalAlign.MIDDLE
*
* @see feathers.layout.VerticalAlign#TOP
* @see feathers.layout.VerticalAlign#MIDDLE
* @see feathers.layout.VerticalAlign#BOTTOM
*/
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.invalidate(INVALIDATION_FLAG_LAYOUT);
}
/**
* @private
*/
protected var _gap:Number = 0;
/**
* The spacing, in pixels, between symbols.
*
* In the following example, the gap between symbols is set to 20 pixels:
*
*
* pages.gap = 20;
*
* @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.invalidate(INVALIDATION_FLAG_LAYOUT);
}
/**
* 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.
*
* In the following example, the padding is set to 20 pixels:
*
*
* pages.padding = 20;
*
* @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, between the top edge of the component
* and the top edge of the content.
*
* In the following example, the top padding is set to 20 pixels:
*
*
* pages.paddingTop = 20;
*
* @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.invalidate(INVALIDATION_FLAG_STYLES);
}
/**
* @private
*/
protected var _paddingRight:Number = 0;
/**
* The minimum space, in pixels, between the right edge of the component
* and the right edge of the content.
*
* In the following example, the right padding is set to 20 pixels:
*
*
* pages.paddingRight = 20;
*
* @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.invalidate(INVALIDATION_FLAG_STYLES);
}
/**
* @private
*/
protected var _paddingBottom:Number = 0;
/**
* The minimum space, in pixels, between the bottom edge of the component
* and the bottom edge of the content.
*
* In the following example, the bottom padding is set to 20 pixels:
*
*
* pages.paddingBottom = 20;
*
* @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.invalidate(INVALIDATION_FLAG_STYLES);
}
/**
* @private
*/
protected var _paddingLeft:Number = 0;
/**
* The minimum space, in pixels, between the left edge of the component
* and the left edge of the content.
*
* In the following example, the left padding is set to 20 pixels:
*
*
* pages.paddingLeft = 20;
*
* @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.invalidate(INVALIDATION_FLAG_STYLES);
}
/**
* @private
*/
protected var _normalSymbolFactory:Function = defaultNormalSymbolFactory;
/**
* A function used to create a normal symbol. May be any Starling
* display object.
*
* This function should have the following signature:
* function():DisplayObject
*
* In the following example, a custom normal symbol factory is provided
* to the page indicator:
*
*
* pages.normalSymbolFactory = function():DisplayObject
* {
* return new Image( texture );
* };
*
* @see http://doc.starling-framework.org/core/starling/display/DisplayObject.html starling.display.DisplayObject
* @see #selectedSymbolFactory
*/
public function get normalSymbolFactory():Function
{
return this._normalSymbolFactory;
}
/**
* @private
*/
public function set normalSymbolFactory(value:Function):void
{
if(this._normalSymbolFactory == value)
{
return;
}
this._normalSymbolFactory = value;
this.invalidate(INVALIDATION_FLAG_STYLES);
}
/**
* @private
*/
protected var _selectedSymbolFactory:Function = defaultSelectedSymbolFactory;
/**
* A function used to create a selected symbol. May be any Starling
* display object.
*
* This function should have the following signature:
* function():DisplayObject
*
* In the following example, a custom selected symbol factory is provided
* to the page indicator:
*
*
* pages.selectedSymbolFactory = function():DisplayObject
* {
* return new Image( texture );
* };
*
* @see http://doc.starling-framework.org/core/starling/display/DisplayObject.html starling.display.DisplayObject
* @see #normalSymbolFactory
*/
public function get selectedSymbolFactory():Function
{
return this._selectedSymbolFactory;
}
/**
* @private
*/
public function set selectedSymbolFactory(value:Function):void
{
if(this._selectedSymbolFactory == value)
{
return;
}
this._selectedSymbolFactory = value;
this.invalidate(INVALIDATION_FLAG_STYLES);
}
/**
* @private
*/
override protected function draw():void
{
var dataInvalid:Boolean = this.isInvalid(INVALIDATION_FLAG_DATA);
var selectionInvalid:Boolean = this.isInvalid(INVALIDATION_FLAG_SELECTED);
var stylesInvalid:Boolean = this.isInvalid(INVALIDATION_FLAG_STYLES);
var layoutInvalid:Boolean = this.isInvalid(INVALIDATION_FLAG_LAYOUT);
if(dataInvalid || selectionInvalid || stylesInvalid)
{
this.refreshSymbols(stylesInvalid);
}
this.layoutSymbols(layoutInvalid);
}
/**
* @private
*/
protected function refreshSymbols(symbolsInvalid:Boolean):void
{
this.symbols.length = 0;
var temp:Vector. = this.cache;
if(symbolsInvalid)
{
var symbolCount:int = this.unselectedSymbols.length;
for(var i:int = 0; i < symbolCount; i++)
{
var symbol:DisplayObject = this.unselectedSymbols.shift();
this.removeChild(symbol, true);
}
if(this.selectedSymbol)
{
this.removeChild(this.selectedSymbol, true);
this.selectedSymbol = null;
}
}
this.cache = this.unselectedSymbols;
this.unselectedSymbols = temp;
for(i = 0; i < this._pageCount; i++)
{
if(i == this._selectedIndex)
{
if(!this.selectedSymbol)
{
this.selectedSymbol = this._selectedSymbolFactory();
this.addChild(this.selectedSymbol);
}
this.symbols.push(this.selectedSymbol);
if(this.selectedSymbol is IValidating)
{
IValidating(this.selectedSymbol).validate();
}
}
else
{
if(this.cache.length > 0)
{
symbol = this.cache.shift();
}
else
{
symbol = this._normalSymbolFactory();
this.addChild(symbol);
}
this.unselectedSymbols.push(symbol);
this.symbols.push(symbol);
if(symbol is IValidating)
{
IValidating(symbol).validate();
}
}
}
symbolCount = this.cache.length;
for(i = 0; i < symbolCount; i++)
{
symbol = this.cache.shift();
this.removeChild(symbol, true);
}
}
/**
* @private
*/
protected function layoutSymbols(layoutInvalid:Boolean):void
{
if(layoutInvalid)
{
if(this._direction == Direction.VERTICAL && !(this._layout is VerticalLayout))
{
this._layout = new VerticalLayout();
IVirtualLayout(this._layout).useVirtualLayout = false;
}
else if(this._direction != Direction.VERTICAL && !(this._layout is HorizontalLayout))
{
this._layout = new HorizontalLayout();
IVirtualLayout(this._layout).useVirtualLayout = false;
}
if(this._layout is VerticalLayout)
{
var verticalLayout:VerticalLayout = VerticalLayout(this._layout);
verticalLayout.paddingTop = this._paddingTop;
verticalLayout.paddingRight = this._paddingRight;
verticalLayout.paddingBottom = this._paddingBottom;
verticalLayout.paddingLeft = this._paddingLeft;
verticalLayout.gap = this._gap;
verticalLayout.horizontalAlign = this._horizontalAlign;
verticalLayout.verticalAlign = this._verticalAlign;
}
if(this._layout is HorizontalLayout)
{
var horizontalLayout:HorizontalLayout = HorizontalLayout(this._layout);
horizontalLayout.paddingTop = this._paddingTop;
horizontalLayout.paddingRight = this._paddingRight;
horizontalLayout.paddingBottom = this._paddingBottom;
horizontalLayout.paddingLeft = this._paddingLeft;
horizontalLayout.gap = this._gap;
horizontalLayout.horizontalAlign = this._horizontalAlign;
horizontalLayout.verticalAlign = this._verticalAlign;
}
}
SUGGESTED_BOUNDS.x = SUGGESTED_BOUNDS.y = 0;
SUGGESTED_BOUNDS.scrollX = SUGGESTED_BOUNDS.scrollY = 0;
SUGGESTED_BOUNDS.explicitWidth = this._explicitWidth;
SUGGESTED_BOUNDS.explicitHeight = this._explicitHeight;
SUGGESTED_BOUNDS.maxWidth = this._maxWidth;
SUGGESTED_BOUNDS.maxHeight = this._maxHeight;
SUGGESTED_BOUNDS.minWidth = this._explicitMinWidth;
SUGGESTED_BOUNDS.minHeight = this._explicitMinHeight;
this._layout.layout(this.symbols, SUGGESTED_BOUNDS, LAYOUT_RESULT);
this.saveMeasurements(LAYOUT_RESULT.contentWidth, LAYOUT_RESULT.contentHeight,
LAYOUT_RESULT.contentWidth, LAYOUT_RESULT.contentHeight);
}
/**
* @private
*/
protected function touchHandler(event:TouchEvent):void
{
if(!this._isEnabled || this._pageCount < 2)
{
this.touchPointID = -1;
return;
}
if(this.touchPointID >= 0)
{
var touch:Touch = event.getTouch(this, TouchPhase.ENDED, this.touchPointID);
if(!touch)
{
return;
}
this.touchPointID = -1;
touch.getLocation(this.stage, HELPER_POINT);
var isInBounds:Boolean = this.contains(this.stage.hitTest(HELPER_POINT));
if(isInBounds)
{
var lastPageIndex:int = this._pageCount - 1;
this.globalToLocal(HELPER_POINT, HELPER_POINT);
if(this._direction == Direction.VERTICAL)
{
if(this._interactionMode == INTERACTION_MODE_PRECISE)
{
var symbolHeight:Number = this.selectedSymbol.height + (this.unselectedSymbols[0].height + this._gap) * lastPageIndex;
var newIndex:int = Math.round(lastPageIndex * (HELPER_POINT.y - this.symbols[0].y) / symbolHeight);
if(newIndex < 0)
{
newIndex = 0;
}
else if(newIndex > lastPageIndex)
{
newIndex = lastPageIndex;
}
this.selectedIndex = newIndex;
}
else
{
if(HELPER_POINT.y < this.selectedSymbol.y)
{
this.selectedIndex = Math.max(0, this._selectedIndex - 1);
}
if(HELPER_POINT.y > (this.selectedSymbol.y + this.selectedSymbol.height))
{
this.selectedIndex = Math.min(lastPageIndex, this._selectedIndex + 1);
}
}
}
else
{
if(this._interactionMode == INTERACTION_MODE_PRECISE)
{
var symbolWidth:Number = this.selectedSymbol.width + (this.unselectedSymbols[0].width + this._gap) * lastPageIndex;
newIndex = Math.round(lastPageIndex * (HELPER_POINT.x - this.symbols[0].x) / symbolWidth);
if(newIndex < 0)
{
newIndex = 0;
}
else if(newIndex >= this._pageCount)
{
newIndex = lastPageIndex;
}
this.selectedIndex = newIndex;
}
else
{
if(HELPER_POINT.x < this.selectedSymbol.x)
{
this.selectedIndex = Math.max(0, this._selectedIndex - 1);
}
if(HELPER_POINT.x > (this.selectedSymbol.x + this.selectedSymbol.width))
{
this.selectedIndex = Math.min(lastPageIndex, this._selectedIndex + 1);
}
}
}
}
}
else //if we get here, we don't have a saved touch ID yet
{
touch = event.getTouch(this, TouchPhase.BEGAN);
if(!touch)
{
return;
}
this.touchPointID = touch.id;
}
}
}
}