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