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         for (var c = this; c != null; c = c.parent) {
399             if (!c.isVisible())
400                 return false;
401         }
402         //var frameOriginal = this.getParent().convertToWorldSpace(this.getPosition());
403         //var frame = cc.rect(frameOriginal.x, frameOriginal.y, this._viewSize.width, this._viewSize.height);
404         var frame = this._getViewRect();
405 
406         //dispatcher does not know about clipping. reject touches outside visible bounds.
407         var locContainer = this._container;
408         var locPoint = locContainer.convertToWorldSpace(locContainer.convertTouchToNodeSpace(touch));
409         var locTouches = this._touches;
410         if (locTouches.length > 2 || this._touchMoved || !cc.rectContainsPoint(frame, locPoint))
411             return false;
412 
413         locTouches.push(touch);
414         //}
415 
416         if (locTouches.length === 1) { // scrolling
417             this._touchPoint = this.convertTouchToNodeSpace(touch);
418             this._touchMoved = false;
419             this._dragging = true; //dragging started
420             this._scrollDistance.x = 0;
421             this._scrollDistance.y = 0;
422             this._touchLength = 0.0;
423         } else if (locTouches.length === 2) {
424             this._touchPoint = cc.pMidpoint(this.convertTouchToNodeSpace(locTouches[0]),
425                 this.convertTouchToNodeSpace(locTouches[1]));
426             this._touchLength = cc.pDistance(locContainer.convertTouchToNodeSpace(locTouches[0]),
427                 locContainer.convertTouchToNodeSpace(locTouches[1]));
428             this._dragging = false;
429         }
430         return true;
431     },
432 
433     onTouchMoved:function (touch, event) {
434         if (!this.isVisible())
435             return;
436 
437         this.setNodeDirty();
438 
439         if (this._touches.length === 1 && this._dragging) { // scrolling
440             this._touchMoved = true;
441             //var frameOriginal = this.getParent().convertToWorldSpace(this.getPosition());
442             //var frame = cc.rect(frameOriginal.x, frameOriginal.y, this._viewSize.width, this._viewSize.height);
443             var frame = this._getViewRect();
444 
445             //var newPoint = this.convertTouchToNodeSpace(this._touches[0]);
446             var newPoint = this.convertTouchToNodeSpace(touch);
447             var moveDistance = cc.pSub(newPoint, this._touchPoint);
448 
449             var dis = 0.0, locDirection = this._direction, pos;
450             if (locDirection === cc.SCROLLVIEW_DIRECTION_VERTICAL){
451                 dis = moveDistance.y;
452                 pos = this._container.getPositionY();
453                 if (!(this.minContainerOffset().y <= pos && pos <= this.maxContainerOffset().y))
454                     moveDistance.y *= BOUNCE_BACK_FACTOR;
455             } else if (locDirection === cc.SCROLLVIEW_DIRECTION_HORIZONTAL){
456                 dis = moveDistance.x;
457                 pos = this._container.getPositionX();
458                 if (!(this.minContainerOffset().x <= pos && pos <= this.maxContainerOffset().x))
459                     moveDistance.x *= BOUNCE_BACK_FACTOR;
460             }else {
461                 dis = Math.sqrt(moveDistance.x * moveDistance.x + moveDistance.y * moveDistance.y);
462 
463                 pos = this._container.getPositionY();
464                 var _minOffset = this.minContainerOffset(), _maxOffset = this.maxContainerOffset();
465                 if (!(_minOffset.y <= pos && pos <= _maxOffset.y))
466                     moveDistance.y *= BOUNCE_BACK_FACTOR;
467 
468                 pos = this._container.getPositionX();
469                 if (!(_minOffset.x <= pos && pos <= _maxOffset.x))
470                     moveDistance.x *= BOUNCE_BACK_FACTOR;
471             }
472 
473             if (!this._touchMoved && Math.abs(cc.convertDistanceFromPointToInch(dis)) < MOVE_INCH ){
474                 //CCLOG("Invalid movement, distance = [%f, %f], disInch = %f", moveDistance.x, moveDistance.y);
475                 return;
476             }
477 
478             if (!this._touchMoved){
479                 moveDistance.x = 0;
480                 moveDistance.y = 0;
481             }
482 
483             this._touchPoint = newPoint;
484             this._touchMoved = true;
485 
486             if (this._dragging) {
487                 switch (locDirection) {
488                     case cc.SCROLLVIEW_DIRECTION_VERTICAL:
489                         moveDistance.x = 0.0;
490                         break;
491                     case cc.SCROLLVIEW_DIRECTION_HORIZONTAL:
492                         moveDistance.y = 0.0;
493                         break;
494                     default:
495                         break;
496                 }
497 
498                 var locPosition = this._container.getPosition();
499                 var newX = locPosition.x + moveDistance.x;
500                 var newY = locPosition.y + moveDistance.y;
501 
502                 this._scrollDistance = moveDistance;
503                 this.setContentOffset(cc.p(newX, newY));
504             }
505         } else if (this._touches.length === 2 && !this._dragging) {
506             var len = cc.pDistance(this._container.convertTouchToNodeSpace(this._touches[0]),
507                 this._container.convertTouchToNodeSpace(this._touches[1]));
508             this.setZoomScale(this.getZoomScale() * len / this._touchLength);
509         }
510     },
511 
512     onTouchEnded:function (touch, event) {
513         if (!this.isVisible())
514             return;
515 
516         if (this._touches.length === 1 && this._touchMoved)
517             this.schedule(this._deaccelerateScrolling);
518 
519         this._touches.length = 0;
520         this._dragging = false;
521         this._touchMoved = false;
522     },
523 
524     onTouchCancelled:function (touch, event) {
525         if (!this.isVisible())
526             return;
527 
528         this._touches.length = 0;
529         this._dragging = false;
530         this._touchMoved = false;
531     },
532 
533     setContentSize: function (size, height) {
534         if (this.getContainer() !== null) {
535             if(height === undefined)
536                 this.getContainer().setContentSize(size);
537             else
538                 this.getContainer().setContentSize(size, height);
539             this.updateInset();
540         }
541     },
542 	_setWidth: function (value) {
543 		var container = this.getContainer();
544 		if (container !== null) {
545 			container._setWidth(value);
546 			this.updateInset();
547 		}
548 	},
549 	_setHeight: function (value) {
550 		var container = this.getContainer();
551 		if (container !== null) {
552 			container._setHeight(value);
553 			this.updateInset();
554 		}
555 	},
556 
557     getContentSize:function () {
558         return this._container.getContentSize();
559     },
560 
561     updateInset:function () {
562         if (this.getContainer() !== null) {
563             var locViewSize = this._viewSize;
564             var tempOffset = this.maxContainerOffset();
565             this._maxInset.x = tempOffset.x + locViewSize.width * INSET_RATIO;
566             this._maxInset.y = tempOffset.y + locViewSize.height * INSET_RATIO;
567             tempOffset = this.minContainerOffset();
568             this._minInset.x = tempOffset.x - locViewSize.width * INSET_RATIO;
569             this._minInset.y = tempOffset.y - locViewSize.height * INSET_RATIO;
570         }
571     },
572 
573     /**
574      * Determines whether it clips its children or not.
575      */
576     isClippingToBounds:function () {
577         return this._clippingToBounds;
578     },
579 
580     setClippingToBounds:function (clippingToBounds) {
581         this._clippingToBounds = clippingToBounds;
582     },
583 
584     addChild:function (child, zOrder, tag) {
585         if (!child)
586             throw new Error("child must not nil!");
587 
588         zOrder = zOrder || child.getLocalZOrder();
589         tag = tag || child.getTag();
590 
591         //child.ignoreAnchorPointForPosition(false);
592         //child.setAnchorPoint(0, 0);
593         if (this._container !== child) {
594             this._container.addChild(child, zOrder, tag);
595         } else {
596             cc.Layer.prototype.addChild.call(this, child, zOrder, tag);
597         }
598     },
599 
600     isTouchEnabled: function(){
601         return this._touchListener !== null;
602     },
603 
604     setTouchEnabled:function (e) {
605         if(this._touchListener)
606             cc.eventManager.removeListener(this._touchListener);
607         this._touchListener = null;
608         if (!e) {
609             this._dragging = false;
610             this._touchMoved = false;
611             this._touches.length = 0;
612         } else {
613             var listener = cc.EventListener.create({
614                 event: cc.EventListener.TOUCH_ONE_BY_ONE
615             });
616             if(this.onTouchBegan)
617                 listener.onTouchBegan = this.onTouchBegan.bind(this);
618             if(this.onTouchMoved)
619                 listener.onTouchMoved = this.onTouchMoved.bind(this);
620             if(this.onTouchEnded)
621                 listener.onTouchEnded = this.onTouchEnded.bind(this);
622             if(this.onTouchCancelled)
623                 listener.onTouchCancelled = this.onTouchCancelled.bind(this);
624             this._touchListener = listener;
625             cc.eventManager.addListener(listener, this);
626         }
627     },
628 
629     /**
630      * Init this object with a given size to clip its content.
631      *
632      * @param size view size
633      * @return initialized scroll view object
634      */
635     _initWithViewSize:function (size) {
636         return null;
637     },
638 
639     /**
640      * Relocates the container at the proper offset, in bounds of max/min offsets.
641      *
642      * @param animated If YES, relocation is animated
643      */
644     _relocateContainer:function (animated) {
645         var min = this.minContainerOffset();
646         var max = this.maxContainerOffset();
647         var locDirection = this._direction;
648 
649         var oldPoint = this._container.getPosition();
650         var newX = oldPoint.x;
651         var newY = oldPoint.y;
652         if (locDirection === cc.SCROLLVIEW_DIRECTION_BOTH || locDirection === cc.SCROLLVIEW_DIRECTION_HORIZONTAL) {
653             newX = Math.max(newX, min.x);
654             newX = Math.min(newX, max.x);
655         }
656 
657         if (locDirection === cc.SCROLLVIEW_DIRECTION_BOTH || locDirection === cc.SCROLLVIEW_DIRECTION_VERTICAL) {
658             newY = Math.min(newY, max.y);
659             newY = Math.max(newY, min.y);
660         }
661 
662         if (newY !== oldPoint.y || newX !== oldPoint.x) {
663             this.setContentOffset(cc.p(newX, newY), animated);
664         }
665     },
666     /**
667      * implements auto-scrolling behavior. change SCROLL_DEACCEL_RATE as needed to choose    <br/>
668      * deacceleration speed. it must be less than 1.0.
669      *
670      * @param {Number} dt delta
671      */
672     _deaccelerateScrolling:function (dt) {
673         if (this._dragging) {
674             this.unschedule(this._deaccelerateScrolling);
675             return;
676         }
677 
678         var maxInset, minInset;
679         var oldPosition = this._container.getPosition();
680         var locScrollDistance = this._scrollDistance;
681         this._container.setPosition(oldPosition.x + locScrollDistance.x , oldPosition.y + locScrollDistance.y);
682         if (this._bounceable) {
683             maxInset = this._maxInset;
684             minInset = this._minInset;
685         } else {
686             maxInset = this.maxContainerOffset();
687             minInset = this.minContainerOffset();
688         }
689 
690         //check to see if offset lies within the inset bounds
691         var newX = this._container.getPositionX();
692         var newY = this._container.getPositionY();
693         
694         locScrollDistance.x = locScrollDistance.x * SCROLL_DEACCEL_RATE;
695         locScrollDistance.y = locScrollDistance.y * SCROLL_DEACCEL_RATE;
696 
697         this.setContentOffset(cc.p(newX, newY));
698 
699         if ((Math.abs(locScrollDistance.x) <= SCROLL_DEACCEL_DIST &&
700             Math.abs(locScrollDistance.y) <= SCROLL_DEACCEL_DIST) ||
701             newY > maxInset.y || newY < minInset.y ||
702             newX > maxInset.x || newX < minInset.x ||
703             newX === maxInset.x || newX === minInset.x ||
704             newY === maxInset.y || newY === minInset.y) {
705             this.unschedule(this._deaccelerateScrolling);
706             this._relocateContainer(true);
707         }
708     },
709     /**
710      * This method makes sure auto scrolling causes delegate to invoke its method
711      */
712     _performedAnimatedScroll:function (dt) {
713         if (this._dragging) {
714             this.unschedule(this._performedAnimatedScroll);
715             return;
716         }
717 
718         if (this._delegate && this._delegate.scrollViewDidScroll)
719             this._delegate.scrollViewDidScroll(this);
720     },
721     /**
722      * Expire animated scroll delegate calls
723      */
724     _stoppedAnimatedScroll:function (node) {
725         this.unschedule(this._performedAnimatedScroll);
726         // After the animation stopped, "scrollViewDidScroll" should be invoked, this could fix the bug of lack of tableview cells.
727         if (this._delegate && this._delegate.scrollViewDidScroll) {
728             this._delegate.scrollViewDidScroll(this);
729         }
730     },
731 
732     /**
733      * Zoom handling
734      */
735     _handleZoom:function () {
736     },
737 
738     _getViewRect:function(){
739         var screenPos = this.convertToWorldSpace(cc.p(0,0));
740         var locViewSize = this._viewSize;
741 
742         var scaleX = this.getScaleX();
743         var scaleY = this.getScaleY();
744 
745         for (var p = this._parent; p != null; p = p.getParent()) {
746             scaleX *= p.getScaleX();
747             scaleY *= p.getScaleY();
748         }
749 
750         // Support negative scaling. Not doing so causes intersectsRect calls
751         // (eg: to check if the touch was within the bounds) to return false.
752         // Note, CCNode::getScale will assert if X and Y scales are different.
753         if (scaleX < 0) {
754             screenPos.x += locViewSize.width * scaleX;
755             scaleX = -scaleX;
756         }
757         if (scaleY < 0) {
758             screenPos.y += locViewSize.height * scaleY;
759             scaleY = -scaleY;
760         }
761 
762         var locViewRect = this._tmpViewRect;
763         locViewRect.x = screenPos.x;
764         locViewRect.y = screenPos.y;
765         locViewRect.width = locViewSize.width * scaleX;
766         locViewRect.height = locViewSize.height * scaleY;
767         return locViewRect;
768     },
769 
770     _createRenderCmd: function(){
771         if (cc._renderType === cc.game.RENDER_TYPE_CANVAS) {
772             return new cc.ScrollView.CanvasRenderCmd(this);
773         } else {
774             return new cc.ScrollView.WebGLRenderCmd(this);
775         }
776     }
777 });
778 
779 var _p = cc.ScrollView.prototype;
780 
781 // Extended properties
782 /** @expose */
783 _p.minOffset;
784 cc.defineGetterSetter(_p, "minOffset", _p.minContainerOffset);
785 /** @expose */
786 _p.maxOffset;
787 cc.defineGetterSetter(_p, "maxOffset", _p.maxContainerOffset);
788 /** @expose */
789 _p.bounceable;
790 cc.defineGetterSetter(_p, "bounceable", _p.isBounceable, _p.setBounceable);
791 /** @expose */
792 _p.viewSize;
793 cc.defineGetterSetter(_p, "viewSize", _p.getViewSize, _p.setViewSize);
794 /** @expose */
795 _p.container;
796 cc.defineGetterSetter(_p, "container", _p.getContainer, _p.setContainer);
797 /** @expose */
798 _p.direction;
799 cc.defineGetterSetter(_p, "direction", _p.getDirection, _p.setDirection);
800 /** @expose */
801 _p.delegate;
802 cc.defineGetterSetter(_p, "delegate", _p.getDelegate, _p.setDelegate);
803 /** @expose */
804 _p.clippingToBounds;
805 cc.defineGetterSetter(_p, "clippingToBounds", _p.isClippingToBounds, _p.setClippingToBounds);
806 
807 _p = null;
808 
809 /**
810  * Returns an autoreleased scroll view object.
811  * @deprecated
812  * @param {cc.Size} size view size
813  * @param {cc.Node} container parent object
814  * @return {cc.ScrollView} scroll view object
815  */
816 cc.ScrollView.create = function (size, container) {
817     return new cc.ScrollView(size, container);
818 };