1 /****************************************************************************
  2  Copyright (c) 2008-2010 Ricardo Quesada
  3  Copyright (c) 2011-2012 cocos2d-x.org
  4  Copyright (c) 2013-2014 Chukong Technologies Inc.
  5  Copyright (c) 2010 Sangwoo Im
  6 
  7  http://www.cocos2d-x.org
  8 
  9  Permission is hereby granted, free of charge, to any person obtaining a copy
 10  of this software and associated documentation files (the "Software"), to deal
 11  in the Software without restriction, including without limitation the rights
 12  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 13  copies of the Software, and to permit persons to whom the Software is
 14  furnished to do so, subject to the following conditions:
 15 
 16  The above copyright notice and this permission notice shall be included in
 17  all copies or substantial portions of the Software.
 18 
 19  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 20  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 21  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 22  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 23  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 24  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 25  THE SOFTWARE.
 26  ****************************************************************************/
 27 
 28 /**
 29  * @ignore
 30  */
 31 cc.SCROLLVIEW_DIRECTION_NONE = -1;
 32 
 33 cc.SCROLLVIEW_DIRECTION_HORIZONTAL = 0;
 34 
 35 cc.SCROLLVIEW_DIRECTION_VERTICAL = 1;
 36 
 37 cc.SCROLLVIEW_DIRECTION_BOTH = 2;
 38 
 39 var SCROLL_DEACCEL_RATE = 0.95;
 40 var SCROLL_DEACCEL_DIST = 1.0;
 41 var BOUNCE_DURATION = 0.15;
 42 var INSET_RATIO = 0.2;
 43 var MOVE_INCH = 7.0/160.0;
 44 var BOUNCE_BACK_FACTOR = 0.35;
 45 
 46 cc.convertDistanceFromPointToInch = function(pointDis){
 47     var eglViewer = cc.view;
 48     var factor = (eglViewer.getScaleX() + eglViewer.getScaleY())/2;
 49     return (pointDis * factor) / 160;               // CCDevice::getDPI() default value
 50 };
 51 
 52 cc.ScrollViewDelegate = cc.Class.extend({
 53     scrollViewDidScroll:function (view) {
 54     },
 55     scrollViewDidZoom:function (view) {
 56     }
 57 });
 58 
 59 /**
 60  * ScrollView support for cocos2d -x.
 61  * It provides scroll view functionalities to cocos2d projects natively.
 62  * @class
 63  * @extends cc.Layer
 64  *
 65  * @property {cc.Point}                 minOffset   - <@readonly> The current container's minimum offset
 66  * @property {cc.Point}                 maxOffset   - <@readonly> The current container's maximum offset
 67  * @property {Boolean}                  bounceable  - Indicate whether the scroll view is bounceable
 68  * @property {cc.Size}                  viewSize    - The size of the scroll view
 69  * @property {cc.Layer}                 container   - The inside container of the scroll view
 70  * @property {Number}                   direction   - The direction allowed to scroll: cc.SCROLLVIEW_DIRECTION_BOTH by default, or cc.SCROLLVIEW_DIRECTION_NONE | cc.SCROLLVIEW_DIRECTION_HORIZONTAL | cc.SCROLLVIEW_DIRECTION_VERTICAL
 71  * @property {cc.ScrollViewDelegate}    delegate    - The inside container of the scroll view
 72  * @property {Boolean}           clippingToBounds   - Indicate whether the scroll view clips its children
 73  */
 74 cc.ScrollView = cc.Layer.extend(/** @lends cc.ScrollView# */{
 75     _zoomScale:0,
 76     _minZoomScale:0,
 77     _maxZoomScale:0,
 78     _delegate:null,
 79     _direction:cc.SCROLLVIEW_DIRECTION_BOTH,
 80     _dragging:false,
 81     _contentOffset:null,
 82     _container:null,
 83     _touchMoved:false,
 84     _maxInset:null,
 85     _minInset:null,
 86     _bounceable:false,
 87     _clippingToBounds:false,
 88     _scrollDistance:null,
 89     _touchPoint:null,
 90     _touchLength:0,
 91     _touches:null,
 92     _viewSize:null,
 93     _minScale:0,
 94     _maxScale:0,
 95 
 96     //scissor rect for parent, just for restoring GL_SCISSOR_BOX
 97     _parentScissorRect:null,
 98     _scissorRestored:false,
 99 
100     // cache object
101     _tmpViewRect:null,
102     _touchListener: null,
103     _className:"ScrollView",
104 
105     /**
106      * @contructor
107      * @param size
108      * @param container
109      * @returns {ScrollView}
110      */
111     ctor:function (size, container) {
112         cc.Layer.prototype.ctor.call(this);
113         this._contentOffset = cc.p(0,0);
114         this._maxInset = cc.p(0, 0);
115         this._minInset = cc.p(0, 0);
116         this._scrollDistance = cc.p(0, 0);
117         this._touchPoint = cc.p(0, 0);
118         this._touches = [];
119         this._viewSize = cc.size(0, 0);
120         this._parentScissorRect = new cc.Rect(0,0,0,0);
121         this._tmpViewRect = new cc.Rect(0,0,0,0);
122 
123         if(container != undefined)
124             this.initWithViewSize(size, container);
125         else
126             this.initWithViewSize(cc.size(200, 200), null);
127 
128     },
129 
130     init:function () {
131         return this.initWithViewSize(cc.size(200, 200), null);
132     },
133 
134     /**
135      * initialized whether success or fail
136      * @param {cc.Size} size
137      * @param {cc.Node} container
138      * @return {Boolean}
139      */
140     initWithViewSize:function (size, container) {
141         var pZero = cc.p(0,0);
142         if (cc.Layer.prototype.init.call(this)) {
143             if (!container && !this._container) {
144                 container = new cc.Layer();
145             }
146             if (container) {
147                 this.setContainer(container);
148             }
149             this.setViewSize(size);
150 
151             this.setTouchEnabled(true);
152             this._touches.length = 0;
153             this._delegate = null;
154             this._bounceable = true;
155             this._clippingToBounds = true;
156 
157             //this._container.setContentSize(CCSizeZero);
158             this._direction = cc.SCROLLVIEW_DIRECTION_BOTH;
159             this._container.setPosition(pZero);
160             this._touchLength = 0.0;
161 
162             this._minScale = this._maxScale = 1.0;
163             return true;
164         }
165         return false;
166     },
167 
168     /**
169      * Sets a new content offset. It ignores max/min offset. It just sets what's given. (just like UIKit's UIScrollView)
170      *
171      * @param {cc.Point} offset new offset
172      * @param {Number} [animated=] If true, the view will scroll to the new offset
173      */
174     setContentOffset: function (offset, animated) {
175         if (animated) { //animate scrolling
176             this.setContentOffsetInDuration(offset, BOUNCE_DURATION);
177             return;
178         }
179         if (!this._bounceable) {
180             var minOffset = this.minContainerOffset();
181             var maxOffset = this.maxContainerOffset();
182 
183             offset.x = Math.max(minOffset.x, Math.min(maxOffset.x, offset.x));
184             offset.y = Math.max(minOffset.y, Math.min(maxOffset.y, offset.y));
185         }
186 
187         this._container.setPosition(offset);
188         var locDelegate = this._delegate;
189         if (locDelegate != null && locDelegate.scrollViewDidScroll) {
190             locDelegate.scrollViewDidScroll(this);
191         }
192 
193     },
194 
195     getContentOffset:function () {
196         var locPos = this._container.getPosition();
197         return cc.p(locPos.x, locPos.y);
198     },
199 
200     /**
201      * <p>Sets a new content offset. It ignores max/min offset. It just sets what's given. (just like UIKit's UIScrollView) <br/>
202      * You can override the animation duration with this method.
203      * </p>
204      * @param {cc.Point} offset new offset
205      * @param {Number} dt animation duration
206      */
207     setContentOffsetInDuration:function (offset, dt) {
208         var scroll = cc.moveTo(dt, offset);
209         var expire = cc.callFunc(this._stoppedAnimatedScroll, this);
210         this._container.runAction(cc.sequence(scroll, expire));
211         this.schedule(this._performedAnimatedScroll);
212     },
213 
214     /**
215      * Sets a new scale and does that for a predefined duration.
216      *
217      * @param {Number} scale a new scale vale
218      * @param {Boolean} [animated=null] if YES, scaling is animated
219      */
220     setZoomScale: function (scale, animated) {
221         if (animated) {
222             this.setZoomScaleInDuration(scale, BOUNCE_DURATION);
223             return;
224         }
225 
226         var locContainer = this._container;
227         if (locContainer.getScale() !== scale) {
228             var oldCenter, newCenter;
229             var center;
230 
231             if (this._touchLength === 0.0) {
232                 var locViewSize = this._viewSize;
233                 center = cc.p(locViewSize.width * 0.5, locViewSize.height * 0.5);
234                 center = this.convertToWorldSpace(center);
235             } else
236                 center = this._touchPoint;
237 
238             oldCenter = locContainer.convertToNodeSpace(center);
239             locContainer.setScale(Math.max(this._minScale, Math.min(this._maxScale, scale)));
240             newCenter = locContainer.convertToWorldSpace(oldCenter);
241 
242             var offset = cc.pSub(center, newCenter);
243             if (this._delegate && this._delegate.scrollViewDidZoom)
244                 this._delegate.scrollViewDidZoom(this);
245             this.setContentOffset(cc.pAdd(locContainer.getPosition(), offset));
246         }
247     },
248 
249     getZoomScale:function () {
250         return this._container.getScale();
251     },
252 
253     /**
254      * Sets a new scale for container in a given duration.
255      *
256      * @param {Number} s a new scale value
257      * @param {Number} dt animation duration
258      */
259     setZoomScaleInDuration:function (s, dt) {
260         if (dt > 0) {
261             var locScale = this._container.getScale();
262             if (locScale !== s) {
263                 var scaleAction = cc.actionTween(dt, "zoomScale", locScale, s);
264                 this.runAction(scaleAction);
265             }
266         } else {
267             this.setZoomScale(s);
268         }
269     },
270 
271     /**
272      * Returns the current container's minimum offset. You may want this while you animate scrolling by yourself
273      * @return {cc.Point} Returns the current container's minimum offset.
274      */
275     minContainerOffset:function () {
276         var locContainer = this._container;
277         var locContentSize = locContainer.getContentSize(), locViewSize = this._viewSize;
278         return cc.p(locViewSize.width - locContentSize.width * locContainer.getScaleX(),
279             locViewSize.height - locContentSize.height * locContainer.getScaleY());
280     },
281 
282     /**
283      * Returns the current container's maximum offset. You may want this while you animate scrolling by yourself
284      * @return {cc.Point} Returns the current container's maximum offset.
285      */
286     maxContainerOffset:function () {
287         return cc.p(0.0, 0.0);
288     },
289 
290     /**
291      * Determines if a given node's bounding box is in visible bounds
292      * @param {cc.Node} node
293      * @return {Boolean} YES if it is in visible bounds
294      */
295     isNodeVisible:function (node) {
296         var offset = this.getContentOffset();
297         var size = this.getViewSize();
298         var scale = this.getZoomScale();
299 
300         var viewRect = cc.rect(-offset.x / scale, -offset.y / scale, size.width / scale, size.height / scale);
301 
302         return cc.rectIntersectsRect(viewRect, node.getBoundingBox());
303     },
304 
305     /**
306      * Provided to make scroll view compatible with SWLayer's pause method
307      */
308     pause:function (sender) {
309         this._container.pause();
310         var selChildren = this._container.getChildren();
311         for (var i = 0; i < selChildren.length; i++) {
312             selChildren[i].pause();
313         }
314         this._super();
315     },
316 
317     /**
318      * Provided to make scroll view compatible with SWLayer's resume method
319      */
320     resume:function (sender) {
321         var selChildren = this._container.getChildren();
322         for (var i = 0, len = selChildren.length; i < len; i++) {
323             selChildren[i].resume();
324         }
325         this._container.resume();
326         this._super();
327     },
328 
329     isDragging:function () {
330         return this._dragging;
331     },
332     isTouchMoved:function () {
333         return this._touchMoved;
334     },
335     isBounceable:function () {
336         return this._bounceable;
337     },
338     setBounceable:function (bounceable) {
339         this._bounceable = bounceable;
340     },
341 
342     /**
343      * <p>
344      * size to clip. CCNode boundingBox uses contentSize directly.                   <br/>
345      * It's semantically different what it actually means to common scroll views.    <br/>
346      * Hence, this scroll view will use a separate size property.
347      * </p>
348      */
349     getViewSize:function () {
350         return this._viewSize;
351     },
352 
353     setViewSize:function (size) {
354         this._viewSize = size;
355         cc.Node.prototype.setContentSize.call(this,size);
356     },
357 
358     getContainer:function () {
359         return this._container;
360     },
361 
362     setContainer:function (container) {
363         // Make sure that 'm_pContainer' has a non-NULL value since there are
364         // lots of logic that use 'm_pContainer'.
365         if (!container)
366             return;
367 
368         this.removeAllChildren(true);
369 
370         this._container = container;
371         container.ignoreAnchorPointForPosition(false);
372         container.setAnchorPoint(0, 0);
373 
374         this.addChild(container);
375         this.setViewSize(this._viewSize);
376     },
377 
378     /**
379      * direction allowed to scroll. CCScrollViewDirectionBoth by default.
380      */
381     getDirection:function () {
382         return this._direction;
383     },
384     setDirection:function (direction) {
385         this._direction = direction;
386     },
387 
388     getDelegate:function () {
389         return this._delegate;
390     },
391     setDelegate:function (delegate) {
392         this._delegate = delegate;
393     },
394 
395     /** override functions */
396     // optional
397     onTouchBegan:function (touch, event) {
398         if (!this.isVisible())
399             return false;
400         //var frameOriginal = this.getParent().convertToWorldSpace(this.getPosition());
401         //var frame = cc.rect(frameOriginal.x, frameOriginal.y, this._viewSize.width, this._viewSize.height);
402         var frame = this._getViewRect();
403 
404         //dispatcher does not know about clipping. reject touches outside visible bounds.
405         var locContainer = this._container;
406         var locPoint = locContainer.convertToWorldSpace(locContainer.convertTouchToNodeSpace(touch));
407         var locTouches = this._touches;
408         if (locTouches.length > 2 || this._touchMoved || !cc.rectContainsPoint(frame, locPoint))
409             return false;
410 
411         locTouches.push(touch);
412         //}
413 
414         if (locTouches.length === 1) { // scrolling
415             this._touchPoint = this.convertTouchToNodeSpace(touch);
416             this._touchMoved = false;
417             this._dragging = true; //dragging started
418             this._scrollDistance.x = 0;
419             this._scrollDistance.y = 0;
420             this._touchLength = 0.0;
421         } else if (locTouches.length === 2) {
422             this._touchPoint = cc.pMidpoint(this.convertTouchToNodeSpace(locTouches[0]),
423                 this.convertTouchToNodeSpace(locTouches[1]));
424             this._touchLength = cc.pDistance(locContainer.convertTouchToNodeSpace(locTouches[0]),
425                 locContainer.convertTouchToNodeSpace(locTouches[1]));
426             this._dragging = false;
427         }
428         return true;
429     },
430 
431     onTouchMoved:function (touch, event) {
432         if (!this.isVisible())
433             return;
434 
435         this.setNodeDirty();
436 
437         if (this._touches.length === 1 && this._dragging) { // scrolling
438             this._touchMoved = true;
439             //var frameOriginal = this.getParent().convertToWorldSpace(this.getPosition());
440             //var frame = cc.rect(frameOriginal.x, frameOriginal.y, this._viewSize.width, this._viewSize.height);
441             var frame = this._getViewRect();
442 
443             //var newPoint = this.convertTouchToNodeSpace(this._touches[0]);
444             var newPoint = this.convertTouchToNodeSpace(touch);
445             var moveDistance = cc.pSub(newPoint, this._touchPoint);
446 
447             var dis = 0.0, locDirection = this._direction, pos;
448             if (locDirection === cc.SCROLLVIEW_DIRECTION_VERTICAL){
449                 dis = moveDistance.y;
450                 pos = this._container.getPositionY();
451                 if (!(this.minContainerOffset().y <= pos && pos <= this.maxContainerOffset().y))
452                     moveDistance.y *= BOUNCE_BACK_FACTOR;
453             } else if (locDirection === cc.SCROLLVIEW_DIRECTION_HORIZONTAL){
454                 dis = moveDistance.x;
455                 pos = this._container.getPositionX();
456                 if (!(this.minContainerOffset().x <= pos && pos <= this.maxContainerOffset().x))
457                     moveDistance.x *= BOUNCE_BACK_FACTOR;
458             }else {
459                 dis = Math.sqrt(moveDistance.x * moveDistance.x + moveDistance.y * moveDistance.y);
460 
461                 pos = this._container.getPositionY();
462                 var _minOffset = this.minContainerOffset(), _maxOffset = this.maxContainerOffset();
463                 if (!(_minOffset.y <= pos && pos <= _maxOffset.y))
464                     moveDistance.y *= BOUNCE_BACK_FACTOR;
465 
466                 pos = this._container.getPositionX();
467                 if (!(_minOffset.x <= pos && pos <= _maxOffset.x))
468                     moveDistance.x *= BOUNCE_BACK_FACTOR;
469             }
470 
471             if (!this._touchMoved && Math.abs(cc.convertDistanceFromPointToInch(dis)) < MOVE_INCH ){
472                 //CCLOG("Invalid movement, distance = [%f, %f], disInch = %f", moveDistance.x, moveDistance.y);
473                 return;
474             }
475 
476             if (!this._touchMoved){
477                 moveDistance.x = 0;
478                 moveDistance.y = 0;
479             }
480 
481             this._touchPoint = newPoint;
482             this._touchMoved = true;
483 
484             if (this._dragging) {
485                 switch (locDirection) {
486                     case cc.SCROLLVIEW_DIRECTION_VERTICAL:
487                         moveDistance.x = 0.0;
488                         break;
489                     case cc.SCROLLVIEW_DIRECTION_HORIZONTAL:
490                         moveDistance.y = 0.0;
491                         break;
492                     default:
493                         break;
494                 }
495 
496                 var locPosition = this._container.getPosition();
497                 var newX = locPosition.x + moveDistance.x;
498                 var newY = locPosition.y + moveDistance.y;
499 
500                 this._scrollDistance = moveDistance;
501                 this.setContentOffset(cc.p(newX, newY));
502             }
503         } else if (this._touches.length === 2 && !this._dragging) {
504             var len = cc.pDistance(this._container.convertTouchToNodeSpace(this._touches[0]),
505                 this._container.convertTouchToNodeSpace(this._touches[1]));
506             this.setZoomScale(this.getZoomScale() * len / this._touchLength);
507         }
508     },
509 
510     onTouchEnded:function (touch, event) {
511         if (!this.isVisible())
512             return;
513 
514         if (this._touches.length === 1 && this._touchMoved)
515             this.schedule(this._deaccelerateScrolling);
516 
517         this._touches.length = 0;
518         this._dragging = false;
519         this._touchMoved = false;
520     },
521 
522     onTouchCancelled:function (touch, event) {
523         if (!this.isVisible())
524             return;
525 
526         this._touches.length = 0;
527         this._dragging = false;
528         this._touchMoved = false;
529     },
530 
531     setContentSize: function (size, height) {
532         if (this.getContainer() !== null) {
533             if(height === undefined)
534                 this.getContainer().setContentSize(size);
535             else
536                 this.getContainer().setContentSize(size, height);
537             this.updateInset();
538         }
539     },
540 	_setWidth: function (value) {
541 		var container = this.getContainer();
542 		if (container !== null) {
543 			container._setWidth(value);
544 			this.updateInset();
545 		}
546 	},
547 	_setHeight: function (value) {
548 		var container = this.getContainer();
549 		if (container !== null) {
550 			container._setHeight(value);
551 			this.updateInset();
552 		}
553 	},
554 
555     getContentSize:function () {
556         return this._container.getContentSize();
557     },
558 
559     updateInset:function () {
560         if (this.getContainer() !== null) {
561             var locViewSize = this._viewSize;
562             var tempOffset = this.maxContainerOffset();
563             this._maxInset.x = tempOffset.x + locViewSize.width * INSET_RATIO;
564             this._maxInset.y = tempOffset.y + locViewSize.height * INSET_RATIO;
565             tempOffset = this.minContainerOffset();
566             this._minInset.x = tempOffset.x - locViewSize.width * INSET_RATIO;
567             this._minInset.y = tempOffset.y - locViewSize.height * INSET_RATIO;
568         }
569     },
570 
571     /**
572      * Determines whether it clips its children or not.
573      */
574     isClippingToBounds:function () {
575         return this._clippingToBounds;
576     },
577 
578     setClippingToBounds:function (clippingToBounds) {
579         this._clippingToBounds = clippingToBounds;
580     },
581 
582     visit:function (parentCmd) {
583         // quick return if not visible
584         if (!this.isVisible())
585             return;
586 
587         this._renderCmd.visit(parentCmd);
588     },
589 
590     addChild:function (child, zOrder, tag) {
591         if (!child)
592             throw new Error("child must not nil!");
593 
594         zOrder = zOrder || child.getLocalZOrder();
595         tag = tag || child.getTag();
596 
597         //child.ignoreAnchorPointForPosition(false);
598         //child.setAnchorPoint(0, 0);
599         if (this._container !== child) {
600             this._container.addChild(child, zOrder, tag);
601         } else {
602             cc.Layer.prototype.addChild.call(this, child, zOrder, tag);
603         }
604     },
605 
606     isTouchEnabled: function(){
607         return this._touchListener !== null;
608     },
609 
610     setTouchEnabled:function (e) {
611         if(this._touchListener)
612             cc.eventManager.removeListener(this._touchListener);
613         this._touchListener = null;
614         if (!e) {
615             this._dragging = false;
616             this._touchMoved = false;
617             this._touches.length = 0;
618         } else {
619             var listener = cc.EventListener.create({
620                 event: cc.EventListener.TOUCH_ONE_BY_ONE
621             });
622             if(this.onTouchBegan)
623                 listener.onTouchBegan = this.onTouchBegan.bind(this);
624             if(this.onTouchMoved)
625                 listener.onTouchMoved = this.onTouchMoved.bind(this);
626             if(this.onTouchEnded)
627                 listener.onTouchEnded = this.onTouchEnded.bind(this);
628             if(this.onTouchCancelled)
629                 listener.onTouchCancelled = this.onTouchCancelled.bind(this);
630             this._touchListener = listener;
631             cc.eventManager.addListener(listener, this);
632         }
633     },
634 
635     /**
636      * Init this object with a given size to clip its content.
637      *
638      * @param size view size
639      * @return initialized scroll view object
640      */
641     _initWithViewSize:function (size) {
642         return null;
643     },
644 
645     /**
646      * Relocates the container at the proper offset, in bounds of max/min offsets.
647      *
648      * @param animated If YES, relocation is animated
649      */
650     _relocateContainer:function (animated) {
651         var min = this.minContainerOffset();
652         var max = this.maxContainerOffset();
653         var locDirection = this._direction;
654 
655         var oldPoint = this._container.getPosition();
656         var newX = oldPoint.x;
657         var newY = oldPoint.y;
658         if (locDirection === cc.SCROLLVIEW_DIRECTION_BOTH || locDirection === cc.SCROLLVIEW_DIRECTION_HORIZONTAL) {
659             newX = Math.max(newX, min.x);
660             newX = Math.min(newX, max.x);
661         }
662 
663         if (locDirection === cc.SCROLLVIEW_DIRECTION_BOTH || locDirection === cc.SCROLLVIEW_DIRECTION_VERTICAL) {
664             newY = Math.min(newY, max.y);
665             newY = Math.max(newY, min.y);
666         }
667 
668         if (newY !== oldPoint.y || newX !== oldPoint.x) {
669             this.setContentOffset(cc.p(newX, newY), animated);
670         }
671     },
672     /**
673      * implements auto-scrolling behavior. change SCROLL_DEACCEL_RATE as needed to choose    <br/>
674      * deacceleration speed. it must be less than 1.0.
675      *
676      * @param {Number} dt delta
677      */
678     _deaccelerateScrolling:function (dt) {
679         if (this._dragging) {
680             this.unschedule(this._deaccelerateScrolling);
681             return;
682         }
683 
684         var maxInset, minInset;
685         var oldPosition = this._container.getPosition();
686         var locScrollDistance = this._scrollDistance;
687         this._container.setPosition(oldPosition.x + locScrollDistance.x , oldPosition.y + locScrollDistance.y);
688         if (this._bounceable) {
689             maxInset = this._maxInset;
690             minInset = this._minInset;
691         } else {
692             maxInset = this.maxContainerOffset();
693             minInset = this.minContainerOffset();
694         }
695 
696         //check to see if offset lies within the inset bounds
697         var newX = this._container.getPositionX();
698         var newY = this._container.getPositionY();
699         
700         locScrollDistance.x = locScrollDistance.x * SCROLL_DEACCEL_RATE;
701         locScrollDistance.y = locScrollDistance.y * SCROLL_DEACCEL_RATE;
702 
703         this.setContentOffset(cc.p(newX, newY));
704 
705         if ((Math.abs(locScrollDistance.x) <= SCROLL_DEACCEL_DIST &&
706             Math.abs(locScrollDistance.y) <= SCROLL_DEACCEL_DIST) ||
707             newY > maxInset.y || newY < minInset.y ||
708             newX > maxInset.x || newX < minInset.x ||
709             newX === maxInset.x || newX === minInset.x ||
710             newY === maxInset.y || newY === minInset.y) {
711             this.unschedule(this._deaccelerateScrolling);
712             this._relocateContainer(true);
713         }
714     },
715     /**
716      * This method makes sure auto scrolling causes delegate to invoke its method
717      */
718     _performedAnimatedScroll:function (dt) {
719         if (this._dragging) {
720             this.unschedule(this._performedAnimatedScroll);
721             return;
722         }
723 
724         if (this._delegate && this._delegate.scrollViewDidScroll)
725             this._delegate.scrollViewDidScroll(this);
726     },
727     /**
728      * Expire animated scroll delegate calls
729      */
730     _stoppedAnimatedScroll:function (node) {
731         this.unschedule(this._performedAnimatedScroll);
732         // After the animation stopped, "scrollViewDidScroll" should be invoked, this could fix the bug of lack of tableview cells.
733         if (this._delegate && this._delegate.scrollViewDidScroll) {
734             this._delegate.scrollViewDidScroll(this);
735         }
736     },
737 
738     /**
739      * Zoom handling
740      */
741     _handleZoom:function () {
742     },
743 
744     _getViewRect:function(){
745         var screenPos = this.convertToWorldSpace(cc.p(0,0));
746         var locViewSize = this._viewSize;
747 
748         var scaleX = this.getScaleX();
749         var scaleY = this.getScaleY();
750 
751         for (var p = this._parent; p != null; p = p.getParent()) {
752             scaleX *= p.getScaleX();
753             scaleY *= p.getScaleY();
754         }
755 
756         // Support negative scaling. Not doing so causes intersectsRect calls
757         // (eg: to check if the touch was within the bounds) to return false.
758         // Note, CCNode::getScale will assert if X and Y scales are different.
759         if (scaleX < 0) {
760             screenPos.x += locViewSize.width * scaleX;
761             scaleX = -scaleX;
762         }
763         if (scaleY < 0) {
764             screenPos.y += locViewSize.height * scaleY;
765             scaleY = -scaleY;
766         }
767 
768         var locViewRect = this._tmpViewRect;
769         locViewRect.x = screenPos.x;
770         locViewRect.y = screenPos.y;
771         locViewRect.width = locViewSize.width * scaleX;
772         locViewRect.height = locViewSize.height * scaleY;
773         return locViewRect;
774     },
775 
776     _createRenderCmd: function(){
777         if (cc._renderType === cc._RENDER_TYPE_CANVAS) {
778             return new cc.ScrollView.CanvasRenderCmd(this);
779         } else {
780             return new cc.ScrollView.WebGLRenderCmd(this);
781         }
782     }
783 });
784 
785 var _p = cc.ScrollView.prototype;
786 
787 // Extended properties
788 /** @expose */
789 _p.minOffset;
790 cc.defineGetterSetter(_p, "minOffset", _p.minContainerOffset);
791 /** @expose */
792 _p.maxOffset;
793 cc.defineGetterSetter(_p, "maxOffset", _p.maxContainerOffset);
794 /** @expose */
795 _p.bounceable;
796 cc.defineGetterSetter(_p, "bounceable", _p.isBounceable, _p.setBounceable);
797 /** @expose */
798 _p.viewSize;
799 cc.defineGetterSetter(_p, "viewSize", _p.getViewSize, _p.setViewSize);
800 /** @expose */
801 _p.container;
802 cc.defineGetterSetter(_p, "container", _p.getContainer, _p.setContainer);
803 /** @expose */
804 _p.direction;
805 cc.defineGetterSetter(_p, "direction", _p.getDirection, _p.setDirection);
806 /** @expose */
807 _p.delegate;
808 cc.defineGetterSetter(_p, "delegate", _p.getDelegate, _p.setDelegate);
809 /** @expose */
810 _p.clippingToBounds;
811 cc.defineGetterSetter(_p, "clippingToBounds", _p.isClippingToBounds, _p.setClippingToBounds);
812 
813 _p = null;
814 
815 /**
816  * Returns an autoreleased scroll view object.
817  * @deprecated
818  * @param {cc.Size} size view size
819  * @param {cc.Node} container parent object
820  * @return {cc.ScrollView} scroll view object
821  */
822 cc.ScrollView.create = function (size, container) {
823     return new cc.ScrollView(size, container);
824 };