/libs/cocos2d/CCMenu.m
Objective C | 419 lines | 297 code | 88 blank | 34 comment | 26 complexity | 70a5fb26222dc1ab25fa1d6f55488b2b MD5 | raw file
Possible License(s): Apache-2.0
1/* 2 * cocos2d for iPhone: http://www.cocos2d-iphone.org 3 * 4 * Copyright (c) 2008-2010 Ricardo Quesada 5 * 6 * Permission is hereby granted, free of charge, to any person obtaining a copy 7 * of this software and associated documentation files (the "Software"), to deal 8 * in the Software without restriction, including without limitation the rights 9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 * copies of the Software, and to permit persons to whom the Software is 11 * furnished to do so, subject to the following conditions: 12 * 13 * The above copyright notice and this permission notice shall be included in 14 * all copies or substantial portions of the Software. 15 * 16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 * THE SOFTWARE. 23 * 24 */ 25 26 27 28#import "CCMenu.h" 29#import "CCDirector.h" 30#import "CCTouchDispatcher.h" 31#import "Support/CGPointExtension.h" 32 33enum { 34 kDefaultPadding = 5, 35}; 36 37@interface CCMenu (Private) 38// returns touched menu item, if any 39-(CCMenuItem *) itemForTouch: (UITouch *) touch; 40@end 41 42@implementation CCMenu 43 44@synthesize opacity=opacity_, color=color_; 45 46- (id) init 47{ 48 NSException* myException = [NSException 49 exceptionWithName:@"MenuInit" 50 reason:@"Use initWithItems instead" 51 userInfo:nil]; 52 @throw myException; 53} 54 55+(id) menuWithItems: (CCMenuItem*) item, ... 56{ 57 va_list args; 58 va_start(args,item); 59 60 id s = [[[self alloc] initWithItems: item vaList:args] autorelease]; 61 62 va_end(args); 63 return s; 64} 65 66-(id) initWithItems: (CCMenuItem*) item vaList: (va_list) args 67{ 68 if( (self=[super init]) ) { 69 70 self.isTouchEnabled = YES; 71 72 // menu in the center of the screen 73 CGSize s = [[CCDirector sharedDirector] winSize]; 74 75 self.isRelativeAnchorPoint = NO; 76 anchorPoint_ = ccp(0.5f, 0.5f); 77 [self setContentSize:s]; 78 79 // XXX: in v0.7, winSize should return the visible size 80 // XXX: so the bar calculation should be done there 81 CGRect r = [[UIApplication sharedApplication] statusBarFrame]; 82 ccDeviceOrientation orientation = [[CCDirector sharedDirector] deviceOrientation]; 83 if( orientation == CCDeviceOrientationLandscapeLeft || orientation == CCDeviceOrientationLandscapeRight ) 84 s.height -= r.size.width; 85 else 86 s.height -= r.size.height; 87 self.position = ccp(s.width/2, s.height/2); 88 89 int z=0; 90 91 if (item) { 92 [self addChild: item z:z]; 93 CCMenuItem *i = va_arg(args, CCMenuItem*); 94 while(i) { 95 z++; 96 [self addChild: i z:z]; 97 i = va_arg(args, CCMenuItem*); 98 } 99 } 100 // [self alignItemsVertically]; 101 102 selectedItem = nil; 103 state = kMenuStateWaiting; 104 } 105 106 return self; 107} 108 109-(void) dealloc 110{ 111 [super dealloc]; 112} 113 114/* 115 * override add: 116 */ 117-(id) addChild:(CCMenuItem*)child z:(int)z tag:(int) aTag 118{ 119 NSAssert( [child isKindOfClass:[CCMenuItem class]], @"Menu only supports MenuItem objects as children"); 120 return [super addChild:child z:z tag:aTag]; 121} 122 123#pragma mark Menu - Events 124 125-(void) registerWithTouchDispatcher 126{ 127 [[CCTouchDispatcher sharedDispatcher] addTargetedDelegate:self priority:INT_MIN+1 swallowsTouches:YES]; 128} 129 130-(BOOL) ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event 131{ 132 if( state != kMenuStateWaiting || !visible_ ) 133 return NO; 134 135 selectedItem = [self itemForTouch:touch]; 136 [selectedItem selected]; 137 138 if( selectedItem ) { 139 state = kMenuStateTrackingTouch; 140 return YES; 141 } 142 return NO; 143} 144 145-(void) ccTouchEnded:(UITouch *)touch withEvent:(UIEvent *)event 146{ 147 NSAssert(state == kMenuStateTrackingTouch, @"[Menu ccTouchEnded] -- invalid state"); 148 149 [selectedItem unselected]; 150 [selectedItem activate]; 151 152 state = kMenuStateWaiting; 153} 154 155-(void) ccTouchCancelled:(UITouch *)touch withEvent:(UIEvent *)event 156{ 157 NSAssert(state == kMenuStateTrackingTouch, @"[Menu ccTouchCancelled] -- invalid state"); 158 159 [selectedItem unselected]; 160 161 state = kMenuStateWaiting; 162} 163 164-(void) ccTouchMoved:(UITouch *)touch withEvent:(UIEvent *)event 165{ 166 NSAssert(state == kMenuStateTrackingTouch, @"[Menu ccTouchMoved] -- invalid state"); 167 168 CCMenuItem *currentItem = [self itemForTouch:touch]; 169 170 if (currentItem != selectedItem) { 171 [selectedItem unselected]; 172 selectedItem = currentItem; 173 [selectedItem selected]; 174 } 175} 176 177#pragma mark Menu - Alignment 178-(void) alignItemsVertically 179{ 180 return [self alignItemsVerticallyWithPadding:kDefaultPadding]; 181} 182-(void) alignItemsVerticallyWithPadding:(float)padding 183{ 184 float height = -padding; 185 186 CCMenuItem *item; 187 CCARRAY_FOREACH(children_, item) 188 height += item.contentSize.height * item.scaleY + padding; 189 190 float y = height / 2.0f; 191 192 CCARRAY_FOREACH(children_, item) { 193 CGSize itemSize = item.contentSize; 194 [item setPosition:ccp(0, y - itemSize.height * item.scaleY / 2.0f)]; 195 y -= itemSize.height * item.scaleY + padding; 196 } 197} 198 199-(void) alignItemsHorizontally 200{ 201 return [self alignItemsHorizontallyWithPadding:kDefaultPadding]; 202} 203 204-(void) alignItemsHorizontallyWithPadding:(float)padding 205{ 206 207 float width = -padding; 208 CCMenuItem *item; 209 CCARRAY_FOREACH(children_, item) 210 width += item.contentSize.width * item.scaleX + padding; 211 212 float x = -width / 2.0f; 213 214 CCARRAY_FOREACH(children_, item){ 215 CGSize itemSize = item.contentSize; 216 [item setPosition:ccp(x + itemSize.width * item.scaleX / 2.0f, 0)]; 217 x += itemSize.width * item.scaleX + padding; 218 } 219} 220 221-(void) alignItemsInColumns: (NSNumber *) columns, ... 222{ 223 va_list args; 224 va_start(args, columns); 225 226 [self alignItemsInColumns:columns vaList:args]; 227 228 va_end(args); 229} 230 231-(void) alignItemsInColumns: (NSNumber *) columns vaList: (va_list) args 232{ 233 NSMutableArray *rows = [[NSMutableArray alloc] initWithObjects:columns, nil]; 234 columns = va_arg(args, NSNumber*); 235 while(columns) { 236 [rows addObject:columns]; 237 columns = va_arg(args, NSNumber*); 238 } 239 240 int height = -5; 241 NSUInteger row = 0, rowHeight = 0, columnsOccupied = 0, rowColumns; 242 CCMenuItem *item; 243 CCARRAY_FOREACH(children_, item){ 244 NSAssert( row < [rows count], @"Too many menu items for the amount of rows/columns."); 245 246 rowColumns = [(NSNumber *) [rows objectAtIndex:row] unsignedIntegerValue]; 247 NSAssert( rowColumns, @"Can't have zero columns on a row"); 248 249 rowHeight = fmaxf(rowHeight, item.contentSize.height); 250 ++columnsOccupied; 251 252 if(columnsOccupied >= rowColumns) { 253 height += rowHeight + 5; 254 255 columnsOccupied = 0; 256 rowHeight = 0; 257 ++row; 258 } 259 } 260 NSAssert( !columnsOccupied, @"Too many rows/columns for available menu items." ); 261 262 CGSize winSize = [[CCDirector sharedDirector] winSize]; 263 264 row = 0; rowHeight = 0; rowColumns = 0; 265 float w, x, y = height / 2; 266 CCARRAY_FOREACH(children_, item) { 267 if(rowColumns == 0) { 268 rowColumns = [(NSNumber *) [rows objectAtIndex:row] unsignedIntegerValue]; 269 w = winSize.width / (1 + rowColumns); 270 x = w; 271 } 272 273 CGSize itemSize = item.contentSize; 274 rowHeight = fmaxf(rowHeight, itemSize.height); 275 [item setPosition:ccp(x - winSize.width / 2, 276 y - itemSize.height / 2)]; 277 278 x += w + 10; 279 ++columnsOccupied; 280 281 if(columnsOccupied >= rowColumns) { 282 y -= rowHeight + 5; 283 284 columnsOccupied = 0; 285 rowColumns = 0; 286 rowHeight = 0; 287 ++row; 288 } 289 } 290 291 [rows release]; 292} 293 294-(void) alignItemsInRows: (NSNumber *) rows, ... 295{ 296 va_list args; 297 va_start(args, rows); 298 299 [self alignItemsInRows:rows vaList:args]; 300 301 va_end(args); 302} 303 304-(void) alignItemsInRows: (NSNumber *) rows vaList: (va_list) args 305{ 306 NSMutableArray *columns = [[NSMutableArray alloc] initWithObjects:rows, nil]; 307 rows = va_arg(args, NSNumber*); 308 while(rows) { 309 [columns addObject:rows]; 310 rows = va_arg(args, NSNumber*); 311 } 312 313 NSMutableArray *columnWidths = [[NSMutableArray alloc] init]; 314 NSMutableArray *columnHeights = [[NSMutableArray alloc] init]; 315 316 int width = -10, columnHeight = -5; 317 NSUInteger column = 0, columnWidth = 0, rowsOccupied = 0, columnRows; 318 CCMenuItem *item; 319 CCARRAY_FOREACH(children_, item){ 320 NSAssert( column < [columns count], @"Too many menu items for the amount of rows/columns."); 321 322 columnRows = [(NSNumber *) [columns objectAtIndex:column] unsignedIntegerValue]; 323 NSAssert( columnRows, @"Can't have zero rows on a column"); 324 325 CGSize itemSize = item.contentSize; 326 columnWidth = fmaxf(columnWidth, itemSize.width); 327 columnHeight += itemSize.height + 5; 328 ++rowsOccupied; 329 330 if(rowsOccupied >= columnRows) { 331 [columnWidths addObject:[NSNumber numberWithUnsignedInteger:columnWidth]]; 332 [columnHeights addObject:[NSNumber numberWithUnsignedInteger:columnHeight]]; 333 width += columnWidth + 10; 334 335 rowsOccupied = 0; 336 columnWidth = 0; 337 columnHeight = -5; 338 ++column; 339 } 340 } 341 NSAssert( !rowsOccupied, @"Too many rows/columns for available menu items."); 342 343 CGSize winSize = [[CCDirector sharedDirector] winSize]; 344 345 column = 0; columnWidth = 0; columnRows = 0; 346 float x = -width / 2, y; 347 348 CCARRAY_FOREACH(children_, item){ 349 if(columnRows == 0) { 350 columnRows = [(NSNumber *) [columns objectAtIndex:column] unsignedIntegerValue]; 351 y = ([(NSNumber *) [columnHeights objectAtIndex:column] intValue] + winSize.height) / 2; 352 } 353 354 CGSize itemSize = item.contentSize; 355 columnWidth = fmaxf(columnWidth, itemSize.width); 356 [item setPosition:ccp(x + [(NSNumber *) [columnWidths objectAtIndex:column] unsignedIntegerValue] / 2, 357 y - winSize.height / 2)]; 358 359 y -= itemSize.height + 10; 360 ++rowsOccupied; 361 362 if(rowsOccupied >= columnRows) { 363 x += columnWidth + 5; 364 365 rowsOccupied = 0; 366 columnRows = 0; 367 columnWidth = 0; 368 ++column; 369 } 370 } 371 372 [columns release]; 373 [columnWidths release]; 374 [columnHeights release]; 375} 376 377#pragma mark Menu - Opacity Protocol 378 379/** Override synthesized setOpacity to recurse items */ 380- (void) setOpacity:(GLubyte)newOpacity 381{ 382 opacity_ = newOpacity; 383 id<CCRGBAProtocol> item; 384 CCARRAY_FOREACH(children_, item) 385 [item setOpacity:opacity_]; 386} 387 388-(void) setColor:(ccColor3B)color 389{ 390 color_ = color; 391 id<CCRGBAProtocol> item; 392 CCARRAY_FOREACH(children_, item) 393 [item setColor:color_]; 394} 395 396#pragma mark Menu - Private 397 398-(CCMenuItem *) itemForTouch: (UITouch *) touch 399{ 400 CGPoint touchLocation = [touch locationInView: [touch view]]; 401 touchLocation = [[CCDirector sharedDirector] convertToGL: touchLocation]; 402 403 CCMenuItem* item; 404 CCARRAY_FOREACH(children_, item){ 405 // ignore invisible and disabled items: issue #779, #866 406 if ( [item visible] && [item isEnabled] ) { 407 408 CGPoint local = [item convertToNodeSpace:touchLocation]; 409 410 CGRect r = [item rect]; 411 r.origin = CGPointZero; 412 413 if( CGRectContainsPoint( r, local ) ) 414 return item; 415 } 416 } 417 return nil; 418} 419@end