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 visit:function (parentCmd) { 585 // quick return if not visible 586 if (!this.isVisible()) 587 return; 588 589 this._renderCmd.visit(parentCmd); 590 }, 591 592 addChild:function (child, zOrder, tag) { 593 if (!child) 594 throw new Error("child must not nil!"); 595 596 zOrder = zOrder || child.getLocalZOrder(); 597 tag = tag || child.getTag(); 598 599 //child.ignoreAnchorPointForPosition(false); 600 //child.setAnchorPoint(0, 0); 601 if (this._container !== child) { 602 this._container.addChild(child, zOrder, tag); 603 } else { 604 cc.Layer.prototype.addChild.call(this, child, zOrder, tag); 605 } 606 }, 607 608 isTouchEnabled: function(){ 609 return this._touchListener !== null; 610 }, 611 612 setTouchEnabled:function (e) { 613 if(this._touchListener) 614 cc.eventManager.removeListener(this._touchListener); 615 this._touchListener = null; 616 if (!e) { 617 this._dragging = false; 618 this._touchMoved = false; 619 this._touches.length = 0; 620 } else { 621 var listener = cc.EventListener.create({ 622 event: cc.EventListener.TOUCH_ONE_BY_ONE 623 }); 624 if(this.onTouchBegan) 625 listener.onTouchBegan = this.onTouchBegan.bind(this); 626 if(this.onTouchMoved) 627 listener.onTouchMoved = this.onTouchMoved.bind(this); 628 if(this.onTouchEnded) 629 listener.onTouchEnded = this.onTouchEnded.bind(this); 630 if(this.onTouchCancelled) 631 listener.onTouchCancelled = this.onTouchCancelled.bind(this); 632 this._touchListener = listener; 633 cc.eventManager.addListener(listener, this); 634 } 635 }, 636 637 /** 638 * Init this object with a given size to clip its content. 639 * 640 * @param size view size 641 * @return initialized scroll view object 642 */ 643 _initWithViewSize:function (size) { 644 return null; 645 }, 646 647 /** 648 * Relocates the container at the proper offset, in bounds of max/min offsets. 649 * 650 * @param animated If YES, relocation is animated 651 */ 652 _relocateContainer:function (animated) { 653 var min = this.minContainerOffset(); 654 var max = this.maxContainerOffset(); 655 var locDirection = this._direction; 656 657 var oldPoint = this._container.getPosition(); 658 var newX = oldPoint.x; 659 var newY = oldPoint.y; 660 if (locDirection === cc.SCROLLVIEW_DIRECTION_BOTH || locDirection === cc.SCROLLVIEW_DIRECTION_HORIZONTAL) { 661 newX = Math.max(newX, min.x); 662 newX = Math.min(newX, max.x); 663 } 664 665 if (locDirection === cc.SCROLLVIEW_DIRECTION_BOTH || locDirection === cc.SCROLLVIEW_DIRECTION_VERTICAL) { 666 newY = Math.min(newY, max.y); 667 newY = Math.max(newY, min.y); 668 } 669 670 if (newY !== oldPoint.y || newX !== oldPoint.x) { 671 this.setContentOffset(cc.p(newX, newY), animated); 672 } 673 }, 674 /** 675 * implements auto-scrolling behavior. change SCROLL_DEACCEL_RATE as needed to choose <br/> 676 * deacceleration speed. it must be less than 1.0. 677 * 678 * @param {Number} dt delta 679 */ 680 _deaccelerateScrolling:function (dt) { 681 if (this._dragging) { 682 this.unschedule(this._deaccelerateScrolling); 683 return; 684 } 685 686 var maxInset, minInset; 687 var oldPosition = this._container.getPosition(); 688 var locScrollDistance = this._scrollDistance; 689 this._container.setPosition(oldPosition.x + locScrollDistance.x , oldPosition.y + locScrollDistance.y); 690 if (this._bounceable) { 691 maxInset = this._maxInset; 692 minInset = this._minInset; 693 } else { 694 maxInset = this.maxContainerOffset(); 695 minInset = this.minContainerOffset(); 696 } 697 698 //check to see if offset lies within the inset bounds 699 var newX = this._container.getPositionX(); 700 var newY = this._container.getPositionY(); 701 702 locScrollDistance.x = locScrollDistance.x * SCROLL_DEACCEL_RATE; 703 locScrollDistance.y = locScrollDistance.y * SCROLL_DEACCEL_RATE; 704 705 this.setContentOffset(cc.p(newX, newY)); 706 707 if ((Math.abs(locScrollDistance.x) <= SCROLL_DEACCEL_DIST && 708 Math.abs(locScrollDistance.y) <= SCROLL_DEACCEL_DIST) || 709 newY > maxInset.y || newY < minInset.y || 710 newX > maxInset.x || newX < minInset.x || 711 newX === maxInset.x || newX === minInset.x || 712 newY === maxInset.y || newY === minInset.y) { 713 this.unschedule(this._deaccelerateScrolling); 714 this._relocateContainer(true); 715 } 716 }, 717 /** 718 * This method makes sure auto scrolling causes delegate to invoke its method 719 */ 720 _performedAnimatedScroll:function (dt) { 721 if (this._dragging) { 722 this.unschedule(this._performedAnimatedScroll); 723 return; 724 } 725 726 if (this._delegate && this._delegate.scrollViewDidScroll) 727 this._delegate.scrollViewDidScroll(this); 728 }, 729 /** 730 * Expire animated scroll delegate calls 731 */ 732 _stoppedAnimatedScroll:function (node) { 733 this.unschedule(this._performedAnimatedScroll); 734 // After the animation stopped, "scrollViewDidScroll" should be invoked, this could fix the bug of lack of tableview cells. 735 if (this._delegate && this._delegate.scrollViewDidScroll) { 736 this._delegate.scrollViewDidScroll(this); 737 } 738 }, 739 740 /** 741 * Zoom handling 742 */ 743 _handleZoom:function () { 744 }, 745 746 _getViewRect:function(){ 747 var screenPos = this.convertToWorldSpace(cc.p(0,0)); 748 var locViewSize = this._viewSize; 749 750 var scaleX = this.getScaleX(); 751 var scaleY = this.getScaleY(); 752 753 for (var p = this._parent; p != null; p = p.getParent()) { 754 scaleX *= p.getScaleX(); 755 scaleY *= p.getScaleY(); 756 } 757 758 // Support negative scaling. Not doing so causes intersectsRect calls 759 // (eg: to check if the touch was within the bounds) to return false. 760 // Note, CCNode::getScale will assert if X and Y scales are different. 761 if (scaleX < 0) { 762 screenPos.x += locViewSize.width * scaleX; 763 scaleX = -scaleX; 764 } 765 if (scaleY < 0) { 766 screenPos.y += locViewSize.height * scaleY; 767 scaleY = -scaleY; 768 } 769 770 var locViewRect = this._tmpViewRect; 771 locViewRect.x = screenPos.x; 772 locViewRect.y = screenPos.y; 773 locViewRect.width = locViewSize.width * scaleX; 774 locViewRect.height = locViewSize.height * scaleY; 775 return locViewRect; 776 }, 777 778 _createRenderCmd: function(){ 779 if (cc._renderType === cc.game.RENDER_TYPE_CANVAS) { 780 return new cc.ScrollView.CanvasRenderCmd(this); 781 } else { 782 return new cc.ScrollView.WebGLRenderCmd(this); 783 } 784 } 785 }); 786 787 var _p = cc.ScrollView.prototype; 788 789 // Extended properties 790 /** @expose */ 791 _p.minOffset; 792 cc.defineGetterSetter(_p, "minOffset", _p.minContainerOffset); 793 /** @expose */ 794 _p.maxOffset; 795 cc.defineGetterSetter(_p, "maxOffset", _p.maxContainerOffset); 796 /** @expose */ 797 _p.bounceable; 798 cc.defineGetterSetter(_p, "bounceable", _p.isBounceable, _p.setBounceable); 799 /** @expose */ 800 _p.viewSize; 801 cc.defineGetterSetter(_p, "viewSize", _p.getViewSize, _p.setViewSize); 802 /** @expose */ 803 _p.container; 804 cc.defineGetterSetter(_p, "container", _p.getContainer, _p.setContainer); 805 /** @expose */ 806 _p.direction; 807 cc.defineGetterSetter(_p, "direction", _p.getDirection, _p.setDirection); 808 /** @expose */ 809 _p.delegate; 810 cc.defineGetterSetter(_p, "delegate", _p.getDelegate, _p.setDelegate); 811 /** @expose */ 812 _p.clippingToBounds; 813 cc.defineGetterSetter(_p, "clippingToBounds", _p.isClippingToBounds, _p.setClippingToBounds); 814 815 _p = null; 816 817 /** 818 * Returns an autoreleased scroll view object. 819 * @deprecated 820 * @param {cc.Size} size view size 821 * @param {cc.Node} container parent object 822 * @return {cc.ScrollView} scroll view object 823 */ 824 cc.ScrollView.create = function (size, container) { 825 return new cc.ScrollView(size, container); 826 };