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     visit:function (parentCmd) {
585         // quick return if not visible
586         if (!this.isVisible())
587             return;
588 
589         this._renderCmd.visit(parentCmd);
590     },
591 
592     addChild:function (child, zOrder, tag) {
593         if (!child)
594             throw new Error("child must not nil!");
595 
596         zOrder = zOrder || child.getLocalZOrder();
597         tag = tag || child.getTag();
598 
599         //child.ignoreAnchorPointForPosition(false);
600         //child.setAnchorPoint(0, 0);
601         if (this._container !== child) {
602             this._container.addChild(child, zOrder, tag);
603         } else {
604             cc.Layer.prototype.addChild.call(this, child, zOrder, tag);
605         }
606     },
607 
608     isTouchEnabled: function(){
609         return this._touchListener !== null;
610     },
611 
612     setTouchEnabled:function (e) {
613         if(this._touchListener)
614             cc.eventManager.removeListener(this._touchListener);
615         this._touchListener = null;
616         if (!e) {
617             this._dragging = false;
618             this._touchMoved = false;
619             this._touches.length = 0;
620         } else {
621             var listener = cc.EventListener.create({
622                 event: cc.EventListener.TOUCH_ONE_BY_ONE
623             });
624             if(this.onTouchBegan)
625                 listener.onTouchBegan = this.onTouchBegan.bind(this);
626             if(this.onTouchMoved)
627                 listener.onTouchMoved = this.onTouchMoved.bind(this);
628             if(this.onTouchEnded)
629                 listener.onTouchEnded = this.onTouchEnded.bind(this);
630             if(this.onTouchCancelled)
631                 listener.onTouchCancelled = this.onTouchCancelled.bind(this);
632             this._touchListener = listener;
633             cc.eventManager.addListener(listener, this);
634         }
635     },
636 
637     /**
638      * Init this object with a given size to clip its content.
639      *
640      * @param size view size
641      * @return initialized scroll view object
642      */
643     _initWithViewSize:function (size) {
644         return null;
645     },
646 
647     /**
648      * Relocates the container at the proper offset, in bounds of max/min offsets.
649      *
650      * @param animated If YES, relocation is animated
651      */
652     _relocateContainer:function (animated) {
653         var min = this.minContainerOffset();
654         var max = this.maxContainerOffset();
655         var locDirection = this._direction;
656 
657         var oldPoint = this._container.getPosition();
658         var newX = oldPoint.x;
659         var newY = oldPoint.y;
660         if (locDirection === cc.SCROLLVIEW_DIRECTION_BOTH || locDirection === cc.SCROLLVIEW_DIRECTION_HORIZONTAL) {
661             newX = Math.max(newX, min.x);
662             newX = Math.min(newX, max.x);
663         }
664 
665         if (locDirection === cc.SCROLLVIEW_DIRECTION_BOTH || locDirection === cc.SCROLLVIEW_DIRECTION_VERTICAL) {
666             newY = Math.min(newY, max.y);
667             newY = Math.max(newY, min.y);
668         }
669 
670         if (newY !== oldPoint.y || newX !== oldPoint.x) {
671             this.setContentOffset(cc.p(newX, newY), animated);
672         }
673     },
674     /**
675      * implements auto-scrolling behavior. change SCROLL_DEACCEL_RATE as needed to choose    <br/>
676      * deacceleration speed. it must be less than 1.0.
677      *
678      * @param {Number} dt delta
679      */
680     _deaccelerateScrolling:function (dt) {
681         if (this._dragging) {
682             this.unschedule(this._deaccelerateScrolling);
683             return;
684         }
685 
686         var maxInset, minInset;
687         var oldPosition = this._container.getPosition();
688         var locScrollDistance = this._scrollDistance;
689         this._container.setPosition(oldPosition.x + locScrollDistance.x , oldPosition.y + locScrollDistance.y);
690         if (this._bounceable) {
691             maxInset = this._maxInset;
692             minInset = this._minInset;
693         } else {
694             maxInset = this.maxContainerOffset();
695             minInset = this.minContainerOffset();
696         }
697 
698         //check to see if offset lies within the inset bounds
699         var newX = this._container.getPositionX();
700         var newY = this._container.getPositionY();
701         
702         locScrollDistance.x = locScrollDistance.x * SCROLL_DEACCEL_RATE;
703         locScrollDistance.y = locScrollDistance.y * SCROLL_DEACCEL_RATE;
704 
705         this.setContentOffset(cc.p(newX, newY));
706 
707         if ((Math.abs(locScrollDistance.x) <= SCROLL_DEACCEL_DIST &&
708             Math.abs(locScrollDistance.y) <= SCROLL_DEACCEL_DIST) ||
709             newY > maxInset.y || newY < minInset.y ||
710             newX > maxInset.x || newX < minInset.x ||
711             newX === maxInset.x || newX === minInset.x ||
712             newY === maxInset.y || newY === minInset.y) {
713             this.unschedule(this._deaccelerateScrolling);
714             this._relocateContainer(true);
715         }
716     },
717     /**
718      * This method makes sure auto scrolling causes delegate to invoke its method
719      */
720     _performedAnimatedScroll:function (dt) {
721         if (this._dragging) {
722             this.unschedule(this._performedAnimatedScroll);
723             return;
724         }
725 
726         if (this._delegate && this._delegate.scrollViewDidScroll)
727             this._delegate.scrollViewDidScroll(this);
728     },
729     /**
730      * Expire animated scroll delegate calls
731      */
732     _stoppedAnimatedScroll:function (node) {
733         this.unschedule(this._performedAnimatedScroll);
734         // After the animation stopped, "scrollViewDidScroll" should be invoked, this could fix the bug of lack of tableview cells.
735         if (this._delegate && this._delegate.scrollViewDidScroll) {
736             this._delegate.scrollViewDidScroll(this);
737         }
738     },
739 
740     /**
741      * Zoom handling
742      */
743     _handleZoom:function () {
744     },
745 
746     _getViewRect:function(){
747         var screenPos = this.convertToWorldSpace(cc.p(0,0));
748         var locViewSize = this._viewSize;
749 
750         var scaleX = this.getScaleX();
751         var scaleY = this.getScaleY();
752 
753         for (var p = this._parent; p != null; p = p.getParent()) {
754             scaleX *= p.getScaleX();
755             scaleY *= p.getScaleY();
756         }
757 
758         // Support negative scaling. Not doing so causes intersectsRect calls
759         // (eg: to check if the touch was within the bounds) to return false.
760         // Note, CCNode::getScale will assert if X and Y scales are different.
761         if (scaleX < 0) {
762             screenPos.x += locViewSize.width * scaleX;
763             scaleX = -scaleX;
764         }
765         if (scaleY < 0) {
766             screenPos.y += locViewSize.height * scaleY;
767             scaleY = -scaleY;
768         }
769 
770         var locViewRect = this._tmpViewRect;
771         locViewRect.x = screenPos.x;
772         locViewRect.y = screenPos.y;
773         locViewRect.width = locViewSize.width * scaleX;
774         locViewRect.height = locViewSize.height * scaleY;
775         return locViewRect;
776     },
777 
778     _createRenderCmd: function(){
779         if (cc._renderType === cc.game.RENDER_TYPE_CANVAS) {
780             return new cc.ScrollView.CanvasRenderCmd(this);
781         } else {
782             return new cc.ScrollView.WebGLRenderCmd(this);
783         }
784     }
785 });
786 
787 var _p = cc.ScrollView.prototype;
788 
789 // Extended properties
790 /** @expose */
791 _p.minOffset;
792 cc.defineGetterSetter(_p, "minOffset", _p.minContainerOffset);
793 /** @expose */
794 _p.maxOffset;
795 cc.defineGetterSetter(_p, "maxOffset", _p.maxContainerOffset);
796 /** @expose */
797 _p.bounceable;
798 cc.defineGetterSetter(_p, "bounceable", _p.isBounceable, _p.setBounceable);
799 /** @expose */
800 _p.viewSize;
801 cc.defineGetterSetter(_p, "viewSize", _p.getViewSize, _p.setViewSize);
802 /** @expose */
803 _p.container;
804 cc.defineGetterSetter(_p, "container", _p.getContainer, _p.setContainer);
805 /** @expose */
806 _p.direction;
807 cc.defineGetterSetter(_p, "direction", _p.getDirection, _p.setDirection);
808 /** @expose */
809 _p.delegate;
810 cc.defineGetterSetter(_p, "delegate", _p.getDelegate, _p.setDelegate);
811 /** @expose */
812 _p.clippingToBounds;
813 cc.defineGetterSetter(_p, "clippingToBounds", _p.isClippingToBounds, _p.setClippingToBounds);
814 
815 _p = null;
816 
817 /**
818  * Returns an autoreleased scroll view object.
819  * @deprecated
820  * @param {cc.Size} size view size
821  * @param {cc.Node} container parent object
822  * @return {cc.ScrollView} scroll view object
823  */
824 cc.ScrollView.create = function (size, container) {
825     return new cc.ScrollView(size, container);
826 };