var Control = require('../core/Control'),
Tween = require('../utils/Tween'),
Scrollable = require('./Scrollable'),
ScrollBar = require('./ScrollBar');
// LayoutAlignment = require('../../external/pixi-layout/src/layout/LayoutAlignment');
/**
* 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.
*
* @class Scroller
* @extends GOWN.Control
* @memberof GOWN
* @constructor
*/
function Scroller(theme) {
Control.call(this);
this.setTheme(theme);
this.interactive = true;
/**
* use mask to clip content
*/
this._clipContent = true;
/**
* offsets for the mask of the viewport
* (see this._viewport.mask)
*/
this._viewPortOffset = {left: 0, right: 0, top: 0, bottom: 0};
/**
* scroll policy for vertical and horizontal ScrollBar
* (translates to x/y position of the viewport and scroll positions)
*/
this._verticalScrollPolicy = Scroller.SCROLL_POLICY_AUTO;
this._horizontalScrollPolicy = Scroller.SCROLL_POLICY_AUTO;
// min/max horizontal and
this._scrollBounds = new PIXI.Rectangle(0, 0, Infinity, Infinity);
/**
* the default interaction mode is drag-and-drop OR use the scrollbars
*/
this._interactionMode = Scroller.INTERACTION_TOUCH_AND_SCROLL_BARS;
/**
* start touch/mouse position
* (changed on touchstart/mousedown)
*/
this._startTouch = new PIXI.Point(0, 0);
/**
* calculated horizontal and vertical scroll positions
*/
this._scrollPosition = new PIXI.Point(0, 0);
/**
* scroll positions at the start of an interaction
* (changed on touchstart/mousedown)
*/
this._startScrollPosition = new PIXI.Point(0, 0);
// mouse/ouch has to be moved at least this many pixel to be a valid drag.
this.minimumDragDistance = 3;
/**
* add events
*/
this.refreshInteractionModeEvents();
/**
* scrollInvalid will force viewport to set its x/y position
* according to horizontal/vertical Scroll Position next redraw
*/
this.scrollInvalid = false;
this.scrollBarInvalid = false;
this.mask = undefined;
this.enabled = true;
this.horizontalScrollBarStyleName = Scroller.DEFAULT_CHILD_STYLE_NAME_HORIZONTAL_SCROLL_BAR;
this.verticalScrollBarStyleName = Scroller.DEFAULT_CHILD_STYLE_NAME_VERTICAL_SCROLL_BAR;
this._hasHorizontalScrollBar = false;
this._hasVerticalScrollBar = false;
this._touchPointID = -1;
this._velocity = {x: 0, y: 0};
this._previousVelocity = {x: [], y: []};
this._pendingVelocityChange = false;
this._hasViewPortBoundsChanged = false;
this._isDraggingHorizontally = false;
this._isDraggingVertically = false;
this._measureViewPort = true;
this._snapToPages = false;
this._snapOnComplete = false;
this._horizontalScrollBarFactory = this._verticalScrollBarFactory = this.defaultScrollBarFactory;
this._horizontalScrollPosition = 0;
this._minHorizontalScrollPosition = 0;
this._maxHorizontalScrollPosition = 0;
this._horizontalPageIndex = 0;
this._minHorizontalPageIndex = 0;
this._maxHorizontalPageIndex = 0;
this.actualVerticalScrollStep = 1;
this.explicitVerticalScrollStep = NaN;
this._verticalMouseWheelScrollStep = NaN;
this._verticalScrollPosition = 0;
this._minVerticalScrollPosition = 0;
this._maxVerticalScrollPosition = 0;
this._verticalPageIndex = 0;
this._minVerticalPageIndex = 0;
this._maxVerticalPageIndex = 0;
this.actualPageWidth = 0;
this.explicitPageWidth = NaN;
this.actualPageHeight = 0;
this.explicitPageHeight = NaN;
this._minimumPageThrowVelocity = 5;
this._paddingTop = 0;
this._elasticSnapDuration = 0.5;
this._horizontalScrollBarIsScrolling = false;
this._verticalScrollBarIsScrolling = false;
this._isScrolling = false;
this._isScrollingStopped = false;
this.pendingHorizontalScrollPosition = NaN;
this.pendingVerticalScrollPosition = NaN;
this.hasPendingHorizontalPageIndex = false;
this.hasPendingVerticalPageIndex = false;
this.isScrollBarRevealPending = false;
this._revealScrollBarsDuration = 1.0;
this._isTopPullActive = false;
this._topPullView = null;
this._isRightPullActive = false;
this._rightPullView = null;
this._isBottomPullActive = false;
this._bottomPullView = null;
this._isLeftPullActive = false;
this._leftPullView = null;
this._hasElasticEdges = true;
this._pageThrowDuration = 0.5;
this._mouseWheelScrollDuration = 0.35;
this._elasticity = 0.33;
this._throwElasticity = 0.05;
this.scrolldelta = 10;
}
Scroller.prototype = Object.create(Control.prototype);
Scroller.prototype.constructor = Scroller;
module.exports = Scroller;
/**
* The scroller may scroll if the view port is larger than the
* scroller's bounds. Only than the scroll bar will be visible.
*/
Scroller.SCROLL_POLICY_AUTO = 'auto';
/**
* The scroller will always scroll, the scroll bar will always be visible.
*/
Scroller.SCROLL_POLICY_ON = 'on';
/**
* The scroller does not scroll at all, the scroll bar will never be visible.
*/
Scroller.SCROLL_POLICY_OFF = 'off';
/**
* The user may touch anywhere on the scroller and drag to scroll. The
* scroll bars will be visual indicator of position, but they will not
* be interactive.
*/
Scroller.INTERACTION_TOUCH = 'touch';
/**
* Allow touch and use the Scrollbars
*/
Scroller.INTERACTION_TOUCH_AND_SCROLL_BARS = 'touchAndScrollBars';
/**
* The user may only interact with the scroll bars to scroll.
*/
Scroller.INTERACTION_MOUSE = Scroller.INTERACTION_SCROLL_BARS = 'scrollBars';
Scroller.HELPER_POINT = new PIXI.Point(0, 0);
Scroller.INVALIDATION_FLAG_SCROLL_BAR_RENDERER = 'scrollBarRenderer';
Scroller.INVALIDATION_FLAG_PENDING_SCROLL = 'pendingScroll';
Scroller.INVALIDATION_FLAG_PENDING_REVEAL_SCROLL_BARS = 'pendingRevealScrollBars';
Scroller.SCROLL_BAR_DISPLAY_MODE_FLOAT = 'float';
Scroller.SCROLL_BAR_DISPLAY_MODE_FIXED = 'fixed';
Scroller.SCROLL_BAR_DISPLAY_MODE_FIXED_FLOAT = 'fixedFloat';
Scroller.SCROLL_BAR_DISPLAY_MODE_NONE = 'none';
Scroller.VERTICAL_SCROLL_BAR_POSITION_RIGHT = 'right';
Scroller.VERTICAL_SCROLL_BAR_POSITION_LEFT = 'left';
Scroller.INTERACTION_MODE_TOUCH = 'touch';
Scroller.INTERACTION_MODE_MOUSE = 'mouse';
Scroller.INTERACTION_MODE_TOUCH_AND_SCROLL_BARS = 'touchAndScrollBars';
Scroller.MOUSE_WHEEL_SCROLL_DIRECTION_VERTICAL = 'vertical';
Scroller.MOUSE_WHEEL_SCROLL_DIRECTION_HORIZONTAL = 'horizontal';
Scroller.INVALIDATION_FLAG_CLIPPING = 'clipping';
Scroller.MINIMUM_VELOCITY = 0.02;
Scroller.CURRENT_VELOCITY_WEIGHT = 2.33;
Scroller.VELOCITY_WEIGHTS = [1, 1.33, 1.66, 2];
Scroller.MAXIMUM_SAVED_VELOCITY_COUNT = 4;
Scroller.DECELERATION_RATE_NORMAL = 0.998;
Scroller.DECELERATION_RATE_FAST = 0.99;
// Scroller.DEFAULT_CHILD_STYLE_NAME_HORIZONTAL_SCROLL_BAR = 'scroller-horizontal-scroll-bar';
// Scroller.DEFAULT_CHILD_STYLE_NAME_VERTICAL_SCROLL_BAR = 'scroller-vertical-scroll-bar';
Scroller.FUZZY_PAGE_SIZE_PADDING = 0.000001;
Scroller.PAGE_INDEX_EPSILON = 0.01;
/**
* change horizontal scroll position.
* (will update x position of viewport next redraw)
*/
Object.defineProperty(Scroller.prototype, 'horizontalScrollPosition', {
get: function () {
return this._scrollPosition.x;
},
set: function (value) {
if (this._scrollPosition.x === value) {
return;
}
this._scrollPosition.x = value;
this.scrollInvalid = true;
}
});
/**
* change vertical scroll position.
* (will update y position of viewport next redraw)
*/
Object.defineProperty(Scroller.prototype, 'verticalScrollPosition', {
get: function () {
return this._scrollPosition.y;
},
set: function (value) {
if (this._scrollPosition.y === value) {
return;
}
this._scrollPosition.y = value;
this.scrollInvalid = true;
}
});
/**
* us a mask to clip content
*/
Object.defineProperty(Scroller.prototype, 'interactionMode', {
get: function () {
return this._interactionMode;
},
set: function (value) {
if (this._interactionMode === value) {
return;
}
this._interactionMode = value;
this.refreshInteractionModeEvents();
}
});
/**
* us a mask to clip the content.
*/
Object.defineProperty(Scroller.prototype, 'clipContent', {
get: function () {
return this._clipContent;
},
set: function (value) {
if (this._clipContent === value) {
return;
}
this._clipContent = value;
this.clippingInvalid = true;
}
});
/**
* set the viewport. This is the content you'd like to scroll.
*/
Object.defineProperty(Scroller.prototype, 'viewPort', {
get: function () {
return this._viewPort;
},
set: function (value) {
if (this._viewPort === value) {
return;
}
this._viewPort = value;
if (this._viewPort) {
this.addChildAt(this._viewPort, 0);
}
// position according to horizontal/vertical ScrollPosition
this.scrollInvalid = true;
this.clippingInvalid = true;
this.sizeInvalid = true;
}
});
/**
* change scrollbar factory
*/
Object.defineProperty(Scroller.prototype, 'horizontalScrollBarFactory', {
get: function () {
return this._horizontalScrollBarFactory;
},
set: function (value) {
if (this._horizontalScrollBarFactory === value) {
return;
}
this._horizontalScrollBarFactory = value;
this.scrollBarInvalid = true;
}
});
/**
* change scrollbar factory
*/
Object.defineProperty(Scroller.prototype, 'verticalScrollBarFactory', {
get: function () {
return this._verticalScrollBarFactory;
},
set: function (value) {
if (this._verticalScrollBarFactory === value) {
return;
}
this._verticalScrollBarFactory = value;
this.scrollBarInvalid = true;
}
});
Object.defineProperty(Scroller.prototype, 'measureViewPort', {
get: function () {
return this._measureViewPort;
},
set: function (value) {
if (this._measureViewPort === value) {
return;
}
this._measureViewPort = value;
this.sizeInvalid = true;
}
});
Object.defineProperty(Scroller.prototype, 'snapToPages', {
get: function () {
return this._snapToPages;
},
set: function (value) {
if (this._snapToPages === value) {
return;
}
this._snapToPages = value;
this.sizeInvalid = true;
}
});
Object.defineProperty(Scroller.prototype, 'horizontalScrollStep', {
get: function () {
return this._horizontalScrollStep;
},
set: function (value) {
if (this._horizontalScrollStep === value) {
return;
}
this._horizontalScrollStep = value;
this.scrollInvalid = true;
}
});
Object.defineProperty(Scroller.prototype, 'horizontalPageIndex', {
get: function () {
if (this.hasPendingHorizontalPageIndex) {
return this.pendingHorizontalPageIndex;
}
return this._horizontalPageIndex;
}
});
Object.defineProperty(Scroller.prototype, 'horizontalScrollPolicy', {
get: function () {
return this._horizontalScrollPolicy;
},
set: function (value) {
if (this._horizontalScrollPolicy === value) {
return;
}
this._horizontalScrollPolicy = value;
this.scrollInvalid = true;
this.scrollBarInvalid = true;
}
});
Object.defineProperty(Scroller.prototype, 'verticalScrollStep', {
get: function () {
return this.actualVerticalScrollStep;
},
set: function (value) {
if (this.explicitVerticalScrollStep === value) {
return;
}
this.explicitVerticalScrollStep = value;
this.scrollInvalid = true;
}
});
Object.defineProperty(Scroller.prototype, 'verticalMouseWheelScrollStep', {
get: function () {
return this._verticalMouseWheelScrollStep;
},
set: function (value) {
if (this._verticalMouseWheelScrollStep === value) {
return;
}
this._verticalMouseWheelScrollStep = value;
this.scrollInvalid = true;
}
});
Object.defineProperty(Scroller.prototype, 'verticalPageIndex', {
get: function () {
if (this.hasPendingVerticalPageIndex) {
return this.pendingVerticalPageIndex;
}
return this._verticalPageIndex;
}
});
Object.defineProperty(Scroller.prototype, 'verticalScrollPolicy', {
get: function () {
if (this.hasPendingVerticalPageIndex) {
return this.pendingVerticalPageIndex;
}
return this._verticalPageIndex;
},
set: function (value) {
if (this._verticalScrollPolicy === value) {
return;
}
this._verticalScrollPolicy = value;
this.scrollInvalid = true;
this.scrollBarInvalid = true;
}
});
Object.defineProperty(Scroller.prototype, 'pageWidth', {
get: function () {
return this.actualPageWidth;
},
set: function (value) {
if (this.explicitPageWidth === value) {
return;
}
var valueIsNaN = isNaN(value);
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;
}
}
});
Object.defineProperty(Scroller.prototype, 'pageHeight', {
get: function () {
return this.actualPageHeight;
},
set: function (value) {
if (this.explicitPageHeight === value) {
return;
}
var valueIsNaN = isNaN(value);
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;
}
}
});
Object.defineProperty(Scroller.prototype, 'padding', {
get: function () {
return this._paddingTop;
},
set: function (value) {
this.paddingTop = value;
this.paddingRight = value;
this.paddingBottom = value;
this.paddingLeft = value;
}
});
Object.defineProperty(Scroller.prototype, 'hasElasticEdges', {
get: function () {
return this._hasElasticEdges;
},
set: function (value) {
this._hasElasticEdges = value;
}
});
Object.defineProperty(Scroller.prototype, 'pageThrowDuration', {
get: function () {
return this._pageThrowDuration;
},
set: function (value) {
this._pageThrowDuration = value;
}
});
Object.defineProperty(Scroller.prototype, 'mouseWheelScrollDuration', {
get: function () {
return this._mouseWheelScrollDuration;
},
set: function (value) {
this._mouseWheelScrollDuration = value;
}
});
Object.defineProperty(Scroller.prototype, 'elasticity', {
get: function () {
return this._elasticity;
},
set: function (value) {
this._elasticity = value;
}
});
Object.defineProperty(Scroller.prototype, 'throwElasticity', {
get: function () {
return this._throwElasticity;
},
set: function (value) {
this._throwElasticity = value;
}
});
Scroller.prototype.scrollToPageIndex = function (horizontalPageIndex, verticalPageIndex, animationDuration) {
if (isNaN(animationDuration)) {
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;
};
Scroller.prototype.refreshInteractionModeEvents = function () {
if (!this.startEventsAdded &&
(this._interactionMode === Scroller.INTERACTION_TOUCH ||
this._interactionMode === Scroller.INTERACTION_TOUCH_AND_SCROLL_BARS)) {
this.on('touchstart', this.onDown, this);
this.on('mousedown', this.onDown, this);
this.startEventsAdded = true;
} else if (this.startEventsAdded &&
this._interactionMode === Scroller.INTERACTION_SCROLL_BARS) {
this.off('touchstart', this.onDown, this);
this.off('mousedown', this.onDown, this);
if (this.touchMoveEventsAdded) {
this.off('touchend', this.onUp, this);
this.off('mouseupoutside', this.onUp, this);
this.off('mouseup', this.onUp, this);
this.off('touchendoutside', this.onUp, this);
// TODO: global move (add events to root element from pixi renderer?)
this.off('touchmove', this.onMove, this);
this.off('mousemove', this.onMove, this);
}
this.touchMoveEventsAdded = this.startEventsAdded = false;
}
// TODO: interactive scrollbars
};
Scroller.prototype.onDown = function (event) {
this._startTouch = event.data.getLocalPosition(this);
this._isScrollingStopped = false;
if (!this.touchMoveEventsAdded) {
this.on('touchend', this.onUp, this);
this.on('mouseupoutside', this.onUp, this);
this.on('mouseup', this.onUp, this);
this.on('touchendoutside', this.onUp, this);
this.on('touchmove', this.onMove, this);
this.on('mousemove', this.onMove, this);
this.touchMoveEventsAdded = true;
}
this._startScrollPosition.x = this._scrollPosition.x;
this._startScrollPosition.y = this._scrollPosition.y;
};
Scroller.prototype.onUp = function () {
this._isScrollingStopped = true;
};
Scroller.prototype.onMove = function (event) {
var pos = event.data.getLocalPosition(this);
this.checkForDrag(pos);
};
Scroller.prototype.checkForDrag = function (currentTouch) {
if (this._isScrollingStopped) {
return;
}
var horizontalMoved = Math.abs(currentTouch.x - this._startTouch.x);
var verticalMoved = Math.abs(currentTouch.y - this._startTouch.y);
if ((this._horizontalScrollPolicy === Scroller.SCROLL_POLICY_ON ||
this._horizontalScrollPolicy === Scroller.SCROLL_POLICY_AUTO) &&
!this._isDraggingHorizontally && horizontalMoved >= this.minimumDragDistance) {
if (this.horizontalScrollBar) {
this.revealHorizontalScrollBar();
}
this._startTouch.x = currentTouch.x;
this._startScrollPosition.x = this._scrollPosition.x;
this._isDraggingHorizontally = true;
if (!this._isDraggingVertically) {
this.startScroll();
}
}
if ((this._verticalScrollPolicy === Scroller.SCROLL_POLICY_ON ||
this._verticalScrollPolicy === Scroller.SCROLL_POLICY_AUTO) &&
!this._isDraggingVertically && verticalMoved >= this.minimumDragDistance) {
if (this.verticalScrollBar) {
this.revealVerticalScrollBar();
}
this._startTouch.y = currentTouch.y;
this._startScrollPosition.y = this._scrollPosition.y;
this._isDraggingVertically = true;
if (!this._isDraggingHorizontally) {
this.startScroll();
}
}
if (this._isDraggingHorizontally && !this._horizontalAutoScrollTween) {
this.updateHorizontalScrollFromTouchPosition(currentTouch.x);
}
if (this._isDraggingVertically && !this._verticalAutoScrollTween) {
this.updateVerticalScrollFromTouchPosition(currentTouch.y);
}
};
// performance increase to avoid using call.. (10x faster)
Scroller.prototype.controlRedraw = Control.prototype.redraw;
/**
* update before draw call
*
*/
Scroller.prototype.redraw = function () {
this.scrollBarInvalid = true;
if (this.scrollBarInvalid) {
this.createScrollBars();
}
if (this.clippingInvalid) {
this.refreshMask();
}
if (this._viewPort && this._viewPort.updateRenderable) {
this._viewPort.updateRenderable(
-this._viewPort.x, -this._viewPort.y,
this.width, this.height);
}
this.controlRedraw();
};
Scroller.prototype.updateHorizontalScrollFromTouchPosition = function (touchX, isScrollBar) {
var offset;
if (isScrollBar) {
offset = this._startTouch.x - touchX;
} else {
offset = touchX - this._startTouch.x;
}
var position = this._startScrollPosition.x + offset;
if (this.viewPort.width > this.width) {
position = Math.min(position, 0);
if (this.viewPort.width && this.viewPort.x < 0) {
position = Math.max(position, -(this.viewPort.width - this.width));
}
this.viewPort.x = Math.floor(position);
}
this.horizontalScrollPosition = position;
};
Scroller.prototype.updateVerticalScrollFromTouchPosition = function (touchY, isScrollBar) {
var offset;
if (isScrollBar) {
offset = this._startTouch.y - touchY;
} else {
offset = touchY - this._startTouch.y;
}
var position = this._startScrollPosition.y + offset;
if (this.viewPort.height > this.height) {
position = Math.min(position, 0);
if (this.viewPort.height && this.viewPort.y < 0) {
position = Math.max(position, -(this.viewPort.height - this.height));
}
this.viewPort.y = Math.floor(position);
}
this.verticalScrollPosition = position;
};
Scroller.prototype.startScroll = function () {
if (this._isScrolling) {
return;
}
this._isScrolling = true;
};
// 3333
Scroller.prototype.stopScrolling = function () {
if (this._horizontalAutoScrollTween) {
this._horizontalAutoScrollTween.remove();
this._horizontalAutoScrollTween = null;
}
if (this._verticalAutoScrollTween) {
this._verticalAutoScrollTween.remove();
this._verticalAutoScrollTween = null;
}
this._isScrollingStopped = true;
this._velocity.x = 0;
this._velocity.y = 0;
this.hideHorizontalScrollBar();
this.hideVerticalScrollBar();
};
Scroller.prototype.scrollToPosition = function (horizontalScrollPosition, verticalScrollPosition, animationDuration) {
if (isNaN(animationDuration)) {
if (this._useFixedThrowDuration) {
animationDuration = this._fixedThrowDuration;
} else {
Scroller.HELPER_POINT.setTo(horizontalScrollPosition - this._horizontalScrollPosition, verticalScrollPosition - this._verticalScrollPosition);
animationDuration = this.calculateDynamicThrowDuration(Scroller.HELPER_POINT.length * this._logDecelerationRate + Scroller.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;
};
Scroller.prototype.handlePendingScroll = function () {
if (!isNaN(this.pendingHorizontalScrollPosition) || !isNaN(this.pendingVerticalScrollPosition)) {
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;
};
Scroller.prototype.completeScroll = function () {
if (!this._isScrolling || this._verticalAutoScrollTween || this._horizontalAutoScrollTween ||
this._isDraggingHorizontally || this._isDraggingVertically ||
this._horizontalScrollBarIsScrolling || this._verticalScrollBarIsScrolling) {
return;
}
this._isScrolling = false;
this.hideHorizontalScrollBar();
this.hideVerticalScrollBar();
};
Scroller.prototype.revealScrollBars = function () {
this.isScrollBarRevealPending = true;
};
Scroller.prototype.refreshEnabled = function () {
if (this._viewPort) {
this._viewPort.enabled = this.enabled;
}
if (this.horizontalScrollBar) {
this.horizontalScrollBar.enabled = this.enabled;
}
if (this.verticalScrollBar) {
this.verticalScrollBar.enabled = this.enabled;
}
};
Scroller.prototype.refreshScrollValues = function () {
this.refreshScrollSteps();
var oldMaxHSP = this._maxHorizontalScrollPosition;
var oldMaxVSP = this._maxVerticalScrollPosition;
this.refreshMinAndMaxScrollPositions();
var maximumPositionsChanged = this._maxHorizontalScrollPosition !== oldMaxHSP || this._maxVerticalScrollPosition !== oldMaxVSP;
if (maximumPositionsChanged && this._touchPointID < 0) {
this.clampScrollPositions();
}
this.refreshPageCount();
this.refreshPageIndices();
};
Scroller.prototype.refreshPageCount = function () {
if (this._snapToPages) {
var horizontalScrollRange = this._maxHorizontalScrollPosition - this._minHorizontalScrollPosition;
var roundedDownRange;
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 = Number.MIN_SAFE_INTEGER;
} else {
this._minHorizontalPageIndex = 0;
}
this._maxHorizontalPageIndex = Number.MAX_SAFE_INTEGER;
} else {
this._minHorizontalPageIndex = 0;
//floating point errors could result in the max page index
//being 1 larger than it should be.
roundedDownRange =
Math.floor(horizontalScrollRange / this.actualPageWidth) * this.actualPageWidth;
if ((horizontalScrollRange - roundedDownRange) < Scroller.FUZZY_PAGE_SIZE_PADDING) {
horizontalScrollRange = roundedDownRange;
}
this._maxHorizontalPageIndex = Math.ceil(horizontalScrollRange / this.actualPageWidth);
}
var verticalScrollRange = 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 = Number.MIN_SAFE_INTEGER;
} else {
this._minVerticalPageIndex = 0;
}
this._maxVerticalPageIndex = Number.MAX_SAFE_INTEGER;
} else {
this._minVerticalPageIndex = 0;
//floating point errors could result in the max page index
//being 1 larger than it should be.
roundedDownRange =
Math.floor(verticalScrollRange / this.actualPageHeight) * this.actualPageHeight;
if ((verticalScrollRange - roundedDownRange) < Scroller.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;
}
};
Scroller.prototype.clampScrollPositions = function () {
if (!this._horizontalAutoScrollTween) {
var targetHorizontalScrollPosition = this._horizontalScrollPosition;
if (targetHorizontalScrollPosition < this._minHorizontalScrollPosition) {
targetHorizontalScrollPosition = this._minHorizontalScrollPosition;
}
else if (targetHorizontalScrollPosition > this._maxHorizontalScrollPosition) {
targetHorizontalScrollPosition = this._maxHorizontalScrollPosition;
}
this.horizontalScrollPosition = targetHorizontalScrollPosition;
}
};
Scroller.prototype.refreshScrollSteps = function () {
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;
}
};
Scroller.prototype.refreshMinAndMaxScrollPositions = function () {
var visibleViewPortWidth = this.actualWidth - (this._viewPortOffset.left + this._viewPortOffset.right);
var visibleViewPortHeight = this.actualHeight - (this._viewPortOffset.top + this._viewPortOffset.bottom);
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.content.x;
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.content.y;
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;
}
} else {
this._minHorizontalScrollPosition = 0;
this._minVerticalScrollPosition = 0;
this._maxHorizontalScrollPosition = 0;
this._maxVerticalScrollPosition = 0;
}
};
Scroller.prototype.showOrHideChildren = function () {
var childCount = this.numRawChildrenInternal;
if (this._touchBlocker !== null && this._touchBlocker.parent !== null) {
//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 !== Scroller.INTERACTION_TOUCH_AND_SCROLL_BARS;
// this.setRawChildIndexInternal(DisplayObject(this.verticalScrollBar), childCount - 1);
}
if (this.horizontalScrollBar) {
this.horizontalScrollBar.visible = this._hasHorizontalScrollBar;
this.horizontalScrollBar.touchable =
this._hasHorizontalScrollBar && this._interactionMode !== Scroller.INTERACTION_TOUCH_AND_SCROLL_BARS;
// if(this.verticalScrollBar) {
// this.setRawChildIndexInternal(DisplayObject(this.horizontalScrollBar), childCount - 2);
// } else {
// this.setRawChildIndexInternal(DisplayObject(this.horizontalScrollBar), childCount - 1);
// }
}
};
Scroller.prototype.calculateViewPortOffsetsForFixedVerticalScrollBar = function (forceScrollBars, useActualBounds) {
forceScrollBars = forceScrollBars || false;
useActualBounds = useActualBounds || false;
if (this.verticalScrollBar && (this._measureViewPort || useActualBounds)) {
var scrollerHeight = useActualBounds ? this.actualHeight : this._explicitHeight;
var totalHeight = this._viewPort.height + this._viewPortOffset.top + this._viewPortOffset.bottom;
this._hasVerticalScrollBar =
forceScrollBars || this._verticalScrollPolicy === Scroller.SCROLL_POLICY_ON ||
((totalHeight > scrollerHeight || totalHeight > this._explicitMaxHeight) &&
this._verticalScrollPolicy !== Scroller.SCROLL_POLICY_OFF);
} else {
this._hasVerticalScrollBar = false;
}
};
Scroller.prototype.calculateViewPortOffsets = function (forceScrollBars, useActualBounds) {
forceScrollBars = forceScrollBars || false;
useActualBounds = useActualBounds || false;
//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._viewPortOffset.top = this._paddingTop;
this._viewPortOffset.rigth = this._paddingRight;
this._viewPortOffset.bottom = this._paddingBottom;
this._viewPortOffset.left = this._paddingLeft;
//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.
};
Scroller.prototype.throwToPage = function (targetHorizontalPageIndex, targetVerticalPageIndex, duration) {
duration = duration || 0.5;
var targetHorizontalScrollPosition = 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 = 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;
}
};
Scroller.prototype.horizontalScrollBarHideTweenOnComplete = function () {
this._horizontalScrollBarHideTween = null;
};
Scroller.prototype.verticalScrollBarHideTweenOnComplete = function () {
this._verticalScrollBarHideTween = null;
};
Scroller.prototype.scrollerEnterFrameHandler = function () {
this.saveVelocity();
};
/**
* update the rectangle that defines the clipping area
*/
Scroller.prototype.refreshMask = function () {
if (!this._clipContent) {
if (this._viewPort) {
this._viewPort.mask = null;
}
return;
}
var clipWidth = this.width - this._viewPortOffset.left - this._viewPortOffset.right;
if (clipWidth < 0 || isNaN(clipWidth)) {
clipWidth = 0;
}
var clipHeight = this.height - this._viewPortOffset.top - this._viewPortOffset.bottom;
if (clipHeight < 0 || isNaN(clipHeight)) {
clipHeight = 0;
}
if (!this.mask) {
this.mask = new PIXI.Graphics();
}
var global = this.toGlobal(new PIXI.Point(0, 0));
this.mask.clear()
.beginFill('#fff', 1)
.drawRect(
global.x,
global.y,
clipWidth,
clipHeight)
.endFill();
this.clippingInvalid = false;
};
/**
* Creates and adds the <code>horizontalScrollBar</code> and
* <code>verticalScrollBar</code> sub-components and removes the old
* instances, if they exist.
*
* <p>Meant for internal use, and subclasses may override this function
* with a custom implementation.</p>
*
* @see #horizontalScrollBar
* @see #verticalScrollBar
* @see #horizontalScrollBarFactory
* @see #verticalScrollBarFactory
*/
Scroller.prototype.createScrollBars = function () {
if(this.horizontalScrollBar) {
this.removeChild(this.horizontalScrollBar);
this.horizontalScrollBar = null;
}
if(this.verticalScrollBar) {
this.removeChild(this.verticalScrollBar);
this.verticalScrollBar = null;
}
this.horizontalScrollBar = this._horizontalScrollBarFactory(Scrollable.HORIZONTAL);
this.verticalScrollBar = this._verticalScrollBarFactory(Scrollable.VERTICAL);
};
Scroller.prototype.defaultScrollBarFactory = function (direction) {
// TODO: SimpleScrollBar (like feathers?)
var sb = new ScrollBar(direction, this.theme);
if (direction === Scrollable.HORIZONTAL) {
sb.skinName = this.horizontalScrollBarStyleName;
} else {
sb.skinName = this.verticalScrollBarStyleName;
}
return sb;
};
Scroller.prototype.revealHorizontalScrollBar = function () {
if (this.horizontalScrollBar) {
this.addChild(this.horizontalScrollBar);
}
};
Scroller.prototype.revealVerticalScrollBar = function () {
if (this.verticalScrollBar) {
this.addChild(this.verticalScrollBar);
}
};
Scroller.prototype.hideHorizontalScrollBar = function () {
if (this.horizontalScrollBar) {
this.removeChild(this.horizontalScrollBar);
}
};
Scroller.prototype.hideVerticalScrollBar = function () {
if (this.verticalScrollBar) {
this.removeChild(this.verticalScrollBar);
}
};
Scroller.prototype.throwHorizontally = function (pixelsPerMS) {
var absPixelsPerMS = Math.abs(pixelsPerMS);
if (absPixelsPerMS <= Scroller.MINIMUM_VELOCITY) {
this.finishScrollingHorizontally();
return;
}
var duration = this._fixedThrowDuration;
if (!this._useFixedThrowDuration) {
duration = this.calculateDynamicThrowDuration(pixelsPerMS);
}
this.throwTo(this._horizontalScrollPosition + this.calculateThrowDistance(pixelsPerMS), NaN, duration);
};
Scroller.prototype.throwVertically = function (pixelsPerMS) {
var absPixelsPerMS = Math.abs(pixelsPerMS);
if (absPixelsPerMS <= Scroller.MINIMUM_VELOCITY) {
this.finishScrollingVertically();
return;
}
var duration = this._fixedThrowDuration;
if (!this._useFixedThrowDuration) {
duration = this.calculateDynamicThrowDuration(pixelsPerMS);
}
this.throwTo(NaN, this._verticalScrollPosition + this.calculateThrowDistance(pixelsPerMS), duration);
};
/**
* @private
*/
Scroller.prototype.calculateDynamicThrowDuration = function (pixelsPerMS) {
return (Math.log(Scroller.MINIMUM_VELOCITY / Math.abs(pixelsPerMS)) / this._logDecelerationRate) / 1000;
};
/**
* @private
*/
Scroller.prototype.calculateThrowDistance = function (pixelsPerMS) {
return (pixelsPerMS - Scroller.MINIMUM_VELOCITY) / this._logDecelerationRate;
};
/**
* @private
*/
Scroller.prototype.finishScrollingHorizontally = function () {
var targetHorizontalScrollPosition = 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
*/
Scroller.prototype.finishScrollingVertically = function () {
var targetVerticalScrollPosition = 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);
}
};
/**
* manage tween to throw to horizontal or vertical position
* call finishScrolling when tween reaches the end position
*
* @param targetPosition {number} target position in pixel
* @param direction {String} direction ('horizontal' or 'vertical')
* @param duration {number} time needed to reach target position (in ms)
*/
Scroller.prototype._throwToTween = function (targetPosition, direction, duration) {
if (!this.tweens) {
this.tweens = {};
}
// remove old tween
var tween;
if (this.tweens.hasOwnProperty(direction)) {
tween = this.tweens[direction];
tween.remove();
delete this.tweens[direction];
}
tween = new Tween(this._viewport, duration);
this.tween[direction] = tween;
var to = {};
to[direction + 'ScrollPosition'] = targetPosition;
this.tween.to(to);
return targetPosition;
};
/**
* throw the scroller to the specified position
* @param targetHorizontalScrollPosition as PIXI.Point
* @param targetVerticalScrollPosition as PIXI.Point
* @param duration
*/
//TODO: see https://github.com/BowlerHatLLC/feathers/blob/master/source/feathers/controls/Scroller.as#L4939
Scroller.prototype.throwTo = function (targetHorizontalScrollPosition, targetVerticalScrollPosition, duration) {
duration = duration || 500;
var verticalScrollPosition = this._throwToTween(targetHorizontalScrollPosition, 'horizontal');
var horizontalScrollPosition = this._throwToTween(targetVerticalScrollPosition, 'vertical');
var changedPosition = false;
if (verticalScrollPosition !== this.verticalScrollPosition) {
changedPosition = true;
this.revealVerticalScrollBar();
this.startScroll();
// pass
if (duration === 0) {
this.verticalScrollPosition = targetVerticalScrollPosition;
}
// else {}
} else {
this.finishScrollingVertically();
}
if (horizontalScrollPosition !== this.horizontalScrollPosition) {
changedPosition = true;
this.revealHorizontalScrollBar();
this.startScroll();
// pass
if (duration === 0) {
this.horizontalScrollPosition = targetHorizontalScrollPosition;
}
// else {}
} else {
this.finishScrollingHorizontally();
}
if (changedPosition && duration === 0) {
this.completeScroll();
}
};
Scroller.prototype.direction = function () {
var scrollAuto =
this._verticalScrollPolicy === Scroller.SCROLL_POLICY_AUTO &&
this._horizontalScrollPolicy === Scroller.SCROLL_POLICY_AUTO;
var scroll = 'vertical';
var scrollVertical =
this._verticalScrollPolicy === Scroller.SCROLL_POLICY_AUTO ||
this._verticalScrollPolicy === Scroller.SCROLL_POLICY_ON;
var scrollHorizontal =
this._horizontalScrollPolicy === Scroller.SCROLL_POLICY_AUTO ||
this._horizontalScrollPolicy === Scroller.SCROLL_POLICY_ON;
// if the scroll direction is set to SCROLL_AUTO we check, if the
// layout of the content is set to horizontal or the content
// width is bigger than the current
if (!scrollVertical || scrollHorizontal ||
(scrollAuto && (this.layoutHorizontalAlign() || this.upright()) )) {
scroll = 'horizontal';
}
return scroll;
};