├── CCScrollLayer.h ├── CCScrollLayer.m └── README /CCScrollLayer.h: -------------------------------------------------------------------------------- 1 | // 2 | // CCScrollLayer.h 3 | // 4 | // Copyright 2010 DK101 5 | // http://dk101.net/2010/11/30/implementing-page-scrolling-in-cocos2d/ 6 | // 7 | // Copyright 2010 Giv Parvaneh. 8 | // http://www.givp.org/blog/2010/12/30/scrolling-menus-in-cocos2d/ 9 | // 10 | // Copyright 2011 Stepan Generalov 11 | // 12 | // Permission is hereby granted, free of charge, to any person obtaining a copy 13 | // of this software and associated documentation files (the "Software"), to deal 14 | // in the Software without restriction, including without limitation the rights 15 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 16 | // copies of the Software, and to permit persons to whom the Software is 17 | // furnished to do so, subject to the following conditions: 18 | // 19 | // The above copyright notice and this permission notice shall be included in 20 | // all copies or substantial portions of the Software. 21 | // 22 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 25 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 26 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 27 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 28 | // THE SOFTWARE. 29 | 30 | #import 31 | #import "cocos2d.h" 32 | 33 | #ifndef __MAC_OS_X_VERSION_MAX_ALLOWED 34 | 35 | /* 36 | It is a very clean and elegant subclass of CCLayer that lets you pass-in an array 37 | of layers and it will then create a smooth scroller. 38 | Complete with the "snapping" effect. You can create screens with anything that can be added to a CCLayer. 39 | 40 | Additions since Giv Parvaneh version: 41 | 1. Added ability to swipe above targetedTouchDelegates. 42 | 2. Added touches lengths & screens properties. 43 | 3. Added factory class method. 44 | 4. Code cleanup. 45 | 5. Added current page number indicator (iOS Style Dots). 46 | 47 | Limitations: 48 | 1. Mac OS X not supported. 49 | 2. Standard Touch Delegates will still receive touch events after layer starts sliding. 50 | */ 51 | @interface CCScrollLayer : CCLayer { 52 | 53 | // Holds the current width of the screen substracting offset. 54 | CGFloat scrollWidth_; 55 | 56 | // Holds the current page being displayed. 57 | int currentScreen_; 58 | 59 | // A count of the total screens available. 60 | int totalScreens_; 61 | 62 | // The x coord of initial point the user starts their swipe. 63 | CGFloat startSwipe_; 64 | 65 | // For what distance user must slide finger to start scrolling menu. 66 | CGFloat minimumTouchLengthToSlide_; 67 | 68 | // For what distance user must slide finger to change the page. 69 | CGFloat minimumTouchLengthToChangePage_; 70 | 71 | // Whenever show or not gray/white dots under scrolling content. 72 | BOOL showPagesIndicator_; 73 | 74 | // Internal state of scrollLayer (scrolling or idle). 75 | int state_; 76 | 77 | } 78 | @property(readwrite, assign) CGFloat minimumTouchLengthToSlide; 79 | @property(readwrite, assign) CGFloat minimumTouchLengthToChangePage; 80 | @property(readwrite, assign) BOOL showPagesIndicator; 81 | @property(readonly) int totalScreens; 82 | @property(readonly) int currentScreen; 83 | 84 | +(id) nodeWithLayers:(NSArray *)layers widthOffset: (int) widthOffset; 85 | -(id) initWithLayers:(NSArray *)layers widthOffset: (int) widthOffset; 86 | 87 | @end 88 | 89 | #endif 90 | -------------------------------------------------------------------------------- /CCScrollLayer.m: -------------------------------------------------------------------------------- 1 | // 2 | // CCScrollLayer.m 3 | // 4 | // Copyright 2010 DK101 5 | // http://dk101.net/2010/11/30/implementing-page-scrolling-in-cocos2d/ 6 | // 7 | // Copyright 2010 Giv Parvaneh. 8 | // http://www.givp.org/blog/2010/12/30/scrolling-menus-in-cocos2d/ 9 | // 10 | // Copyright 2011 Stepan Generalov 11 | // 12 | // Permission is hereby granted, free of charge, to any person obtaining a copy 13 | // of this software and associated documentation files (the "Software"), to deal 14 | // in the Software without restriction, including without limitation the rights 15 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 16 | // copies of the Software, and to permit persons to whom the Software is 17 | // furnished to do so, subject to the following conditions: 18 | // 19 | // The above copyright notice and this permission notice shall be included in 20 | // all copies or substantial portions of the Software. 21 | // 22 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 25 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 26 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 27 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 28 | // THE SOFTWARE. 29 | 30 | #ifndef __MAC_OS_X_VERSION_MAX_ALLOWED 31 | 32 | #import "CCScrollLayer.h" 33 | #import "CCGL.h" 34 | 35 | enum 36 | { 37 | kCCScrollLayerStateIdle, 38 | kCCScrollLayerStateSliding, 39 | }; 40 | 41 | @interface CCTouchDispatcher (targetedHandlersGetter) 42 | 43 | - (NSMutableArray *) targetedHandlers; 44 | 45 | @end 46 | 47 | @implementation CCTouchDispatcher (targetedHandlersGetter) 48 | 49 | - (NSMutableArray *) targetedHandlers 50 | { 51 | return targetedHandlers; 52 | } 53 | 54 | @end 55 | 56 | 57 | @implementation CCScrollLayer 58 | 59 | @synthesize minimumTouchLengthToSlide = minimumTouchLengthToSlide_; 60 | @synthesize minimumTouchLengthToChangePage = minimumTouchLengthToChangePage_; 61 | @synthesize totalScreens = totalScreens_; 62 | @synthesize currentScreen = currentScreen_; 63 | @synthesize showPagesIndicator = showPagesIndicator_; 64 | 65 | +(id) nodeWithLayers:(NSArray *)layers widthOffset: (int) widthOffset 66 | { 67 | return [[[self alloc] initWithLayers: layers widthOffset:widthOffset] autorelease]; 68 | } 69 | 70 | -(id) initWithLayers:(NSArray *)layers widthOffset: (int) widthOffset 71 | { 72 | if ( (self = [super init]) ) 73 | { 74 | NSAssert([layers count], @"CCScrollLayer#initWithLayers:widthOffset: you must provide at least one layer!"); 75 | 76 | // Enable touches. 77 | self.isTouchEnabled = YES; 78 | 79 | // Set default minimum touch length to scroll. 80 | self.minimumTouchLengthToSlide = 30.0f; 81 | self.minimumTouchLengthToChangePage = 100.0f; 82 | 83 | // Show indicator by default. 84 | self.showPagesIndicator = YES; 85 | 86 | // Set up the starting variables 87 | currentScreen_ = 1; 88 | 89 | // offset added to show preview of next/previous screens 90 | scrollWidth_ = [[CCDirector sharedDirector] winSize].width - widthOffset; 91 | 92 | // Loop through the array and add the screens 93 | int i = 0; 94 | for (CCLayer *l in layers) 95 | { 96 | l.anchorPoint = ccp(0,0); 97 | l.position = ccp((i*scrollWidth_),0); 98 | [self addChild:l]; 99 | i++; 100 | } 101 | 102 | // Setup a count of the available screens 103 | totalScreens_ = [layers count]; 104 | 105 | } 106 | return self; 107 | } 108 | 109 | #pragma mark CCLayer Methods ReImpl 110 | 111 | // Register with more priority than CCMenu's but don't swallow touches 112 | -(void) registerWithTouchDispatcher 113 | { 114 | [[CCTouchDispatcher sharedDispatcher] addTargetedDelegate:self priority:kCCMenuTouchPriority - 1 swallowsTouches:NO]; 115 | } 116 | 117 | - (void) visit 118 | { 119 | [super visit];//< Will draw after glPopScene. 120 | 121 | if (self.showPagesIndicator) 122 | { 123 | // Prepare Points Array 124 | CGFloat n = (CGFloat)totalScreens_; //< Total points count in CGFloat. 125 | CGFloat pY = ceilf ( self.contentSize.height / 8.0f ); //< Points y-coord in parent coord sys. 126 | CGFloat d = 16.0f * CC_CONTENT_SCALE_FACTOR(); //< Distance between points. 127 | CGPoint points[totalScreens_]; 128 | for (int i=0; i < totalScreens_; ++i) 129 | { 130 | CGFloat pX = 0.5f * self.contentSize.width + d * ( (CGFloat)i - 0.5f*(n-1.0f) ); 131 | points[i] = ccp (pX, pY); 132 | } 133 | 134 | // Set GL Values 135 | glEnable(GL_POINT_SMOOTH); 136 | glHint(GL_POINT_SMOOTH_HINT, GL_NICEST); 137 | glPointSize(6.0f * CC_CONTENT_SCALE_FACTOR()); 138 | 139 | // Draw Gray Points 140 | glColor4ub(0x96,0x96,0x96,0xFF); 141 | ccDrawPoints( points, totalScreens_ ); 142 | 143 | // Draw White Point for Selected Page 144 | glColor4ub(0xFF,0xFF,0xFF,0xFF); 145 | ccDrawPoint(points[currentScreen_ - 1]); 146 | 147 | // Restore GL Values 148 | glPointSize(1.0f); 149 | glHint(GL_POINT_SMOOTH_HINT,GL_FASTEST); 150 | glDisable(GL_POINT_SMOOTH); 151 | } 152 | } 153 | 154 | #pragma mark Pages Control 155 | 156 | -(void) moveToPage:(int)page 157 | { 158 | id changePage = [CCMoveTo actionWithDuration:0.3 position:ccp(-((page-1)*scrollWidth_),0)]; 159 | [self runAction:changePage]; 160 | currentScreen_ = page; 161 | } 162 | 163 | #pragma mark Hackish Stuff 164 | 165 | - (void) claimTouch: (UITouch *) aTouch 166 | { 167 | // Enumerate through all targeted handlers. 168 | for ( CCTargetedTouchHandler *handler in [[CCTouchDispatcher sharedDispatcher] targetedHandlers] ) 169 | { 170 | // Only our handler should claim the touch. 171 | if (handler.delegate == self) 172 | { 173 | if (![handler.claimedTouches containsObject: aTouch]) 174 | { 175 | [handler.claimedTouches addObject: aTouch]; 176 | } 177 | else 178 | { 179 | CCLOGERROR(@"CCScrollLayer#claimTouch: %@ is already claimed!", touch); 180 | } 181 | return; 182 | } 183 | } 184 | } 185 | 186 | - (void) cancelAndStoleTouch:(UITouch *)touch withEvent:(UIEvent *)event 187 | { 188 | // Throw Cancel message for everybody in TouchDispatcher. 189 | [[CCTouchDispatcher sharedDispatcher] touchesCancelled: [NSSet setWithObject: touch] withEvent:event]; 190 | 191 | //< after doing this touch is already removed from all targeted handlers 192 | 193 | // Squirrel away the touch 194 | [self claimTouch: touch]; 195 | } 196 | 197 | #pragma mark Touches 198 | 199 | -(BOOL) ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event 200 | { 201 | CGPoint touchPoint = [touch locationInView:[touch view]]; 202 | touchPoint = [[CCDirector sharedDirector] convertToGL:touchPoint]; 203 | 204 | startSwipe_ = touchPoint.x; 205 | state_ = kCCScrollLayerStateIdle; 206 | return YES; 207 | } 208 | 209 | - (void)ccTouchMoved:(UITouch *)touch withEvent:(UIEvent *)event 210 | { 211 | CGPoint touchPoint = [touch locationInView:[touch view]]; 212 | touchPoint = [[CCDirector sharedDirector] convertToGL:touchPoint]; 213 | 214 | 215 | // If finger is dragged for more distance then minimum - start sliding and cancel pressed buttons. 216 | // Of course only if we not already in sliding mode 217 | if ( (state_ != kCCScrollLayerStateSliding) 218 | && (fabsf(touchPoint.x-startSwipe_) >= self.minimumTouchLengthToSlide) ) 219 | { 220 | state_ = kCCScrollLayerStateSliding; 221 | 222 | // Avoid jerk after state change. 223 | startSwipe_ = touchPoint.x; 224 | 225 | [self cancelAndStoleTouch: touch withEvent: event]; 226 | } 227 | 228 | if (state_ == kCCScrollLayerStateSliding) 229 | self.position = ccp((-(currentScreen_-1)*scrollWidth_)+(touchPoint.x-startSwipe_),0); 230 | 231 | } 232 | 233 | - (void)ccTouchEnded:(UITouch *)touch withEvent:(UIEvent *)event 234 | { 235 | CGPoint touchPoint = [touch locationInView:[touch view]]; 236 | touchPoint = [[CCDirector sharedDirector] convertToGL:touchPoint]; 237 | 238 | int newX = touchPoint.x; 239 | 240 | if ( (newX - startSwipe_) < -self.minimumTouchLengthToChangePage && (currentScreen_+1) <= totalScreens_ ) 241 | { 242 | [self moveToPage: currentScreen_+1]; 243 | } 244 | else if ( (newX - startSwipe_) > self.minimumTouchLengthToChangePage && (currentScreen_-1) > 0 ) 245 | { 246 | [self moveToPage: currentScreen_-1]; 247 | } 248 | else 249 | { 250 | [self moveToPage:currentScreen_]; 251 | } 252 | } 253 | 254 | @end 255 | 256 | #endif 257 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | =================================================== 2 | IMPORTANT: 3 | Please note CCScrollLayer is now a part of cocos2d-iphone-extensions repo: 4 | https://github.com/cocos2d/cocos2d-iphone-extensions/ 5 | =================================================== 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | =========================== 14 | CCScrollLayer 15 | =========================== 16 | Giv Parvaneh 17 | @givp 18 | http://www.givp.org/blog/2010/12/30/scrolling-menus-in-cocos2d/ 19 | 20 | 21 | 22 | This class was originally written by DK101 23 | http://dk101.net/2010/11/30/implementing-page-scrolling-in-cocos2d/ 24 | 25 | It is a very clean and elegant subclass of CCLayer that lets you pass-in an array of layers and it will then create a smooth scroller. Complete with the "snapping" effect. You can create screens with anything that can be added to a CCLayer. 26 | 27 | It is mostly the same with the following changes: 28 | 29 | - updated to work with Cocos2d 0.99.5 30 | - added the option to change the width of each layer for the "Angry Birds" style preview effect 31 | - removed CCTouchDispatcher on exit (this was messing up touch events on other scenes) 32 | 33 | USAGE: 34 | 35 | 1. add both files to your project 36 | 2. in your scene import CCScrollLayer.h 37 | 3. in your scene's init method construct each layer and pass it to the CCScrollLayer class: 38 | 39 | e.g. 40 | 41 | // get screen size 42 | CGSize screenSize = [CCDirector sharedDirector].winSize; 43 | 44 | ///////////////////////////////////////////////// 45 | // PAGE 1 46 | //////////////////////////////////////////////// 47 | // create a blank layer for page 1 48 | CCLayer *pageOne = [[CCLayer alloc] init]; 49 | 50 | // create a label for page 1 51 | CCLabelTTF *label = [CCLabelTTF labelWithString:@"Page 1" fontName:@"Arial Rounded MT Bold" fontSize:44]; 52 | label.position = ccp( screenSize.width /2 , screenSize.height/2 ); 53 | 54 | // add label to page 1 layer 55 | [pageOne addChild:label]; 56 | 57 | ///////////////////////////////////////////////// 58 | // PAGE 2 59 | //////////////////////////////////////////////// 60 | // create a blank layer for page 2 61 | CCLayer *pageTwo = [[CCLayer alloc] init]; 62 | 63 | // create a custom font menu for page 2 64 | CCLabelBMFont *tlabel = [CCLabelBMFont labelWithString:@"Page 2" fntFile:@"customfont.fnt"]; 65 | CCMenuItemLabel *titem = [CCMenuItemLabel itemWithLabel:tlabel target:self selector:@selector(testCallback:)]; 66 | CCMenu *menu = [CCMenu menuWithItems: titem, nil]; 67 | menu.position = ccp(screenSize.width/2, screenSize.height/2); 68 | 69 | // add menu to page 2 70 | [pageTwo addChild:menu]; 71 | //////////////////////////////////////////////// 72 | 73 | // now create the scroller and pass-in the pages (set widthOffset to 0 for fullscreen pages) 74 | CCScrollLayer *scroller = [[CCScrollLayer alloc] initWithLayers:[NSMutableArray arrayWithObjects: pageOne,pageTwo,pageThree,nil] widthOffset: 230]; 75 | 76 | // finally add the scroller to your scene 77 | [self addChild:scroller]; 78 | 79 | 80 | Enjoy :) 81 | G 82 | --------------------------------------------------------------------------------