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