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