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