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