1 /****************************************************************************
  2  Copyright (c) 2011-2012 cocos2d-x.org
  3  Copyright (c) 2013-2014 Chukong Technologies Inc.
  4 
  5  http://www.cocos2d-x.org
  6 
  7  Permission is hereby granted, free of charge, to any person obtaining a copy
  8  of this software and associated documentation files (the "Software"), to deal
  9  in the Software without restriction, including without limitation the rights
 10  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 11  copies of the Software, and to permit persons to whom the Software is
 12  furnished to do so, subject to the following conditions:
 13 
 14  The above copyright notice and this permission notice shall be included in
 15  all copies or substantial portions of the Software.
 16 
 17  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 18  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 19  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 20  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 21  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 22  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 23  THE SOFTWARE.
 24  ****************************************************************************/
 25 
 26 /**
 27  * The ScrollView control of Cocos UI
 28  * @class
 29  * @extends ccui.Layout
 30  *
 31  * @property {Number}               innerWidth              - Inner container width of the scroll view
 32  * @property {Number}               innerHeight             - Inner container height of the scroll view
 33  * @property {ccui.ScrollView.DIR_NONE | ccui.ScrollView.DIR_VERTICAL | ccui.ScrollView.DIR_HORIZONTAL | ccui.ScrollView.DIR_BOTH}    direction               - Scroll direction of the scroll view
 34  * @property {Boolean}              bounceEnabled           - Indicate whether bounce is enabled
 35  * @property {Boolean}              inertiaScrollEnabled    - Indicate whether inertiaScroll is enabled
 36  * @property {Number}               touchTotalTimeThreshold - Touch total time threshold
 37  */
 38 ccui.ScrollView = ccui.Layout.extend(/** @lends ccui.ScrollView# */{
 39     _innerContainer: null,
 40     _direction: null,
 41 
 42     _topBoundary: 0,
 43     _bottomBoundary: 0,
 44     _leftBoundary: 0,
 45     _rightBoundary: 0,
 46 
 47     _touchMoveDisplacements: null,
 48     _touchMoveTimeDeltas: null,
 49     _touchMovePreviousTimestamp: 0,
 50     _touchTotalTimeThreshold : 0.5,
 51 
 52     _autoScrolling: false,
 53     _autoScrollTargetDelta: null,
 54     _autoScrollAttenuate: true,
 55     _autoScrollStartPosition : null,
 56     _autoScrollTotalTime: 0,
 57     _autoScrollAccumulatedTime: 0,
 58     _autoScrollCurrentlyOutOfBoundary: false,
 59     _autoScrollBraking: false,
 60     _autoScrollBrakingStartPosition: null,
 61 
 62     _bePressed: false,
 63 
 64     _childFocusCancelOffset: 0,
 65 
 66     bounceEnabled: false,
 67 
 68     _outOfBoundaryAmount: null,
 69     _outOfBoundaryAmountDirty: true,
 70 
 71     inertiaScrollEnabled: false,
 72 
 73     _scrollBarEnabled: true,
 74     _verticalScrollBar: null,
 75     _horizontalScrollBar: null,
 76 
 77     _scrollViewEventListener: null,
 78     _scrollViewEventSelector: null,
 79     _className: "ScrollView",
 80 
 81     /**
 82      * Allocates and initializes a UIScrollView.
 83      * Constructor of ccui.ScrollView. override it to extend the construction behavior, remember to call "this._super()" in the extended "ctor" function.
 84      * @example
 85      * // example
 86      * var uiScrollView = new ccui.ScrollView();
 87      */
 88     ctor: function () {
 89         ccui.Layout.prototype.ctor.call(this);
 90         this.setClippingEnabled(true);
 91         this._innerContainer.setTouchEnabled(false);
 92 
 93         this._direction = ccui.ScrollView.DIR_NONE;
 94 
 95         this._childFocusCancelOffset = 5;
 96         this.inertiaScrollEnabled = true;
 97 
 98         this._outOfBoundaryAmount = cc.p(0, 0);
 99         this._autoScrollTargetDelta = cc.p(0, 0);
100         this._autoScrollStartPosition = cc.p(0, 0);
101         this._autoScrollBrakingStartPosition = cc.p(0, 0);
102         this._touchMoveDisplacements = [];
103         this._touchMoveTimeDeltas = [];
104         this._touchMovePreviousTimestamp = 0;
105 
106         this._scrollBarEnabled = true;
107         this._initScrollBar();
108 
109         this.setTouchEnabled(true);
110     },
111 
112     /**
113      * Initializes a ccui.ScrollView. Please do not call this function by yourself, you should pass the parameters to constructor to initialize it.
114      * @returns {boolean}
115      */
116     init: function () {
117         if (ccui.Layout.prototype.init.call(this)) {
118             
119             
120             return true;
121         }
122         return false;
123     },
124 
125     /**
126      * Calls the parent class' onEnter and schedules update function.
127      * @override
128      */
129     onEnter: function () {
130         ccui.Layout.prototype.onEnter.call(this);
131         this.scheduleUpdate();
132     },
133 
134     onExit: function () {
135         cc.renderer._removeCache(this.__instanceId);
136         ccui.Layout.prototype.onExit.call(this);
137     },
138 
139     /**
140      * When a widget is in a layout, you could call this method to get the next focused widget within a specified _direction.             <br/>
141      * If the widget is not in a layout, it will return itself
142      *
143      * @param {Number} _direction the _direction to look for the next focused widget in a layout
144      * @param {ccui.Widget} current the current focused widget
145      * @returns {ccui.Widget}
146      */
147     findNextFocusedWidget: function(direction, current){
148         if (this.getLayoutType() === ccui.Layout.LINEAR_VERTICAL
149             || this.getLayoutType() === ccui.Layout.LINEAR_HORIZONTAL) {
150             return this._innerContainer.findNextFocusedWidget(direction, current);
151         } else
152             return ccui.Widget.prototype.findNextFocusedWidget.call(this, direction, current);
153     },
154 
155     _initRenderer: function () {
156         ccui.Layout.prototype._initRenderer.call(this);
157 
158         this._innerContainer = new ccui.Layout();
159         this._innerContainer.setColor(cc.color(255,255,255));
160         this._innerContainer.setOpacity(255);
161         this._innerContainer.setCascadeColorEnabled(true);
162         this._innerContainer.setCascadeOpacityEnabled(true);
163 
164         this.addProtectedChild(this._innerContainer, 1, 1);
165     },
166 
167     _createRenderCmd: function(){
168         if(cc._renderType === cc.game.RENDER_TYPE_WEBGL)
169             return new ccui.ScrollView.WebGLRenderCmd(this);
170         else
171             return new ccui.ScrollView.CanvasRenderCmd(this);
172     },
173 
174     _onSizeChanged: function () {
175         ccui.Layout.prototype._onSizeChanged.call(this);
176         var locSize = this._contentSize;
177         this._topBoundary = locSize.height;
178         this._rightBoundary = locSize.width;
179         var innerSize = this._innerContainer.getContentSize();
180         this._innerContainer.setContentSize(cc.size(Math.max(innerSize.width, locSize.width), Math.max(innerSize.height, locSize.height)));
181         this._innerContainer.setPosition(0, locSize.height - this._innerContainer.getContentSize().height);
182 
183         if(this._verticalScrollBar)
184             this._verticalScrollBar.onScrolled(this._getHowMuchOutOfBoundary());
185 
186         if(this._horizontalScrollBar)
187             this._horizontalScrollBar.onScrolled(this._getHowMuchOutOfBoundary());
188     },
189 
190     /**
191      * Changes inner container size of ScrollView.     <br/>
192      * Inner container size must be larger than or equal the size of ScrollView.
193      * @param {cc.Size} size inner container size.
194      */
195     setInnerContainerSize: function (size) {
196         var innerContainer = this._innerContainer,
197             locSize = this._contentSize,
198             innerSizeWidth = locSize.width, innerSizeHeight = locSize.height;
199 
200         if (size.width < locSize.width)
201             cc.log("Inner width <= ScrollView width, it will be force sized!");
202         else
203             innerSizeWidth = size.width;
204 
205         if (size.height < locSize.height)
206             cc.log("Inner height <= ScrollView height, it will be force sized!");
207         else
208             innerSizeHeight = size.height;
209 
210         innerContainer.setContentSize(cc.size(innerSizeWidth, innerSizeHeight));
211 
212         var pos = this._innerContainer.getPosition();
213         var contAP = this._innerContainer.getAnchorPoint();
214 
215         if (this._innerContainer.getLeftBoundary() != 0.0)
216         {
217             pos.x = contAP.x * innerSizeWidth;
218         }
219         if (this._innerContainer.getTopBoundary() != this._contentSize.height)
220         {
221             pos.y = this._contentSize.height - (1.0 - contAP.y) * innerSizeHeight;
222         }
223         this.setInnerContainerPosition(pos);
224 
225         this._updateScrollBar(cc.p(0 ,0));
226     },
227 
228     _setInnerWidth: function (width) {
229         var locW = this._contentSize.width,
230             innerWidth = locW,
231             container = this._innerContainer,
232             oldInnerWidth = container.width;
233         if (width < locW)
234             cc.log("Inner width <= scrollview width, it will be force sized!");
235         else
236             innerWidth = width;
237         container.width = innerWidth;
238 
239         switch (this._direction) {
240             case ccui.ScrollView.DIR_HORIZONTAL:
241             case ccui.ScrollView.DIR_BOTH:
242                 if (container.getRightBoundary() <= locW) {
243                     var newInnerWidth = container.width;
244                     var offset = oldInnerWidth - newInnerWidth;
245                     this._scrollChildren(offset, 0);
246                 }
247                 break;
248         }
249         var innerAX = container.anchorX;
250         if (container.getLeftBoundary() > 0.0)
251             container.x = innerAX * innerWidth;
252         if (container.getRightBoundary() < locW)
253             container.x = locW - ((1.0 - innerAX) * innerWidth);
254     },
255 
256     _setInnerHeight: function (height) {
257         var locH = this._contentSize.height,
258             innerHeight = locH,
259             container = this._innerContainer,
260             oldInnerHeight = container.height;
261         if (height < locH)
262             cc.log("Inner height <= scrollview height, it will be force sized!");
263         else
264             innerHeight = height;
265         container.height = innerHeight;
266 
267         switch (this._direction) {
268             case ccui.ScrollView.DIR_VERTICAL:
269             case ccui.ScrollView.DIR_BOTH:
270                 var newInnerHeight = innerHeight;
271                 var offset = oldInnerHeight - newInnerHeight;
272                 this._scrollChildren(0, offset);
273                 break;
274         }
275         var innerAY = container.anchorY;
276         if (container.getLeftBoundary() > 0.0)
277             container.y = innerAY * innerHeight;
278         if (container.getRightBoundary() < locH)
279             container.y = locH - ((1.0 - innerAY) * innerHeight);
280     },
281     /**
282      * Set inner container position
283      *
284      * @param {cc.Point} position Inner container position.
285      */
286     setInnerContainerPosition: function(position)
287     {
288         if(position.x === this._innerContainer.getPositionX() && position.y === this._innerContainer.getPositionY())
289         {
290             return;
291         }
292         this._innerContainer.setPosition(position);
293         this._outOfBoundaryAmountDirty = true;
294 
295         // Process bouncing events
296         if(this.bounceEnabled)
297         {
298             for(var _direction = ccui.ScrollView.MOVEDIR_TOP; _direction < ccui.ScrollView.MOVEDIR_RIGHT; ++_direction)
299             {
300                 if(this._isOutOfBoundary(_direction))
301                 {
302                    this._processScrollEvent(_direction, true);
303                 }
304             }
305         }
306 
307         this._dispatchEvent(ccui.ScrollView.EVENT_CONTAINER_MOVED);
308     },
309 
310     /**
311      * Get inner container position
312      *
313      * @return The inner container position.
314      */
315     getInnerContainerPosition: function()
316     {
317         return this._innerContainer.getPosition();
318     },
319 
320     /**
321      * Returns inner container size of ScrollView.     <br/>
322      * Inner container size must be larger than or equal ScrollView's size.
323      *
324      * @return {cc.Size} inner container size.
325      */
326     getInnerContainerSize: function () {
327         return this._innerContainer.getContentSize();
328     },
329     _getInnerWidth: function () {
330         return this._innerContainer.width;
331     },
332     _getInnerHeight: function () {
333         return this._innerContainer.height;
334     },
335 
336     _isInContainer: function (widget) {
337        if(!this._clippingEnabled) 
338             return true;
339         var wPos = widget._position,
340             wSize = widget._contentSize,
341             wAnchor = widget._anchorPoint,
342             size = this._customSize,
343             pos = this._innerContainer._position,
344             bottom = 0, left = 0;
345         if (
346             // Top
347         (bottom = wPos.y - wAnchor.y * wSize.height) >= size.height - pos.y ||
348             // Bottom
349         bottom + wSize.height <= -pos.y ||
350             // right
351         (left = wPos.x - wAnchor.x * wSize.width) >= size.width - pos.x ||
352             // left
353         left + wSize.width <= -pos.x
354         )
355             return false;
356         else return true;
357     },
358 
359     updateChildren: function () {
360         var child, i, l;
361         var childrenArray = this._innerContainer._children;
362         for(i = 0, l = childrenArray.length; i < l; i++) {
363             child = childrenArray[i];
364             if(child._inViewRect === true && this._isInContainer(child) === false)
365                 child._inViewRect = false;
366             else if (child._inViewRect === false && this._isInContainer(child) === true)
367                 child._inViewRect = true;
368         }
369     },
370     /**
371      * Add child to ccui.ScrollView.
372      * @param {cc.Node} widget
373      * @param {Number} [zOrder]
374      * @param {Number|string} [tag] tag or name
375      * @returns {boolean}
376      */
377     addChild: function (widget, zOrder, tag) {
378         if(!widget)
379             return false;
380         if(this._isInContainer(widget) === false)
381             widget._inViewRect = false;
382         zOrder = zOrder || widget.getLocalZOrder();
383         tag = tag || widget.getTag();
384         return this._innerContainer.addChild(widget, zOrder, tag);
385     },
386 
387     /**
388      * Removes all children.
389      */
390     removeAllChildren: function () {
391         this.removeAllChildrenWithCleanup(true);
392     },
393 
394     /**
395      * Removes all children.
396      * @param {Boolean} cleanup
397      */
398     removeAllChildrenWithCleanup: function(cleanup){
399         this._innerContainer.removeAllChildrenWithCleanup(cleanup);
400     },
401 
402     /**
403      * Removes widget child
404      * @override
405      * @param {ccui.Widget} child
406      * @param {Boolean} cleanup
407      * @returns {boolean}
408      */
409     removeChild: function (child, cleanup) {
410         return this._innerContainer.removeChild(child, cleanup);
411     },
412 
413     /**
414      * Returns inner container's children
415      * @returns {Array}
416      */
417     getChildren: function () {
418         return this._innerContainer.getChildren();
419     },
420 
421     /**
422      * Gets the count of inner container's children
423      * @returns {Number}
424      */
425     getChildrenCount: function () {
426         return this._innerContainer.getChildrenCount();
427     },
428 
429     /**
430      * Gets a child from the container given its tag
431      * @param {Number} tag
432      * @returns {ccui.Widget}
433      */
434     getChildByTag: function (tag) {
435         return this._innerContainer.getChildByTag(tag);
436     },
437 
438     /**
439      * Gets a child from the container given its name
440      * @param {String} name
441      * @returns {ccui.Widget}
442      */
443     getChildByName: function (name) {
444         return this._innerContainer.getChildByName(name);
445     },
446 
447     _flattenVectorByDirection: function(vector)
448     {
449         var result = cc.p(0 ,0);
450         result.x = (this._direction === ccui.ScrollView.DIR_VERTICAL ? 0 : vector.x);
451         result.y = (this._direction === ccui.ScrollView.DIR_HORIZONTAL ? 0 : vector.y);
452         return result;
453     },
454 
455     _getHowMuchOutOfBoundary: function(addition)
456     {
457         if(addition === undefined)
458             addition = cc.p(0, 0);
459 
460         if(addition.x === 0 && addition.y === 0 && !this._outOfBoundaryAmountDirty)
461         {
462             return this._outOfBoundaryAmount;
463         }
464 
465         var outOfBoundaryAmount = cc.p(0, 0);
466 
467         if(this._innerContainer.getLeftBoundary() + addition.x > this._leftBoundary)
468         {
469             outOfBoundaryAmount.x = this._leftBoundary - (this._innerContainer.getLeftBoundary() + addition.x);
470         }
471         else if(this._innerContainer.getRightBoundary() + addition.x < this._rightBoundary)
472         {
473             outOfBoundaryAmount.x = this._rightBoundary - (this._innerContainer.getRightBoundary() + addition.x);
474         }
475 
476         if(this._innerContainer.getTopBoundary() + addition.y < this._topBoundary)
477         {
478             outOfBoundaryAmount.y = this._topBoundary - (this._innerContainer.getTopBoundary() + addition.y);
479         }
480         else if(this._innerContainer.getBottomBoundary() + addition.y > this._bottomBoundary)
481         {
482             outOfBoundaryAmount.y = this._bottomBoundary - (this._innerContainer.getBottomBoundary() + addition.y);
483         }
484 
485         if(addition.x === 0 && addition.y === 0 )
486         {
487             this._outOfBoundaryAmount = outOfBoundaryAmount;
488             this._outOfBoundaryAmountDirty = false;
489         }
490         return outOfBoundaryAmount;
491     },
492 
493     _isOutOfBoundary: function(dir)
494     {
495         var outOfBoundary = this._getHowMuchOutOfBoundary();
496         if(dir !== undefined)
497         {
498             switch (dir)
499             {
500                 case ccui.ScrollView.MOVEDIR_TOP:
501                     return outOfBoundary.y > 0;
502                 case ccui.ScrollView.MOVEDIR_BOTTOM:
503                     return outOfBoundary.y < 0;
504                 case ccui.ScrollView.MOVEDIR_LEFT:
505                     return outOfBoundary.x < 0;
506                 case ccui.ScrollView.MOVEDIR_RIGHT:
507                     return outOfBoundary.x > 0;
508             }
509         }
510         else
511         {
512             return !this._fltEqualZero(outOfBoundary);
513         }
514 
515         return false;
516     },
517 
518 
519     _moveInnerContainer: function(deltaMove, canStartBounceBack)
520     {
521         var adjustedMove = this._flattenVectorByDirection(deltaMove);
522 
523         this.setInnerContainerPosition(cc.pAdd(this.getInnerContainerPosition(), adjustedMove));
524 
525         var outOfBoundary =this._getHowMuchOutOfBoundary();
526         this._updateScrollBar(outOfBoundary);
527 
528         if(this.bounceEnabled && canStartBounceBack)
529         {
530             this._startBounceBackIfNeeded();
531         }
532     },
533 
534     _updateScrollBar: function(outOfBoundary)
535     {
536         if(this._verticalScrollBar)
537         {
538             this._verticalScrollBar.onScrolled(outOfBoundary);
539         }
540         if(this._horizontalScrollBar)
541         {
542             this._horizontalScrollBar.onScrolled(outOfBoundary);
543         }
544     },
545 
546     _calculateTouchMoveVelocity: function()
547     {
548         var totalTime = 0;
549         for(var i = 0; i < this._touchMoveTimeDeltas.length; ++i)
550         {
551             totalTime += this._touchMoveTimeDeltas[i];
552         }
553         if(totalTime == 0 || totalTime >= this._touchTotalTimeThreshold)
554         {
555             return cc.p(0, 0);
556         }
557 
558         var totalMovement = cc.p(0 ,0);
559 
560         for(var i = 0; i < this._touchMoveDisplacements.length; ++i)
561         {
562             totalMovement.x += this._touchMoveDisplacements[i].x;
563             totalMovement.y += this._touchMoveDisplacements[i].y;
564         }
565 
566         return cc.pMult(totalMovement, 1 / totalTime);
567     },
568 
569     /**
570      * Set the touch total time threshold
571      * @param {Number} touchTotalTimeThreshold
572      */
573     setTouchTotalTimeThreshold: function(touchTotalTimeThreshold)
574     {
575         this._touchTotalTimeThreshold = touchTotalTimeThreshold;
576     },
577 
578 
579     /**
580      * Get the touch total time threshold
581      * @returns {Number}
582      */
583     getTouchTotalTimeThreshold: function()
584     {
585         return this._touchTotalTimeThreshold;
586     },
587 
588     _startInertiaScroll: function(touchMoveVelocity)
589     {
590         var MOVEMENT_FACTOR = 0.7;
591         var inertiaTotalMovement = cc.pMult(touchMoveVelocity, MOVEMENT_FACTOR);
592         this._startAttenuatingAutoScroll(inertiaTotalMovement, touchMoveVelocity);
593     },
594 
595     _startBounceBackIfNeeded: function()
596     {
597         if (!this.bounceEnabled)
598         {
599             return false;
600         }
601         var bounceBackAmount = this._getHowMuchOutOfBoundary();
602         if(this._fltEqualZero(bounceBackAmount))
603         {
604             return false;
605         }
606 
607         var BOUNCE_BACK_DURATION = 1.0;
608         this._startAutoScroll(bounceBackAmount, BOUNCE_BACK_DURATION, true);
609         return true;
610     },
611 
612     _startAutoScrollToDestination: function(destination, timeInSec, attenuated)
613     {
614         this._startAutoScroll(cc.pSub(destination , this._innerContainer.getPosition()), timeInSec, attenuated);
615     },
616 
617     _calculateAutoScrollTimeByInitialSpeed: function(initialSpeed)
618     {
619         // Calculate the time from the initial speed according to quintic polynomial.
620         return Math.sqrt(Math.sqrt(initialSpeed / 5));
621     },
622 
623     _startAttenuatingAutoScroll: function(deltaMove, initialVelocity)
624     {
625         var  time = this._calculateAutoScrollTimeByInitialSpeed(cc.pLength(initialVelocity));
626         this._startAutoScroll(deltaMove, time, true);
627     },
628 
629     _startAutoScroll: function(deltaMove, timeInSec, attenuated)
630     {
631         var adjustedDeltaMove = this._flattenVectorByDirection(deltaMove);
632 
633         this._autoScrolling = true;
634         this._autoScrollTargetDelta = adjustedDeltaMove;
635         this._autoScrollAttenuate = attenuated;
636         this._autoScrollStartPosition = this._innerContainer.getPosition();
637         this._autoScrollTotalTime = timeInSec;
638         this._autoScrollAccumulatedTime = 0;
639         this._autoScrollBraking = false;
640         this._autoScrollBrakingStartPosition = cc.p(0,0 );
641 
642         // If the destination is also out of boundary of same side, start brake from beggining.
643         var currentOutOfBoundary = this._getHowMuchOutOfBoundary();
644         if(!this._fltEqualZero(currentOutOfBoundary))
645         {
646             this._autoScrollCurrentlyOutOfBoundary = true;
647             var afterOutOfBoundary = this._getHowMuchOutOfBoundary(adjustedDeltaMove);
648             if(currentOutOfBoundary.x * afterOutOfBoundary.x > 0 || currentOutOfBoundary.y * afterOutOfBoundary.y > 0)
649             {
650                 this._autoScrollBraking = true;
651             }
652         }
653     },
654 
655     /**
656      * Immediately stops inner container scroll initiated by any of the "scrollTo*" member functions
657      */
658     stopAutoScroll: function()
659     {
660         this._autoScrolling = false;
661         this._autoScrollAttenuate = true;
662         this._autoScrollTotalTime = 0;
663         this._autoScrollAccumulatedTime = 0;
664     },
665 
666     _isNecessaryAutoScrollBrake: function()
667     {
668         if(this._autoScrollBraking)
669         {
670             return true;
671         }
672 
673         if(this._isOutOfBoundary())
674         {
675             // It just went out of boundary.
676             if(!this._autoScrollCurrentlyOutOfBoundary)
677             {
678                 this._autoScrollCurrentlyOutOfBoundary = true;
679                 this._autoScrollBraking = true;
680                 this._autoScrollBrakingStartPosition = this.getInnerContainerPosition();
681                 return true;
682             }
683         }
684         else
685         {
686             this._autoScrollCurrentlyOutOfBoundary = false;
687         }
688         return false;
689     },
690 
691     _getAutoScrollStopEpsilon: function()
692     {
693         return 0.0001;
694     },
695 
696     _fltEqualZero: function(point)
697     {
698         return (Math.abs(point.x) <=  0.0001 && Math.abs(point.y) <=  0.0001);
699     },
700 
701     _processAutoScrolling: function(deltaTime)
702     {
703         var OUT_OF_BOUNDARY_BREAKING_FACTOR = 0.05;
704         // Make auto scroll shorter if it needs to deaccelerate.
705         var brakingFactor = (this._isNecessaryAutoScrollBrake() ? OUT_OF_BOUNDARY_BREAKING_FACTOR : 1);
706 
707         // Elapsed time
708         this._autoScrollAccumulatedTime += deltaTime * (1 / brakingFactor);
709 
710         // Calculate the progress percentage
711         var percentage = Math.min(1, this._autoScrollAccumulatedTime / this._autoScrollTotalTime);
712         if(this._autoScrollAttenuate)
713         {
714             percentage -= 1;
715             percentage = percentage * percentage * percentage * percentage * percentage + 1;
716         }
717 
718         // Calculate the new position
719         var newPosition = cc.pAdd(this._autoScrollStartPosition, cc.pMult(this._autoScrollTargetDelta,percentage));
720         var reachedEnd = Math.abs(percentage - 1) <= this._getAutoScrollStopEpsilon();
721 
722         if(this.bounceEnabled)
723         {
724             // The new position is adjusted if out of boundary
725             newPosition = cc.pAdd(this._autoScrollBrakingStartPosition, cc.pMult(cc.pSub(newPosition, this._autoScrollBrakingStartPosition), brakingFactor));
726         }
727         else
728         {
729             // Don't let go out of boundary
730             var moveDelta = cc.pSub(newPosition, this.getInnerContainerPosition());
731             var outOfBoundary = this._getHowMuchOutOfBoundary(moveDelta);
732             if(!this._fltEqualZero(outOfBoundary))
733             {
734                 newPosition.x += outOfBoundary.x;
735                 newPosition.y += outOfBoundary.y;
736 
737                 reachedEnd = true;
738             }
739         }
740 
741         // Finish auto scroll if it ended
742         if(reachedEnd)
743         {
744             this._autoScrolling = false;
745             this._dispatchEvent(ccui.ScrollView.EVENT_AUTOSCROLL_ENDED);
746         }
747 
748         this._moveInnerContainer(cc.pSub(newPosition, this.getInnerContainerPosition()), reachedEnd);
749     },
750 
751     _jumpToDestination: function (desOrX, y)
752     {
753         if(desOrX.x === undefined)
754         {
755             desOrX = cc.p(desOrX, y);
756         }
757 
758         this._autoScrolling = false;
759         this._moveInnerContainer(cc.pSub(desOrX, this.getInnerContainerPosition()), true);
760     },
761 
762     _scrollChildren: function(deltaMove)
763     {
764         var realMove = deltaMove;
765         if(this.bounceEnabled)
766         {
767             // If the position of the inner container is out of the boundary, the offsets should be divided by two.
768             var outOfBoundary = this._getHowMuchOutOfBoundary();
769             realMove.x *= (outOfBoundary.x == 0 ? 1 : 0.5);
770             realMove.y *= (outOfBoundary.y == 0 ? 1 : 0.5);
771         }
772 
773         if(!this.bounceEnabled)
774         {
775             var outOfBoundary = this._getHowMuchOutOfBoundary(realMove);
776             realMove.x += outOfBoundary.x;
777             realMove.y += outOfBoundary.y;
778         }
779 
780         var scrolledToLeft = false;
781         var scrolledToRight = false;
782         var scrolledToTop = false;
783         var scrolledToBottom = false;
784 
785         if (realMove.y > 0.0) // up
786         {
787             var icBottomPos = this._innerContainer.getBottomBoundary();
788             if (icBottomPos + realMove.y >= this._bottomBoundary)
789             {
790                 scrolledToBottom = true;
791             }
792         }
793         else if (realMove.y < 0.0) // down
794         {
795             var icTopPos = this._innerContainer.getTopBoundary();
796             if (icTopPos + realMove.y <= this._topBoundary)
797             {
798                 scrolledToTop = true;
799             }
800         }
801 
802         if (realMove.x < 0.0) // left
803         {
804             var icRightPos = this._innerContainer.getRightBoundary();
805             if (icRightPos + realMove.x <= this._rightBoundary)
806             {
807                 scrolledToRight = true;
808             }
809         }
810         else if (realMove.x > 0.0) // right
811         {
812             var icLeftPos = this._innerContainer.getLeftBoundary();
813             if (icLeftPos + realMove.x >= this._leftBoundary)
814             {
815                 scrolledToLeft = true;
816             }
817         }
818         this._moveInnerContainer(realMove, false);
819 
820         if(realMove.x != 0 || realMove.y != 0)
821         {
822             this._processScrollingEvent();
823         }
824         if(scrolledToBottom)
825         {
826             this._processScrollEvent(ccui.ScrollView.MOVEDIR_BOTTOM, false);
827         }
828         if(scrolledToTop)
829         {
830             this._processScrollEvent(ccui.ScrollView.MOVEDIR_TOP, false);
831         }
832         if(scrolledToLeft)
833         {
834             this._processScrollEvent(ccui.ScrollView.MOVEDIR_LEFT, false);
835         }
836         if(scrolledToRight)
837         {
838             this._processScrollEvent(ccui.ScrollView.MOVEDIR_RIGHT, false);
839         }
840     },
841 
842     /**
843      * Scroll inner container to bottom boundary of ScrollView.
844      * @param {Number} time
845      * @param {Boolean} attenuated
846      */
847     scrollToBottom: function (time, attenuated) {
848         this._startAutoScrollToDestination(cc.p(this._innerContainer.getPositionX(), 0), time, attenuated);
849     },
850 
851     /**
852      * Scroll inner container to top boundary of ScrollView.
853      * @param {Number} time
854      * @param {Boolean} attenuated
855      */
856     scrollToTop: function (time, attenuated) {
857         this._startAutoScrollToDestination(
858             cc.p(this._innerContainer.getPositionX(), this._contentSize.height - this._innerContainer.getContentSize().height), time, attenuated);
859     },
860 
861     /**
862      * Scroll inner container to left boundary of ScrollView.
863      * @param {Number} time
864      * @param {Boolean} attenuated
865      */
866     scrollToLeft: function (time, attenuated) {
867         this._startAutoScrollToDestination(cc.p(0, this._innerContainer.getPositionY()), time, attenuated);
868     },
869 
870     /**
871      * Scroll inner container to right boundary of ScrollView.
872      * @param {Number} time
873      * @param {Boolean} attenuated
874      */
875     scrollToRight: function (time, attenuated) {
876         this._startAutoScrollToDestination(
877             cc.p(this._contentSize.width - this._innerContainer.getContentSize().width, this._innerContainer.getPositionY()), time, attenuated);
878     },
879 
880     /**
881      * Scroll inner container to top and left boundary of ScrollView.
882      * @param {Number} time
883      * @param {Boolean} attenuated
884      */
885     scrollToTopLeft: function (time, attenuated) {
886         if (this._direction !== ccui.ScrollView.DIR_BOTH) {
887             cc.log("Scroll direction is not both!");
888             return;
889         }
890         this._startAutoScrollToDestination(cc.p(0, this._contentSize.height - this._innerContainer.getContentSize().height), time, attenuated);
891     },
892 
893     /**
894      * Scroll inner container to top and right boundary of ScrollView.
895      * @param {Number} time
896      * @param {Boolean} attenuated
897      */
898     scrollToTopRight: function (time, attenuated) {
899         if (this._direction !== ccui.ScrollView.DIR_BOTH) {
900             cc.log("Scroll direction is not both!");
901             return;
902         }
903         var inSize = this._innerContainer.getContentSize();
904         this._startAutoScrollToDestination(cc.p(this._contentSize.width - inSize.width,
905                 this._contentSize.height - inSize.height), time, attenuated);
906     },
907 
908     /**
909      * Scroll inner container to bottom and left boundary of ScrollView.
910      * @param {Number} time
911      * @param {Boolean} attenuated
912      */
913     scrollToBottomLeft: function (time, attenuated) {
914         if (this._direction !== ccui.ScrollView.DIR_BOTH) {
915             cc.log("Scroll direction is not both!");
916             return;
917         }
918         this._startAutoScrollToDestination(cc.p(0, 0), time, attenuated);
919     },
920 
921     /**
922      * Scroll inner container to bottom and right boundary of ScrollView.
923      * @param {Number} time
924      * @param {Boolean} attenuated
925      */
926     scrollToBottomRight: function (time, attenuated) {
927         if (this._direction !== ccui.ScrollView.DIR_BOTH) {
928             cc.log("Scroll direction is not both!");
929             return;
930         }
931         this._startAutoScrollToDestination(cc.p(this._contentSize.width - this._innerContainer.getContentSize().width, 0), time, attenuated);
932     },
933 
934     /**
935      * Scroll inner container to vertical percent position of ScrollView.
936      * @param {Number} percent
937      * @param {Number} time
938      * @param {Boolean} attenuated
939      */
940     scrollToPercentVertical: function (percent, time, attenuated) {
941         var minY = this._contentSize.height - this._innerContainer.getContentSize().height;
942         var h = -minY;
943         this._startAutoScrollToDestination(cc.p(this._innerContainer.getPositionX(), minY + percent * h / 100), time, attenuated);
944     },
945 
946     /**
947      * Scroll inner container to horizontal percent position of ScrollView.
948      * @param {Number} percent
949      * @param {Number} time
950      * @param {Boolean} attenuated
951      */
952     scrollToPercentHorizontal: function (percent, time, attenuated) {
953         var w = this._innerContainer.getContentSize().width - this._contentSize.width;
954         this._startAutoScrollToDestination(cc.p(-(percent * w / 100), this._innerContainer.getPositionY()), time, attenuated);
955     },
956 
957     /**
958      * Scroll inner container to both _direction percent position of ScrollView.
959      * @param {cc.Point} percent
960      * @param {Number} time
961      * @param {Boolean} attenuated
962      */
963     scrollToPercentBothDirection: function (percent, time, attenuated) {
964         if (this._direction !== ccui.ScrollView.DIR_BOTH)
965             return;
966         var minY = this._contentSize.height - this._innerContainer.getContentSize().height;
967         var h = -minY;
968         var w = this._innerContainer.getContentSize().width - this._contentSize.width;
969         this._startAutoScrollToDestination(cc.p(-(percent.x * w / 100), minY + percent.y * h / 100), time, attenuated);
970     },
971 
972     /**
973      * Move inner container to bottom boundary of ScrollView.
974      */
975     jumpToBottom: function () {
976         this._jumpToDestination(this._innerContainer.getPositionX(), 0);
977     },
978 
979     /**
980      * Move inner container to top boundary of ScrollView.
981      */
982     jumpToTop: function () {
983         this._jumpToDestination(this._innerContainer.getPositionX(), this._contentSize.height - this._innerContainer.getContentSize().height);
984     },
985 
986     /**
987      * Move inner container to left boundary of ScrollView.
988      */
989     jumpToLeft: function () {
990         this._jumpToDestination(0, this._innerContainer.getPositionY());
991     },
992 
993     /**
994      * Move inner container to right boundary of ScrollView.
995      */
996     jumpToRight: function () {
997         this._jumpToDestination(this._contentSize.width - this._innerContainer.getContentSize().width, this._innerContainer.getPositionY());
998     },
999 
1000     /**
1001      * Move inner container to top and left boundary of ScrollView.
1002      */
1003     jumpToTopLeft: function () {
1004         if (this._direction !== ccui.ScrollView.DIR_BOTH) {
1005             cc.log("Scroll _direction is not both!");
1006             return;
1007         }
1008         this._jumpToDestination(0, this._contentSize.height - this._innerContainer.getContentSize().height);
1009     },
1010 
1011     /**
1012      * Move inner container to top and right boundary of ScrollView.
1013      */
1014     jumpToTopRight: function () {
1015         if (this._direction !== ccui.ScrollView.DIR_BOTH) {
1016             cc.log("Scroll _direction is not both!");
1017             return;
1018         }
1019         var inSize = this._innerContainer.getContentSize();
1020         this._jumpToDestination(this._contentSize.width - inSize.width, this._contentSize.height - inSize.height);
1021     },
1022 
1023     /**
1024      * Move inner container to bottom and left boundary of ScrollView.
1025      */
1026     jumpToBottomLeft: function () {
1027         if (this._direction !== ccui.ScrollView.DIR_BOTH) {
1028             cc.log("Scroll _direction is not both!");
1029             return;
1030         }
1031         this._jumpToDestination(0, 0);
1032     },
1033 
1034     /**
1035      * Move inner container to bottom and right boundary of ScrollView.
1036      */
1037     jumpToBottomRight: function () {
1038         if (this._direction !== ccui.ScrollView.DIR_BOTH) {
1039             cc.log("Scroll _direction is not both!");
1040             return;
1041         }
1042         this._jumpToDestination(this._contentSize.width - this._innerContainer.getContentSize().width, 0);
1043     },
1044 
1045     /**
1046      * Move inner container to vertical percent position of ScrollView.
1047      * @param {Number} percent The destination vertical percent, accept value between 0 - 100
1048      */
1049     jumpToPercentVertical: function (percent) {
1050         var minY = this._contentSize.height - this._innerContainer.getContentSize().height;
1051         var h = -minY;
1052         this._jumpToDestination(this._innerContainer.getPositionX(), minY + percent * h / 100);
1053     },
1054 
1055     /**
1056      * Move inner container to horizontal percent position of ScrollView.
1057      * @param {Number} percent The destination vertical percent, accept value between 0 - 100
1058      */
1059     jumpToPercentHorizontal: function (percent) {
1060         var w = this._innerContainer.getContentSize().width - this._contentSize.width;
1061         this._jumpToDestination(-(percent * w / 100), this._innerContainer.getPositionY());
1062     },
1063 
1064     /**
1065      * Move inner container to both _direction percent position of ScrollView.
1066      * @param {cc.Point} percent The destination vertical percent, accept value between 0 - 100
1067      */
1068     jumpToPercentBothDirection: function (percent) {
1069         if (this._direction !== ccui.ScrollView.DIR_BOTH)
1070             return;
1071         var inSize = this._innerContainer.getContentSize();
1072         var minY = this._contentSize.height - inSize.height;
1073         var h = -minY;
1074         var w = inSize.width - this._contentSize.width;
1075         this._jumpToDestination(-(percent.x * w / 100), minY + percent.y * h / 100);
1076     },
1077 
1078     _gatherTouchMove: function(delta)
1079     {
1080         var NUMBER_OF_GATHERED_TOUCHES_FOR_MOVE_SPEED = 5;
1081         while(this._touchMoveDisplacements.length  >= NUMBER_OF_GATHERED_TOUCHES_FOR_MOVE_SPEED)
1082         {
1083             this._touchMoveDisplacements.splice(0,1);
1084             this._touchMoveTimeDeltas.splice(0,1)
1085         }
1086         this._touchMoveDisplacements.push(delta);
1087 
1088         var timestamp = (new Date()).getTime();
1089         this._touchMoveTimeDeltas.push((timestamp - this._touchMovePreviousTimestamp) / 1000);
1090         this._touchMovePreviousTimestamp = timestamp;
1091     },
1092 
1093     _handlePressLogic: function (touch) {
1094         this._bePressed = true;
1095         this._autoScrolling = false;
1096 
1097         // Clear gathered touch move information
1098 
1099         this._touchMovePreviousTimestamp = (new Date()).getTime();
1100         this._touchMoveDisplacements.length = 0;
1101         this._touchMoveTimeDeltas.length = 0;
1102 
1103 
1104         if(this._verticalScrollBar)
1105         {
1106            this._verticalScrollBar.onTouchBegan();
1107         }
1108         if(this._horizontalScrollBar)
1109         {
1110             this._horizontalScrollBar.onTouchBegan();
1111         }
1112     },
1113 
1114     _handleMoveLogic: function (touch) {
1115         var touchPositionInNodeSpace = this.convertToNodeSpace(touch.getLocation()),
1116             previousTouchPositionInNodeSpace = this.convertToNodeSpace(touch.getPreviousLocation());
1117         var delta = cc.pSub(touchPositionInNodeSpace, previousTouchPositionInNodeSpace);
1118 
1119         this._scrollChildren(delta);
1120         this._gatherTouchMove(delta);
1121     },
1122 
1123     _handleReleaseLogic: function (touch) {
1124 
1125         var touchPositionInNodeSpace = this.convertToNodeSpace(touch.getLocation()),
1126             previousTouchPositionInNodeSpace = this.convertToNodeSpace(touch.getPreviousLocation());
1127         var delta = cc.pSub(touchPositionInNodeSpace, previousTouchPositionInNodeSpace);
1128 
1129         this._gatherTouchMove(delta);
1130 
1131         this._bePressed = false;
1132 
1133         var bounceBackStarted = this._startBounceBackIfNeeded();
1134         if(!bounceBackStarted && this.inertiaScrollEnabled)
1135         {
1136             var touchMoveVelocity = this._calculateTouchMoveVelocity();
1137             if(touchMoveVelocity.x !== 0 || touchMoveVelocity.y !== 0)
1138             {
1139                 this._startInertiaScroll(touchMoveVelocity);
1140             }
1141         }
1142 
1143         if(this._verticalScrollBar)
1144         {
1145             this._verticalScrollBar.onTouchEnded();
1146         }
1147         if(this._horizontalScrollBar)
1148         {
1149             this._horizontalScrollBar.onTouchEnded();
1150         }
1151     },
1152 
1153     /**
1154      * The touch began event callback handler of ccui.ScrollView.
1155      * @param {cc.Touch} touch
1156      * @param {cc.Event} event
1157      * @returns {boolean}
1158      */
1159     onTouchBegan: function (touch, event) {
1160         var pass = ccui.Layout.prototype.onTouchBegan.call(this, touch, event);
1161         if(!this._isInterceptTouch){
1162             if (this._hit)
1163                 this._handlePressLogic(touch);
1164         }
1165         return pass;
1166     },
1167 
1168     /**
1169      * The touch moved event callback handler of ccui.ScrollView.
1170      * @param {cc.Touch} touch
1171      * @param {cc.Event} event
1172      */
1173     onTouchMoved: function (touch, event) {
1174         ccui.Layout.prototype.onTouchMoved.call(this, touch, event);
1175         if(!this._isInterceptTouch)
1176             this._handleMoveLogic(touch);
1177     },
1178 
1179     /**
1180      * The touch ended event callback handler of ccui.ScrollView.
1181      * @param {cc.Touch} touch
1182      * @param {cc.Event} event
1183      */
1184     onTouchEnded: function (touch, event) {
1185         ccui.Layout.prototype.onTouchEnded.call(this, touch, event);
1186         if(!this._isInterceptTouch)
1187             this._handleReleaseLogic(touch);
1188         this._isInterceptTouch = false;
1189     },
1190 
1191     /**
1192      * The touch canceled event callback of ccui.ScrollView.
1193      * @param {cc.Touch} touch
1194      * @param {cc.Event} event
1195      */
1196     onTouchCancelled: function (touch, event) {
1197         ccui.Layout.prototype.onTouchCancelled.call(this, touch, event);
1198         if (!this._isInterceptTouch)
1199             this._handleReleaseLogic(touch);
1200         this._isInterceptTouch = false;
1201     },
1202 
1203     /**
1204      * The update callback handler.
1205      * @param {Number} dt
1206      */
1207     update: function (dt) {
1208         if (this._autoScrolling)
1209             this._processAutoScrolling(dt);
1210     },
1211 
1212     /**
1213      * Intercept touch event, handle its child's touch event.
1214      * @override
1215      * @param {number} event event type
1216      * @param {ccui.Widget} sender
1217      * @param {cc.Touch} touch
1218      */
1219     interceptTouchEvent: function (event, sender, touch) {
1220         if(!this._touchEnabled)
1221         {
1222             ccui.Layout.prototype.interceptTouchEvent.call(this, event, sender, touch);
1223             return;
1224         }
1225 
1226         if(this._direction === ccui.ScrollView.DIR_NONE)
1227             return;
1228 
1229         var touchPoint = touch.getLocation();
1230         switch (event) {
1231             case ccui.Widget.TOUCH_BEGAN:
1232                 this._isInterceptTouch = true;
1233                 this._touchBeganPosition.x = touchPoint.x;
1234                 this._touchBeganPosition.y = touchPoint.y;
1235                 this._handlePressLogic(touch);
1236                 break;
1237             case ccui.Widget.TOUCH_MOVED:
1238                 var offset = cc.pLength(cc.pSub(sender.getTouchBeganPosition(), touchPoint));
1239                 this._touchMovePosition.x = touchPoint.x;
1240                 this._touchMovePosition.y = touchPoint.y;
1241                 if (offset > this._childFocusCancelOffset) {
1242                     sender.setHighlighted(false);
1243                     this._handleMoveLogic(touch);
1244                 }
1245                 break;
1246             case ccui.Widget.TOUCH_CANCELED:
1247             case ccui.Widget.TOUCH_ENDED:
1248                 this._touchEndPosition.x = touchPoint.x;
1249                 this._touchEndPosition.y = touchPoint.y;
1250                 this._handleReleaseLogic(touch);
1251                 if (sender.isSwallowTouches())
1252                     this._isInterceptTouch = false;
1253                 break;
1254         }
1255     },
1256 
1257     _processScrollEvent: function(_directionEvent, bounce)
1258     {
1259         var event = 0;
1260 
1261         switch(_directionEvent)
1262         {
1263             case ccui.ScrollView.MOVEDIR_TOP:
1264                 event = (bounce ? ccui.ScrollView.EVENT_BOUNCE_TOP : ccui.ScrollView.EVENT_SCROLL_TO_TOP);
1265                 break;
1266             case ccui.ScrollView.MOVEDIR_BOTTOM:
1267                 event = (bounce ? ccui.ScrollView.EVENT_BOUNCE_BOTTOM : ccui.ScrollView.EVENT_SCROLL_TO_BOTTOM);
1268                 break;
1269             case ccui.ScrollView.MOVEDIR_LEFT:
1270                 event = (bounce ? ccui.ScrollView.EVENT_BOUNCE_LEFT : ccui.ScrollView.EVENT_SCROLL_TO_LEFT);
1271                 break;
1272             case ccui.ScrollView.MOVEDIR_RIGHT:
1273                 event = (bounce ? ccui.ScrollView.EVENT_BOUNCE_RIGHT : ccui.ScrollView.EVENT_SCROLL_TO_RIGHT);
1274                 break;
1275         }
1276 
1277         this._dispatchEvent(event);
1278     },
1279 
1280     _processScrollingEvent: function()
1281     {
1282         this._dispatchEvent( ccui.ScrollView.EVENT_SCROLLING);
1283     },
1284 
1285     _dispatchEvent: function(event)
1286     {
1287         if(this._scrollViewEventSelector){
1288             if (this._scrollViewEventListener)
1289                 this._scrollViewEventSelector.call(this._scrollViewEventListener, this, event);
1290             else
1291                 this._scrollViewEventSelector(this, event);
1292         }
1293         if(this._ccEventCallback)
1294             this._ccEventCallback(this, event);
1295     },
1296 
1297     /**
1298      * Adds callback function called ScrollView event triggered
1299      * @param {Function} selector
1300      * @param {Object} [target=]
1301      * @deprecated since v3.0, please use addEventListener instead.
1302      */
1303     addEventListenerScrollView: function (selector, target) {
1304         this._scrollViewEventSelector = selector;
1305         this._scrollViewEventListener = target;
1306     },
1307 
1308     /**
1309      * Adds callback function called ScrollView event triggered
1310      * @param {Function} selector
1311      */
1312     addEventListener: function(selector){
1313         this._ccEventCallback = selector;
1314     },
1315 
1316     /**
1317      * Changes scroll _direction of ScrollView.
1318      * @param {ccui.ScrollView.DIR_NONE | ccui.ScrollView.DIR_VERTICAL | ccui.ScrollView.DIR_HORIZONTAL | ccui.ScrollView.DIR_BOTH} dir
1319      *   Direction::VERTICAL means vertical scroll, Direction::HORIZONTAL means horizontal scroll
1320      */
1321     setDirection: function (dir) {
1322         this._direction = dir;
1323 
1324         if(this._scrollBarEnabled)
1325         {
1326             this._removeScrollBar();
1327             this._initScrollBar();
1328         }
1329     },
1330 
1331     /**
1332      * Returns scroll direction of ScrollView.
1333      * @returns {ccui.ScrollView.DIR_NONE | ccui.ScrollView.DIR_VERTICAL | ccui.ScrollView.DIR_HORIZONTAL | ccui.ScrollView.DIR_BOTH}
1334      */
1335     getDirection: function () {
1336         return this._direction;
1337     },
1338 
1339     /**
1340      * Sets bounce enabled
1341      * @param {Boolean} enabled
1342      */
1343     setBounceEnabled: function (enabled) {
1344         this.bounceEnabled = enabled;
1345     },
1346 
1347     /**
1348      * Returns whether bounce is enabled
1349      * @returns {boolean}
1350      */
1351     isBounceEnabled: function () {
1352         return this.bounceEnabled;
1353     },
1354 
1355     /**
1356      * Sets inertiaScroll enabled
1357      * @param {boolean} enabled
1358      */
1359     setInertiaScrollEnabled: function (enabled) {
1360         this.inertiaScrollEnabled = enabled;
1361     },
1362 
1363     /**
1364      * Returns whether inertiaScroll is enabled
1365      * @returns {boolean}
1366      */
1367     isInertiaScrollEnabled: function () {
1368         return this.inertiaScrollEnabled;
1369     },
1370 
1371     /**
1372      * Toggle scroll bar enabled.
1373      * @param {boolean} enabled True if enable scroll bar, false otherwise.
1374      */
1375     setScrollBarEnabled: function(enabled)
1376     {
1377         if(this._scrollBarEnabled === enabled)
1378         {
1379             return;
1380         }
1381 
1382         if(this._scrollBarEnabled)
1383         {
1384             this._removeScrollBar();
1385         }
1386         this._scrollBarEnabled = enabled;
1387         if(this._scrollBarEnabled)
1388         {
1389             this._initScrollBar();
1390         }
1391     },
1392     /**
1393      * Query scroll bar state.
1394      * @returns {boolean} True if scroll bar is enabled, false otherwise.
1395      */
1396     isScrollBarEnabled: function()
1397     {
1398         return this._scrollBarEnabled;
1399     },
1400 
1401     /**
1402      * Set the scroll bar positions from the left-bottom corner (horizontal) and right-top corner (vertical).
1403      * @param {cc.Point} positionFromCorner The position from the left-bottom corner (horizontal) and right-top corner (vertical).
1404      */
1405     setScrollBarPositionFromCorner: function(positionFromCorner)
1406     {
1407         if(this._direction !== ccui.ScrollView.DIR_HORIZONTAL)
1408         {
1409             this.setScrollBarPositionFromCornerForVertical(positionFromCorner);
1410         }
1411         if(this._direction !== ccui.ScrollView.DIR_VERTICAL)
1412         {
1413             this.setScrollBarPositionFromCornerForHorizontal(positionFromCorner);
1414         }
1415     },
1416 
1417     /**
1418      * Set the vertical scroll bar position from right-top corner.
1419      * @param {cc.Point} positionFromCorner The position from right-top corner
1420      */
1421     setScrollBarPositionFromCornerForVertical: function(positionFromCorner)
1422     {
1423         cc.assert(this._scrollBarEnabled, "Scroll bar should be enabled!");
1424         cc.assert(this._direction !== ccui.ScrollView.DIR_HORIZONTAL, "Scroll view doesn't have a vertical scroll bar!");
1425         this._verticalScrollBar.setPositionFromCorner(positionFromCorner);
1426     },
1427 
1428     /**
1429      * Get the vertical scroll bar's position from right-top corner.
1430      * @returns {cc.Point}
1431      */
1432     getScrollBarPositionFromCornerForVertical: function()
1433     {
1434         cc.assert(this._scrollBarEnabled, "Scroll bar should be enabled!");
1435         cc.assert(this._direction !== ccui.ScrollView.DIR_HORIZONTAL, "Scroll view doesn't have a vertical scroll bar!");
1436         return this._verticalScrollBar.getPositionFromCorner();
1437     },
1438 
1439     /**
1440      * Set the horizontal scroll bar position from left-bottom corner.
1441      * @param {cc.Point} positionFromCorner The position from left-bottom corner
1442      */
1443     setScrollBarPositionFromCornerForHorizontal: function(positionFromCorner)
1444     {
1445         cc.assert(this._scrollBarEnabled, "Scroll bar should be enabled!");
1446         cc.assert(this._direction !== ccui.ScrollView.DIR_VERTICAL, "Scroll view doesn't have a horizontal scroll bar!");
1447         this._horizontalScrollBar.setPositionFromCorner(positionFromCorner);
1448     },
1449 
1450     /**
1451      * Get the horizontal scroll bar's position from right-top corner.
1452      * @returns {cc.Point}
1453      */
1454     getScrollBarPositionFromCornerForHorizontal: function()
1455     {
1456         cc.assert(this._scrollBarEnabled, "Scroll bar should be enabled!");
1457         cc.assert(this._direction !== ccui.ScrollView.DIR_VERTICAL, "Scroll view doesn't have a horizontal scroll bar!");
1458         return this._horizontalScrollBar.getPositionFromCorner();
1459     },
1460 
1461     /**
1462      * Set the scroll bar's width
1463      * @param {number} width The scroll bar's width
1464      */
1465     setScrollBarWidth: function(width)
1466     {
1467         cc.assert(this._scrollBarEnabled, "Scroll bar should be enabled!");
1468         if(this._verticalScrollBar)
1469         {
1470             this._verticalScrollBar.setWidth(width);
1471         }
1472         if(this._horizontalScrollBar)
1473         {
1474             this._horizontalScrollBar.setWidth(width);
1475         }
1476     },
1477 
1478     /**
1479      * Get the scroll bar's width
1480      * @returns {number} the scroll bar's width
1481      */
1482     getScrollBarWidth: function()
1483     {
1484         cc.assert(this._scrollBarEnabled, "Scroll bar should be enabled!");
1485         if(this._verticalScrollBar)
1486         {
1487             return this._verticalScrollBar.getWidth();
1488         }
1489         if(this._horizontalScrollBar)
1490         {
1491             return this._horizontalScrollBar.getWidth();
1492         }
1493         return 0;
1494     },
1495 
1496     /**
1497      * Set the scroll bar's color
1498      * @param {cc.Color} color the scroll bar's color
1499      */
1500     setScrollBarColor: function(color)
1501     {
1502         cc.assert(this._scrollBarEnabled, "Scroll bar should be enabled!");
1503         if(this._verticalScrollBar)
1504         {
1505             this._verticalScrollBar.setColor(color);
1506         }
1507         if(this._horizontalScrollBar)
1508         {
1509             this._horizontalScrollBar.setColor(color);
1510         }
1511     },
1512 
1513     /**
1514      * Get the scroll bar's color
1515      * @returns {cc.Color} the scroll bar's color
1516      */
1517     getScrollBarColor: function()
1518     {
1519         cc.assert(this._scrollBarEnabled, "Scroll bar should be enabled!");
1520         if(this._verticalScrollBar)
1521         {
1522             this._verticalScrollBar.getColor();
1523         }
1524         if(this._horizontalScrollBar)
1525         {
1526             this._horizontalScrollBar.getColor();
1527         }
1528         return cc.color.WHITE;
1529     },
1530 
1531     /**
1532      * Set the scroll bar's opacity
1533      * @param {number} opacity the scroll bar's opacity
1534      */
1535     setScrollBarOpacity: function(opacity)
1536     {
1537         cc.assert(this._scrollBarEnabled, "Scroll bar should be enabled!");
1538         if(this._verticalScrollBar)
1539         {
1540             this._verticalScrollBar.opacity = opacity;
1541         }
1542         if(this._horizontalScrollBar)
1543         {
1544             this._horizontalScrollBar.opacity = opacity;
1545         }
1546     },
1547 
1548     /**
1549      * Get the scroll bar's opacity
1550      * @returns {number}
1551      */
1552     getScrollBarOpacity: function()
1553     {
1554         cc.assert(this._scrollBarEnabled, "Scroll bar should be enabled!");
1555         if(this._verticalScrollBar)
1556         {
1557             return this._verticalScrollBar.opacity;
1558         }
1559         if(this._horizontalScrollBar)
1560         {
1561             return this._horizontalScrollBar.opacity;
1562         }
1563         return -1;
1564     },
1565 
1566     /**
1567      * Set scroll bar auto hide state
1568      * @param {boolean} autoHideEnabled scroll bar auto hide state
1569      */
1570     setScrollBarAutoHideEnabled: function(autoHideEnabled)
1571     {
1572         cc.assert(this._scrollBarEnabled, "Scroll bar should be enabled!");
1573         if(this._verticalScrollBar)
1574         {
1575             this._verticalScrollBar.autoHideEnabled = autoHideEnabled;
1576         }
1577         if(this._horizontalScrollBar)
1578         {
1579             this._horizontalScrollBar.autoHideEnabled = autoHideEnabled;
1580         }
1581     },
1582 
1583     /**
1584      * Query scroll bar auto hide state
1585      * @returns {boolean}
1586      */
1587     isScrollBarAutoHideEnabled: function()
1588     {
1589         cc.assert(this._scrollBarEnabled, "Scroll bar should be enabled!");
1590         if(this._verticalScrollBar)
1591         {
1592             return this._verticalScrollBar.autoHideEnabled;
1593         }
1594         if(this._horizontalScrollBar)
1595         {
1596             return this._horizontalScrollBar.autoHideEnabled;
1597         }
1598         return false;
1599     },
1600 
1601     /**
1602      * Set scroll bar auto hide time
1603      * @param {number} autoHideTime scroll bar auto hide state
1604      */
1605     setScrollBarAutoHideTime: function(autoHideTime)
1606     {
1607         cc.assert(this._scrollBarEnabled, "Scroll bar should be enabled!");
1608         if(this._verticalScrollBar)
1609         {
1610             this._verticalScrollBar.autoHideTime = autoHideTime;
1611         }
1612         if(this._horizontalScrollBar)
1613         {
1614             this._horizontalScrollBar.autoHideTime = autoHideTime;
1615         }
1616     },
1617 
1618     /**
1619      * Get the scroll bar's auto hide time
1620      * @returns {number}
1621      */
1622     getScrollBarAutoHideTime: function()
1623     {
1624         cc.assert(this._scrollBarEnabled, "Scroll bar should be enabled!");
1625         if(this._verticalScrollBar)
1626         {
1627             return this._verticalScrollBar.autoHideTime;
1628         }
1629         if(this._horizontalScrollBar)
1630         {
1631             return this._horizontalScrollBar.autoHideTime;
1632         }
1633         return 0;
1634     },
1635 
1636     /**
1637      * Gets inner container of ScrollView. Inner container is the container of ScrollView's children.
1638      * @returns {ccui.Layout}
1639      */
1640     getInnerContainer: function () {
1641         return this._innerContainer;
1642     },
1643 
1644     /**
1645      * Sets LayoutType of ccui.ScrollView.
1646      * @param {ccui.Layout.ABSOLUTE|ccui.Layout.LINEAR_VERTICAL|ccui.Layout.LINEAR_HORIZONTAL|ccui.Layout.RELATIVE} type
1647      */
1648     setLayoutType: function (type) {
1649         this._innerContainer.setLayoutType(type);
1650     },
1651 
1652     /**
1653      * Returns the layout type of ccui.ScrollView.
1654      * @returns {ccui.Layout.ABSOLUTE|ccui.Layout.LINEAR_VERTICAL|ccui.Layout.LINEAR_HORIZONTAL|ccui.Layout.RELATIVE}
1655      */
1656     getLayoutType: function () {
1657         return this._innerContainer.getLayoutType();
1658     },
1659 
1660     _doLayout: function () {
1661         if (!this._doLayoutDirty)
1662             return;
1663         this._doLayoutDirty = false;
1664     },
1665 
1666     /**
1667      * Returns the "class name" of ccui.ScrollView.
1668      * @returns {string}
1669      */
1670     getDescription: function () {
1671         return "ScrollView";
1672     },
1673 
1674     _createCloneInstance: function(){
1675         return new ccui.ScrollView();
1676     },
1677 
1678     _copyClonedWidgetChildren: function (model) {
1679         ccui.Layout.prototype._copyClonedWidgetChildren.call(this, model);
1680     },
1681 
1682     _copySpecialProperties: function (scrollView) {
1683         if(scrollView instanceof ccui.ScrollView) {
1684             ccui.Layout.prototype._copySpecialProperties.call(this, scrollView);
1685             this.setInnerContainerSize(scrollView.getInnerContainerSize());
1686             this.setInnerContainerPosition(scrollView.getInnerContainerPosition());
1687             this.setDirection(scrollView._direction);
1688 
1689             this._topBoundary = scrollView._topBoundary;
1690             this._bottomBoundary = scrollView._bottomBoundary;
1691             this._leftBoundary = scrollView._leftBoundary;
1692             this._rightBoundary = scrollView._rightBoundary;
1693             this._bePressed = scrollView._bePressed;
1694             this._childFocusCancelOffset = scrollView._childFocusCancelOffset;
1695             this._touchMoveDisplacements = scrollView._touchMoveDisplacements;
1696             this._touchMoveTimeDeltas = scrollView._touchMoveTimeDeltas;
1697             this._touchMovePreviousTimestamp = scrollView._touchMovePreviousTimestamp;
1698             this._autoScrolling = scrollView._autoScrolling;
1699             this._autoScrollAttenuate = scrollView._autoScrollAttenuate;
1700             this._autoScrollStartPosition = scrollView._autoScrollStartPosition;
1701             this._autoScrollTargetDelta = scrollView._autoScrollTargetDelta;
1702             this._autoScrollTotalTime = scrollView._autoScrollTotalTime;
1703             this._autoScrollAccumulatedTime = scrollView._autoScrollAccumulatedTime;
1704             this._autoScrollCurrentlyOutOfBoundary = scrollView._autoScrollCurrentlyOutOfBoundary;
1705             this._autoScrollBraking = scrollView._autoScrollBraking;
1706             this._autoScrollBrakingStartPosition = scrollView._autoScrollBrakingStartPosition;
1707 
1708             this.setBounceEnabled(scrollView.bounceEnabled);
1709             this.setInertiaScrollEnabled(scrollView.inertiaScrollEnabled);
1710 
1711             this._scrollViewEventListener = scrollView._scrollViewEventListener;
1712             this._scrollViewEventSelector = scrollView._scrollViewEventSelector;
1713             this._ccEventCallback = scrollView._ccEventCallback;
1714 
1715             this.setScrollBarEnabled(scrollView.isScrollBarEnabled());
1716             if(this.isScrollBarEnabled())
1717             {
1718                 if(this._direction !== ccui.ScrollView.DIR_HORIZONTAL)
1719                 {
1720                     this.setScrollBarPositionFromCornerForVertical(scrollView.getScrollBarPositionFromCornerForVertical());
1721                 }
1722                 if(this._direction !== ccui.ScrollView.DIR_VERTICAL)
1723                 {
1724                     this.setScrollBarPositionFromCornerForHorizontal(scrollView.getScrollBarPositionFromCornerForHorizontal());
1725                 }
1726                 this.setScrollBarWidth(scrollView.getScrollBarWidth());
1727                 this.setScrollBarColor(scrollView.getScrollBarColor());
1728                 this.setScrollBarAutoHideEnabled(scrollView.isScrollBarAutoHideEnabled());
1729                 this.setScrollBarAutoHideTime(scrollView.getScrollBarAutoHideTime());
1730             }
1731         }
1732     },
1733 
1734     _initScrollBar: function()
1735     {
1736         if(this._direction !== ccui.ScrollView.DIR_HORIZONTAL && !this._verticalScrollBar)
1737         {
1738             this._verticalScrollBar = new ccui.ScrollViewBar(this, ccui.ScrollView.DIR_VERTICAL);
1739             this.addProtectedChild(this._verticalScrollBar, 2);
1740         }
1741         if(this._direction !== ccui.ScrollView.DIR_VERTICAL && !this._horizontalScrollBar)
1742         {
1743             this._horizontalScrollBar = new ccui.ScrollViewBar(this, ccui.ScrollView.DIR_HORIZONTAL);
1744             this.addProtectedChild(this._horizontalScrollBar, 2);
1745         }
1746     },
1747 
1748     _removeScrollBar: function()
1749     {
1750         if(this._verticalScrollBar)
1751         {
1752             this.removeProtectedChild(this._verticalScrollBar);
1753             this._verticalScrollBar = null;
1754         }
1755         if(this._horizontalScrollBar)
1756         {
1757             this.removeProtectedChild(this._horizontalScrollBar);
1758             this._horizontalScrollBar = null;
1759         }
1760     },
1761 
1762     /**
1763      * Returns a node by tag
1764      * @param {Number} tag
1765      * @returns {cc.Node}
1766      * @deprecated  since v3.0, please use getChildByTag instead.
1767      */
1768     getNodeByTag: function (tag) {
1769         return this._innerContainer.getNodeByTag(tag);
1770     },
1771 
1772     /**
1773      * Returns all nodes of inner container
1774      * @returns {Array}
1775      * @deprecated since v3.0, please use getChildren instead.
1776      */
1777     getNodes: function () {
1778         return this._innerContainer.getNodes();
1779     },
1780 
1781     /**
1782      * Removes a node from ccui.ScrollView.
1783      * @param {cc.Node} node
1784      * @deprecated since v3.0, please use removeChild instead.
1785      */
1786     removeNode: function (node) {
1787         this._innerContainer.removeNode(node);
1788     },
1789 
1790     /**
1791      * Removes a node by tag
1792      * @param {Number} tag
1793      * @deprecated since v3.0, please use removeChildByTag instead.
1794      */
1795     removeNodeByTag: function (tag) {
1796         this._innerContainer.removeNodeByTag(tag);
1797     },
1798 
1799     /**
1800      * Remove all node from ccui.ScrollView.
1801      * @deprecated since v3.0, please use removeAllChildren instead.
1802      */
1803     removeAllNodes: function () {
1804         this._innerContainer.removeAllNodes();
1805     },
1806 
1807     /**
1808      * Add node for scrollView
1809      * @param {cc.Node} node
1810      * @param {Number} zOrder
1811      * @param {Number} tag
1812      * @deprecated since v3.0, please use addChild instead.
1813      */
1814     addNode: function (node, zOrder, tag) {
1815         this._innerContainer.addNode(node, zOrder, tag);
1816     }
1817 });
1818 
1819 var _p = ccui.ScrollView.prototype;
1820 
1821 // Extended properties
1822 /** @expose */
1823 _p.innerWidth;
1824 cc.defineGetterSetter(_p, "innerWidth", _p._getInnerWidth, _p._setInnerWidth);
1825 /** @expose */
1826 _p.innerHeight;
1827 cc.defineGetterSetter(_p, "innerHeight", _p._getInnerHeight, _p._setInnerHeight);
1828 /** @expose */
1829 _p.direction;
1830 cc.defineGetterSetter(_p, "direction", _p.getDirection, _p.setDirection);
1831 /** @expose */
1832 _p.touchTotalTimeThreshold;
1833 cc.defineGetterSetter(_p, "touchTotalTimeThreshold", _p.getTouchTotalTimeThreshold, _p.setTouchTotalTimeThreshold);
1834 _p = null;
1835 /**
1836  * allocates and initializes a UIScrollView.
1837  * @deprecated since v3.0, please use new ccui.ScrollView() instead.
1838  * @return {ccui.ScrollView}
1839  */
1840 ccui.ScrollView.create = function () {
1841     return new ccui.ScrollView();
1842 };
1843 
1844 // Constants
1845 //ScrollView direction
1846 /**
1847  * The none flag of ccui.ScrollView's direction.
1848  * @constant
1849  * @type {number}
1850  */
1851 ccui.ScrollView.DIR_NONE = 0;
1852 /**
1853  * The vertical flag of ccui.ScrollView's direction.
1854  * @constant
1855  * @type {number}
1856  */
1857 ccui.ScrollView.DIR_VERTICAL = 1;
1858 /**
1859  * The horizontal flag of ccui.ScrollView's direction.
1860  * @constant
1861  * @type {number}
1862  */
1863 ccui.ScrollView.DIR_HORIZONTAL = 2;
1864 /**
1865  * The both flag of ccui.ScrollView's direction.
1866  * @constant
1867  * @type {number}
1868  */
1869 ccui.ScrollView.DIR_BOTH = 3;
1870 
1871 //ScrollView event
1872 /**
1873  * The flag scroll to top of ccui.ScrollView's event.
1874  * @constant
1875  * @type {number}
1876  */
1877 ccui.ScrollView.EVENT_SCROLL_TO_TOP = 0;
1878 /**
1879  * The flag scroll to bottom of ccui.ScrollView's event.
1880  * @constant
1881  * @type {number}
1882  */
1883 ccui.ScrollView.EVENT_SCROLL_TO_BOTTOM = 1;
1884 /**
1885  * The flag scroll to left of ccui.ScrollView's event.
1886  * @constant
1887  * @type {number}
1888  */
1889 ccui.ScrollView.EVENT_SCROLL_TO_LEFT = 2;
1890 /**
1891  * The flag scroll to right of ccui.ScrollView's event.
1892  * @constant
1893  * @type {number}
1894  */
1895 ccui.ScrollView.EVENT_SCROLL_TO_RIGHT = 3;
1896 /**
1897  * The scrolling flag of ccui.ScrollView's event.
1898  * @constant
1899  * @type {number}
1900  */
1901 ccui.ScrollView.EVENT_SCROLLING = 4;
1902 /**
1903  * The flag bounce top of ccui.ScrollView's event.
1904  * @constant
1905  * @type {number}
1906  */
1907 ccui.ScrollView.EVENT_BOUNCE_TOP = 5;
1908 /**
1909  * The flag bounce bottom of ccui.ScrollView's event.
1910  * @constant
1911  * @type {number}
1912  */
1913 ccui.ScrollView.EVENT_BOUNCE_BOTTOM = 6;
1914 /**
1915  * The flag bounce left of ccui.ScrollView's event.
1916  * @constant
1917  * @type {number}
1918  */
1919 ccui.ScrollView.EVENT_BOUNCE_LEFT = 7;
1920 /**
1921  * The flag bounce right of ccui.ScrollView's event.
1922  * @constant
1923  * @type {number}
1924  */
1925 ccui.ScrollView.EVENT_BOUNCE_RIGHT = 8;
1926 /**
1927  * The flag container moved of ccui.ScrollView's event.
1928  * @constant
1929  * @type {number}
1930  */
1931 ccui.ScrollView.EVENT_CONTAINER_MOVED = 9;
1932 /**
1933  * The flag autoscroll ended of ccui.ScrollView's event.
1934  * @constant
1935  * @type {number}
1936  */
1937 ccui.ScrollView.EVENT_AUTOSCROLL_ENDED = 10;
1938 
1939 /**
1940  * @ignore
1941  */
1942 
1943 ccui.ScrollView.MOVEDIR_TOP = 0;
1944 ccui.ScrollView.MOVEDIR_BOTTOM = 1;
1945 ccui.ScrollView.MOVEDIR_LEFT = 2;
1946 ccui.ScrollView.MOVEDIR_RIGHT = 3;
1947