scaffold.libs_as.feathers.controls.Scroller.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.controls.ScrollInteractionMode;
import feathers.controls.supportClasses.IViewPort;
import feathers.core.FeathersControl;
import feathers.core.IFocusDisplayObject;
import feathers.core.PropertyProxy;
import feathers.events.ExclusiveTouch;
import feathers.events.FeathersEventType;
import feathers.layout.Direction;
import feathers.layout.RelativePosition;
import feathers.system.DeviceCapabilities;
import feathers.utils.math.roundDownToNearest;
import feathers.utils.math.roundToNearest;
import feathers.utils.math.roundUpToNearest;
import flash.events.MouseEvent;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.ui.Keyboard;
import flash.utils.getTimer;
import starling.animation.Transitions;
import starling.animation.Tween;
import starling.core.Starling;
import starling.display.DisplayObject;
import starling.display.Quad;
import starling.events.Event;
import starling.events.KeyboardEvent;
import starling.events.Touch;
import starling.events.TouchEvent;
import starling.events.TouchPhase;
import starling.utils.MathUtil;
/**
* Dispatched when the scroller scrolls in either direction or when the view
* port's scrolling bounds have changed. Listen for FeathersEventType.SCROLL_START
* to know when scrolling starts as a result of user interaction or when
* scrolling is triggered by an animation. Similarly, listen for
* FeathersEventType.SCROLL_COMPLETE
to know when the scrolling
* ends.
*
* 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.SCROLL
* @see #event:scrollStart feathers.events.FeathersEventType.SCROLL_START
* @see #event:scrollComplete feathers.events.FeathersEventType.SCROLL_COMPLETE
*/
[Event(name="scroll",type="starling.events.Event")]
/**
* Dispatched when the scroller starts scrolling in either direction
* as a result of either user interaction or animation.
*
* Note: If horizontalScrollPosition
or verticalScrollPosition
* is set manually (in other words, the scrolling is not triggered by user
* interaction or an animated scroll), no FeathersEventType.SCROLL_START
* or FeathersEventType.SCROLL_COMPLETE
events will be
* dispatched.
*
* 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 feathers.events.FeathersEventType.SCROLL_START
* @see #event:scrollComplete feathers.events.FeathersEventType.SCROLL_COMPLETE
* @see #event:scroll feathers.events.FeathersEventType.SCROLL
*/
[Event(name="scrollStart",type="starling.events.Event")]
/**
* Dispatched when the scroller finishes scrolling in either direction
* as a result of either user interaction or animation. Animations may not
* end at the same time that user interaction ends, so the event may be
* delayed if user interaction triggers scrolling again.
*
* Note: If horizontalScrollPosition
or verticalScrollPosition
* is set manually (in other words, the scrolling is not triggered by user
* interaction or an animated scroll), no FeathersEventType.SCROLL_START
* or FeathersEventType.SCROLL_COMPLETE
events will be
* dispatched.
*
* 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 feathers.events.FeathersEventType.SCROLL_COMPLETE
* @see #event:scrollStart feathers.events.FeathersEventType.SCROLL_START
* @see #event:scroll feathers.events.FeathersEventType.SCROLL
*/
[Event(name="scrollComplete",type="starling.events.Event")]
/**
* Dispatched when the user starts dragging the scroller when
* ScrollInteractionMode.TOUCH
is enabled or when the user
* starts interacting with the scroll bar.
*
* Note: If horizontalScrollPosition
or verticalScrollPosition
* is set manually (in other words, the scrolling is not triggered by user
* interaction), no FeathersEventType.BEGIN_INTERACTION
* or FeathersEventType.END_INTERACTION
events will be
* dispatched.
*
* 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 feathers.events.FeathersEventType.BEGIN_INTERACTION
* @see #event:endInteraction feathers.events.FeathersEventType.END_INTERACTION
* @see #event:scroll feathers.events.FeathersEventType.SCROLL
*/
[Event(name="beginInteraction",type="starling.events.Event")]
/**
* Dispatched when the user stops dragging the scroller when
* ScrollInteractionMode.TOUCH
is enabled or when the user
* stops interacting with the scroll bar. The scroller may continue
* scrolling after this event is dispatched if the user interaction has also
* triggered an animation.
*
* Note: If horizontalScrollPosition
or verticalScrollPosition
* is set manually (in other words, the scrolling is not triggered by user
* interaction), no FeathersEventType.BEGIN_INTERACTION
* or FeathersEventType.END_INTERACTION
events will be
* dispatched.
*
* 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 feathers.events.FeathersEventType.END_INTERACTION
* @see #event:beginInteraction feathers.events.FeathersEventType.BEGIN_INTERACTION
* @see #event:scroll feathers.events.FeathersEventType.SCROLL
*/
[Event(name="endInteraction",type="starling.events.Event")]
/**
* Dispatched when the component receives focus.
*
* 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 feathers.events.FeathersEventType.FOCUS_IN
*/
[Event(name="focusIn",type="starling.events.Event")]
/**
* Dispatched when the component loses focus.
*
* 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 feathers.events.FeathersEventType.FOCUS_OUT
*/
[Event(name="focusOut",type="starling.events.Event")]
/**
* Allows horizontal and vertical scrolling of a view port. Not
* meant to be used as a standalone container or component. Generally meant
* to be the super class of another component that needs to support
* scrolling. To put components in a generic scrollable container (with
* optional layout), see ScrollContainer
. To scroll long
* passages of text, see ScrollText
.
*
* This component is generally not instantiated directly. Instead it is
* typically used as a super class for other scrolling components like lists
* and containers. With that in mind, no code example is included here.
*
* @see feathers.controls.ScrollContainer
*/
public class Scroller extends FeathersControl implements IFocusDisplayObject
{
/**
* @private
*/
private static const HELPER_POINT:Point = new Point();
/**
* @private
*/
protected static const INVALIDATION_FLAG_SCROLL_BAR_RENDERER:String = "scrollBarRenderer";
/**
* @private
*/
protected static const INVALIDATION_FLAG_PENDING_SCROLL:String = "pendingScroll";
/**
* @private
*/
protected static const INVALIDATION_FLAG_PENDING_REVEAL_SCROLL_BARS:String = "pendingRevealScrollBars";
/**
* @private
* DEPRECATED: Replaced by feathers.controls.ScrollPolicy.AUTO
.
*
* 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 SCROLL_POLICY_AUTO:String = "auto";
/**
* @private
* DEPRECATED: Replaced by feathers.controls.ScrollPolicy.ON
.
*
* 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 SCROLL_POLICY_ON:String = "on";
/**
* @private
* DEPRECATED: Replaced by feathers.controls.ScrollPolicy.OFF
.
*
* 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 SCROLL_POLICY_OFF:String = "off";
/**
* @private
* DEPRECATED: Replaced by feathers.controls.ScrollBarDisplayMode.FLOAT
.
*
* 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 SCROLL_BAR_DISPLAY_MODE_FLOAT:String = "float";
/**
* @private
* DEPRECATED: Replaced by feathers.controls.ScrollBarDisplayMode.FIXED
.
*
* 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 SCROLL_BAR_DISPLAY_MODE_FIXED:String = "fixed";
/**
* @private
* DEPRECATED: Replaced by feathers.controls.ScrollBarDisplayMode.FIXED_FLOAT
.
*
* 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 SCROLL_BAR_DISPLAY_MODE_FIXED_FLOAT:String = "fixedFloat";
/**
* @private
* DEPRECATED: Replaced by feathers.controls.ScrollBarDisplayMode.NONE
.
*
* 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 SCROLL_BAR_DISPLAY_MODE_NONE:String = "none";
/**
* @private
* DEPRECATED: Replaced by feathers.layout.RelativePosition.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 VERTICAL_SCROLL_BAR_POSITION_RIGHT:String = "right";
/**
* @private
* DEPRECATED: Replaced by feathers.layout.RelativePosition.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 VERTICAL_SCROLL_BAR_POSITION_LEFT:String = "left";
/**
* @private
* DEPRECATED: Replaced by feathers.controls.ScrollInteractionMode.TOUCH
.
*
* 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 INTERACTION_MODE_TOUCH:String = "touch";
/**
* @private
* DEPRECATED: Replaced by feathers.controls.ScrollInteractionMode.MOUSE
.
*
* 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 INTERACTION_MODE_MOUSE:String = "mouse";
/**
* @private
* DEPRECATED: Replaced by feathers.controls.ScrollInteractionMode.TOUCH_AND_SCROLL_BARS
.
*
* 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 INTERACTION_MODE_TOUCH_AND_SCROLL_BARS:String = "touchAndScrollBars";
/**
* @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 MOUSE_WHEEL_SCROLL_DIRECTION_VERTICAL:String = "vertical";
/**
* @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 MOUSE_WHEEL_SCROLL_DIRECTION_HORIZONTAL:String = "horizontal";
/**
* Flag to indicate that the clipping has changed.
*/
protected static const INVALIDATION_FLAG_CLIPPING:String = "clipping";
/**
* @private
* The point where we stop calculating velocity changes because floating
* point issues can start to appear.
*/
private static const MINIMUM_VELOCITY:Number = 0.02;
/**
* @private
* The current velocity is given high importance.
*/
private static const CURRENT_VELOCITY_WEIGHT:Number = 2.33;
/**
* @private
* Older saved velocities are given less importance.
*/
private static const VELOCITY_WEIGHTS:Vector. = new [1, 1.33, 1.66, 2];
/**
* @private
*/
private static const MAXIMUM_SAVED_VELOCITY_COUNT:int = 4;
/**
* @private
* DEPRECATED: Replaced by feathers.controls.DecelerationRate.NORMAL
.
*
* 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 DECELERATION_RATE_NORMAL:Number = 0.998;
/**
* @private
* DEPRECATED: Replaced by feathers.controls.DecelerationRate.FAST
.
*
* 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 DECELERATION_RATE_FAST:Number = 0.99;
/**
* The default value added to the styleNameList
of the
* horizontal scroll bar.
*
* @see feathers.core.FeathersControl#styleNameList
*/
public static const DEFAULT_CHILD_STYLE_NAME_HORIZONTAL_SCROLL_BAR:String = "feathers-scroller-horizontal-scroll-bar";
/**
* The default value added to the styleNameList
of the vertical
* scroll bar.
*
* @see feathers.core.FeathersControl#styleNameList
*/
public static const DEFAULT_CHILD_STYLE_NAME_VERTICAL_SCROLL_BAR:String = "feathers-scroller-vertical-scroll-bar";
/**
* @private
*/
protected static const FUZZY_PAGE_SIZE_PADDING:Number = 0.000001;
/**
* @private
*/
protected static const PAGE_INDEX_EPSILON:Number = 0.001;
/**
* @private
*/
protected static function defaultScrollBarFactory():IScrollBar
{
return new SimpleScrollBar();
}
/**
* @private
*/
protected static function defaultThrowEase(ratio:Number):Number
{
ratio -= 1;
return 1 - ratio * ratio * ratio * ratio;
}
/**
* Constructor.
*/
public function Scroller()
{
super();
this.addEventListener(Event.ADDED_TO_STAGE, scroller_addedToStageHandler);
this.addEventListener(Event.REMOVED_FROM_STAGE, scroller_removedFromStageHandler);
}
/**
* The value added to the styleNameList
of the horizontal
* scroll bar. This variable is protected
so that
* sub-classes can customize the horizontal scroll bar style name in
* their constructors instead of using the default style name defined by
* DEFAULT_CHILD_STYLE_NAME_HORIZONTAL_SCROLL_BAR
.
*
* To customize the horizontal scroll bar style name without
* subclassing, see customHorizontalScrollBarStyleName
.
*
* @see #customHorizontalScrollBarStyleName
* @see feathers.core.FeathersControl#styleNameList
*/
protected var horizontalScrollBarStyleName:String = DEFAULT_CHILD_STYLE_NAME_HORIZONTAL_SCROLL_BAR;
/**
* The value added to the styleNameList
of the vertical
* scroll bar. This variable is protected
so that
* sub-classes can customize the vertical scroll bar style name in their
* constructors instead of using the default style name defined by
* DEFAULT_CHILD_STYLE_NAME_VERTICAL_SCROLL_BAR
.
*
* To customize the vertical scroll bar style name without
* subclassing, see customVerticalScrollBarStyleName
.
*
* @see #customVerticalScrollBarStyleName
* @see feathers.core.FeathersControl#styleNameList
*/
protected var verticalScrollBarStyleName:String = DEFAULT_CHILD_STYLE_NAME_VERTICAL_SCROLL_BAR;
/**
* The horizontal scrollbar instance. May be null.
*
* For internal use in subclasses.
*
* @see #horizontalScrollBarFactory
* @see #createScrollBars()
*/
protected var horizontalScrollBar:IScrollBar;
/**
* The vertical scrollbar instance. May be null.
*
* For internal use in subclasses.
*
* @see #verticalScrollBarFactory
* @see #createScrollBars()
*/
protected var verticalScrollBar:IScrollBar;
/**
* @private
*/
override public function get isFocusEnabled():Boolean
{
return (this._maxVerticalScrollPosition != this._minVerticalScrollPosition ||
this._maxHorizontalScrollPosition != this._minHorizontalScrollPosition) &&
super.isFocusEnabled;
}
/**
* @private
*/
protected var _topViewPortOffset:Number;
/**
* @private
*/
protected var _rightViewPortOffset:Number;
/**
* @private
*/
protected var _bottomViewPortOffset:Number;
/**
* @private
*/
protected var _leftViewPortOffset:Number;
/**
* @private
*/
protected var _hasHorizontalScrollBar:Boolean = false;
/**
* @private
*/
protected var _hasVerticalScrollBar:Boolean = false;
/**
* @private
*/
protected var _horizontalScrollBarTouchPointID:int = -1;
/**
* @private
*/
protected var _verticalScrollBarTouchPointID:int = -1;
/**
* @private
*/
protected var _touchPointID:int = -1;
/**
* @private
*/
protected var _startTouchX:Number;
/**
* @private
*/
protected var _startTouchY:Number;
/**
* @private
*/
protected var _startHorizontalScrollPosition:Number;
/**
* @private
*/
protected var _startVerticalScrollPosition:Number;
/**
* @private
*/
protected var _currentTouchX:Number;
/**
* @private
*/
protected var _currentTouchY:Number;
/**
* @private
*/
protected var _previousTouchTime:int;
/**
* @private
*/
protected var _previousTouchX:Number;
/**
* @private
*/
protected var _previousTouchY:Number;
/**
* @private
*/
protected var _velocityX:Number = 0;
/**
* @private
*/
protected var _velocityY:Number = 0;
/**
* @private
*/
protected var _previousVelocityX:Vector. = new [];
/**
* @private
*/
protected var _previousVelocityY:Vector. = new [];
/**
* @private
*/
protected var _lastViewPortWidth:Number = 0;
/**
* @private
*/
protected var _lastViewPortHeight:Number = 0;
/**
* @private
*/
protected var _hasViewPortBoundsChanged:Boolean = false;
/**
* @private
*/
protected var _horizontalAutoScrollTween:Tween;
/**
* @private
*/
protected var _verticalAutoScrollTween:Tween;
/**
* @private
*/
protected var _isDraggingHorizontally:Boolean = false;
/**
* @private
*/
protected var _isDraggingVertically:Boolean = false;
/**
* @private
*/
protected var ignoreViewPortResizing:Boolean = false;
/**
* @private
*/
protected var _touchBlocker:Quad;
/**
* @private
*/
protected var _viewPort:IViewPort;
/**
* The display object displayed and scrolled within the Scroller.
*
* @default null
*/
public function get viewPort():IViewPort
{
return this._viewPort;
}
/**
* @private
*/
public function set viewPort(value:IViewPort):void
{
if(this._viewPort == value)
{
return;
}
if(this._viewPort)
{
this._viewPort.removeEventListener(FeathersEventType.RESIZE, viewPort_resizeHandler);
this.removeRawChildInternal(DisplayObject(this._viewPort));
}
this._viewPort = value;
if(this._viewPort)
{
this._viewPort.addEventListener(FeathersEventType.RESIZE, viewPort_resizeHandler);
this.addRawChildAtInternal(DisplayObject(this._viewPort), 0);
}
this.invalidate(INVALIDATION_FLAG_SIZE);
}
/**
* @private
*/
protected var _measureViewPort:Boolean = true;
/**
* Determines if the dimensions of the view port are used when measuring
* the scroller. If disabled, only children other than the view port
* (such as the background skin) are used for measurement.
*
* In the following example, the view port measurement is disabled:
*
*
* scroller.measureViewPort = false;
*
* @default true
*/
public function get measureViewPort():Boolean
{
return this._measureViewPort;
}
/**
* @private
*/
public function set measureViewPort(value:Boolean):void
{
if(this._measureViewPort == value)
{
return;
}
this._measureViewPort = value;
this.invalidate(INVALIDATION_FLAG_SIZE);
}
/**
* @private
*/
protected var _snapToPages:Boolean = false;
/**
* Determines if scrolling will snap to the nearest page.
*
* In the following example, the scroller snaps to the nearest page:
*
*
* scroller.snapToPages = true;
*
* @default false
*/
public function get snapToPages():Boolean
{
return this._snapToPages;
}
/**
* @private
*/
public function set snapToPages(value:Boolean):void
{
if(this._snapToPages == value)
{
return;
}
this._snapToPages = value;
this.invalidate(INVALIDATION_FLAG_SCROLL);
}
/**
* @private
*/
protected var _snapOnComplete:Boolean = false;
/**
* @private
*/
protected var _horizontalScrollBarFactory:Function = defaultScrollBarFactory;
/**
* Creates the horizontal scroll bar. The horizontal scroll bar must be
* an instance of IScrollBar
. This factory can be used to
* change properties on the horizontal scroll bar when it is first
* created. For instance, if you are skinning Feathers components
* without a theme, you might use this factory to set skins and other
* styles on the horizontal scroll bar.
*
* This function is expected to have the following signature:
*
* function():IScrollBar
*
* In the following example, a custom horizontal scroll bar factory
* is passed to the scroller:
*
*
* scroller.horizontalScrollBarFactory = function():IScrollBar
* {
* return new ScrollBar();
* };
*
* @default null
*
* @see feathers.controls.IScrollBar
* @see #horizontalScrollBarProperties
*/
public function get horizontalScrollBarFactory():Function
{
return this._horizontalScrollBarFactory;
}
/**
* @private
*/
public function set horizontalScrollBarFactory(value:Function):void
{
if(this._horizontalScrollBarFactory == value)
{
return;
}
this._horizontalScrollBarFactory = value;
this.invalidate(INVALIDATION_FLAG_SCROLL_BAR_RENDERER);
}
/**
* @private
*/
protected var _customHorizontalScrollBarStyleName:String;
/**
* A style name to add to the container's horizontal scroll bar
* sub-component. Typically used by a theme to provide different styles
* to different containers.
*
* In the following example, a custom horizontal scroll bar style
* name is passed to the scroller:
*
*
* scroller.customHorizontalScrollBarStyleName = "my-custom-horizontal-scroll-bar";
*
* In your theme, you can target this sub-component style name to
* provide different styles than the default:
*
*
* getStyleProviderForClass( SimpleScrollBar ).setFunctionForStyleName( "my-custom-horizontal-scroll-bar", setCustomHorizontalScrollBarStyles );
*
* @default null
*
* @see #DEFAULT_CHILD_STYLE_NAME_HORIZONTAL_SCROLL_BAR
* @see feathers.core.FeathersControl#styleNameList
* @see #horizontalScrollBarFactory
* @see #horizontalScrollBarProperties
*/
public function get customHorizontalScrollBarStyleName():String
{
return this._customHorizontalScrollBarStyleName;
}
/**
* @private
*/
public function set customHorizontalScrollBarStyleName(value:String):void
{
if(this._customHorizontalScrollBarStyleName == value)
{
return;
}
this._customHorizontalScrollBarStyleName = value;
this.invalidate(INVALIDATION_FLAG_SCROLL_BAR_RENDERER);
}
/**
* @private
*/
protected var _horizontalScrollBarProperties:PropertyProxy;
/**
* An object that stores properties for the container's horizontal
* scroll bar, and the properties will be passed down to the horizontal
* scroll bar when the container validates. The available properties
* depend on which IScrollBar
implementation is returned
* by horizontalScrollBarFactory
. Refer to
* feathers.controls.IScrollBar
* for a list of available scroll bar implementations.
*
* If the subcomponent has its own subcomponents, their properties
* can be set too, using attribute @
notation. For example,
* to set the skin on the thumb which is in a SimpleScrollBar
,
* which is in a List
, you can use the following syntax:
* list.verticalScrollBarProperties.@thumbProperties.defaultSkin = new Image(texture);
*
* Setting properties in a horizontalScrollBarFactory
* function instead of using horizontalScrollBarProperties
* will result in better performance.
*
* In the following example, properties for the horizontal scroll bar
* are passed to the scroller:
*
*
* scroller.horizontalScrollBarProperties.liveDragging = false;
*
* @default null
*
* @see #horizontalScrollBarFactory
* @see feathers.controls.IScrollBar
* @see feathers.controls.SimpleScrollBar
* @see feathers.controls.ScrollBar
*/
public function get horizontalScrollBarProperties():Object
{
if(!this._horizontalScrollBarProperties)
{
this._horizontalScrollBarProperties = new PropertyProxy(childProperties_onChange);
}
return this._horizontalScrollBarProperties;
}
/**
* @private
*/
public function set horizontalScrollBarProperties(value:Object):void
{
if(this._horizontalScrollBarProperties == value)
{
return;
}
if(!value)
{
value = new PropertyProxy();
}
if(!(value is PropertyProxy))
{
var newValue:PropertyProxy = new PropertyProxy();
for(var propertyName:String in value)
{
newValue[propertyName] = value[propertyName];
}
value = newValue;
}
if(this._horizontalScrollBarProperties)
{
this._horizontalScrollBarProperties.removeOnChangeCallback(childProperties_onChange);
}
this._horizontalScrollBarProperties = PropertyProxy(value);
if(this._horizontalScrollBarProperties)
{
this._horizontalScrollBarProperties.addOnChangeCallback(childProperties_onChange);
}
this.invalidate(INVALIDATION_FLAG_STYLES);
}
/**
* @private
*/
protected var _verticalScrollBarPosition:String = RelativePosition.RIGHT;
[Inspectable(type="String",enumeration="right,left")]
/**
* Determines where the vertical scroll bar will be positioned.
*
* In the following example, the scroll bars is positioned on the left:
*
*
* scroller.verticalScrollBarPosition = RelativePosition.LEFT;
*
* @default feathers.layout.RelativePosition.RIGHT
*
* @see feathers.layout.RelativePosition#RIGHT
* @see feathers.layout.RelativePosition#LEFT
*/
public function get verticalScrollBarPosition():String
{
return this._verticalScrollBarPosition;
}
/**
* @private
*/
public function set verticalScrollBarPosition(value:String):void
{
if(this._verticalScrollBarPosition == value)
{
return;
}
this._verticalScrollBarPosition = value;
this.invalidate(INVALIDATION_FLAG_STYLES);
}
/**
* @private
*/
protected var _verticalScrollBarFactory:Function = defaultScrollBarFactory;
/**
* Creates the vertical scroll bar. The vertical scroll bar must be an
* instance of Button
. This factory can be used to change
* properties on the vertical scroll bar when it is first created. For
* instance, if you are skinning Feathers components without a theme,
* you might use this factory to set skins and other styles on the
* vertical scroll bar.
*
* This function is expected to have the following signature:
*
* function():IScrollBar
*
* In the following example, a custom vertical scroll bar factory
* is passed to the scroller:
*
*
* scroller.verticalScrollBarFactory = function():IScrollBar
* {
* return new ScrollBar();
* };
*
* @default null
*
* @see feathers.controls.IScrollBar
* @see #verticalScrollBarProperties
*/
public function get verticalScrollBarFactory():Function
{
return this._verticalScrollBarFactory;
}
/**
* @private
*/
public function set verticalScrollBarFactory(value:Function):void
{
if(this._verticalScrollBarFactory == value)
{
return;
}
this._verticalScrollBarFactory = value;
this.invalidate(INVALIDATION_FLAG_SCROLL_BAR_RENDERER);
}
/**
* @private
*/
protected var _customVerticalScrollBarStyleName:String;
/**
* A style name to add to the container's vertical scroll bar
* sub-component. Typically used by a theme to provide different styles
* to different containers.
*
* In the following example, a custom vertical scroll bar style name
* is passed to the scroller:
*
*
* scroller.customVerticalScrollBarStyleName = "my-custom-vertical-scroll-bar";
*
* In your theme, you can target this sub-component style name to
* provide different styles than the default:
*
*
* getStyleProviderForClass( SimpleScrollBar ).setFunctionForStyleName( "my-custom-vertical-scroll-bar", setCustomVerticalScrollBarStyles );
*
* @default null
*
* @see #DEFAULT_CHILD_STYLE_NAME_VERTICAL_SCROLL_BAR
* @see feathers.core.FeathersControl#styleNameList
* @see #verticalScrollBarFactory
* @see #verticalScrollBarProperties
*/
public function get customVerticalScrollBarStyleName():String
{
return this._customVerticalScrollBarStyleName;
}
/**
* @private
*/
public function set customVerticalScrollBarStyleName(value:String):void
{
if(this._customVerticalScrollBarStyleName == value)
{
return;
}
this._customVerticalScrollBarStyleName = value;
this.invalidate(INVALIDATION_FLAG_SCROLL_BAR_RENDERER);
}
/**
* @private
*/
protected var _verticalScrollBarProperties:PropertyProxy;
/**
* An object that stores properties for the container's vertical scroll
* bar, and the properties will be passed down to the vertical scroll
* bar when the container validates. The available properties depend on
* which IScrollBar
implementation is returned by
* verticalScrollBarFactory
. Refer to
* feathers.controls.IScrollBar
* for a list of available scroll bar implementations.
*
* If the subcomponent has its own subcomponents, their properties
* can be set too, using attribute @
notation. For example,
* to set the skin on the thumb which is in a SimpleScrollBar
,
* which is in a List
, you can use the following syntax:
* list.verticalScrollBarProperties.@thumbProperties.defaultSkin = new Image(texture);
*
* Setting properties in a verticalScrollBarFactory
* function instead of using verticalScrollBarProperties
* will result in better performance.
*
* In the following example, properties for the vertical scroll bar
* are passed to the container:
*
*
* scroller.verticalScrollBarProperties.liveDragging = false;
*
* @default null
*
* @see #verticalScrollBarFactory
* @see feathers.controls.IScrollBar
* @see feathers.controls.SimpleScrollBar
* @see feathers.controls.ScrollBar
*/
public function get verticalScrollBarProperties():Object
{
if(!this._verticalScrollBarProperties)
{
this._verticalScrollBarProperties = new PropertyProxy(childProperties_onChange);
}
return this._verticalScrollBarProperties;
}
/**
* @private
*/
public function set verticalScrollBarProperties(value:Object):void
{
if(this._horizontalScrollBarProperties == value)
{
return;
}
if(!value)
{
value = new PropertyProxy();
}
if(!(value is PropertyProxy))
{
var newValue:PropertyProxy = new PropertyProxy();
for(var propertyName:String in value)
{
newValue[propertyName] = value[propertyName];
}
value = newValue;
}
if(this._verticalScrollBarProperties)
{
this._verticalScrollBarProperties.removeOnChangeCallback(childProperties_onChange);
}
this._verticalScrollBarProperties = PropertyProxy(value);
if(this._verticalScrollBarProperties)
{
this._verticalScrollBarProperties.addOnChangeCallback(childProperties_onChange);
}
this.invalidate(INVALIDATION_FLAG_STYLES);
}
/**
* @private
*/
protected var actualHorizontalScrollStep:Number = 1;
/**
* @private
*/
protected var explicitHorizontalScrollStep:Number = NaN;
/**
* The number of pixels the horizontal scroll position can be adjusted
* by a "step". Passed to the horizontal scroll bar, if one exists.
* Touch scrolling is not affected by the step value.
*
* In the following example, the horizontal scroll step is customized:
*
*
* scroller.horizontalScrollStep = 0;
*
* @default NaN
*/
public function get horizontalScrollStep():Number
{
return this.actualHorizontalScrollStep;
}
/**
* @private
*/
public function set horizontalScrollStep(value:Number):void
{
if(this.explicitHorizontalScrollStep == value)
{
return;
}
this.explicitHorizontalScrollStep = value;
this.invalidate(INVALIDATION_FLAG_SCROLL);
}
/**
* @private
*/
protected var _targetHorizontalScrollPosition:Number;
/**
* @private
*/
protected var _horizontalScrollPosition:Number = 0;
/**
* The number of pixels the container has been scrolled horizontally (on
* the x-axis).
*
* In the following example, the horizontal scroll position is customized:
*
*
* scroller.horizontalScrollPosition = scroller.maxHorizontalScrollPosition;
*
* @see #minHorizontalScrollPosition
* @see #maxHorizontalScrollPosition
*/
public function get horizontalScrollPosition():Number
{
return this._horizontalScrollPosition;
}
/**
* @private
*/
public function set horizontalScrollPosition(value:Number):void
{
if(this._snapScrollPositionsToPixels)
{
value = Math.round(value);
}
if(this._horizontalScrollPosition == value)
{
return;
}
if(value !== value) //isNaN
{
//there isn't any recovery from this, so stop it early
throw new ArgumentError("horizontalScrollPosition cannot be NaN.");
}
this._horizontalScrollPosition = value;
this.invalidate(INVALIDATION_FLAG_SCROLL);
}
/**
* @private
*/
protected var _minHorizontalScrollPosition:Number = 0;
/**
* The number of pixels the scroller may be scrolled horizontally to the
* left. This value is automatically calculated based on the bounds of
* the viewport. The horizontalScrollPosition
property may
* have a lower value than the minimum due to elastic edges. However,
* once the user stops interacting with the scroller, it will
* automatically animate back to the maximum or minimum position.
*
* @see #horizontalScrollPosition
* @see #maxHorizontalScrollPosition
*/
public function get minHorizontalScrollPosition():Number
{
return this._minHorizontalScrollPosition;
}
/**
* @private
*/
protected var _maxHorizontalScrollPosition:Number = 0;
/**
* The number of pixels the scroller may be scrolled horizontally to the
* right. This value is automatically calculated based on the bounds of
* the viewport. The horizontalScrollPosition
property may
* have a higher value than the maximum due to elastic edges. However,
* once the user stops interacting with the scroller, it will
* automatically animate back to the maximum or minimum position.
*
* @see #horizontalScrollPosition
* @see #minHorizontalScrollPosition
*/
public function get maxHorizontalScrollPosition():Number
{
return this._maxHorizontalScrollPosition;
}
/**
* @private
*/
protected var _horizontalPageIndex:int = 0;
/**
* The index of the horizontal page, if snapping is enabled. If snapping
* is disabled, the index will always be 0
.
*
* @see #horizontalPageCount
* @see #minHorizontalPageIndex
* @see #maxHorizontalPageIndex
*/
public function get horizontalPageIndex():int
{
if(this.hasPendingHorizontalPageIndex)
{
return this.pendingHorizontalPageIndex;
}
return this._horizontalPageIndex;
}
/**
* @private
*/
protected var _minHorizontalPageIndex:int = 0;
/**
* The minimum horizontal page index that may be displayed by this
* container, if page snapping is enabled.
*
* @see #snapToPages
* @see #horizontalPageCount
* @see #maxHorizontalPageIndex
*/
public function get minHorizontalPageIndex():int
{
return this._minHorizontalPageIndex;
}
/**
* @private
*/
protected var _maxHorizontalPageIndex:int = 0;
/**
* The maximum horizontal page index that may be displayed by this
* container, if page snapping is enabled.
*
* @see #snapToPages
* @see #horizontalPageCount
* @see #minHorizontalPageIndex
*/
public function get maxHorizontalPageIndex():int
{
return this._maxHorizontalPageIndex;
}
/**
* The number of horizontal pages, if snapping is enabled. If snapping
* is disabled, the page count will always be 1
.
*
* If the scroller's view port supports infinite scrolling, this
* property will return int.MAX_VALUE
, since an
* int
cannot hold the value Number.POSITIVE_INFINITY
.
*
* @see #snapToPages
* @see #horizontalPageIndex
* @see #minHorizontalPageIndex
* @see #maxHorizontalPageIndex
*/
public function get horizontalPageCount():int
{
if(this._maxHorizontalPageIndex == int.MAX_VALUE ||
this._minHorizontalPageIndex == int.MIN_VALUE)
{
return int.MAX_VALUE;
}
return this._maxHorizontalPageIndex - this._minHorizontalPageIndex + 1;
}
/**
* @private
*/
protected var _horizontalScrollPolicy:String = ScrollPolicy.AUTO;
[Inspectable(type="String",enumeration="auto,on,off")]
/**
* Determines whether the scroller may scroll horizontally (on the
* x-axis) or not.
*
* In the following example, horizontal scrolling is disabled:
*
*
* scroller.horizontalScrollPolicy = ScrollPolicy.OFF;
*
* @default feathers.controls.ScrollPolicy.AUTO
*
* @see feathers.controls.ScrollPolicy#AUTO
* @see feathers.controls.ScrollPolicy#ON
* @see feathers.controls.ScrollPolicy#OFF
*/
public function get horizontalScrollPolicy():String
{
return this._horizontalScrollPolicy;
}
/**
* @private
*/
public function set horizontalScrollPolicy(value:String):void
{
if(this._horizontalScrollPolicy == value)
{
return;
}
this._horizontalScrollPolicy = value;
this.invalidate(INVALIDATION_FLAG_SCROLL);
this.invalidate(INVALIDATION_FLAG_SCROLL_BAR_RENDERER);
}
/**
* @private
*/
protected var actualVerticalScrollStep:Number = 1;
/**
* @private
*/
protected var explicitVerticalScrollStep:Number = NaN;
/**
* The number of pixels the vertical scroll position can be adjusted
* by a "step". Passed to the vertical scroll bar, if one exists.
* Touch scrolling is not affected by the step value.
*
* In the following example, the vertical scroll step is customized:
*
*
* scroller.verticalScrollStep = 0;
*
* @default NaN
*/
public function get verticalScrollStep():Number
{
return this.actualVerticalScrollStep;
}
/**
* @private
*/
public function set verticalScrollStep(value:Number):void
{
if(this.explicitVerticalScrollStep == value)
{
return;
}
this.explicitVerticalScrollStep = value;
this.invalidate(INVALIDATION_FLAG_SCROLL);
}
/**
* @private
*/
protected var _verticalMouseWheelScrollStep:Number = NaN;
/**
* The number of pixels the vertical scroll position can be adjusted by
* a "step" when using the mouse wheel. If this value is
* NaN
, the mouse wheel will use the same scroll step as the scroll bars.
*
* In the following example, the vertical mouse wheel scroll step is
* customized:
*
*
* scroller.verticalMouseWheelScrollStep = 10;
*
* @default NaN
*/
public function get verticalMouseWheelScrollStep():Number
{
return this._verticalMouseWheelScrollStep;
}
/**
* @private
*/
public function set verticalMouseWheelScrollStep(value:Number):void
{
if(this._verticalMouseWheelScrollStep == value)
{
return;
}
this._verticalMouseWheelScrollStep = value;
this.invalidate(INVALIDATION_FLAG_SCROLL);
}
/**
* @private
*/
protected var _targetVerticalScrollPosition:Number;
/**
* @private
*/
protected var _verticalScrollPosition:Number = 0;
/**
* The number of pixels the container has been scrolled vertically (on
* the y-axis).
*
* In the following example, the vertical scroll position is customized:
*
*
* scroller.verticalScrollPosition = scroller.maxVerticalScrollPosition;
*
* @see #minVerticalScrollPosition
* @see #maxVerticalScrollPosition
*/
public function get verticalScrollPosition():Number
{
return this._verticalScrollPosition;
}
/**
* @private
*/
public function set verticalScrollPosition(value:Number):void
{
if(this._snapScrollPositionsToPixels)
{
value = Math.round(value);
}
if(this._verticalScrollPosition == value)
{
return;
}
if(value !== value) //isNaN
{
//there isn't any recovery from this, so stop it early
throw new ArgumentError("verticalScrollPosition cannot be NaN.");
}
this._verticalScrollPosition = value;
this.invalidate(INVALIDATION_FLAG_SCROLL);
}
/**
* @private
*/
protected var _minVerticalScrollPosition:Number = 0;
/**
* The number of pixels the scroller may be scrolled vertically beyond
* the top edge. This value is automatically calculated based on the
* bounds of the viewport. The verticalScrollPosition
* property may have a lower value than the minimum due to elastic
* edges. However, once the user stops interacting with the scroller, it
* will automatically animate back to the maximum or minimum position.
*
* @see #verticalScrollPosition
* @see #maxVerticalScrollPosition
*/
public function get minVerticalScrollPosition():Number
{
return this._minVerticalScrollPosition;
}
/**
* @private
*/
protected var _maxVerticalScrollPosition:Number = 0;
/**
* The number of pixels the scroller may be scrolled vertically beyond
* the bottom edge. This value is automatically calculated based on the
* bounds of the viewport. The verticalScrollPosition
* property may have a lower value than the minimum due to elastic
* edges. However, once the user stops interacting with the scroller, it
* will automatically animate back to the maximum or minimum position.
*
* @see #verticalScrollPosition
* @see #minVerticalScrollPosition
*/
public function get maxVerticalScrollPosition():Number
{
return this._maxVerticalScrollPosition;
}
/**
* @private
*/
protected var _verticalPageIndex:int = 0;
/**
* The index of the vertical page, if snapping is enabled. If snapping
* is disabled, the index will always be 0
.
*
* @see #verticalPageCount
* @see #minVerticalPageIndex
* @see #maxVerticalPageIndex
*/
public function get verticalPageIndex():int
{
if(this.hasPendingVerticalPageIndex)
{
return this.pendingVerticalPageIndex;
}
return this._verticalPageIndex;
}
/**
* @private
*/
protected var _minVerticalPageIndex:int = 0;
/**
* The minimum vertical page index that may be displayed by this
* container, if page snapping is enabled.
*
* @see #snapToPages
* @see #verticalPageCount
* @see #maxVerticalPageIndex
*/
public function get minVerticalPageIndex():int
{
return this._minVerticalPageIndex;
}
/**
* @private
*/
protected var _maxVerticalPageIndex:int = 0;
/**
* The maximum vertical page index that may be displayed by this
* container, if page snapping is enabled.
*
* @see #snapToPages
* @see #verticalPageCount
* @see #minVerticalPageIndex
*/
public function get maxVerticalPageIndex():int
{
return this._maxVerticalPageIndex;
}
/**
* The number of vertical pages, if snapping is enabled. If snapping
* is disabled, the page count will always be 1
.
*
* If the scroller's view port supports infinite scrolling, this
* property will return int.MAX_VALUE
, since an
* int
cannot hold the value Number.POSITIVE_INFINITY
.
*
* @see #snapToPages
* @see #verticalPageIndex
* @see #minVerticalPageIndex
* @see #maxVerticalPageIndex
*/
public function get verticalPageCount():int
{
if(this._maxVerticalPageIndex == int.MAX_VALUE ||
this._minVerticalPageIndex == int.MIN_VALUE)
{
return int.MAX_VALUE;
}
return this._maxVerticalPageIndex - this._minVerticalPageIndex + 1;
}
/**
* @private
*/
protected var _verticalScrollPolicy:String = ScrollPolicy.AUTO;
[Inspectable(type="String",enumeration="auto,on,off")]
/**
* Determines whether the scroller may scroll vertically (on the
* y-axis) or not.
*
* In the following example, vertical scrolling is disabled:
*
*
* scroller.verticalScrollPolicy = ScrollPolicy.OFF;
*
* @default feathers.controls.ScrollPolicy.AUTO
*
* @see feathers.controls.ScrollPolicy#AUTO
* @see feathers.controls.ScrollPolicy#ON
* @see feathers.controls.ScrollPolicy#OFF
*/
public function get verticalScrollPolicy():String
{
return this._verticalScrollPolicy;
}
/**
* @private
*/
public function set verticalScrollPolicy(value:String):void
{
if(this._verticalScrollPolicy == value)
{
return;
}
this._verticalScrollPolicy = value;
this.invalidate(INVALIDATION_FLAG_SCROLL);
this.invalidate(INVALIDATION_FLAG_SCROLL_BAR_RENDERER);
}
/**
* @private
*/
protected var _clipContent:Boolean = true;
/**
* If true, the viewport will be clipped to the scroller's bounds. In
* other words, anything appearing outside the scroller's bounds will
* not be visible.
*
* To improve performance, turn off clipping and place other display
* objects over the edges of the scroller to hide the content that
* bleeds outside of the scroller's bounds.
*
* In the following example, clipping is disabled:
*
*
* scroller.clipContent = false;
*
* @default true
*/
public function get clipContent():Boolean
{
return this._clipContent;
}
/**
* @private
*/
public function set clipContent(value:Boolean):void
{
if(this._clipContent == value)
{
return;
}
this._clipContent = value;
if(!value && this._viewPort)
{
this._viewPort.mask = null;
}
this.invalidate(INVALIDATION_FLAG_CLIPPING);
}
/**
* @private
*/
protected var actualPageWidth:Number = 0;
/**
* @private
*/
protected var explicitPageWidth:Number = NaN;
/**
* When set, the horizontal pages snap to this width value instead of
* the width of the scroller.
*
* In the following example, the page width is set to 200 pixels:
*
*
* scroller.pageWidth = 200;
*
* @see #snapToPages
*/
public function get pageWidth():Number
{
return this.actualPageWidth;
}
/**
* @private
*/
public function set pageWidth(value:Number):void
{
if(this.explicitPageWidth == value)
{
return;
}
var valueIsNaN:Boolean = value !== value; //isNaN
if(valueIsNaN && this.explicitPageWidth !== this.explicitPageWidth)
{
return;
}
this.explicitPageWidth = value;
if(valueIsNaN)
{
//we need to calculate this value during validation
this.actualPageWidth = 0;
}
else
{
this.actualPageWidth = this.explicitPageWidth;
}
}
/**
* @private
*/
protected var actualPageHeight:Number = 0;
/**
* @private
*/
protected var explicitPageHeight:Number = NaN;
/**
* When set, the vertical pages snap to this height value instead of
* the height of the scroller.
*
* In the following example, the page height is set to 200 pixels:
*
*
* scroller.pageHeight = 200;
*
* @see #snapToPages
*/
public function get pageHeight():Number
{
return this.actualPageHeight;
}
/**
* @private
*/
public function set pageHeight(value:Number):void
{
if(this.explicitPageHeight == value)
{
return;
}
var valueIsNaN:Boolean = value !== value; //isNaN
if(valueIsNaN && this.explicitPageHeight !== this.explicitPageHeight)
{
return;
}
this.explicitPageHeight = value;
if(valueIsNaN)
{
//we need to calculate this value during validation
this.actualPageHeight = 0;
}
else
{
this.actualPageHeight = this.explicitPageHeight;
}
}
/**
* @private
*/
protected var _hasElasticEdges:Boolean = true;
/**
* Determines if the scrolling can go beyond the edges of the viewport.
*
* In the following example, elastic edges are disabled:
*
*
* scroller.hasElasticEdges = false;
*
* @default true
*
* @see #elasticity
* @see #throwElasticity
*/
public function get hasElasticEdges():Boolean
{
return this._hasElasticEdges;
}
/**
* @private
*/
public function set hasElasticEdges(value:Boolean):void
{
this._hasElasticEdges = value;
}
/**
* @private
*/
protected var _elasticity:Number = 0.33;
/**
* If the scroll position goes outside the minimum or maximum bounds
* when the scroller's content is being actively dragged, the scrolling
* will be constrained using this multiplier. A value of 0
* means that the scroller will not go beyond its minimum or maximum
* bounds. A value of 1
means that going beyond the minimum
* or maximum bounds is completely unrestrained.
*
* In the following example, the elasticity of dragging beyond the
* scroller's edges is customized:
*
*
* scroller.elasticity = 0.5;
*
* @default 0.33
*
* @see #hasElasticEdges
* @see #throwElasticity
*/
public function get elasticity():Number
{
return this._elasticity;
}
/**
* @private
*/
public function set elasticity(value:Number):void
{
this._elasticity = value;
}
/**
* @private
*/
protected var _throwElasticity:Number = 0.05;
/**
* If the scroll position goes outside the minimum or maximum bounds
* when the scroller's content is "thrown", the scrolling will be
* constrained using this multiplier. A value of 0
means
* that the scroller will not go beyond its minimum or maximum bounds.
* A value of 1
means that going beyond the minimum or
* maximum bounds is completely unrestrained.
*
* In the following example, the elasticity of throwing beyond the
* scroller's edges is customized:
*
*
* scroller.throwElasticity = 0.1;
*
* @default 0.05
*
* @see #hasElasticEdges
* @see #elasticity
*/
public function get throwElasticity():Number
{
return this._throwElasticity;
}
/**
* @private
*/
public function set throwElasticity(value:Number):void
{
this._throwElasticity = value;
}
/**
* @private
*/
protected var _scrollBarDisplayMode:String = ScrollBarDisplayMode.FLOAT;
[Inspectable(type="String",enumeration="float,fixed,none")]
/**
* Determines how the scroll bars are displayed.
*
* In the following example, the scroll bars are fixed:
*
*
* scroller.scrollBarDisplayMode = ScrollBarDisplayMode.FIXED;
*
* @default feathers.controls.ScrollBarDisplayMode.FLOAT
*
* @see feathers.controls.ScrollBarDisplayMode#FLOAT
* @see feathers.controls.ScrollBarDisplayMode#FIXED
* @see feathers.controls.ScrollBarDisplayMode#FIXED_FLOAT
* @see feathers.controls.ScrollBarDisplayMode#NONE
*/
public function get scrollBarDisplayMode():String
{
return this._scrollBarDisplayMode;
}
/**
* @private
*/
public function set scrollBarDisplayMode(value:String):void
{
if(this._scrollBarDisplayMode == value)
{
return;
}
this._scrollBarDisplayMode = value;
this.invalidate(INVALIDATION_FLAG_STYLES);
}
/**
* @private
*/
protected var _interactionMode:String = ScrollInteractionMode.TOUCH;
[Inspectable(type="String",enumeration="touch,mouse,touchAndScrollBars")]
/**
* Determines how the user may interact with the scroller.
*
* In the following example, the interaction mode is optimized for mouse:
*
*
* scroller.interactionMode = ScrollInteractionMode.MOUSE;
*
* @default feathers.controls.ScrollInteractionMode.TOUCH
*
* @see feathers.controls.ScrollInteractionMode#TOUCH
* @see feathers.controls.ScrollInteractionMode#MOUSE
* @see feathers.controls.ScrollInteractionMode#TOUCH_AND_SCROLL_BARS
*/
public function get interactionMode():String
{
return this._interactionMode;
}
/**
* @private
*/
public function set interactionMode(value:String):void
{
if(this._interactionMode == value)
{
return;
}
this._interactionMode = value;
this.invalidate(INVALIDATION_FLAG_STYLES);
}
/**
* @private
*/
protected var originalBackgroundWidth:Number = NaN;
/**
* @private
*/
protected var originalBackgroundHeight:Number = NaN;
/**
* @private
*/
protected var currentBackgroundSkin:DisplayObject;
/**
* @private
*/
protected var _backgroundSkin:DisplayObject;
/**
* The default background to display.
*
* In the following example, the scroller is given a background skin:
*
*
* scroller.backgroundSkin = new Image( texture );
*
* @default null
*/
public function get backgroundSkin():DisplayObject
{
return this._backgroundSkin;
}
/**
* @private
*/
public function set backgroundSkin(value:DisplayObject):void
{
if(this._backgroundSkin == value)
{
return;
}
if(this._backgroundSkin && this.currentBackgroundSkin == this._backgroundSkin)
{
this.removeRawChildInternal(this._backgroundSkin);
this.currentBackgroundSkin = null;
}
this._backgroundSkin = value;
this.invalidate(INVALIDATION_FLAG_STYLES);
}
/**
* @private
*/
protected var _backgroundDisabledSkin:DisplayObject;
/**
* A background to display when the container is disabled.
*
* In the following example, the scroller is given a disabled background skin:
*
*
* scroller.backgroundDisabledSkin = new Image( texture );
*
* @default null
*/
public function get backgroundDisabledSkin():DisplayObject
{
return this._backgroundDisabledSkin;
}
/**
* @private
*/
public function set backgroundDisabledSkin(value:DisplayObject):void
{
if(this._backgroundDisabledSkin == value)
{
return;
}
if(this._backgroundDisabledSkin && this.currentBackgroundSkin == this._backgroundDisabledSkin)
{
this.removeRawChildInternal(this._backgroundDisabledSkin);
this.currentBackgroundSkin = null;
}
this._backgroundDisabledSkin = value;
this.invalidate(INVALIDATION_FLAG_STYLES);
}
/**
* @private
*/
protected var _autoHideBackground:Boolean = false;
/**
* If true
, the background's visible
property
* will be set to false
when the scroll position is greater
* than or equal to the minimum scroll position and less than or equal
* to the maximum scroll position. The background will be visible when
* the content is extended beyond the scrolling bounds, such as when
* hasElasticEdges
is true
.
*
* If the content is not fully opaque, this setting should not be
* enabled.
*
* This setting may be enabled to potentially improve performance.
*
* In the following example, the background is automatically hidden:
*
*
* scroller.autoHideBackground = true;
*
* @default false
*/
public function get autoHideBackground():Boolean
{
return this._autoHideBackground;
}
/**
* @private
*/
public function set autoHideBackground(value:Boolean):void
{
if(this._autoHideBackground == value)
{
return;
}
this._autoHideBackground = value;
this.invalidate(INVALIDATION_FLAG_STYLES);
}
/**
* @private
*/
protected var _minimumDragDistance:Number = 0.04;
/**
* The minimum physical distance (in inches) that a touch must move
* before the scroller starts scrolling.
*
* In the following example, the minimum drag distance is customized:
*
*
* scroller.minimumDragDistance = 0.1;
*
* @default 0.04
*/
public function get minimumDragDistance():Number
{
return this._minimumDragDistance;
}
/**
* @private
*/
public function set minimumDragDistance(value:Number):void
{
this._minimumDragDistance = value;
}
/**
* @private
*/
protected var _minimumPageThrowVelocity:Number = 5;
/**
* The minimum physical velocity (in inches per second) that a touch
* must move before the scroller will "throw" to the next page.
* Otherwise, it will settle to the nearest page.
*
* In the following example, the minimum page throw velocity is customized:
*
*
* scroller.minimumPageThrowVelocity = 2;
*
* @default 5
*/
public function get minimumPageThrowVelocity():Number
{
return this._minimumPageThrowVelocity;
}
/**
* @private
*/
public function set minimumPageThrowVelocity(value:Number):void
{
this._minimumPageThrowVelocity = value;
}
/**
* 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:
*
*
* scroller.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 container's top edge and the
* container's content.
*
* In the following example, the top padding is set to 20 pixels:
*
*
* scroller.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 container's right edge and
* the container's content.
*
* In the following example, the right padding is set to 20 pixels:
*
*
* scroller.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 container's bottom edge and
* the container's content.
*
* In the following example, the bottom padding is set to 20 pixels:
*
*
* scroller.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 container's left edge and the
* container's content.
*
* In the following example, the left padding is set to 20 pixels:
*
*
* scroller.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 _horizontalScrollBarHideTween:Tween;
/**
* @private
*/
protected var _verticalScrollBarHideTween:Tween;
/**
* @private
*/
protected var _hideScrollBarAnimationDuration:Number = 0.2;
/**
* The duration, in seconds, of the animation when a scroll bar fades
* out.
*
* In the following example, the duration of the animation that hides
* the scroll bars is set to 500 milliseconds:
*
*
* scroller.hideScrollBarAnimationDuration = 0.5;
*
* @default 0.2
*/
public function get hideScrollBarAnimationDuration():Number
{
return this._hideScrollBarAnimationDuration;
}
/**
* @private
*/
public function set hideScrollBarAnimationDuration(value:Number):void
{
this._hideScrollBarAnimationDuration = value;
}
/**
* @private
*/
protected var _hideScrollBarAnimationEase:Object = Transitions.EASE_OUT;
/**
* The easing function used for hiding the scroll bars, if applicable.
*
* In the following example, the ease of the animation that hides
* the scroll bars is customized:
*
*
* scroller.hideScrollBarAnimationEase = Transitions.EASE_IN_OUT;
*
* @default starling.animation.Transitions.EASE_OUT
*
* @see http://doc.starling-framework.org/core/starling/animation/Transitions.html starling.animation.Transitions
*/
public function get hideScrollBarAnimationEase():Object
{
return this._hideScrollBarAnimationEase;
}
/**
* @private
*/
public function set hideScrollBarAnimationEase(value:Object):void
{
this._hideScrollBarAnimationEase = value;
}
/**
* @private
*/
protected var _elasticSnapDuration:Number = 0.5;
/**
* The duration, in seconds, of the animation when a the scroller snaps
* back to the minimum or maximum position after going out of bounds.
*
* In the following example, the duration of the animation that snaps
* the content back after pulling it beyond the edge is set to 750
* milliseconds:
*
*
* scroller.elasticSnapDuration = 0.75;
*
* @default 0.5
*/
public function get elasticSnapDuration():Number
{
return this._elasticSnapDuration;
}
/**
* @private
*/
public function set elasticSnapDuration(value:Number):void
{
this._elasticSnapDuration = value;
}
/**
* @private
* This value is precalculated. See the decelerationRate
* setter for the dynamic calculation.
*/
protected var _logDecelerationRate:Number = -0.0020020026706730793;
/**
* @private
*/
protected var _decelerationRate:Number = DecelerationRate.NORMAL;
/**
* This value is used to decelerate the scroller when "thrown". The
* velocity of a throw is multiplied by this value once per millisecond
* to decelerate. A value greater than 0
and less than
* 1
is expected.
*
* In the following example, deceleration rate is lowered to adjust
* the behavior of a throw:
*
*
* scroller.decelerationRate = DecelerationRate.FAST;
*
* @default feathers.controls.DecelerationRate.NORMAL
*
* @see feathers.controls.DecelerationRate#NORMAL
* @see feathers.controls.DecelerationRate#FAST
*/
public function get decelerationRate():Number
{
return this._decelerationRate;
}
/**
* @private
*/
public function set decelerationRate(value:Number):void
{
if(this._decelerationRate == value)
{
return;
}
this._decelerationRate = value;
this._logDecelerationRate = Math.log(this._decelerationRate);
this._fixedThrowDuration = -0.1 / Math.log(Math.pow(this._decelerationRate, 1000 / 60))
}
/**
* @private
* This value is precalculated. See the decelerationRate
* setter for the dynamic calculation.
*/
protected var _fixedThrowDuration:Number = 2.996998998998728;
/**
* @private
*/
protected var _useFixedThrowDuration:Boolean = true;
/**
* If true
, the duration of a "throw" animation will be the
* same no matter the value of the throw's initial velocity. This value
* may be set to false
to have the scroller calculate a
* variable duration based on the velocity of the throw.
*
* It may seem unintuitive, but using the same fixed duration for any
* velocity is recommended if you are looking to closely match the
* behavior of native scrolling on iOS.
*
* In the following example, the behavior of a "throw" animation is
* changed:
*
*
* scroller.useFixedThrowDuration = false;
*
* @default true
*
* @see #decelerationRate
* @see #pageThrowDuration
*/
public function get useFixedThrowDuration():Boolean
{
return this._useFixedThrowDuration;
}
/**
* @private
*/
public function set useFixedThrowDuration(value:Boolean):void
{
this._useFixedThrowDuration = value;
}
/**
* @private
*/
protected var _pageThrowDuration:Number = 0.5;
/**
* The duration, in seconds, of the animation when the scroller is
* thrown to a page.
*
* In the following example, the duration of the animation that
* changes the page when thrown is set to 250 milliseconds:
*
*
* scroller.pageThrowDuration = 0.25;
*
* @default 0.5
*/
public function get pageThrowDuration():Number
{
return this._pageThrowDuration;
}
/**
* @private
*/
public function set pageThrowDuration(value:Number):void
{
this._pageThrowDuration = value;
}
/**
* @private
*/
protected var _mouseWheelScrollDuration:Number = 0.35;
/**
* The duration, in seconds, of the animation when the mouse wheel
* initiates a scroll action.
*
* In the following example, the duration of the animation that runs
* when the mouse wheel is scrolled is set to 500 milliseconds:
*
*
* scroller.mouseWheelScrollDuration = 0.5;
*
* @default 0.35
*/
public function get mouseWheelScrollDuration():Number
{
return this._mouseWheelScrollDuration;
}
/**
* @private
*/
public function set mouseWheelScrollDuration(value:Number):void
{
this._mouseWheelScrollDuration = value;
}
/**
* @private
*/
protected var _verticalMouseWheelScrollDirection:String = Direction.VERTICAL;
/**
* The direction of scrolling when the user scrolls the mouse wheel
* vertically. In some cases, it is common for a container that only
* scrolls horizontally to scroll even when the mouse wheel is scrolled
* vertically.
*
* In the following example, the direction of scrolling when using
* the mouse wheel is changed:
*
*
* scroller.verticalMouseWheelScrollDirection = Direction.HORIZONTAL;
*
* @default feathers.layout.Direction.VERTICAL
*
* @see feathers.layout.Direction#HORIZONTAL
* @see feathers.layout.Direction#VERTICAL
*/
public function get verticalMouseWheelScrollDirection():String
{
return this._verticalMouseWheelScrollDirection;
}
/**
* @private
*/
public function set verticalMouseWheelScrollDirection(value:String):void
{
this._verticalMouseWheelScrollDirection = value;
}
/**
* @private
*/
protected var _throwEase:Object = defaultThrowEase;
/**
* The easing function used for "throw" animations.
*
* In the following example, the ease of throwing animations is
* customized:
*
*
* scroller.throwEase = Transitions.EASE_IN_OUT;
*
* @see http://doc.starling-framework.org/core/starling/animation/Transitions.html starling.animation.Transitions
*/
public function get throwEase():Object
{
return this._throwEase;
}
/**
* @private
*/
public function set throwEase(value:Object):void
{
if(value == null)
{
value = defaultThrowEase;
}
this._throwEase = value;
}
/**
* @private
*/
protected var _snapScrollPositionsToPixels:Boolean = true;
/**
* If enabled, the scroll position will always be adjusted to whole
* pixels.
*
* In the following example, the scroll position is not snapped to pixels:
*
*
* scroller.snapScrollPositionsToPixels = false;
*
* @default true
*/
public function get snapScrollPositionsToPixels():Boolean
{
return this._snapScrollPositionsToPixels;
}
/**
* @private
*/
public function set snapScrollPositionsToPixels(value:Boolean):void
{
if(this._snapScrollPositionsToPixels == value)
{
return;
}
this._snapScrollPositionsToPixels = value;
if(this._snapScrollPositionsToPixels)
{
this.horizontalScrollPosition = Math.round(this._horizontalScrollPosition);
this.verticalScrollPosition = Math.round(this._verticalScrollPosition);
}
}
/**
* @private
*/
protected var _horizontalScrollBarIsScrolling:Boolean = false;
/**
* @private
*/
protected var _verticalScrollBarIsScrolling:Boolean = false;
/**
* @private
*/
protected var _isScrolling:Boolean = false;
/**
* Determines if the scroller is currently scrolling with user
* interaction or with animation.
*/
public function get isScrolling():Boolean
{
return this._isScrolling;
}
/**
* @private
*/
protected var _isScrollingStopped:Boolean = false;
/**
* The pending horizontal scroll position to scroll to after validating.
* A value of NaN
means that the scroller won't scroll to a
* horizontal position after validating.
*/
protected var pendingHorizontalScrollPosition:Number = NaN;
/**
* The pending vertical scroll position to scroll to after validating.
* A value of NaN
means that the scroller won't scroll to a
* vertical position after validating.
*/
protected var pendingVerticalScrollPosition:Number = NaN;
/**
* A flag that indicates if the scroller should scroll to a new page
* when it validates. If true
, it will use the value of
* pendingHorizontalPageIndex
as the target page index.
*
* @see #pendingHorizontalPageIndex
*/
protected var hasPendingHorizontalPageIndex:Boolean = false;
/**
* A flag that indicates if the scroller should scroll to a new page
* when it validates. If true
, it will use the value of
* pendingVerticalPageIndex
as the target page index.
*
* @see #pendingVerticalPageIndex
*/
protected var hasPendingVerticalPageIndex:Boolean = false;
/**
* The pending horizontal page index to scroll to after validating. The
* flag hasPendingHorizontalPageIndex
must be set to true
* if there is a pending page index to scroll to.
*
* @see #hasPendingHorizontalPageIndex
*/
protected var pendingHorizontalPageIndex:int;
/**
* The pending vertical page index to scroll to after validating. The
* flag hasPendingVerticalPageIndex
must be set to true
* if there is a pending page index to scroll to.
*
* @see #hasPendingVerticalPageIndex
*/
protected var pendingVerticalPageIndex:int;
/**
* The duration of the pending scroll action.
*/
protected var pendingScrollDuration:Number;
/**
* @private
*/
protected var isScrollBarRevealPending:Boolean = false;
/**
* @private
*/
protected var _revealScrollBarsDuration:Number = 1.0;
/**
* The duration, in seconds, that the scroll bars will be shown when
* calling revealScrollBars()
*
* @default 1.0
*
* @see #revealScrollBars()
*/
public function get revealScrollBarsDuration():Number
{
return this._revealScrollBarsDuration;
}
/**
* @private
*/
public function set revealScrollBarsDuration(value:Number):void
{
this._revealScrollBarsDuration = value;
}
/**
* @private
*/
protected var _horizontalAutoScrollTweenEndRatio:Number = 1;
/**
* @private
*/
protected var _verticalAutoScrollTweenEndRatio:Number = 1;
/**
* @private
*/
override public function dispose():void
{
Starling.current.nativeStage.removeEventListener(MouseEvent.MOUSE_WHEEL, nativeStage_mouseWheelHandler);
Starling.current.nativeStage.removeEventListener("orientationChange", nativeStage_orientationChangeHandler);
//we don't dispose it if the text input is the parent because it'll
//already get disposed in super.dispose()
if(this._backgroundSkin && this._backgroundSkin.parent !== this)
{
this._backgroundSkin.dispose();
}
if(this._backgroundDisabledSkin && this._backgroundDisabledSkin.parent !== this)
{
this._backgroundDisabledSkin.dispose();
}
super.dispose();
}
/**
* If the user is scrolling with touch or if the scrolling is animated,
* calling stopScrolling() will cause the scroller to ignore the drag
* and stop animations. This function may only be called during scrolling,
* so if you need to stop scrolling on a TouchEvent
with
* TouchPhase.BEGAN
, you may need to wait for the scroller
* to start scrolling before you can call this function.
*
* In the following example, we listen for FeathersEventType.SCROLL_START
* to stop scrolling:
*
*
* scroller.addEventListener( FeathersEventType.SCROLL_START, function( event:Event ):void
* {
* scroller.stopScrolling();
* });
*/
public function stopScrolling():void
{
if(this._horizontalAutoScrollTween)
{
Starling.juggler.remove(this._horizontalAutoScrollTween);
this._horizontalAutoScrollTween = null;
}
if(this._verticalAutoScrollTween)
{
Starling.juggler.remove(this._verticalAutoScrollTween);
this._verticalAutoScrollTween = null;
}
this._isScrollingStopped = true;
this._velocityX = 0;
this._velocityY = 0;
this._previousVelocityX.length = 0;
this._previousVelocityY.length = 0;
this.hideHorizontalScrollBar();
this.hideVerticalScrollBar();
}
/**
* After the next validation, animates the scroll positions to a
* specific location. May scroll in only one direction by passing in a
* value of NaN
for either scroll position. If the
* animationDuration
argument is NaN
(the
* default value), the duration of a standard throw is used. The
* duration is in seconds.
*
* Because this function is primarily designed for animation, using a
* duration of 0
may require a frame or two before the
* scroll position updates.
*
* In the following example, we scroll to the maximum vertical scroll
* position:
*
*
* scroller.scrollToPosition( scroller.horizontalScrollPosition, scroller.maxVerticalScrollPosition );
*
* @see #horizontalScrollPosition
* @see #verticalScrollPosition
* @see #throwEase
*/
public function scrollToPosition(horizontalScrollPosition:Number, verticalScrollPosition:Number, animationDuration:Number = NaN):void
{
if(animationDuration !== animationDuration) //isNaN
{
if(this._useFixedThrowDuration)
{
animationDuration = this._fixedThrowDuration;
}
else
{
HELPER_POINT.setTo(horizontalScrollPosition - this._horizontalScrollPosition, verticalScrollPosition - this._verticalScrollPosition);
animationDuration = this.calculateDynamicThrowDuration(HELPER_POINT.length * this._logDecelerationRate + MINIMUM_VELOCITY);
}
}
//cancel any pending scroll to a different page. we can have only
//one type of pending scroll at a time.
this.hasPendingHorizontalPageIndex = false;
this.hasPendingVerticalPageIndex = false;
if(this.pendingHorizontalScrollPosition == horizontalScrollPosition &&
this.pendingVerticalScrollPosition == verticalScrollPosition &&
this.pendingScrollDuration == animationDuration)
{
return;
}
this.pendingHorizontalScrollPosition = horizontalScrollPosition;
this.pendingVerticalScrollPosition = verticalScrollPosition;
this.pendingScrollDuration = animationDuration;
this.invalidate(INVALIDATION_FLAG_PENDING_SCROLL);
}
/**
* After the next validation, animates the scroll position to a specific
* page index. To scroll in only one direction, pass in the value of the
* horizontalPageIndex
or the
* verticalPageIndex
property to the appropriate parameter.
* If the animationDuration
argument is NaN
* (the default value) the value of the pageThrowDuration
* property is used for the duration. The duration is in seconds.
*
* You can only scroll to a page if the snapToPages
* property is true
.
*
* In the following example, we scroll to the last horizontal page:
*
*
* scroller.scrollToPageIndex( scroller.horizontalPageCount - 1, scroller.verticalPageIndex );
*
* @see #snapToPages
* @see #pageThrowDuration
* @see #throwEase
* @see #horizontalPageIndex
* @see #verticalPageIndex
*/
public function scrollToPageIndex(horizontalPageIndex:int, verticalPageIndex:int, animationDuration:Number = NaN):void
{
if(animationDuration !== animationDuration) //isNaN
{
animationDuration = this._pageThrowDuration;
}
//cancel any pending scroll to a specific scroll position. we can
//have only one type of pending scroll at a time.
this.pendingHorizontalScrollPosition = NaN;
this.pendingVerticalScrollPosition = NaN;
this.hasPendingHorizontalPageIndex = this._horizontalPageIndex !== horizontalPageIndex;
this.hasPendingVerticalPageIndex = this._verticalPageIndex !== verticalPageIndex;
if(!this.hasPendingHorizontalPageIndex && !this.hasPendingVerticalPageIndex)
{
return;
}
this.pendingHorizontalPageIndex = horizontalPageIndex;
this.pendingVerticalPageIndex = verticalPageIndex;
this.pendingScrollDuration = animationDuration;
this.invalidate(INVALIDATION_FLAG_PENDING_SCROLL);
}
/**
* If the scroll bars are floating, briefly show them as a hint to the
* user. Useful when first navigating to a screen to give the user
* context about both the ability to scroll and the current scroll
* position.
*
* @see #revealScrollBarsDuration
*/
public function revealScrollBars():void
{
this.isScrollBarRevealPending = true;
this.invalidate(INVALIDATION_FLAG_PENDING_REVEAL_SCROLL_BARS);
}
/**
* @private
*/
override public function hitTest(localPoint:Point):DisplayObject
{
//save localX and localY because localPoint could change after the
//call to super.hitTest().
var localX:Number = localPoint.x;
var localY:Number = localPoint.y;
//first check the children for touches
var result:DisplayObject = super.hitTest(localPoint);
if(!result)
{
//we want to register touches in our hitArea as a last resort
if(!this.visible || !this.touchable)
{
return null;
}
return this._hitArea.contains(localX, localY) ? this : null;
}
return result;
}
/**
* @private
*/
override protected function draw():void
{
var sizeInvalid:Boolean = this.isInvalid(INVALIDATION_FLAG_SIZE);
//we don't use this flag in this class, but subclasses will use it,
//and it's better to handle it here instead of having them
//invalidate unrelated flags
var dataInvalid:Boolean = this.isInvalid(INVALIDATION_FLAG_DATA);
var scrollInvalid:Boolean = this.isInvalid(INVALIDATION_FLAG_SCROLL);
var clippingInvalid:Boolean = this.isInvalid(INVALIDATION_FLAG_CLIPPING);
var stylesInvalid:Boolean = this.isInvalid(INVALIDATION_FLAG_STYLES);
var stateInvalid:Boolean = this.isInvalid(INVALIDATION_FLAG_STATE);
var scrollBarInvalid:Boolean = this.isInvalid(INVALIDATION_FLAG_SCROLL_BAR_RENDERER);
var pendingScrollInvalid:Boolean = this.isInvalid(INVALIDATION_FLAG_PENDING_SCROLL);
var pendingRevealScrollBarsInvalid:Boolean = this.isInvalid(INVALIDATION_FLAG_PENDING_REVEAL_SCROLL_BARS);
if(scrollBarInvalid)
{
this.createScrollBars();
}
if(sizeInvalid || stylesInvalid || stateInvalid)
{
this.refreshBackgroundSkin();
}
if(scrollBarInvalid || stylesInvalid)
{
this.refreshScrollBarStyles();
this.refreshInteractionModeEvents();
}
if(scrollBarInvalid || stateInvalid)
{
this.refreshEnabled();
}
if(this.horizontalScrollBar)
{
this.horizontalScrollBar.validate();
}
if(this.verticalScrollBar)
{
this.verticalScrollBar.validate();
}
var needsWidthOrHeight:Boolean = this._explicitWidth !== this._explicitWidth ||
this._explicitHeight !== this._explicitHeight; //isNaN
var oldMaxHorizontalScrollPosition:Number = this._maxHorizontalScrollPosition;
var oldMaxVerticalScrollPosition:Number = this._maxVerticalScrollPosition;
var loopCount:int = 0;
do
{
this._hasViewPortBoundsChanged = false;
//if we don't need to do any measurement, we can skip this stuff
//and improve performance
if(needsWidthOrHeight && this._measureViewPort)
{
//even if fixed, we need to measure without them first because
//if the scroll policy is auto, we only show them when needed.
if(scrollInvalid || dataInvalid || sizeInvalid || stylesInvalid || scrollBarInvalid)
{
this.calculateViewPortOffsets(true, false);
this.refreshViewPortBoundsWithoutFixedScrollBars();
this.calculateViewPortOffsets(false, false);
}
}
sizeInvalid = this.autoSizeIfNeeded() || sizeInvalid;
//just in case autoSizeIfNeeded() is overridden, we need to call
//this again and use actualWidth/Height instead of
//explicitWidth/Height.
this.calculateViewPortOffsets(false, true);
if(scrollInvalid || dataInvalid || sizeInvalid || stylesInvalid || scrollBarInvalid)
{
this.refreshViewPortBoundsWithFixedScrollBars();
this.refreshScrollValues();
}
loopCount++;
if(loopCount >= 10)
{
//if it still fails after ten tries, we've probably entered
//an infinite loop due to rounding errors or something
break;
}
}
while(this._hasViewPortBoundsChanged)
this._lastViewPortWidth = viewPort.width;
this._lastViewPortHeight = viewPort.height;
if(oldMaxHorizontalScrollPosition != this._maxHorizontalScrollPosition)
{
this.refreshHorizontalAutoScrollTweenEndRatio();
scrollInvalid = true;
}
if(oldMaxVerticalScrollPosition != this._maxVerticalScrollPosition)
{
this.refreshVerticalAutoScrollTweenEndRatio();
scrollInvalid = true;
}
if(scrollInvalid)
{
this.dispatchEventWith(Event.SCROLL);
}
this.showOrHideChildren();
if(scrollInvalid || sizeInvalid || stylesInvalid || stateInvalid || scrollBarInvalid)
{
this.layoutChildren();
}
if(scrollInvalid || sizeInvalid || stylesInvalid || scrollBarInvalid)
{
this.refreshScrollBarValues();
}
if(scrollInvalid || sizeInvalid || stylesInvalid || scrollBarInvalid || clippingInvalid)
{
this.refreshMask();
}
this.refreshFocusIndicator();
if(pendingScrollInvalid)
{
this.handlePendingScroll();
}
if(pendingRevealScrollBarsInvalid)
{
this.handlePendingRevealScrollBars();
}
}
/**
* If the component's dimensions have not been set explicitly, it will
* measure its content and determine an ideal size for itself. If the
* explicitWidth
or explicitHeight
member
* variables are set, those value will be used without additional
* measurement. If one is set, but not the other, the dimension with the
* explicit value will not be measured, but the other non-explicit
* dimension will still need measurement.
*
* Calls setSizeInternal()
to set up the
* actualWidth
and actualHeight
member
* variables used for layout.
*
* Meant for internal use, and subclasses may override this function
* with a custom implementation.
*/
protected function autoSizeIfNeeded():Boolean
{
var needsWidth:Boolean = this._explicitWidth !== this._explicitWidth; //isNaN
var needsHeight:Boolean = this._explicitHeight !== this._explicitHeight; //isNaN
if(!needsWidth && !needsHeight)
{
return false;
}
var newWidth:Number = this._explicitWidth;
var newHeight:Number = this._explicitHeight;
if(needsWidth)
{
if(this._measureViewPort)
{
newWidth = this._viewPort.visibleWidth;
if(newWidth !== newWidth) //isNaN
{
newWidth = this._viewPort.width;
}
newWidth += this._rightViewPortOffset + this._leftViewPortOffset;
}
else
{
newWidth = 0;
}
if(this.originalBackgroundWidth === this.originalBackgroundWidth && //!isNaN
this.originalBackgroundWidth > newWidth)
{
newWidth = this.originalBackgroundWidth;
}
}
if(needsHeight)
{
if(this._measureViewPort)
{
newHeight = this._viewPort.visibleHeight;
if(newHeight !== newHeight) //isNaN
{
newHeight = this._viewPort.height;
}
newHeight += this._bottomViewPortOffset + this._topViewPortOffset;
}
else
{
newHeight = 0;
}
if(this.originalBackgroundHeight === this.originalBackgroundHeight && //!isNaN
this.originalBackgroundHeight > newHeight)
{
newHeight = this.originalBackgroundHeight;
}
}
return this.setSizeInternal(newWidth, newHeight, false);
}
/**
* Creates and adds the horizontalScrollBar
and
* verticalScrollBar
sub-components and removes the old
* instances, if they exist.
*
* Meant for internal use, and subclasses may override this function
* with a custom implementation.
*
* @see #horizontalScrollBar
* @see #verticalScrollBar
* @see #horizontalScrollBarFactory
* @see #verticalScrollBarFactory
*/
protected function createScrollBars():void
{
if(this.horizontalScrollBar)
{
this.horizontalScrollBar.removeEventListener(FeathersEventType.BEGIN_INTERACTION, horizontalScrollBar_beginInteractionHandler);
this.horizontalScrollBar.removeEventListener(FeathersEventType.END_INTERACTION, horizontalScrollBar_endInteractionHandler);
this.horizontalScrollBar.removeEventListener(Event.CHANGE, horizontalScrollBar_changeHandler);
this.removeRawChildInternal(DisplayObject(this.horizontalScrollBar), true);
this.horizontalScrollBar = null;
}
if(this.verticalScrollBar)
{
this.verticalScrollBar.removeEventListener(FeathersEventType.BEGIN_INTERACTION, verticalScrollBar_beginInteractionHandler);
this.verticalScrollBar.removeEventListener(FeathersEventType.END_INTERACTION, verticalScrollBar_endInteractionHandler);
this.verticalScrollBar.removeEventListener(Event.CHANGE, verticalScrollBar_changeHandler);
this.removeRawChildInternal(DisplayObject(this.verticalScrollBar), true);
this.verticalScrollBar = null;
}
if(this._scrollBarDisplayMode != ScrollBarDisplayMode.NONE &&
this._horizontalScrollPolicy != ScrollPolicy.OFF && this._horizontalScrollBarFactory != null)
{
this.horizontalScrollBar = IScrollBar(this._horizontalScrollBarFactory());
if(this.horizontalScrollBar is IDirectionalScrollBar)
{
IDirectionalScrollBar(this.horizontalScrollBar).direction = Direction.HORIZONTAL;
}
var horizontalScrollBarStyleName:String = this._customHorizontalScrollBarStyleName != null ? this._customHorizontalScrollBarStyleName : this.horizontalScrollBarStyleName;
this.horizontalScrollBar.styleNameList.add(horizontalScrollBarStyleName);
this.horizontalScrollBar.addEventListener(Event.CHANGE, horizontalScrollBar_changeHandler);
this.horizontalScrollBar.addEventListener(FeathersEventType.BEGIN_INTERACTION, horizontalScrollBar_beginInteractionHandler);
this.horizontalScrollBar.addEventListener(FeathersEventType.END_INTERACTION, horizontalScrollBar_endInteractionHandler);
this.addRawChildInternal(DisplayObject(this.horizontalScrollBar));
}
if(this._scrollBarDisplayMode != ScrollBarDisplayMode.NONE &&
this._verticalScrollPolicy != ScrollPolicy.OFF && this._verticalScrollBarFactory != null)
{
this.verticalScrollBar = IScrollBar(this._verticalScrollBarFactory());
if(this.verticalScrollBar is IDirectionalScrollBar)
{
IDirectionalScrollBar(this.verticalScrollBar).direction = Direction.VERTICAL;
}
var verticalScrollBarStyleName:String = this._customVerticalScrollBarStyleName != null ? this._customVerticalScrollBarStyleName : this.verticalScrollBarStyleName;
this.verticalScrollBar.styleNameList.add(verticalScrollBarStyleName);
this.verticalScrollBar.addEventListener(Event.CHANGE, verticalScrollBar_changeHandler);
this.verticalScrollBar.addEventListener(FeathersEventType.BEGIN_INTERACTION, verticalScrollBar_beginInteractionHandler);
this.verticalScrollBar.addEventListener(FeathersEventType.END_INTERACTION, verticalScrollBar_endInteractionHandler);
this.addRawChildInternal(DisplayObject(this.verticalScrollBar));
}
}
/**
* Choose the appropriate background skin based on the control's current
* state.
*/
protected function refreshBackgroundSkin():void
{
var newCurrentBackgroundSkin:DisplayObject = this._backgroundSkin;
if(!this._isEnabled && this._backgroundDisabledSkin)
{
newCurrentBackgroundSkin = this._backgroundDisabledSkin;
}
if(this.currentBackgroundSkin != newCurrentBackgroundSkin)
{
if(this.currentBackgroundSkin)
{
this.removeRawChildInternal(this.currentBackgroundSkin);
}
this.currentBackgroundSkin = newCurrentBackgroundSkin;
if(this.currentBackgroundSkin)
{
this.addRawChildAtInternal(this.currentBackgroundSkin, 0);
}
}
if(this.currentBackgroundSkin)
{
//force it to the bottom
this.setRawChildIndexInternal(this.currentBackgroundSkin, 0);
if(this.originalBackgroundWidth !== this.originalBackgroundWidth) //isNaN
{
this.originalBackgroundWidth = this.currentBackgroundSkin.width;
}
if(this.originalBackgroundHeight !== this.originalBackgroundHeight) //isNaN
{
this.originalBackgroundHeight = this.currentBackgroundSkin.height;
}
}
}
/**
* @private
*/
protected function refreshScrollBarStyles():void
{
if(this.horizontalScrollBar)
{
for(var propertyName:String in this._horizontalScrollBarProperties)
{
var propertyValue:Object = this._horizontalScrollBarProperties[propertyName];
this.horizontalScrollBar[propertyName] = propertyValue;
}
if(this._horizontalScrollBarHideTween)
{
Starling.juggler.remove(this._horizontalScrollBarHideTween);
this._horizontalScrollBarHideTween = null;
}
this.horizontalScrollBar.alpha = this._scrollBarDisplayMode == ScrollBarDisplayMode.FLOAT ? 0 : 1;
}
if(this.verticalScrollBar)
{
for(propertyName in this._verticalScrollBarProperties)
{
propertyValue = this._verticalScrollBarProperties[propertyName];
this.verticalScrollBar[propertyName] = propertyValue;
}
if(this._verticalScrollBarHideTween)
{
Starling.juggler.remove(this._verticalScrollBarHideTween);
this._verticalScrollBarHideTween = null;
}
this.verticalScrollBar.alpha = this._scrollBarDisplayMode == ScrollBarDisplayMode.FLOAT ? 0 : 1;
}
}
/**
* @private
*/
protected function refreshEnabled():void
{
if(this._viewPort)
{
this._viewPort.isEnabled = this._isEnabled;
}
if(this.horizontalScrollBar)
{
this.horizontalScrollBar.isEnabled = this._isEnabled;
}
if(this.verticalScrollBar)
{
this.verticalScrollBar.isEnabled = this._isEnabled;
}
}
/**
* @private
*/
override protected function refreshFocusIndicator():void
{
if(this._focusIndicatorSkin)
{
if(this._hasFocus && this._showFocus)
{
if(this._focusIndicatorSkin.parent != this)
{
this.addRawChildInternal(this._focusIndicatorSkin);
}
else
{
this.setRawChildIndexInternal(this._focusIndicatorSkin, this.numRawChildrenInternal - 1);
}
}
else if(this._focusIndicatorSkin.parent == this)
{
this.removeRawChildInternal(this._focusIndicatorSkin, false);
}
this._focusIndicatorSkin.x = this._focusPaddingLeft;
this._focusIndicatorSkin.y = this._focusPaddingTop;
this._focusIndicatorSkin.width = this.actualWidth - this._focusPaddingLeft - this._focusPaddingRight;
this._focusIndicatorSkin.height = this.actualHeight - this._focusPaddingTop - this._focusPaddingBottom;
}
}
/**
* @private
*/
protected function refreshViewPortBoundsWithoutFixedScrollBars():void
{
var horizontalWidthOffset:Number = this._leftViewPortOffset + this._rightViewPortOffset;
var verticalHeightOffset:Number = this._topViewPortOffset + this._bottomViewPortOffset;
//if scroll bars are fixed, we're going to include the offsets even
//if they may not be needed in the final pass. if not fixed, the
//view port fills the entire bounds.
this._viewPort.visibleWidth = this._explicitWidth - horizontalWidthOffset;
this._viewPort.visibleHeight = this._explicitHeight - verticalHeightOffset;
var minVisibleWidth:Number = this._explicitMinWidth;
if(minVisibleWidth !== minVisibleWidth) //isNaN
{
minVisibleWidth = 0;
}
minVisibleWidth -= horizontalWidthOffset;
if(this.originalBackgroundWidth === this.originalBackgroundWidth && //!isNaN
this.originalBackgroundWidth > minVisibleWidth)
{
//to avoid going through the loop too many times, we need to
//account for the background skin's size.
minVisibleWidth = this.originalBackgroundWidth;
}
if(minVisibleWidth < 0)
{
minVisibleWidth = 0;
}
this._viewPort.minVisibleWidth = minVisibleWidth;
this._viewPort.maxVisibleWidth = this._maxWidth - horizontalWidthOffset;
var minVisibleHeight:Number = this.actualMinHeight;
if(minVisibleHeight !== minVisibleHeight) //isNaN
{
minVisibleHeight = 0;
}
minVisibleHeight -= verticalHeightOffset;
if(this.originalBackgroundHeight === this.originalBackgroundHeight && //!isNaN
this.originalBackgroundHeight > minVisibleHeight)
{
//see note above about the background skin size
minVisibleHeight = this.originalBackgroundHeight;
}
if(minVisibleHeight < 0)
{
minVisibleHeight = 0;
}
this._viewPort.minVisibleHeight = minVisibleHeight;
this._viewPort.maxVisibleHeight = this._maxHeight - verticalHeightOffset;
this._viewPort.horizontalScrollPosition = this._horizontalScrollPosition;
this._viewPort.verticalScrollPosition = this._verticalScrollPosition;
var oldIgnoreViewPortResizing:Boolean = this.ignoreViewPortResizing;
if(this._scrollBarDisplayMode == ScrollBarDisplayMode.FIXED)
{
this.ignoreViewPortResizing = true;
}
this._viewPort.validate();
this.ignoreViewPortResizing = oldIgnoreViewPortResizing;
}
/**
* @private
*/
protected function refreshViewPortBoundsWithFixedScrollBars():void
{
var horizontalWidthOffset:Number = this._leftViewPortOffset + this._rightViewPortOffset;
var verticalHeightOffset:Number = this._topViewPortOffset + this._bottomViewPortOffset;
var needsWidthOrHeight:Boolean = this._explicitWidth != this._explicitWidth ||
this._explicitHeight !== this._explicitHeight; //isNaN
if(!(this._measureViewPort && needsWidthOrHeight))
{
//if we didn't need to do any measurement, we would have skipped
//setting this stuff earlier, and now is the last chance
var minVisibleWidth:Number = this._explicitMinWidth;
if(minVisibleWidth !== minVisibleWidth) //isNaN
{
minVisibleWidth = 0;
}
minVisibleWidth -= horizontalWidthOffset;
if(this.originalBackgroundWidth === this.originalBackgroundWidth && //!isNaN
this.originalBackgroundWidth > minVisibleWidth)
{
//to avoid going through the loop too many times, we need to
//account for the background skin's size
minVisibleWidth = this.originalBackgroundWidth;
}
if(minVisibleWidth < 0)
{
minVisibleWidth = 0;
}
this._viewPort.minVisibleWidth = minVisibleWidth;
this._viewPort.maxVisibleWidth = this._maxWidth - horizontalWidthOffset;
var minVisibleHeight:Number = this._explicitMinHeight;
if(minVisibleHeight !== minVisibleHeight) //isNaN
{
minVisibleHeight = 0;
}
minVisibleHeight -= verticalHeightOffset;
if(this.originalBackgroundHeight === this.originalBackgroundHeight && //!isNaN
this.originalBackgroundHeight > minVisibleHeight)
{
//see note above about the background skin size
minVisibleHeight = this.originalBackgroundHeight;
}
if(minVisibleHeight < 0)
{
minVisibleHeight = 0;
}
this._viewPort.minVisibleHeight = minVisibleHeight;
this._viewPort.maxVisibleHeight = this._maxHeight - verticalHeightOffset;
this._viewPort.horizontalScrollPosition = this._horizontalScrollPosition;
this._viewPort.verticalScrollPosition = this._verticalScrollPosition;
}
this._viewPort.visibleWidth = this.actualWidth - horizontalWidthOffset;
this._viewPort.visibleHeight = this.actualHeight - verticalHeightOffset;
this._viewPort.validate();
}
/**
* @private
*/
protected function refreshScrollValues():void
{
this.refreshScrollSteps();
var oldMaxHSP:Number = this._maxHorizontalScrollPosition;
var oldMaxVSP:Number = this._maxVerticalScrollPosition;
this.refreshMinAndMaxScrollPositions();
var maximumPositionsChanged:Boolean = this._maxHorizontalScrollPosition != oldMaxHSP || this._maxVerticalScrollPosition != oldMaxVSP;
if(maximumPositionsChanged && this._touchPointID < 0)
{
this.clampScrollPositions();
}
this.refreshPageCount();
this.refreshPageIndices();
}
/**
* @private
*/
protected function clampScrollPositions():void
{
if(!this._horizontalAutoScrollTween)
{
if(this._snapToPages)
{
this._horizontalScrollPosition = roundToNearest(this._horizontalScrollPosition, this.actualPageWidth);
}
var targetHorizontalScrollPosition:Number = this._horizontalScrollPosition;
if(targetHorizontalScrollPosition < this._minHorizontalScrollPosition)
{
targetHorizontalScrollPosition = this._minHorizontalScrollPosition;
}
else if(targetHorizontalScrollPosition > this._maxHorizontalScrollPosition)
{
targetHorizontalScrollPosition = this._maxHorizontalScrollPosition;
}
this.horizontalScrollPosition = targetHorizontalScrollPosition;
}
if(!this._verticalAutoScrollTween)
{
if(this._snapToPages)
{
this._verticalScrollPosition = roundToNearest(this._verticalScrollPosition, this.actualPageHeight);
}
var targetVerticalScrollPosition:Number = this._verticalScrollPosition;
if(targetVerticalScrollPosition < this._minVerticalScrollPosition)
{
targetVerticalScrollPosition = this._minVerticalScrollPosition;
}
else if(targetVerticalScrollPosition > this._maxVerticalScrollPosition)
{
targetVerticalScrollPosition = this._maxVerticalScrollPosition;
}
this.verticalScrollPosition = targetVerticalScrollPosition;
}
}
/**
* @private
*/
protected function refreshScrollSteps():void
{
if(this.explicitHorizontalScrollStep !== this.explicitHorizontalScrollStep) //isNaN
{
if(this._viewPort)
{
this.actualHorizontalScrollStep = this._viewPort.horizontalScrollStep;
}
else
{
this.actualHorizontalScrollStep = 1;
}
}
else
{
this.actualHorizontalScrollStep = this.explicitHorizontalScrollStep;
}
if(this.explicitVerticalScrollStep !== this.explicitVerticalScrollStep) //isNaN
{
if(this._viewPort)
{
this.actualVerticalScrollStep = this._viewPort.verticalScrollStep;
}
else
{
this.actualVerticalScrollStep = 1;
}
}
else
{
this.actualVerticalScrollStep = this.explicitVerticalScrollStep;
}
}
/**
* @private
*/
protected function refreshMinAndMaxScrollPositions():void
{
var visibleViewPortWidth:Number = this.actualWidth - (this._leftViewPortOffset + this._rightViewPortOffset);
var visibleViewPortHeight:Number = this.actualHeight - (this._topViewPortOffset + this._bottomViewPortOffset);
if(this.explicitPageWidth !== this.explicitPageWidth) //isNaN
{
this.actualPageWidth = visibleViewPortWidth;
}
if(this.explicitPageHeight !== this.explicitPageHeight) //isNaN
{
this.actualPageHeight = visibleViewPortHeight;
}
if(this._viewPort)
{
this._minHorizontalScrollPosition = this._viewPort.contentX;
if(this._viewPort.width == Number.POSITIVE_INFINITY)
{
//we don't want to risk the possibility of negative infinity
//being added to positive infinity. the result is NaN.
this._maxHorizontalScrollPosition = Number.POSITIVE_INFINITY;
}
else
{
this._maxHorizontalScrollPosition = this._minHorizontalScrollPosition + this._viewPort.width - visibleViewPortWidth;
}
if(this._maxHorizontalScrollPosition < this._minHorizontalScrollPosition)
{
this._maxHorizontalScrollPosition = this._minHorizontalScrollPosition;
}
this._minVerticalScrollPosition = this._viewPort.contentY;
if(this._viewPort.height == Number.POSITIVE_INFINITY)
{
//we don't want to risk the possibility of negative infinity
//being added to positive infinity. the result is NaN.
this._maxVerticalScrollPosition = Number.POSITIVE_INFINITY;
}
else
{
this._maxVerticalScrollPosition = this._minVerticalScrollPosition + this._viewPort.height - visibleViewPortHeight;
}
if(this._maxVerticalScrollPosition < this._minVerticalScrollPosition)
{
this._maxVerticalScrollPosition = this._minVerticalScrollPosition;
}
if(this._snapScrollPositionsToPixels)
{
this._minHorizontalScrollPosition = Math.round(this._minHorizontalScrollPosition);
this._minVerticalScrollPosition = Math.round(this._minVerticalScrollPosition);
this._maxHorizontalScrollPosition = Math.round(this._maxHorizontalScrollPosition);
this._maxVerticalScrollPosition = Math.round(this._maxVerticalScrollPosition);
}
}
else
{
this._minHorizontalScrollPosition = 0;
this._minVerticalScrollPosition = 0;
this._maxHorizontalScrollPosition = 0;
this._maxVerticalScrollPosition = 0;
}
}
/**
* @private
*/
protected function refreshPageCount():void
{
if(this._snapToPages)
{
var horizontalScrollRange:Number = this._maxHorizontalScrollPosition - this._minHorizontalScrollPosition;
if(horizontalScrollRange == Number.POSITIVE_INFINITY)
{
//trying to put positive infinity into an int results in 0
//so we need a special case to provide a large int value.
if(this._minHorizontalScrollPosition == Number.NEGATIVE_INFINITY)
{
this._minHorizontalPageIndex = int.MIN_VALUE;
}
else
{
this._minHorizontalPageIndex = 0;
}
this._maxHorizontalPageIndex = int.MAX_VALUE;
}
else
{
this._minHorizontalPageIndex = 0;
//floating point errors could result in the max page index
//being 1 larger than it should be.
var roundedDownRange:Number = roundDownToNearest(horizontalScrollRange, this.actualPageWidth);
if((horizontalScrollRange - roundedDownRange) < FUZZY_PAGE_SIZE_PADDING)
{
horizontalScrollRange = roundedDownRange;
}
this._maxHorizontalPageIndex = Math.ceil(horizontalScrollRange / this.actualPageWidth);
}
var verticalScrollRange:Number = this._maxVerticalScrollPosition - this._minVerticalScrollPosition;
if(verticalScrollRange == Number.POSITIVE_INFINITY)
{
//trying to put positive infinity into an int results in 0
//so we need a special case to provide a large int value.
if(this._minVerticalScrollPosition == Number.NEGATIVE_INFINITY)
{
this._minVerticalPageIndex = int.MIN_VALUE;
}
else
{
this._minVerticalPageIndex = 0;
}
this._maxVerticalPageIndex = int.MAX_VALUE;
}
else
{
this._minVerticalPageIndex = 0;
//floating point errors could result in the max page index
//being 1 larger than it should be.
roundedDownRange = roundDownToNearest(verticalScrollRange, this.actualPageHeight);
if((verticalScrollRange - roundedDownRange) < FUZZY_PAGE_SIZE_PADDING)
{
verticalScrollRange = roundedDownRange;
}
this._maxVerticalPageIndex = Math.ceil(verticalScrollRange / this.actualPageHeight);
}
}
else
{
this._maxHorizontalPageIndex = 0;
this._maxHorizontalPageIndex = 0;
this._minVerticalPageIndex = 0;
this._maxVerticalPageIndex = 0;
}
}
/**
* @private
*/
protected function refreshPageIndices():void
{
if(!this._horizontalAutoScrollTween && !this.hasPendingHorizontalPageIndex)
{
if(this._snapToPages)
{
if(this._horizontalScrollPosition == this._maxHorizontalScrollPosition)
{
this._horizontalPageIndex = this._maxHorizontalPageIndex;
}
else if(this._horizontalScrollPosition == this._minHorizontalScrollPosition)
{
this._horizontalPageIndex = this._minHorizontalPageIndex;
}
else if(this._minHorizontalScrollPosition == Number.NEGATIVE_INFINITY && this._horizontalScrollPosition < 0)
{
this._horizontalPageIndex = Math.floor(this._horizontalScrollPosition / this.actualPageWidth);
}
else if(this._maxHorizontalScrollPosition == Number.POSITIVE_INFINITY && this._horizontalScrollPosition >= 0)
{
this._horizontalPageIndex = Math.floor(this._horizontalScrollPosition / this.actualPageWidth);
}
else
{
var adjustedHorizontalScrollPosition:Number = this._horizontalScrollPosition - this._minHorizontalScrollPosition;
var unroundedPageIndex:Number = adjustedHorizontalScrollPosition / this.actualPageWidth;
var nextPageIndex:int = Math.ceil(unroundedPageIndex);
if(unroundedPageIndex !== nextPageIndex &&
MathUtil.isEquivalent(unroundedPageIndex, nextPageIndex, PAGE_INDEX_EPSILON))
{
//we almost always want to round down, but a
//floating point math error may result in the page
//index being 1 too small in rare cases.
this._horizontalPageIndex = nextPageIndex;
}
else
{
this._horizontalPageIndex = Math.floor(unroundedPageIndex);
}
}
}
else
{
this._horizontalPageIndex = this._minHorizontalPageIndex;
}
if(this._horizontalPageIndex < this._minHorizontalPageIndex)
{
this._horizontalPageIndex = this._minHorizontalPageIndex;
}
if(this._horizontalPageIndex > this._maxHorizontalPageIndex)
{
this._horizontalPageIndex = this._maxHorizontalPageIndex;
}
}
if(!this._verticalAutoScrollTween && !this.hasPendingVerticalPageIndex)
{
if(this._snapToPages)
{
if(this._verticalScrollPosition == this._maxVerticalScrollPosition)
{
this._verticalPageIndex = this._maxVerticalPageIndex;
}
else if(this._verticalScrollPosition == this._minVerticalScrollPosition)
{
this._verticalPageIndex = this._minVerticalPageIndex;
}
else if(this._minVerticalScrollPosition == Number.NEGATIVE_INFINITY && this._verticalScrollPosition < 0)
{
this._verticalPageIndex = Math.floor(this._verticalScrollPosition / this.actualPageHeight);
}
else if(this._maxVerticalScrollPosition == Number.POSITIVE_INFINITY && this._verticalScrollPosition >= 0)
{
this._verticalPageIndex = Math.floor(this._verticalScrollPosition / this.actualPageHeight);
}
else
{
var adjustedVerticalScrollPosition:Number = this._verticalScrollPosition - this._minVerticalScrollPosition;
unroundedPageIndex = adjustedVerticalScrollPosition / this.actualPageHeight;
nextPageIndex = Math.ceil(unroundedPageIndex);
if(unroundedPageIndex !== nextPageIndex &&
MathUtil.isEquivalent(unroundedPageIndex, nextPageIndex, PAGE_INDEX_EPSILON))
{
//we almost always want to round down, but a
//floating point math error may result in the page
//index being 1 too small in rare cases.
this._verticalPageIndex = nextPageIndex;
}
else
{
this._verticalPageIndex = Math.floor(unroundedPageIndex);
}
}
}
else
{
this._verticalPageIndex = this._minVerticalScrollPosition;
}
if(this._verticalPageIndex < this._minVerticalScrollPosition)
{
this._verticalPageIndex = this._minVerticalScrollPosition;
}
if(this._verticalPageIndex > this._maxVerticalPageIndex)
{
this._verticalPageIndex = this._maxVerticalPageIndex;
}
}
}
/**
* @private
*/
protected function refreshScrollBarValues():void
{
if(this.horizontalScrollBar)
{
this.horizontalScrollBar.minimum = this._minHorizontalScrollPosition;
this.horizontalScrollBar.maximum = this._maxHorizontalScrollPosition;
this.horizontalScrollBar.value = this._horizontalScrollPosition;
this.horizontalScrollBar.page = (this._maxHorizontalScrollPosition - this._minHorizontalScrollPosition) * this.actualPageWidth / this._viewPort.width;
this.horizontalScrollBar.step = this.actualHorizontalScrollStep;
}
if(this.verticalScrollBar)
{
this.verticalScrollBar.minimum = this._minVerticalScrollPosition;
this.verticalScrollBar.maximum = this._maxVerticalScrollPosition;
this.verticalScrollBar.value = this._verticalScrollPosition;
this.verticalScrollBar.page = (this._maxVerticalScrollPosition - this._minVerticalScrollPosition) * this.actualPageHeight / this._viewPort.height;
this.verticalScrollBar.step = this.actualVerticalScrollStep;
}
}
/**
* @private
*/
protected function showOrHideChildren():void
{
var childCount:int = this.numRawChildrenInternal;
if(this._touchBlocker)
{
//keep scroll bars below the touch blocker, if it exists
childCount--;
}
if(this.verticalScrollBar)
{
this.verticalScrollBar.visible = this._hasVerticalScrollBar;
this.verticalScrollBar.touchable = this._hasVerticalScrollBar && this._interactionMode != ScrollInteractionMode.TOUCH;
this.setRawChildIndexInternal(DisplayObject(this.verticalScrollBar), childCount - 1);
}
if(this.horizontalScrollBar)
{
this.horizontalScrollBar.visible = this._hasHorizontalScrollBar;
this.horizontalScrollBar.touchable = this._hasHorizontalScrollBar && this._interactionMode != ScrollInteractionMode.TOUCH;
if(this.verticalScrollBar)
{
this.setRawChildIndexInternal(DisplayObject(this.horizontalScrollBar), childCount - 2);
}
else
{
this.setRawChildIndexInternal(DisplayObject(this.horizontalScrollBar), childCount - 1);
}
}
if(this.currentBackgroundSkin)
{
if(this._autoHideBackground)
{
this.currentBackgroundSkin.visible = this._viewPort.width < this.actualWidth ||
this._viewPort.height < this.actualHeight ||
this._horizontalScrollPosition < 0 ||
this._horizontalScrollPosition > this._maxHorizontalScrollPosition ||
this._verticalScrollPosition < 0 ||
this._verticalScrollPosition > this._maxVerticalScrollPosition;
}
else
{
this.currentBackgroundSkin.visible = true;
}
}
}
/**
* @private
*/
protected function calculateViewPortOffsetsForFixedHorizontalScrollBar(forceScrollBars:Boolean = false, useActualBounds:Boolean = false):void
{
if(this.horizontalScrollBar && (this._measureViewPort || useActualBounds))
{
var scrollerWidth:Number = useActualBounds ? this.actualWidth : this._explicitWidth;
var totalWidth:Number = this._viewPort.width + this._leftViewPortOffset + this._rightViewPortOffset;
if(forceScrollBars || this._horizontalScrollPolicy == ScrollPolicy.ON ||
((totalWidth > scrollerWidth || totalWidth > this._maxWidth) &&
this._horizontalScrollPolicy != ScrollPolicy.OFF))
{
this._hasHorizontalScrollBar = true;
if(this._scrollBarDisplayMode == ScrollBarDisplayMode.FIXED)
{
this._bottomViewPortOffset += this.horizontalScrollBar.height;
}
}
else
{
this._hasHorizontalScrollBar = false;
}
}
else
{
this._hasHorizontalScrollBar = false;
}
}
/**
* @private
*/
protected function calculateViewPortOffsetsForFixedVerticalScrollBar(forceScrollBars:Boolean = false, useActualBounds:Boolean = false):void
{
if(this.verticalScrollBar && (this._measureViewPort || useActualBounds))
{
var scrollerHeight:Number = useActualBounds ? this.actualHeight : this._explicitHeight;
var totalHeight:Number = this._viewPort.height + this._topViewPortOffset + this._bottomViewPortOffset;
if(forceScrollBars || this._verticalScrollPolicy == ScrollPolicy.ON ||
((totalHeight > scrollerHeight || totalHeight > this._maxHeight) &&
this._verticalScrollPolicy != ScrollPolicy.OFF))
{
this._hasVerticalScrollBar = true;
if(this._scrollBarDisplayMode == ScrollBarDisplayMode.FIXED)
{
if(this._verticalScrollBarPosition == RelativePosition.LEFT)
{
this._leftViewPortOffset += this.verticalScrollBar.width;
}
else
{
this._rightViewPortOffset += this.verticalScrollBar.width;
}
}
}
else
{
this._hasVerticalScrollBar = false;
}
}
else
{
this._hasVerticalScrollBar = false;
}
}
/**
* @private
*/
protected function calculateViewPortOffsets(forceScrollBars:Boolean = false, useActualBounds:Boolean = false):void
{
//in fixed mode, if we determine that scrolling is required, we
//remember the offsets for later. if scrolling is not needed, then
//we will ignore the offsets from here forward
this._topViewPortOffset = this._paddingTop;
this._rightViewPortOffset = this._paddingRight;
this._bottomViewPortOffset = this._paddingBottom;
this._leftViewPortOffset = this._paddingLeft;
this.calculateViewPortOffsetsForFixedHorizontalScrollBar(forceScrollBars, useActualBounds);
this.calculateViewPortOffsetsForFixedVerticalScrollBar(forceScrollBars, useActualBounds);
//we need to double check the horizontal scroll bar if the scroll
//bars are fixed because adding a vertical scroll bar may require a
//horizontal one too.
if(this._scrollBarDisplayMode == ScrollBarDisplayMode.FIXED &&
this._hasVerticalScrollBar && !this._hasHorizontalScrollBar)
{
this.calculateViewPortOffsetsForFixedHorizontalScrollBar(forceScrollBars, useActualBounds);
}
}
/**
* @private
*/
protected function refreshInteractionModeEvents():void
{
if(this._interactionMode == ScrollInteractionMode.TOUCH || this._interactionMode == ScrollInteractionMode.TOUCH_AND_SCROLL_BARS)
{
this.addEventListener(TouchEvent.TOUCH, scroller_touchHandler);
if(!this._touchBlocker)
{
this._touchBlocker = new Quad(100, 100, 0xff00ff);
this._touchBlocker.alpha = 0;
}
}
else
{
this.removeEventListener(TouchEvent.TOUCH, scroller_touchHandler);
if(this._touchBlocker)
{
this.removeRawChildInternal(this._touchBlocker, true);
this._touchBlocker = null;
}
}
if((this._interactionMode == ScrollInteractionMode.MOUSE || this._interactionMode == ScrollInteractionMode.TOUCH_AND_SCROLL_BARS) &&
this._scrollBarDisplayMode == ScrollBarDisplayMode.FLOAT)
{
if(this.horizontalScrollBar)
{
this.horizontalScrollBar.addEventListener(TouchEvent.TOUCH, horizontalScrollBar_touchHandler);
}
if(this.verticalScrollBar)
{
this.verticalScrollBar.addEventListener(TouchEvent.TOUCH, verticalScrollBar_touchHandler);
}
}
else
{
if(this.horizontalScrollBar)
{
this.horizontalScrollBar.removeEventListener(TouchEvent.TOUCH, horizontalScrollBar_touchHandler);
}
if(this.verticalScrollBar)
{
this.verticalScrollBar.removeEventListener(TouchEvent.TOUCH, verticalScrollBar_touchHandler);
}
}
}
/**
* Positions and sizes children based on the actual width and height
* values.
*/
protected function layoutChildren():void
{
if(this.currentBackgroundSkin)
{
this.currentBackgroundSkin.width = this.actualWidth;
this.currentBackgroundSkin.height = this.actualHeight;
}
if(this.horizontalScrollBar)
{
this.horizontalScrollBar.validate();
}
if(this.verticalScrollBar)
{
this.verticalScrollBar.validate();
}
if(this._touchBlocker)
{
this._touchBlocker.x = this._leftViewPortOffset;
this._touchBlocker.y = this._topViewPortOffset;
this._touchBlocker.width = this._viewPort.visibleWidth;
this._touchBlocker.height = this._viewPort.visibleHeight;
}
this._viewPort.x = this._leftViewPortOffset - this._horizontalScrollPosition;
this._viewPort.y = this._topViewPortOffset - this._verticalScrollPosition;
if(this.horizontalScrollBar)
{
this.horizontalScrollBar.x = this._leftViewPortOffset;
this.horizontalScrollBar.y = this._topViewPortOffset + this._viewPort.visibleHeight;
if(this._scrollBarDisplayMode != ScrollBarDisplayMode.FIXED)
{
this.horizontalScrollBar.y -= this.horizontalScrollBar.height;
if((this._hasVerticalScrollBar || this._verticalScrollBarHideTween) && this.verticalScrollBar)
{
this.horizontalScrollBar.width = this._viewPort.visibleWidth - this.verticalScrollBar.width;
}
else
{
this.horizontalScrollBar.width = this._viewPort.visibleWidth;
}
}
else
{
this.horizontalScrollBar.width = this._viewPort.visibleWidth;
}
}
if(this.verticalScrollBar)
{
if(this._verticalScrollBarPosition == RelativePosition.LEFT)
{
this.verticalScrollBar.x = this._paddingLeft;
}
else
{
this.verticalScrollBar.x = this._leftViewPortOffset + this._viewPort.visibleWidth;
}
this.verticalScrollBar.y = this._topViewPortOffset;
if(this._scrollBarDisplayMode != ScrollBarDisplayMode.FIXED)
{
this.verticalScrollBar.x -= this.verticalScrollBar.width;
if((this._hasHorizontalScrollBar || this._horizontalScrollBarHideTween) && this.horizontalScrollBar)
{
this.verticalScrollBar.height = this._viewPort.visibleHeight - this.horizontalScrollBar.height;
}
else
{
this.verticalScrollBar.height = this._viewPort.visibleHeight;
}
}
else
{
this.verticalScrollBar.height = this._viewPort.visibleHeight;
}
}
}
/**
* @private
*/
protected function refreshMask():void
{
if(!this._clipContent)
{
return;
}
var clipWidth:Number = this.actualWidth - this._leftViewPortOffset - this._rightViewPortOffset;
if(clipWidth < 0)
{
clipWidth = 0;
}
var clipHeight:Number = this.actualHeight - this._topViewPortOffset - this._bottomViewPortOffset;
if(clipHeight < 0)
{
clipHeight = 0;
}
var mask:Quad = this._viewPort.mask as Quad;
if(!mask)
{
mask = new Quad(1, 1, 0xff0ff);
this._viewPort.mask = mask;
}
mask.x = this._horizontalScrollPosition;
mask.y = this._verticalScrollPosition;
mask.width = clipWidth;
mask.height = clipHeight;
}
/**
* @private
*/
protected function get numRawChildrenInternal():int
{
if(this is IScrollContainer)
{
return IScrollContainer(this).numRawChildren;
}
return this.numChildren;
}
/**
* @private
*/
protected function addRawChildInternal(child:DisplayObject):DisplayObject
{
if(this is IScrollContainer)
{
return IScrollContainer(this).addRawChild(child);
}
return this.addChild(child);
}
/**
* @private
*/
protected function addRawChildAtInternal(child:DisplayObject, index:int):DisplayObject
{
if(this is IScrollContainer)
{
return IScrollContainer(this).addRawChildAt(child, index);
}
return this.addChildAt(child, index);
}
/**
* @private
*/
protected function removeRawChildInternal(child:DisplayObject, dispose:Boolean = false):DisplayObject
{
if(this is IScrollContainer)
{
return IScrollContainer(this).removeRawChild(child, dispose);
}
return this.removeChild(child, dispose);
}
/**
* @private
*/
protected function removeRawChildAtInternal(index:int, dispose:Boolean = false):DisplayObject
{
if(this is IScrollContainer)
{
return IScrollContainer(this).removeRawChildAt(index, dispose);
}
return this.removeChildAt(index, dispose);
}
/**
* @private
*/
protected function setRawChildIndexInternal(child:DisplayObject, index:int):void
{
if(this is IScrollContainer)
{
IScrollContainer(this).setRawChildIndex(child, index);
return;
}
this.setChildIndex(child, index);
}
/**
* @private
*/
protected function updateHorizontalScrollFromTouchPosition(touchX:Number):void
{
var offset:Number = this._startTouchX - touchX;
var position:Number = this._startHorizontalScrollPosition + offset;
if(position < this._minHorizontalScrollPosition)
{
if(this._hasElasticEdges)
{
position -= (position - this._minHorizontalScrollPosition) * (1 - this._elasticity);
}
else
{
position = this._minHorizontalScrollPosition;
}
}
else if(position > this._maxHorizontalScrollPosition)
{
if(this._hasElasticEdges)
{
position -= (position - this._maxHorizontalScrollPosition) * (1 - this._elasticity);
}
else
{
position = this._maxHorizontalScrollPosition;
}
}
this.horizontalScrollPosition = position;
}
/**
* @private
*/
protected function updateVerticalScrollFromTouchPosition(touchY:Number):void
{
var offset:Number = this._startTouchY - touchY;
var position:Number = this._startVerticalScrollPosition + offset;
if(position < this._minVerticalScrollPosition)
{
if(this._hasElasticEdges)
{
position -= (position - this._minVerticalScrollPosition) * (1 - this._elasticity);
}
else
{
position = this._minVerticalScrollPosition;
}
}
else if(position > this._maxVerticalScrollPosition)
{
if(this._hasElasticEdges)
{
position -= (position - this._maxVerticalScrollPosition) * (1 - this._elasticity);
}
else
{
position = this._maxVerticalScrollPosition;
}
}
this.verticalScrollPosition = position;
}
/**
* Immediately throws the scroller to the specified position, with
* optional animation. If you want to throw in only one direction, pass
* in NaN
for the value that you do not want to change. The
* scroller should be validated before throwing.
*
* @see #scrollToPosition()
*/
protected function throwTo(targetHorizontalScrollPosition:Number = NaN, targetVerticalScrollPosition:Number = NaN, duration:Number = 0.5):void
{
var changedPosition:Boolean = false;
if(targetHorizontalScrollPosition === targetHorizontalScrollPosition) //!isNaN
{
if(this._snapToPages && targetHorizontalScrollPosition > this._minHorizontalScrollPosition &&
targetHorizontalScrollPosition < this._maxHorizontalScrollPosition)
{
targetHorizontalScrollPosition = roundToNearest(targetHorizontalScrollPosition, this.actualPageWidth);
}
if(this._horizontalAutoScrollTween)
{
Starling.juggler.remove(this._horizontalAutoScrollTween);
this._horizontalAutoScrollTween = null;
}
if(this._horizontalScrollPosition != targetHorizontalScrollPosition)
{
changedPosition = true;
this.revealHorizontalScrollBar();
this.startScroll();
if(duration == 0)
{
this.horizontalScrollPosition = targetHorizontalScrollPosition;
}
else
{
this._startHorizontalScrollPosition = this._horizontalScrollPosition;
this._targetHorizontalScrollPosition = targetHorizontalScrollPosition;
this._horizontalAutoScrollTween = new Tween(this, duration, this._throwEase);
this._horizontalAutoScrollTween.animate("horizontalScrollPosition", targetHorizontalScrollPosition);
//warning: if you try to set onUpdate here, it may be
//replaced elsewhere.
this._horizontalAutoScrollTween.onComplete = horizontalAutoScrollTween_onComplete;
Starling.juggler.add(this._horizontalAutoScrollTween);
this.refreshHorizontalAutoScrollTweenEndRatio();
}
}
else
{
this.finishScrollingHorizontally();
}
}
if(targetVerticalScrollPosition === targetVerticalScrollPosition) //!isNaN
{
if(this._snapToPages && targetVerticalScrollPosition > this._minVerticalScrollPosition &&
targetVerticalScrollPosition < this._maxVerticalScrollPosition)
{
targetVerticalScrollPosition = roundToNearest(targetVerticalScrollPosition, this.actualPageHeight);
}
if(this._verticalAutoScrollTween)
{
Starling.juggler.remove(this._verticalAutoScrollTween);
this._verticalAutoScrollTween = null;
}
if(this._verticalScrollPosition != targetVerticalScrollPosition)
{
changedPosition = true;
this.revealVerticalScrollBar();
this.startScroll();
if(duration == 0)
{
this.verticalScrollPosition = targetVerticalScrollPosition;
}
else
{
this._startVerticalScrollPosition = this._verticalScrollPosition;
this._targetVerticalScrollPosition = targetVerticalScrollPosition;
this._verticalAutoScrollTween = new Tween(this, duration, this._throwEase);
this._verticalAutoScrollTween.animate("verticalScrollPosition", targetVerticalScrollPosition);
//warning: if you try to set onUpdate here, it may be
//replaced elsewhere.
this._verticalAutoScrollTween.onComplete = verticalAutoScrollTween_onComplete;
Starling.juggler.add(this._verticalAutoScrollTween);
this.refreshVerticalAutoScrollTweenEndRatio();
}
}
else
{
this.finishScrollingVertically();
}
}
if(changedPosition && duration == 0)
{
this.completeScroll();
}
}
/**
* Immediately throws the scroller to the specified page index, with
* optional animation. If you want to throw in only one direction, pass
* in the value from the horizontalPageIndex
or
* verticalPageIndex
property to the appropriate parameter.
* The scroller must be validated before throwing, to ensure that the
* minimum and maximum scroll positions are accurate.
*
* @see #scrollToPageIndex()
*/
protected function throwToPage(targetHorizontalPageIndex:int, targetVerticalPageIndex:int, duration:Number = 0.5):void
{
var targetHorizontalScrollPosition:Number = this._horizontalScrollPosition;
if(targetHorizontalPageIndex >= this._minHorizontalPageIndex)
{
targetHorizontalScrollPosition = this.actualPageWidth * targetHorizontalPageIndex;
}
if(targetHorizontalScrollPosition < this._minHorizontalScrollPosition)
{
targetHorizontalScrollPosition = this._minHorizontalScrollPosition;
}
if(targetHorizontalScrollPosition > this._maxHorizontalScrollPosition)
{
targetHorizontalScrollPosition = this._maxHorizontalScrollPosition;
}
var targetVerticalScrollPosition:Number = this._verticalScrollPosition;
if(targetVerticalPageIndex >= this._minVerticalPageIndex)
{
targetVerticalScrollPosition = this.actualPageHeight * targetVerticalPageIndex;
}
if(targetVerticalScrollPosition < this._minVerticalScrollPosition)
{
targetVerticalScrollPosition = this._minVerticalScrollPosition;
}
if(targetVerticalScrollPosition > this._maxVerticalScrollPosition)
{
targetVerticalScrollPosition = this._maxVerticalScrollPosition;
}
if(duration > 0)
{
this.throwTo(targetHorizontalScrollPosition, targetVerticalScrollPosition, duration);
}
else
{
this.horizontalScrollPosition = targetHorizontalScrollPosition;
this.verticalScrollPosition = targetVerticalScrollPosition;
}
if(targetHorizontalPageIndex >= this._minHorizontalPageIndex)
{
this._horizontalPageIndex = targetHorizontalPageIndex;
}
if(targetVerticalPageIndex >= this._minVerticalPageIndex)
{
this._verticalPageIndex = targetVerticalPageIndex;
}
}
/**
* @private
*/
protected function calculateDynamicThrowDuration(pixelsPerMS:Number):Number
{
return (Math.log(MINIMUM_VELOCITY / Math.abs(pixelsPerMS)) / this._logDecelerationRate) / 1000;
}
/**
* @private
*/
protected function calculateThrowDistance(pixelsPerMS:Number):Number
{
return (pixelsPerMS - MINIMUM_VELOCITY) / this._logDecelerationRate;
}
/**
* @private
*/
protected function finishScrollingHorizontally():void
{
var targetHorizontalScrollPosition:Number = NaN;
if(this._horizontalScrollPosition < this._minHorizontalScrollPosition)
{
targetHorizontalScrollPosition = this._minHorizontalScrollPosition;
}
else if(this._horizontalScrollPosition > this._maxHorizontalScrollPosition)
{
targetHorizontalScrollPosition = this._maxHorizontalScrollPosition;
}
this._isDraggingHorizontally = false;
if(targetHorizontalScrollPosition !== targetHorizontalScrollPosition) //isNaN
{
this.completeScroll();
}
else if(Math.abs(targetHorizontalScrollPosition - this._horizontalScrollPosition) < 1)
{
//this distance is too small to animate. just finish now.
this.horizontalScrollPosition = targetHorizontalScrollPosition;
this.completeScroll();
}
else
{
this.throwTo(targetHorizontalScrollPosition, NaN, this._elasticSnapDuration);
}
}
/**
* @private
*/
protected function finishScrollingVertically():void
{
var targetVerticalScrollPosition:Number = NaN;
if(this._verticalScrollPosition < this._minVerticalScrollPosition)
{
targetVerticalScrollPosition = this._minVerticalScrollPosition;
}
else if(this._verticalScrollPosition > this._maxVerticalScrollPosition)
{
targetVerticalScrollPosition = this._maxVerticalScrollPosition;
}
this._isDraggingVertically = false;
if(targetVerticalScrollPosition !== targetVerticalScrollPosition) //isNaN
{
this.completeScroll();
}
else if(Math.abs(targetVerticalScrollPosition - this._verticalScrollPosition) < 1)
{
//this distance is too small to animate. just finish now.
this.verticalScrollPosition = targetVerticalScrollPosition;
this.completeScroll();
}
else
{
this.throwTo(NaN, targetVerticalScrollPosition, this._elasticSnapDuration);
}
}
/**
* @private
*/
protected function throwHorizontally(pixelsPerMS:Number):void
{
if(this._snapToPages && !this._snapOnComplete)
{
var inchesPerSecond:Number = 1000 * pixelsPerMS / (DeviceCapabilities.dpi / Starling.contentScaleFactor);
if(inchesPerSecond > this._minimumPageThrowVelocity)
{
var snappedPageHorizontalScrollPosition:Number = roundDownToNearest(this._horizontalScrollPosition, this.actualPageWidth);
}
else if(inchesPerSecond < -this._minimumPageThrowVelocity)
{
snappedPageHorizontalScrollPosition = roundUpToNearest(this._horizontalScrollPosition, this.actualPageWidth);
}
else
{
var lastPageWidth:Number = this._maxHorizontalScrollPosition % this.actualPageWidth;
var startOfLastPage:Number = this._maxHorizontalScrollPosition - lastPageWidth;
if(lastPageWidth < this.actualPageWidth && this._horizontalScrollPosition >= startOfLastPage)
{
var lastPagePosition:Number = this._horizontalScrollPosition - startOfLastPage;
if(inchesPerSecond > this._minimumPageThrowVelocity)
{
snappedPageHorizontalScrollPosition = startOfLastPage + roundDownToNearest(lastPagePosition, lastPageWidth);
}
else if(inchesPerSecond < -this._minimumPageThrowVelocity)
{
snappedPageHorizontalScrollPosition = startOfLastPage + roundUpToNearest(lastPagePosition, lastPageWidth);
}
else
{
snappedPageHorizontalScrollPosition = startOfLastPage + roundToNearest(lastPagePosition, lastPageWidth);
}
}
else
{
snappedPageHorizontalScrollPosition = roundToNearest(this._horizontalScrollPosition, this.actualPageWidth);
}
}
if(snappedPageHorizontalScrollPosition < this._minHorizontalScrollPosition)
{
snappedPageHorizontalScrollPosition = this._minHorizontalScrollPosition;
}
else if(snappedPageHorizontalScrollPosition > this._maxHorizontalScrollPosition)
{
snappedPageHorizontalScrollPosition = this._maxHorizontalScrollPosition;
}
if(snappedPageHorizontalScrollPosition == this._maxHorizontalScrollPosition)
{
var targetHorizontalPageIndex:int = this._maxHorizontalPageIndex;
}
else
{
//we need to use Math.round() on these values to avoid
//floating-point errors that could result in the values
//being rounded down too far.
if(this._minHorizontalScrollPosition == Number.NEGATIVE_INFINITY)
{
targetHorizontalPageIndex = Math.round(snappedPageHorizontalScrollPosition / this.actualPageWidth);
}
else
{
targetHorizontalPageIndex = Math.round((snappedPageHorizontalScrollPosition - this._minHorizontalScrollPosition) / this.actualPageWidth);
}
}
this.throwToPage(targetHorizontalPageIndex, -1, this._pageThrowDuration);
return;
}
var absPixelsPerMS:Number = Math.abs(pixelsPerMS);
if(!this._snapToPages && absPixelsPerMS <= MINIMUM_VELOCITY)
{
this.finishScrollingHorizontally();
return;
}
var duration:Number = this._fixedThrowDuration;
if(!this._useFixedThrowDuration)
{
duration = this.calculateDynamicThrowDuration(pixelsPerMS);
}
this.throwTo(this._horizontalScrollPosition + this.calculateThrowDistance(pixelsPerMS), NaN, duration);
}
/**
* @private
*/
protected function throwVertically(pixelsPerMS:Number):void
{
if(this._snapToPages && !this._snapOnComplete)
{
var inchesPerSecond:Number = 1000 * pixelsPerMS / (DeviceCapabilities.dpi / Starling.contentScaleFactor);
if(inchesPerSecond > this._minimumPageThrowVelocity)
{
var snappedPageVerticalScrollPosition:Number = roundDownToNearest(this._verticalScrollPosition, this.actualPageHeight);
}
else if(inchesPerSecond < -this._minimumPageThrowVelocity)
{
snappedPageVerticalScrollPosition = roundUpToNearest(this._verticalScrollPosition, this.actualPageHeight);
}
else
{
var lastPageHeight:Number = this._maxVerticalScrollPosition % this.actualPageHeight;
var startOfLastPage:Number = this._maxVerticalScrollPosition - lastPageHeight;
if(lastPageHeight < this.actualPageHeight && this._verticalScrollPosition >= startOfLastPage)
{
var lastPagePosition:Number = this._verticalScrollPosition - startOfLastPage;
if(inchesPerSecond > this._minimumPageThrowVelocity)
{
snappedPageVerticalScrollPosition = startOfLastPage + roundDownToNearest(lastPagePosition, lastPageHeight);
}
else if(inchesPerSecond < -this._minimumPageThrowVelocity)
{
snappedPageVerticalScrollPosition = startOfLastPage + roundUpToNearest(lastPagePosition, lastPageHeight);
}
else
{
snappedPageVerticalScrollPosition = startOfLastPage + roundToNearest(lastPagePosition, lastPageHeight);
}
}
else
{
snappedPageVerticalScrollPosition = roundToNearest(this._verticalScrollPosition, this.actualPageHeight);
}
}
if(snappedPageVerticalScrollPosition < this._minVerticalScrollPosition)
{
snappedPageVerticalScrollPosition = this._minVerticalScrollPosition;
}
else if(snappedPageVerticalScrollPosition > this._maxVerticalScrollPosition)
{
snappedPageVerticalScrollPosition = this._maxVerticalScrollPosition;
}
if(snappedPageVerticalScrollPosition == this._maxVerticalScrollPosition)
{
var targetVerticalPageIndex:int = this._maxVerticalPageIndex;
}
else
{
//we need to use Math.round() on these values to avoid
//floating-point errors that could result in the values
//being rounded down too far.
if(this._minVerticalScrollPosition == Number.NEGATIVE_INFINITY)
{
targetVerticalPageIndex = Math.round(snappedPageVerticalScrollPosition / this.actualPageHeight);
}
else
{
targetVerticalPageIndex = Math.round((snappedPageVerticalScrollPosition - this._minVerticalScrollPosition) / this.actualPageHeight);
}
}
this.throwToPage(-1, targetVerticalPageIndex, this._pageThrowDuration);
return;
}
var absPixelsPerMS:Number = Math.abs(pixelsPerMS);
if(!this._snapToPages && absPixelsPerMS <= MINIMUM_VELOCITY)
{
this.finishScrollingVertically();
return;
}
var duration:Number = this._fixedThrowDuration;
if(!this._useFixedThrowDuration)
{
duration = this.calculateDynamicThrowDuration(pixelsPerMS);
}
this.throwTo(NaN, this._verticalScrollPosition + this.calculateThrowDistance(pixelsPerMS), duration);
}
/**
* @private
*/
protected function onHorizontalAutoScrollTweenUpdate():void
{
var ratio:Number = this._horizontalAutoScrollTween.transitionFunc(this._horizontalAutoScrollTween.currentTime / this._horizontalAutoScrollTween.totalTime);
if(ratio >= this._horizontalAutoScrollTweenEndRatio)
{
if(!this._hasElasticEdges)
{
if(this._horizontalScrollPosition < this._minHorizontalScrollPosition)
{
this._horizontalScrollPosition = this._minHorizontalScrollPosition;
}
else if(this._horizontalScrollPosition > this._maxHorizontalScrollPosition)
{
this._horizontalScrollPosition = this._maxHorizontalScrollPosition;
}
}
Starling.juggler.remove(this._horizontalAutoScrollTween);
this._horizontalAutoScrollTween = null;
this.finishScrollingHorizontally();
}
}
/**
* @private
*/
protected function onVerticalAutoScrollTweenUpdate():void
{
var ratio:Number = this._verticalAutoScrollTween.transitionFunc(this._verticalAutoScrollTween.currentTime / this._verticalAutoScrollTween.totalTime);
if(ratio >= this._verticalAutoScrollTweenEndRatio)
{
if(!this._hasElasticEdges)
{
if(this._verticalScrollPosition < this._minVerticalScrollPosition)
{
this._verticalScrollPosition = this._minVerticalScrollPosition;
}
else if(this._verticalScrollPosition > this._maxVerticalScrollPosition)
{
this._verticalScrollPosition = this._maxVerticalScrollPosition;
}
}
Starling.juggler.remove(this._verticalAutoScrollTween);
this._verticalAutoScrollTween = null;
this.finishScrollingVertically();
}
}
/**
* @private
*/
protected function refreshHorizontalAutoScrollTweenEndRatio():void
{
var distance:Number = Math.abs(this._targetHorizontalScrollPosition - this._startHorizontalScrollPosition);
var ratioOutOfBounds:Number = 0;
if(this._targetHorizontalScrollPosition > this._maxHorizontalScrollPosition)
{
ratioOutOfBounds = (this._targetHorizontalScrollPosition - this._maxHorizontalScrollPosition) / distance;
}
else if(this._targetHorizontalScrollPosition < this._minHorizontalScrollPosition)
{
ratioOutOfBounds = (this._minHorizontalScrollPosition - this._targetHorizontalScrollPosition) / distance;
}
if(ratioOutOfBounds > 0)
{
if(this._hasElasticEdges)
{
this._horizontalAutoScrollTweenEndRatio = (1 - ratioOutOfBounds) + (ratioOutOfBounds * this._throwElasticity);
}
else
{
this._horizontalAutoScrollTweenEndRatio = 1 - ratioOutOfBounds;
}
}
else
{
this._horizontalAutoScrollTweenEndRatio = 1;
}
if(this._horizontalAutoScrollTween)
{
if(this._horizontalAutoScrollTweenEndRatio < 1)
{
this._horizontalAutoScrollTween.onUpdate = onHorizontalAutoScrollTweenUpdate;
}
else
{
this._horizontalAutoScrollTween.onUpdate = null;
}
}
}
/**
* @private
*/
protected function refreshVerticalAutoScrollTweenEndRatio():void
{
var distance:Number = Math.abs(this._targetVerticalScrollPosition - this._startVerticalScrollPosition);
var ratioOutOfBounds:Number = 0;
if(this._targetVerticalScrollPosition > this._maxVerticalScrollPosition)
{
ratioOutOfBounds = (this._targetVerticalScrollPosition - this._maxVerticalScrollPosition) / distance;
}
else if(this._targetVerticalScrollPosition < this._minVerticalScrollPosition)
{
ratioOutOfBounds = (this._minVerticalScrollPosition - this._targetVerticalScrollPosition) / distance;
}
if(ratioOutOfBounds > 0)
{
if(this._hasElasticEdges)
{
this._verticalAutoScrollTweenEndRatio = (1 - ratioOutOfBounds) + (ratioOutOfBounds * this._throwElasticity);
}
else
{
this._verticalAutoScrollTweenEndRatio = 1 - ratioOutOfBounds;
}
}
else
{
this._verticalAutoScrollTweenEndRatio = 1;
}
if(this._verticalAutoScrollTween)
{
if(this._verticalAutoScrollTweenEndRatio < 1)
{
this._verticalAutoScrollTween.onUpdate = onVerticalAutoScrollTweenUpdate;
}
else
{
this._verticalAutoScrollTween.onUpdate = null;
}
}
}
/**
* @private
*/
protected function hideHorizontalScrollBar(delay:Number = 0):void
{
if(!this.horizontalScrollBar || this._scrollBarDisplayMode != ScrollBarDisplayMode.FLOAT || this._horizontalScrollBarHideTween)
{
return;
}
if(this.horizontalScrollBar.alpha == 0)
{
return;
}
if(this._hideScrollBarAnimationDuration == 0 && delay == 0)
{
this.horizontalScrollBar.alpha = 0;
}
else
{
this._horizontalScrollBarHideTween = new Tween(this.horizontalScrollBar, this._hideScrollBarAnimationDuration, this._hideScrollBarAnimationEase);
this._horizontalScrollBarHideTween.fadeTo(0);
this._horizontalScrollBarHideTween.delay = delay;
this._horizontalScrollBarHideTween.onComplete = horizontalScrollBarHideTween_onComplete;
Starling.juggler.add(this._horizontalScrollBarHideTween);
}
}
/**
* @private
*/
protected function hideVerticalScrollBar(delay:Number = 0):void
{
if(!this.verticalScrollBar || this._scrollBarDisplayMode != ScrollBarDisplayMode.FLOAT || this._verticalScrollBarHideTween)
{
return;
}
if(this.verticalScrollBar.alpha == 0)
{
return;
}
if(this._hideScrollBarAnimationDuration == 0 && delay == 0)
{
this.verticalScrollBar.alpha = 0;
}
else
{
this._verticalScrollBarHideTween = new Tween(this.verticalScrollBar, this._hideScrollBarAnimationDuration, this._hideScrollBarAnimationEase);
this._verticalScrollBarHideTween.fadeTo(0);
this._verticalScrollBarHideTween.delay = delay;
this._verticalScrollBarHideTween.onComplete = verticalScrollBarHideTween_onComplete;
Starling.juggler.add(this._verticalScrollBarHideTween);
}
}
/**
* @private
*/
protected function revealHorizontalScrollBar():void
{
if(!this.horizontalScrollBar || this._scrollBarDisplayMode != ScrollBarDisplayMode.FLOAT)
{
return;
}
if(this._horizontalScrollBarHideTween)
{
Starling.juggler.remove(this._horizontalScrollBarHideTween);
this._horizontalScrollBarHideTween = null;
}
this.horizontalScrollBar.alpha = 1;
}
/**
* @private
*/
protected function revealVerticalScrollBar():void
{
if(!this.verticalScrollBar || this._scrollBarDisplayMode != ScrollBarDisplayMode.FLOAT)
{
return;
}
if(this._verticalScrollBarHideTween)
{
Starling.juggler.remove(this._verticalScrollBarHideTween);
this._verticalScrollBarHideTween = null;
}
this.verticalScrollBar.alpha = 1;
}
/**
* If scrolling hasn't already started, prepares the scroller to scroll
* and dispatches FeathersEventType.SCROLL_START
.
*/
protected function startScroll():void
{
if(this._isScrolling)
{
return;
}
this._isScrolling = true;
if(this._touchBlocker)
{
this.addRawChildInternal(this._touchBlocker);
}
this.dispatchEventWith(FeathersEventType.SCROLL_START);
}
/**
* Prepares the scroller for normal interaction and dispatches
* FeathersEventType.SCROLL_COMPLETE
.
*/
protected function completeScroll():void
{
if(!this._isScrolling || this._verticalAutoScrollTween || this._horizontalAutoScrollTween ||
this._isDraggingHorizontally || this._isDraggingVertically ||
this._horizontalScrollBarIsScrolling || this._verticalScrollBarIsScrolling)
{
return;
}
this._isScrolling = false;
if(this._touchBlocker)
{
this.removeRawChildInternal(this._touchBlocker, false);
}
this.hideHorizontalScrollBar();
this.hideVerticalScrollBar();
//we validate to ensure that the final Event.SCROLL
//dispatched before FeathersEventType.SCROLL_COMPLETE
this.validate();
this.dispatchEventWith(FeathersEventType.SCROLL_COMPLETE);
}
/**
* Scrolls to a pending scroll position, if required.
*/
protected function handlePendingScroll():void
{
if(this.pendingHorizontalScrollPosition === this.pendingHorizontalScrollPosition ||
this.pendingVerticalScrollPosition === this.pendingVerticalScrollPosition) //!isNaN
{
this.throwTo(this.pendingHorizontalScrollPosition, this.pendingVerticalScrollPosition, this.pendingScrollDuration);
this.pendingHorizontalScrollPosition = NaN;
this.pendingVerticalScrollPosition = NaN;
}
if(this.hasPendingHorizontalPageIndex && this.hasPendingVerticalPageIndex)
{
//both
this.throwToPage(this.pendingHorizontalPageIndex, this.pendingVerticalPageIndex, this.pendingScrollDuration);
}
else if(this.hasPendingHorizontalPageIndex)
{
//horizontal only
this.throwToPage(this.pendingHorizontalPageIndex, this._verticalPageIndex, this.pendingScrollDuration);
}
else if(this.hasPendingVerticalPageIndex)
{
//vertical only
this.throwToPage(this._horizontalPageIndex, this.pendingVerticalPageIndex, this.pendingScrollDuration);
}
this.hasPendingHorizontalPageIndex = false;
this.hasPendingVerticalPageIndex = false;
}
/**
* @private
*/
protected function handlePendingRevealScrollBars():void
{
if(!this.isScrollBarRevealPending)
{
return;
}
this.isScrollBarRevealPending = false;
if(this._scrollBarDisplayMode != ScrollBarDisplayMode.FLOAT)
{
return;
}
this.revealHorizontalScrollBar();
this.revealVerticalScrollBar();
this.hideHorizontalScrollBar(this._revealScrollBarsDuration);
this.hideVerticalScrollBar(this._revealScrollBarsDuration);
}
/**
* @private
*/
protected function viewPort_resizeHandler(event:Event):void
{
if(this.ignoreViewPortResizing ||
(this._viewPort.width == this._lastViewPortWidth && this._viewPort.height == this._lastViewPortHeight))
{
return;
}
this._lastViewPortWidth = this._viewPort.width;
this._lastViewPortHeight = this._viewPort.height;
if(this._isValidating)
{
this._hasViewPortBoundsChanged = true;
}
else
{
this.invalidate(INVALIDATION_FLAG_SIZE);
}
}
/**
* @private
*/
protected function childProperties_onChange(proxy:PropertyProxy, name:String):void
{
this.invalidate(INVALIDATION_FLAG_STYLES);
}
/**
* @private
*/
protected function verticalScrollBar_changeHandler(event:Event):void
{
this.verticalScrollPosition = this.verticalScrollBar.value;
}
/**
* @private
*/
protected function horizontalScrollBar_changeHandler(event:Event):void
{
this.horizontalScrollPosition = this.horizontalScrollBar.value;
}
/**
* @private
*/
protected function horizontalScrollBar_beginInteractionHandler(event:Event):void
{
if(this._horizontalAutoScrollTween)
{
Starling.juggler.remove(this._horizontalAutoScrollTween);
this._horizontalAutoScrollTween = null;
}
this._isDraggingHorizontally = false;
this._horizontalScrollBarIsScrolling = true;
this.dispatchEventWith(FeathersEventType.BEGIN_INTERACTION);
if(!this._isScrolling)
{
this.startScroll();
}
}
/**
* @private
*/
protected function horizontalScrollBar_endInteractionHandler(event:Event):void
{
this._horizontalScrollBarIsScrolling = false;
this.dispatchEventWith(FeathersEventType.END_INTERACTION);
this.completeScroll();
}
/**
* @private
*/
protected function verticalScrollBar_beginInteractionHandler(event:Event):void
{
if(this._verticalAutoScrollTween)
{
Starling.juggler.remove(this._verticalAutoScrollTween);
this._verticalAutoScrollTween = null;
}
this._isDraggingVertically = false;
this._verticalScrollBarIsScrolling = true;
this.dispatchEventWith(FeathersEventType.BEGIN_INTERACTION);
if(!this._isScrolling)
{
this.startScroll();
}
}
/**
* @private
*/
protected function verticalScrollBar_endInteractionHandler(event:Event):void
{
this._verticalScrollBarIsScrolling = false;
this.dispatchEventWith(FeathersEventType.END_INTERACTION);
this.completeScroll();
}
/**
* @private
*/
protected function horizontalAutoScrollTween_onComplete():void
{
this._horizontalAutoScrollTween = null;
//the page index will not have updated during the animation, so we
//need to ensure that it is updated now.
this.invalidate(INVALIDATION_FLAG_SCROLL);
this.finishScrollingHorizontally();
}
/**
* @private
*/
protected function verticalAutoScrollTween_onComplete():void
{
this._verticalAutoScrollTween = null;
//the page index will not have updated during the animation, so we
//need to ensure that it is updated now.
this.invalidate(INVALIDATION_FLAG_SCROLL);
this.finishScrollingVertically();
}
/**
* @private
*/
protected function horizontalScrollBarHideTween_onComplete():void
{
this._horizontalScrollBarHideTween = null;
}
/**
* @private
*/
protected function verticalScrollBarHideTween_onComplete():void
{
this._verticalScrollBarHideTween = null;
}
/**
* @private
*/
protected function scroller_touchHandler(event:TouchEvent):void
{
if(!this._isEnabled)
{
this._touchPointID = -1;
return;
}
if(this._touchPointID >= 0)
{
return;
}
//any began touch is okay here. we don't need to check all touches.
var touch:Touch = event.getTouch(this, TouchPhase.BEGAN);
if(!touch)
{
return;
}
if(this._interactionMode == ScrollInteractionMode.TOUCH_AND_SCROLL_BARS &&
(event.interactsWith(DisplayObject(this.horizontalScrollBar)) || event.interactsWith(DisplayObject(this.verticalScrollBar))))
{
return;
}
touch.getLocation(this, HELPER_POINT);
var touchX:Number = HELPER_POINT.x;
var touchY:Number = HELPER_POINT.y;
if(touchX < this._leftViewPortOffset || touchY < this._topViewPortOffset ||
touchX >= this.actualWidth - this._rightViewPortOffset ||
touchY >= this.actualHeight - this._bottomViewPortOffset)
{
return;
}
var exclusiveTouch:ExclusiveTouch = ExclusiveTouch.forStage(this.stage);
if(exclusiveTouch.getClaim(touch.id))
{
//already claimed
return;
}
//if the scroll policy is off, we shouldn't stop this animation
if(this._horizontalAutoScrollTween && this._horizontalScrollPolicy != ScrollPolicy.OFF)
{
Starling.juggler.remove(this._horizontalAutoScrollTween);
this._horizontalAutoScrollTween = null;
if(this._isScrolling)
{
//immediately start dragging, since it was scrolling already
this._isDraggingHorizontally = true;
}
}
if(this._verticalAutoScrollTween && this._verticalScrollPolicy != ScrollPolicy.OFF)
{
Starling.juggler.remove(this._verticalAutoScrollTween);
this._verticalAutoScrollTween = null;
if(this._isScrolling)
{
//immediately start dragging, since it was scrolling already
this._isDraggingVertically = true;
}
}
this._touchPointID = touch.id;
this._velocityX = 0;
this._velocityY = 0;
this._previousVelocityX.length = 0;
this._previousVelocityY.length = 0;
this._previousTouchTime = getTimer();
this._previousTouchX = this._startTouchX = this._currentTouchX = touchX;
this._previousTouchY = this._startTouchY = this._currentTouchY = touchY;
this._startHorizontalScrollPosition = this._horizontalScrollPosition;
this._startVerticalScrollPosition = this._verticalScrollPosition;
this._isScrollingStopped = false;
this.addEventListener(Event.ENTER_FRAME, scroller_enterFrameHandler);
//we need to listen on the stage because if we scroll the bottom or
//right edge past the top of the scroller, it gets stuck and we stop
//receiving touch events for "this".
this.stage.addEventListener(TouchEvent.TOUCH, stage_touchHandler);
if(this._isScrolling && (this._isDraggingHorizontally || this._isDraggingVertically))
{
exclusiveTouch.claimTouch(this._touchPointID, this);
}
else
{
exclusiveTouch.addEventListener(Event.CHANGE, exclusiveTouch_changeHandler);
}
}
/**
* @private
*/
protected function scroller_enterFrameHandler(event:Event):void
{
if(this._isScrollingStopped)
{
return;
}
var now:int = getTimer();
var timeOffset:int = now - this._previousTouchTime;
if(timeOffset > 0)
{
//we're keeping previous velocity updates to improve accuracy
this._previousVelocityX[this._previousVelocityX.length] = this._velocityX;
if(this._previousVelocityX.length > MAXIMUM_SAVED_VELOCITY_COUNT)
{
this._previousVelocityX.shift();
}
this._previousVelocityY[this._previousVelocityY.length] = this._velocityY;
if(this._previousVelocityY.length > MAXIMUM_SAVED_VELOCITY_COUNT)
{
this._previousVelocityY.shift();
}
this._velocityX = (this._currentTouchX - this._previousTouchX) / timeOffset;
this._velocityY = (this._currentTouchY - this._previousTouchY) / timeOffset;
this._previousTouchTime = now;
this._previousTouchX = this._currentTouchX;
this._previousTouchY = this._currentTouchY;
}
var horizontalInchesMoved:Number = Math.abs(this._currentTouchX - this._startTouchX) / (DeviceCapabilities.dpi / Starling.contentScaleFactor);
var verticalInchesMoved:Number = Math.abs(this._currentTouchY - this._startTouchY) / (DeviceCapabilities.dpi / Starling.contentScaleFactor);
if((this._horizontalScrollPolicy == ScrollPolicy.ON ||
(this._horizontalScrollPolicy == ScrollPolicy.AUTO && this._minHorizontalScrollPosition != this._maxHorizontalScrollPosition)) &&
!this._isDraggingHorizontally && horizontalInchesMoved >= this._minimumDragDistance)
{
if(this.horizontalScrollBar)
{
this.revealHorizontalScrollBar();
}
this._startTouchX = this._currentTouchX;
this._startHorizontalScrollPosition = this._horizontalScrollPosition;
this._isDraggingHorizontally = true;
//if we haven't already started dragging in the other direction,
//we need to dispatch the event that says we're starting.
if(!this._isDraggingVertically)
{
this.dispatchEventWith(FeathersEventType.BEGIN_INTERACTION);
var exclusiveTouch:ExclusiveTouch = ExclusiveTouch.forStage(this.stage);
exclusiveTouch.removeEventListener(Event.CHANGE, exclusiveTouch_changeHandler);
exclusiveTouch.claimTouch(this._touchPointID, this);
this.startScroll();
}
}
if((this._verticalScrollPolicy == ScrollPolicy.ON ||
(this._verticalScrollPolicy == ScrollPolicy.AUTO && this._minVerticalScrollPosition != this._maxVerticalScrollPosition)) &&
!this._isDraggingVertically && verticalInchesMoved >= this._minimumDragDistance)
{
if(this.verticalScrollBar)
{
this.revealVerticalScrollBar();
}
this._startTouchY = this._currentTouchY;
this._startVerticalScrollPosition = this._verticalScrollPosition;
this._isDraggingVertically = true;
if(!this._isDraggingHorizontally)
{
exclusiveTouch = ExclusiveTouch.forStage(this.stage);
exclusiveTouch.removeEventListener(Event.CHANGE, exclusiveTouch_changeHandler);
exclusiveTouch.claimTouch(this._touchPointID, this);
this.dispatchEventWith(FeathersEventType.BEGIN_INTERACTION);
this.startScroll();
}
}
if(this._isDraggingHorizontally && !this._horizontalAutoScrollTween)
{
this.updateHorizontalScrollFromTouchPosition(this._currentTouchX);
}
if(this._isDraggingVertically && !this._verticalAutoScrollTween)
{
this.updateVerticalScrollFromTouchPosition(this._currentTouchY);
}
}
/**
* @private
*/
protected function stage_touchHandler(event:TouchEvent):void
{
var touch:Touch = event.getTouch(this.stage, null, this._touchPointID);
if(!touch)
{
return;
}
if(touch.phase == TouchPhase.MOVED)
{
//we're saving these to use in the enter frame handler because
//that provides a longer time offset
touch.getLocation(this, HELPER_POINT);
this._currentTouchX = HELPER_POINT.x;
this._currentTouchY = HELPER_POINT.y;
}
else if(touch.phase == TouchPhase.ENDED)
{
if(!this._isDraggingHorizontally && !this._isDraggingVertically)
{
ExclusiveTouch.forStage(this.stage).removeEventListener(Event.CHANGE, exclusiveTouch_changeHandler);
}
this.removeEventListener(Event.ENTER_FRAME, scroller_enterFrameHandler);
this.stage.removeEventListener(TouchEvent.TOUCH, stage_touchHandler);
this._touchPointID = -1;
this.dispatchEventWith(FeathersEventType.END_INTERACTION);
var isFinishingHorizontally:Boolean = false;
var isFinishingVertically:Boolean = false;
if(this._horizontalScrollPosition < this._minHorizontalScrollPosition ||
this._horizontalScrollPosition > this._maxHorizontalScrollPosition)
{
isFinishingHorizontally = true;
this.finishScrollingHorizontally();
}
if(this._verticalScrollPosition < this._minVerticalScrollPosition ||
this._verticalScrollPosition > this._maxVerticalScrollPosition)
{
isFinishingVertically = true;
this.finishScrollingVertically();
}
if(isFinishingHorizontally && isFinishingVertically)
{
return;
}
if(!isFinishingHorizontally && this._isDraggingHorizontally)
{
//take the average for more accuracy
var sum:Number = this._velocityX * CURRENT_VELOCITY_WEIGHT;
var velocityCount:int = this._previousVelocityX.length;
var totalWeight:Number = CURRENT_VELOCITY_WEIGHT;
for(var i:int = 0; i < velocityCount; i++)
{
var weight:Number = VELOCITY_WEIGHTS[i];
sum += this._previousVelocityX.shift() * weight;
totalWeight += weight;
}
this.throwHorizontally(sum / totalWeight);
}
else
{
this.hideHorizontalScrollBar();
}
if(!isFinishingVertically && this._isDraggingVertically)
{
sum = this._velocityY * CURRENT_VELOCITY_WEIGHT;
velocityCount = this._previousVelocityY.length;
totalWeight = CURRENT_VELOCITY_WEIGHT;
for(i = 0; i < velocityCount; i++)
{
weight = VELOCITY_WEIGHTS[i];
sum += this._previousVelocityY.shift() * weight;
totalWeight += weight;
}
this.throwVertically(sum / totalWeight);
}
else
{
this.hideVerticalScrollBar();
}
}
}
/**
* @private
*/
protected function exclusiveTouch_changeHandler(event:Event, touchID:int):void
{
if(this._touchPointID < 0 || this._touchPointID != touchID || this._isDraggingHorizontally || this._isDraggingVertically)
{
return;
}
var exclusiveTouch:ExclusiveTouch = ExclusiveTouch.forStage(this.stage);
if(exclusiveTouch.getClaim(touchID) == this)
{
return;
}
this._touchPointID = -1;
this.removeEventListener(Event.ENTER_FRAME, scroller_enterFrameHandler);
this.stage.removeEventListener(TouchEvent.TOUCH, stage_touchHandler);
exclusiveTouch.removeEventListener(Event.CHANGE, exclusiveTouch_changeHandler);
this.dispatchEventWith(FeathersEventType.END_INTERACTION);
}
/**
* @private
*/
protected function nativeStage_mouseWheelHandler(event:MouseEvent):void
{
if(!this._isEnabled)
{
this._touchPointID = -1;
return;
}
if((this._verticalMouseWheelScrollDirection == Direction.VERTICAL && (this._maxVerticalScrollPosition == this._minVerticalScrollPosition || this._verticalScrollPolicy == ScrollPolicy.OFF)) ||
(this._verticalMouseWheelScrollDirection == Direction.HORIZONTAL && (this._maxHorizontalScrollPosition == this._minHorizontalScrollPosition || this._horizontalScrollPolicy == ScrollPolicy.OFF)))
{
return;
}
var nativeScaleFactor:Number = 1;
if(Starling.current.supportHighResolutions)
{
nativeScaleFactor = Starling.current.nativeStage.contentsScaleFactor;
}
var starlingViewPort:Rectangle = Starling.current.viewPort;
var scaleFactor:Number = nativeScaleFactor / Starling.contentScaleFactor;
HELPER_POINT.x = (event.stageX - starlingViewPort.x) * scaleFactor;
HELPER_POINT.y = (event.stageY - starlingViewPort.y) * scaleFactor;
if(this.contains(this.stage.hitTest(HELPER_POINT)))
{
this.globalToLocal(HELPER_POINT, HELPER_POINT);
var localMouseX:Number = HELPER_POINT.x;
var localMouseY:Number = HELPER_POINT.y;
if(localMouseX < this._leftViewPortOffset || localMouseY < this._topViewPortOffset ||
localMouseX >= this.actualWidth - this._rightViewPortOffset ||
localMouseY >= this.actualHeight - this._bottomViewPortOffset)
{
return;
}
var targetHorizontalScrollPosition:Number = this._horizontalScrollPosition;
var targetVerticalScrollPosition:Number = this._verticalScrollPosition;
var scrollStep:Number = this._verticalMouseWheelScrollStep;
if(this._verticalMouseWheelScrollDirection == Direction.HORIZONTAL)
{
if(scrollStep !== scrollStep) //isNaN
{
scrollStep = this.actualHorizontalScrollStep;
}
targetHorizontalScrollPosition -= event.delta * scrollStep;
if(targetHorizontalScrollPosition < this._minHorizontalScrollPosition)
{
targetHorizontalScrollPosition = this._minHorizontalScrollPosition;
}
else if(targetHorizontalScrollPosition > this._maxHorizontalScrollPosition)
{
targetHorizontalScrollPosition = this._maxHorizontalScrollPosition;
}
}
else //vertical
{
if(scrollStep !== scrollStep) //isNaN
{
scrollStep = this.actualVerticalScrollStep;
}
targetVerticalScrollPosition -= event.delta * scrollStep;
if(targetVerticalScrollPosition < this._minVerticalScrollPosition)
{
targetVerticalScrollPosition = this._minVerticalScrollPosition;
}
else if(targetVerticalScrollPosition > this._maxVerticalScrollPosition)
{
targetVerticalScrollPosition = this._maxVerticalScrollPosition;
}
}
this.throwTo(targetHorizontalScrollPosition, targetVerticalScrollPosition, this._mouseWheelScrollDuration);
}
}
/**
* @private
*/
protected function nativeStage_orientationChangeHandler(event:flash.events.Event):void
{
if(this._touchPointID < 0)
{
return;
}
this._startTouchX = this._previousTouchX = this._currentTouchX;
this._startTouchY = this._previousTouchY = this._currentTouchY;
this._startHorizontalScrollPosition = this._horizontalScrollPosition;
this._startVerticalScrollPosition = this._verticalScrollPosition;
}
/**
* @private
*/
protected function horizontalScrollBar_touchHandler(event:TouchEvent):void
{
if(!this._isEnabled)
{
this._horizontalScrollBarTouchPointID = -1;
return;
}
var displayHorizontalScrollBar:DisplayObject = DisplayObject(event.currentTarget);
if(this._horizontalScrollBarTouchPointID >= 0)
{
var touch:Touch = event.getTouch(displayHorizontalScrollBar, TouchPhase.ENDED, this._horizontalScrollBarTouchPointID);
if(!touch)
{
return;
}
this._horizontalScrollBarTouchPointID = -1;
touch.getLocation(displayHorizontalScrollBar, HELPER_POINT);
var isInBounds:Boolean = this.horizontalScrollBar.hitTest(HELPER_POINT) !== null;
if(!isInBounds)
{
this.hideHorizontalScrollBar();
}
}
else
{
touch = event.getTouch(displayHorizontalScrollBar, TouchPhase.BEGAN);
if(touch)
{
this._horizontalScrollBarTouchPointID = touch.id;
return;
}
if(this._isScrolling)
{
return;
}
touch = event.getTouch(displayHorizontalScrollBar, TouchPhase.HOVER);
if(touch)
{
this.revealHorizontalScrollBar();
return;
}
//end hover
this.hideHorizontalScrollBar();
}
}
/**
* @private
*/
protected function verticalScrollBar_touchHandler(event:TouchEvent):void
{
if(!this._isEnabled)
{
this._verticalScrollBarTouchPointID = -1;
return;
}
var displayVerticalScrollBar:DisplayObject = DisplayObject(event.currentTarget);
if(this._verticalScrollBarTouchPointID >= 0)
{
var touch:Touch = event.getTouch(displayVerticalScrollBar, TouchPhase.ENDED, this._verticalScrollBarTouchPointID);
if(!touch)
{
return;
}
this._verticalScrollBarTouchPointID = -1;
touch.getLocation(displayVerticalScrollBar, HELPER_POINT);
var isInBounds:Boolean = this.verticalScrollBar.hitTest(HELPER_POINT) !== null;
if(!isInBounds)
{
this.hideVerticalScrollBar();
}
}
else
{
touch = event.getTouch(displayVerticalScrollBar, TouchPhase.BEGAN);
if(touch)
{
this._verticalScrollBarTouchPointID = touch.id;
return;
}
if(this._isScrolling)
{
return;
}
touch = event.getTouch(displayVerticalScrollBar, TouchPhase.HOVER);
if(touch)
{
this.revealVerticalScrollBar();
return;
}
//end hover
this.hideVerticalScrollBar();
}
}
/**
* @private
*/
protected function scroller_addedToStageHandler(event:Event):void
{
Starling.current.nativeStage.addEventListener(MouseEvent.MOUSE_WHEEL, nativeStage_mouseWheelHandler, false, 0, true);
Starling.current.nativeStage.addEventListener("orientationChange", nativeStage_orientationChangeHandler, false, 0, true);
}
/**
* @private
*/
protected function scroller_removedFromStageHandler(event:Event):void
{
Starling.current.nativeStage.removeEventListener(MouseEvent.MOUSE_WHEEL, nativeStage_mouseWheelHandler);
Starling.current.nativeStage.removeEventListener("orientationChange", nativeStage_orientationChangeHandler);
if(this._touchPointID >= 0)
{
var exclusiveTouch:ExclusiveTouch = ExclusiveTouch.forStage(this.stage);
exclusiveTouch.removeEventListener(Event.CHANGE, exclusiveTouch_changeHandler);
}
this._touchPointID = -1;
this._horizontalScrollBarTouchPointID = -1;
this._verticalScrollBarTouchPointID = -1;
this._isDraggingHorizontally = false;
this._isDraggingVertically = false;
this._velocityX = 0;
this._velocityY = 0;
this._previousVelocityX.length = 0;
this._previousVelocityY.length = 0;
this._horizontalScrollBarIsScrolling = false;
this._verticalScrollBarIsScrolling = false;
this.removeEventListener(Event.ENTER_FRAME, scroller_enterFrameHandler);
this.stage.removeEventListener(TouchEvent.TOUCH, stage_touchHandler);
if(this._verticalAutoScrollTween)
{
Starling.juggler.remove(this._verticalAutoScrollTween);
this._verticalAutoScrollTween = null;
}
if(this._horizontalAutoScrollTween)
{
Starling.juggler.remove(this._horizontalAutoScrollTween);
this._horizontalAutoScrollTween = null;
}
//if we stopped the animation while the list was outside the scroll
//bounds, then let's account for that
var oldHorizontalScrollPosition:Number = this._horizontalScrollPosition;
var oldVerticalScrollPosition:Number = this._verticalScrollPosition;
if(this._horizontalScrollPosition < this._minHorizontalScrollPosition)
{
this._horizontalScrollPosition = this._minHorizontalScrollPosition;
}
else if(this._horizontalScrollPosition > this._maxHorizontalScrollPosition)
{
this._horizontalScrollPosition = this._maxHorizontalScrollPosition;
}
if(this._verticalScrollPosition < this._minVerticalScrollPosition)
{
this._verticalScrollPosition = this._minVerticalScrollPosition;
}
else if(this._verticalScrollPosition > this._maxVerticalScrollPosition)
{
this._verticalScrollPosition = this._maxVerticalScrollPosition;
}
if(oldHorizontalScrollPosition != this._horizontalScrollPosition ||
oldVerticalScrollPosition != this._verticalScrollPosition)
{
this.dispatchEventWith(Event.SCROLL);
}
this.completeScroll();
}
/**
* @private
*/
override protected function focusInHandler(event:Event):void
{
super.focusInHandler(event);
this.stage.addEventListener(KeyboardEvent.KEY_DOWN, stage_keyDownHandler);
}
/**
* @private
*/
override protected function focusOutHandler(event:Event):void
{
super.focusOutHandler(event);
this.stage.removeEventListener(KeyboardEvent.KEY_DOWN, stage_keyDownHandler);
}
/**
* @private
*/
protected function stage_keyDownHandler(event:KeyboardEvent):void
{
if(event.keyCode == Keyboard.HOME)
{
this.verticalScrollPosition = this._minVerticalScrollPosition;
}
else if(event.keyCode == Keyboard.END)
{
this.verticalScrollPosition = this._maxVerticalScrollPosition;
}
else if(event.keyCode == Keyboard.PAGE_UP)
{
this.verticalScrollPosition = Math.max(this._minVerticalScrollPosition, this._verticalScrollPosition - this.viewPort.visibleHeight);
}
else if(event.keyCode == Keyboard.PAGE_DOWN)
{
this.verticalScrollPosition = Math.min(this._maxVerticalScrollPosition, this._verticalScrollPosition + this.viewPort.visibleHeight);
}
else if(event.keyCode == Keyboard.UP)
{
this.verticalScrollPosition = Math.max(this._minVerticalScrollPosition, this._verticalScrollPosition - this.verticalScrollStep);
}
else if(event.keyCode == Keyboard.DOWN)
{
this.verticalScrollPosition = Math.min(this._maxVerticalScrollPosition, this._verticalScrollPosition + this.verticalScrollStep);
}
else if(event.keyCode == Keyboard.LEFT)
{
this.horizontalScrollPosition = Math.max(this._maxHorizontalScrollPosition, this._horizontalScrollPosition - this.horizontalScrollStep);
}
else if(event.keyCode == Keyboard.RIGHT)
{
this.horizontalScrollPosition = Math.min(this._maxHorizontalScrollPosition, this._horizontalScrollPosition + this.horizontalScrollStep);
}
}
}
}