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(arguments.length === 2) 493 this.getContainer().setContentSize(size, height); 494 else 495 this.getContainer().setContentSize(size); 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 };