(function(){

Ext.ScrollManager = new Ext.AbstractManager();

/**
 * @class Ext.util.ScrollView
 * @extends Ext.util.Observable
 *
 * A wrapper class around {@link Ext.util.Scroller Ext.util.Scroller} and {@link Ext.util.Scroller.Indicator Ext.util.Scroller.Indicator}
 * that listens to scroll events and control the scroll indicators
 */
Ext.util.ScrollView = Ext.extend(Ext.util.Observable, {

    /**
     * @cfg {Boolean/String} useIndicators
     * Whether or not to use indicators. Can be either: <ul>
     * <li>{Boolean} true to display both directions, false otherwise</li>
     * <li>{String} 'vertical' or 'horizontal' to display for that specific direction only</li>
     * Defaults to true
     */
    useIndicators: true,

    /**
     * @cfg {Object} indicatorConfig
     * A valid config object for {@link Ext.util.Scroller.Indicator Ext.util.Scroller.Indicator}
     */
    indicatorConfig: {},

    /**
     * @cfg {Number} indicatorMargin
     * The margin value for the indicator relatively to the container.
     * Defaults to <tt>4</tt>
     */
    indicatorMargin: 4,

    constructor: function(el, config) {
        var indicators = [],
            directions = ['vertical', 'horizontal'];

        Ext.util.ScrollView.superclass.constructor.call(this);

        ['useIndicators', 'indicatorConfig', 'indicatorMargin'].forEach(function(c) {
            if (config.hasOwnProperty(c)) {
                this[c] = config[c];
                delete config[c];
            }
        }, this);

        config.scrollView = this;
        this.scroller = new Ext.util.Scroller(el, config);

        if (this.useIndicators === true) {
            directions.forEach(function(d) {
                if (this.scroller[d]) {
                    indicators.push(d);
                }
            }, this);
        } else if (directions.indexOf(this.useIndicators) !== -1) {
            indicators.push(this.useIndicators);
        }

        this.indicators = {};
        this.indicatorOffsetExtras = {};

        indicators.forEach(function(i) {
            this.indicators[i] = new Ext.util.Scroller.Indicator(this.scroller.container, Ext.apply({}, this.indicatorConfig, {type: i}));
        }, this);

        this.mon(this.scroller, {
            scrollstart: this.onScrollStart,
            scrollend: this.onScrollEnd,
            scroll: this.onScroll,
            scope: this
        });
    },

    // @private
    onScrollStart: function() {
        this.showIndicators();
    },

    // @private
    onScrollEnd: function() {
        this.hideIndicators();
    },

    // @private
    onScroll: function(scroller) {
        if (scroller.offsetBoundary == null || (!this.indicators.vertical && !this.indicators.horizontal))
            return;

        var sizeAxis,
            offsetAxis,
            offsetMark,
            boundary = scroller.offsetBoundary,
            offset = scroller.offset;

        this.containerSize = scroller.containerBox;
        this.scrollerSize = scroller.size;
        this.outOfBoundOffset = boundary.getOutOfBoundOffset(offset);
        this.restrictedOffset = boundary.restrict(offset);
        this.boundarySize = boundary.getSize();

        if (!this.indicatorSizes) {
            this.indicatorSizes = {vertical: 0, horizontal: 0};
        }

        if (!this.indicatorOffsets) {
            this.indicatorOffsets = {vertical: 0, horizontal: 0};
        }

        Ext.iterate(this.indicators, function(axis, indicator) {
            sizeAxis = (axis == 'vertical') ? 'height' : 'width';
            offsetAxis = (axis == 'vertical') ? 'y' : 'x';
            offsetMark = (axis == 'vertical') ? 'bottom' : 'right';

            if (this.scrollerSize[sizeAxis] < this.containerSize[sizeAxis]) {
                this.indicatorSizes[axis] = this.containerSize[sizeAxis] * (this.scrollerSize[sizeAxis] / this.containerSize[sizeAxis]);
            }
            else {
                this.indicatorSizes[axis] = this.containerSize[sizeAxis] * (this.containerSize[sizeAxis] / this.scrollerSize[sizeAxis]);
            }
            this.indicatorSizes[axis] -= Math.abs(this.outOfBoundOffset[offsetAxis]);
            this.indicatorSizes[axis] = Math.max(this.indicatorMargin * 4, this.indicatorSizes[axis]);

            if (this.boundarySize[sizeAxis] != 0) {
                this.indicatorOffsets[axis] = (((boundary[offsetMark] - this.restrictedOffset[offsetAxis]) / this.boundarySize[sizeAxis])
                                              * (this.containerSize[sizeAxis] - this.indicatorSizes[axis]));
            } else if (offset[offsetAxis] < boundary[offsetMark]) {
                this.indicatorOffsets[axis] = this.containerSize[sizeAxis] - this.indicatorSizes[axis];
            } else {
                this.indicatorOffsets[axis] = 0;
            }

            indicator.setOffset(this.indicatorOffsetExtras[axis] + this.indicatorOffsets[axis] + this.indicatorMargin);
            indicator.setSize(this.indicatorSizes[axis] - (this.indicatorMargin * 2));
        }, this);
    },

    /*
     * Show the indicators if they are enabled; called automatically when the Scroller starts moving
     * @return {Ext.util.ScrollView} this This ScrollView
     */
    showIndicators : function() {
        Ext.iterate(this.indicators, function(axis, indicator) {
            indicator.show();
            this.indicatorOffsetExtras[axis] = indicator.el.dom.parentNode[axis === 'vertical' ? 'scrollTop' : 'scrollLeft'];
        }, this);

        return this;
    },

     /*
     * Hide the indicators if they are enabled; called automatically when the scrolling ends
     * @return {Ext.util.ScrollView} this This ScrollView
     */
    hideIndicators : function() {
        Ext.iterate(this.indicators, function(axis, indicator) {
            indicator.hide();
        }, this);
    },

    // Inherited docs
    destroy: function() {
        this.scroller.destroy();

        if (this.indicators) {
            Ext.iterate(this.indicators, function(axis, indicator) {
                indicator.destroy();
            }, this);
        }

        return Ext.util.ScrollView.superclass.destroy.apply(this, arguments);
    }
});

/**
 * @class Ext.util.Scroller
 * @extends Ext.util.Draggable
 *
 * Provide the native scrolling experience on iDevices for any DOM element
 */
Ext.util.Scroller = Ext.extend(Ext.util.Draggable, {
    // Inherited
    baseCls: '',

    // Inherited
    draggingCls: '',

    // Inherited
    direction: 'both',

    // Inherited
    constrain: 'parent',

    /**
     * @cfg {Number} outOfBoundRestrictFactor
     * Determines the offset ratio when the scroller is pulled / pushed out of bound (when it's not decelerating)
     * A value of 0.5 means 1px allowed for every 2px. Defaults to 0.5
     */
    outOfBoundRestrictFactor: 0.5,

    /**
     * @cfg {Number} acceleration
     * A higher acceleration gives the scroller more initial velocity. Defaults to 30
     */
    acceleration: 20,

    /**
     * @cfg {Number} fps
     * The desired fps of the deceleration. Defaults to 70.
     */
    // Inherited

    autoAdjustFps: false,

    /**
     * @cfg {Number} friction
     * The friction of the scroller.
     * By raising this value the length that momentum scrolls becomes shorter. This value is best kept
     * between 0 and 1. The default value is 0.5
     */
    friction: 0.5,

    /**
     * @cfg {Number} startMomentumResetTime
     * The time duration in ms to reset the start time of momentum
     * Defaults to 350
     */
    startMomentumResetTime: 350,

    /**
     * @cfg {Number} springTension
     * The tension of the spring that is attached to the scroller when it bounces.
     * By raising this value the bounce becomes shorter. This value is best kept
     * between 0 and 1. The default value is 0.3
     */
    springTension: 0.3,

    /**
     * @cfg {Number} minVelocityForAnimation
     * The minimum velocity to keep animating. Defaults to 1 (1px per second)
     */
    minVelocityForAnimation: 1,

    /**
     * @cfg {Boolean/String} bounces
     * Enable bouncing during scrolling past the bounds. Defaults to true. (Which is 'both').
     * You can also specify 'vertical', 'horizontal', or 'both'
     */
    bounces: true,

    /**
     * @cfg {Boolean} momentum
     * Whether or not to enable scrolling momentum. Defaults to true
     */
    momentum: true,

    cancelRevert: true,

    threshold: 5,

    constructor: function(el, config) {
        el = Ext.get(el);

        var scroller = Ext.ScrollManager.get(el.id);
        if (scroller) {
            return Ext.apply(scroller, config);
        }

        Ext.util.Scroller.superclass.constructor.apply(this, arguments);

        this.addEvents(
            /**
             * @event scrollstart
             * @param {Ext.util.Scroller} this
             * @param {Ext.EventObject} e
             */
            'scrollstart',
            /**
             * @event scroll
             * @param {Ext.util.Scroller} this
             * @param {Object} offsets An object containing the x and y offsets for the scroller.
             */
            'scroll',
            /**
             * @event scrollend
             * @param {Ext.util.Scroller} this
             * @param {Object} offsets An object containing the x and y offsets for the scroller.
             */
            'scrollend',
            /**
             * @event bouncestart
             * @param {Ext.util.Scroller} this
             * @param {Object} info Object containing information regarding the bounce
             */
             'bouncestart',
             /**
              * @event bounceend
              * @param {Ext.util.Scroller} this
              * @param {Object} info Object containing information regarding the bounce
              */
             'bounceend'
        );

        this.on({
            dragstart: this.onDragStart,
            offsetchange: this.onOffsetChange,
            scope: this
        });

        Ext.ScrollManager.register(this);

        this.el.addCls('x-scroller');
        this.container.addCls('x-scroller-parent');

        if (this.bounces !== false) {
            var both = this.bounces === 'both' || this.bounces === true,
                horizontal = both || this.bounces === 'horizontal',
                vertical = both || this.bounces === 'vertical';

            this.bounces = {
                x: horizontal,
                y: vertical
            };
        }

        this.theta = Math.log(1 - (this.friction / 10));
        this.bouncingVelocityFactor = this.springTension * Math.E;
        this.bouncingTimeFactor = ((1 / this.springTension) * this.acceleration);

        if (!this.decelerationAnimation) {
            this.decelerationAnimation = {};
        }

        if (!this.bouncingAnimation) {
            this.bouncingAnimation = {};
        }

        ['x', 'y'].forEach(function(a) {
            if (!this.decelerationAnimation[a]) {
                this.decelerationAnimation[a] = new Ext.util.Scroller.Animation.Deceleration({
                    acceleration: this.acceleration,
                    theta: this.theta
                });
            }

            if (!this.bouncingAnimation[a]) {
                this.bouncingAnimation[a] = new Ext.util.Scroller.Animation.Bouncing({
                    acceleration: this.acceleration,
                    springTension: this.springTension
                });
            }
        }, this);

        return this;
    },

    // Inherited docs
    updateBoundary: function(animate) {
        Ext.util.Scroller.superclass.updateBoundary.apply(this, arguments);

        this.snapToBoundary(animate);

        return this;
    },

    // Inherited docs
    onOffsetChange: function(scroller, offset) {
        this.fireEvent('scroll', scroller, {
            x: -offset.x,
            y: -offset.y
        });
    },

    // @private
    onTouchStart: function(e) {
        Ext.util.Scroller.superclass.onTouchStart.apply(this, arguments);

        this.stopMomentumAnimation();
    },

    // @private
    onDragStart: function(e) {
        this.fireEvent('scrollstart', this, e);
    },

    // @private
    setStartTime: function(e) {
        this.startTime = e.time;
        this.originalStartTime = (e.event.originalTimeStamp) ? e.event.originalTimeStamp : e.time;
    },

    // @private
    onStart: function(e) {
        if (Ext.util.Scroller.superclass.onStart.apply(this, arguments) !== true)
            return;

        this.setStartTime(e);
        this.lastEventTime = e.time;
        this.startTimeOffset = this.offset.copy();
        this.isScrolling = true;

        //<debug>
        this.momentumAnimationFramesHandled = 0;
        //</debug>
    },

    // @private
    onDrag: function(e) {
        if (Ext.util.Scroller.superclass.onDrag.apply(this, arguments) !== true)
            return;

        this.lastEventTime = e.time;

        if (this.lastEventTime - this.startTime > this.startMomentumResetTime) {
            this.setStartTime(e);
            this.startTimeOffset = this.offset.copy();
        }
    },

    // @private
    onDragEnd: function(e) {
        if (Ext.util.Scroller.superclass.onDragEnd.apply(this, arguments) !== true)
            return;

        if (!this.startMomentumAnimation(e)) {
            this.fireScrollEndEvent();
        }
    },

    // @private
    onOrientationChange: function() {
        Ext.util.Scroller.superclass.onOrientationChange.apply(this, arguments);

        this.snapToBoundary();
    },

    // @private
    fireScrollEndEvent: function() {
        this.isScrolling = false;
        this.isMomentumAnimating = false;
        this.snapToBoundary();
        this.fireEvent('scrollend', this, this.getOffset());

        this.snapToSlot();
    },


    //<debug>
    /*
     * Get the last actual fps performed by this Scroller. Useful for benchmarking
     * @return {Number} The actual fps
     */
    getLastActualFps: function() {
        var duration = (this.momentumAnimationEndTime - this.momentumAnimationStartTime - this.momentumAnimationProcessingTime) / 1000;
        return this.momentumAnimationFramesHandled / duration;
    },
    //</debug>

    /*
     * Similar to {@link Ext.util.Scroller#setOffset setOffset}, but will stop any existing animation
     * @param {Object} pos The new scroll position, e.g {x: 100, y: 200}
     * @param {Number/Boolean} animate Whether or not to animate while changing the scroll position.
     * If it's a number, will be treated as the duration in ms
     * @return {Ext.util.Scroller} this This Scroller
     */
    scrollTo: function(pos, animate) {
        this.stopMomentumAnimation();

        var newOffset = this.offsetBoundary.restrict(new Ext.util.Offset(-pos.x, -pos.y));

        this.setOffset(newOffset, animate);

        return this;
    },

    /*
     * Change the scroll offset by the given amount
     * @param {Ext.util.Offset/Object} offset The amount to scroll by, e.g {x: 100, y: 200}
     * @param {Number/Boolean} animate Whether or not to animate while changing the scroll position.
     * If it's a number, will be treated as the duration in ms
     * @return {Ext.util.Scroller} this This Scroller
     */
    scrollBy: function(offset, animate) {
        this.stopMomentumAnimation();

        var newOffset = this.offset.copy();
        newOffset.x += offset.x;
        newOffset.y += offset.y;

        this.setOffset(newOffset, animate);

        return this;
    },

    // @private
    setSnap: function(snap) {
        this.snap = snap;
    },

    /*
     * Snap this scrollable content back to the container's boundary, if it's currently out of bound
     * @return {Ext.util.Scroller} this This Scroller
     */
    snapToBoundary: function(animate) {
        var offset = this.offsetBoundary.restrict(this.offset);
        this.setOffset(offset, animate);

        return this;
    },

    snapToSlot: function() {
        var offset = this.offsetBoundary.restrict(this.offset);
        offset.round();

        if (this.snap) {
            if (this.snap === true) {
                this.snap = {
                    x: 50,
                    y: 50
                };
            }
            else if (Ext.isNumber(this.snap)) {
                this.snap = {
                    x: this.snap,
                    y: this.snap
                };
            }
            if (this.snap.y) {
                offset.y = Math.round(offset.y / this.snap.y) * this.snap.y;
            }
            if (this.snap.x) {
                offset.x = Math.round(offset.x / this.snap.x) * this.snap.x;
            }

            if (!this.offset.equals(offset)) {
                this.scrollTo({x: -offset.x, y: -offset.y}, this.snapDuration);
            }
        }
    },

    // @private
    startMomentumAnimation: function(e) {
        var me = this,
            originalTime = (e.event.originalTimeStamp) ? e.event.originalTimeStamp : e.time,
            duration = Math.max(40, originalTime - this.originalStartTime);

        this.fireEvent('beforemomentumanimationstart');

        if (
            (!this.momentum || !(duration <= this.startMomentumResetTime)) &&
            !this.offsetBoundary.isOutOfBound(this.offset)
        ) {
            return false;
        }

        // Determine the duration of the momentum
        var minVelocity = this.minVelocityForAnimation,
            currentVelocity,
            currentOffset = this.offset.copy(),
            restrictedOffset,
            acceleration = (duration / this.acceleration);

        this.isBouncing = {x: false, y: false};
        this.isDecelerating = {x: false, y: false};
        this.momentumAnimationStartTime = e.time;
        this.momentumAnimationProcessingTime = 0;
        this.bouncingData = {x: null, y: null};

        // Determine the deceleration velocity
        this.momentumAnimationStartVelocity = {
            x: (this.offset.x - this.startTimeOffset.x) / acceleration,
            y: (this.offset.y - this.startTimeOffset.y) / acceleration
        };

        this.momentumAnimationStartOffset = currentOffset;

        ['x', 'y'].forEach(function(axis) {

            this.isDecelerating[axis] = (Math.abs(this.momentumAnimationStartVelocity[axis]) > minVelocity);

            if (this.bounces && this.bounces[axis]) {
                restrictedOffset = this.offsetBoundary.restrict(axis, currentOffset[axis]);

                if (restrictedOffset !== currentOffset[axis]) {
                    currentVelocity = (currentOffset[axis] - restrictedOffset) * this.bouncingVelocityFactor;

                    this.bouncingData[axis] = {
                        axis: axis,
                        offset: restrictedOffset,
                        time: this.momentumAnimationStartTime,
                        velocity: currentVelocity
                    };

                    this.isBouncing[axis] = true;
                    this.isDecelerating[axis] = false;

                    this.fireEvent('bouncestart', this, this.bouncingData[axis]);

                    this.bouncingAnimation[axis].set({
                        startTime: this.bouncingData[axis].time - this.bouncingTimeFactor,
                        startOffset: this.bouncingData[axis].offset,
                        startVelocity: this.bouncingData[axis].velocity
                    });
                }
            }

            if (this.isDecelerating[axis]) {
                this.decelerationAnimation[axis].set({
                    startVelocity: this.momentumAnimationStartVelocity[axis],
                    startOffset: this.momentumAnimationStartOffset[axis],
                    startTime: this.momentumAnimationStartTime
                });
            }
        }, this);

        if (this.isDecelerating.x || this.isDecelerating.y || this.isBouncing.x || this.isBouncing.y) {
            this.isMomentumAnimating = true;
            this.momentumAnimationFramesHandled = 0;
            this.fireEvent('momentumanimationstart');

            me.handleMomentumAnimationFrame();
            this.momentumAnimationTimer = setInterval(function() {
                me.handleMomentumAnimationFrame();
            }, this.getFrameDuration());
            return true;
        }

        return false;
    },

    // @private
    stopMomentumAnimation: function() {
        if (this.isMomentumAnimating) {
            if (this.momentumAnimationTimer) {
                clearInterval(this.momentumAnimationTimer);
            }
            this.momentumAnimationEndTime = Date.now();

            //<debug>
            var lastFps = this.getLastActualFps();

            if (!this.maxFps || lastFps > this.maxFps) {
                this.maxFps = lastFps;
            }

            if (this.autoAdjustFps) {
                this.fps = this.maxFps;
            }
            //</debug>

            this.isDecelerating = {};
            this.isBouncing = {};

            this.fireEvent('momentumanimationend');
            this.fireScrollEndEvent();

        }

        return this;
    },

    /**
     * @private
     */
    handleMomentumAnimationFrame : function() {
        if (!this.isMomentumAnimating) {
            return;
        }

        var currentTime = Date.now(),
            newOffset = this.offset.copy(),
            offsetBoundary = this.offsetBoundary,
            currentVelocity,
            restrictedOffset,
            outOfBoundDistance;

        ['x', 'y'].forEach(function(axis) {
            if (this.isDecelerating[axis]) {
                newOffset[axis] = this.decelerationAnimation[axis].getOffset();
                currentVelocity = this.momentumAnimationStartVelocity[axis] * this.decelerationAnimation[axis].getFrictionFactor();
                outOfBoundDistance = offsetBoundary.getOutOfBoundOffset(axis, newOffset[axis]);

                // If the new offset is out of boundary, we are going to start a bounce
                if (outOfBoundDistance !== 0) {
                    restrictedOffset = offsetBoundary.restrict(axis, newOffset[axis]);

                    if (this.bounces && this.bounces[axis]) {
                        this.bouncingData[axis] = {
                            axis: axis,
                            offset: restrictedOffset,
                            time: currentTime,
                            velocity: currentVelocity
                        };

                        this.fireEvent('bouncestart', this, this.bouncingData[axis]);

                        this.bouncingAnimation[axis].set({
                            startTime: this.bouncingData[axis].time,
                            startOffset: this.bouncingData[axis].offset,
                            startVelocity: this.bouncingData[axis].velocity
                        });
                        this.isBouncing[axis] = true;
                    }

                    this.isDecelerating[axis] = false;
                }
                else if (Math.abs(currentVelocity) <= 1) {
                    this.isDecelerating[axis] = false;
                }
            }
            else if (this.isBouncing[axis]) {
                newOffset[axis] = this.bouncingAnimation[axis].getOffset();
                restrictedOffset = offsetBoundary.restrict(axis, newOffset[axis]);

                if (Math.abs(newOffset[axis] - restrictedOffset) <= 1) {
                    this.isBouncing[axis] = false;
                    this.fireEvent('bounceend', this, {axis: axis});
                    newOffset[axis] = restrictedOffset;
                }
            }
        }, this);

        if (!this.isDecelerating.x && !this.isDecelerating.y && !this.isBouncing.x && !this.isBouncing.y) {
            this.stopMomentumAnimation();
            return;
        }

        //<debug>
        this.momentumAnimationFramesHandled++;
        this.momentumAnimationProcessingTime += Date.now() - currentTime;
        //</debug>

        this.setOffset(newOffset);
    },

    // Inherited docs
    destroy: function() {
        Ext.ScrollManager.unregister(this);
        return Ext.util.Scroller.superclass.destroy.apply(this, arguments);
    }
});

Ext.util.Scroller.Animation = {};

Ext.util.Scroller.Animation.Deceleration = Ext.extend(Ext.util.Draggable.Animation.Abstract, {
    acceleration: 30,
    theta: null,
    startVelocity: null,

    getOffset: function() {
        return this.startOffset - this.startVelocity * (1 - this.getFrictionFactor()) / this.theta;
    },

    getFrictionFactor : function() {
        var deltaTime = Date.now() - this.startTime;

        return Math.exp(deltaTime / this.acceleration * this.theta);
    }
});

Ext.util.Scroller.Animation.Bouncing = Ext.extend(Ext.util.Draggable.Animation.Abstract, {
    springTension: 0.3,
    acceleration: 30,
    startVelocity: null,

    getOffset: function() {
        var deltaTime = (Date.now() - this.startTime),
            powTime = (deltaTime / this.acceleration) * Math.pow(Math.E, -this.springTension * (deltaTime / this.acceleration));

        return this.startOffset + (this.startVelocity * powTime);
    }
});

/**
 * @class Ext.util.Indicator
 * @extends Object
 *
 * Represent the Scroll Indicator to be used in a {@link Ext.util.ScrollView ScrollView}
 */
Ext.util.Scroller.Indicator = Ext.extend(Object, {
    baseCls: 'x-scrollbar',

    ui: 'dark',

    /**
     * @cfg {String} type
     * The type of this Indicator, valid values are 'vertical' or 'horizontal'
     */
    type: 'horizontal',

    constructor: function(container, config) {
        this.container = container;

        Ext.apply(this, config);

        this.el = this.container.createChild({
            cls: [this.baseCls, this.baseCls + '-' + this.type, this.baseCls + '-' + this.ui].join(' ')
        });

        this.offset = new Ext.util.Offset();

        this.hide();
    },

    /*
     * Hide this Indicator
     * @return {Ext.util.Scroller.Indicator} this This Indicator
     */
    hide: function() {
        var me = this;

        if (this.hideTimer) {
            clearTimeout(this.hideTimer);
        }

        this.hideTimer = setTimeout(function() {
            me.el.setStyle('opacity', 0);
        }, 100);

        return this;
    },

    /*
     * Show this Indicator
     * @return {Ext.util.Scroller.Indicator} this This Indicator
     */
    show: function() {
        if (this.hideTimer) {
            clearTimeout(this.hideTimer);
        }

        this.el.setStyle('opacity', 1);

        return this;
    },

    /*
     * Set the visibility of this Indicator, a wrapper function for
     * {@link Ext.util.Scroller.Indicator#show show} and {@link Ext.util.Scroller.Indicator#show hide}
     * @param {Boolean} isVisible True to show this Indicator, false to hide
     * @return {Ext.util.Scroller.Indicator} this This Indicator
     */
    setVisibility: function(isVisible) {
        return this[isVisible ? 'show' : 'hide']();
    },

    /*
     * Adjust the size of this Indicator, will change the height if {@link Ext.util.Scroller.Indicator#type type}
     * is 'vertical', and width for 'horizontal'
     * @param {Number} size The new size to change to
     * @return {Ext.util.Scroller.Indicator} this This Indicator
     */
    setSize: function(size) {
        if (this.size && size > this.size) {
            size = Math.round(size);
        }

        // this.el.setStyle(height) is cleaner here but let's save some little performance...
        this.el.dom.style[(this.type == 'horizontal') ? 'width' : 'height'] = size + 'px';

        this.size = size;

        return this;
    },

    /*
     * Set the offset position of this Indicator, relative to its container
     * @param {Number} offset The new offset
     * @return {Ext.util.Scroller.Indicator} this This Indicator
     */
    setOffset: function(offset) {
        if (this.type == 'vertical') {
            this.offset.y = offset;
        } else {
            this.offset.x = offset;
        }

        if (!Ext.is.iOS && !Ext.is.Desktop) {
            if (this.type == 'vertical') {
                this.el.dom.style.top = this.offset.y + 'px';
            } else {
                this.el.dom.style.left = this.offset.x + 'px';
            }
        } else {
            Ext.Element.cssTranslate(this.el, this.offset);
        }

        return this;
    }

});

})();