1 /**************************************************************************** 2 Copyright (c) 2010-2012 cocos2d-x.org 3 Copyright (c) 2008-2010 Ricardo Quesada 4 Copyright (c) 2011 Zynga Inc. 5 6 http://www.cocos2d-x.org 7 8 Permission is hereby granted, free of charge, to any person obtaining a copy 9 of this software and associated documentation files (the "Software"), to deal 10 in the Software without restriction, including without limitation the rights 11 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 copies of the Software, and to permit persons to whom the Software is 13 furnished to do so, subject to the following conditions: 14 15 The above copyright notice and this permission notice shall be included in 16 all copies or substantial portions of the Software. 17 18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 THE SOFTWARE. 25 ****************************************************************************/ 26 27 /** 28 * @constant 29 * @type Number 30 */ 31 cc.MENU_STATE_WAITING = 0; 32 /** 33 * @constant 34 * @type Number 35 */ 36 cc.MENU_STATE_TRACKING_TOUCH = 1; 37 /** 38 * @constant 39 * @type Number 40 */ 41 cc.MENU_HANDLER_PRIORITY = -128; 42 /** 43 * @constant 44 * @type Number 45 */ 46 cc.DEFAULT_PADDING = 5; 47 48 /** 49 * <p> Features and Limitation:<br/> 50 * - You can add MenuItem objects in runtime using addChild:<br/> 51 * - But the only accecpted children are MenuItem objects</p> 52 * @class 53 * @extends cc.LayerRGBA 54 */ 55 cc.Menu = cc.LayerRGBA.extend(/** @lends cc.Menu# */{ 56 _color:null, 57 _enabled:false, 58 _opacity:0, 59 _selectedItem:null, 60 _state:-1, 61 62 ctor:function(){ 63 cc.LayerRGBA.prototype.ctor.call(this); 64 this._color = cc.white(); 65 this._enabled = false; 66 this._opacity = 255; 67 this._selectedItem = null; 68 this._state = -1; 69 }, 70 71 /** 72 * @return {cc.Color3B} 73 */ 74 getColor:function () { 75 return this._color; 76 }, 77 78 /** 79 * @param {cc.Color3B} color 80 */ 81 setColor:function (color) { 82 this._color = color; 83 var locChildren = this._children; 84 if (locChildren && locChildren.length > 0) { 85 for (var i = 0; i < locChildren.length; i++) 86 locChildren[i].setColor(color); 87 } 88 }, 89 90 /** 91 * @return {Number} 92 */ 93 getOpacity:function () { 94 return this._opacity; 95 }, 96 97 /** 98 * @param {Number} opa 99 */ 100 setOpacity:function (opa) { 101 this._opacity = opa; 102 var locChildren = this._children; 103 if (locChildren && locChildren.length > 0) { 104 for (var i = 0; i < locChildren.length; i++) 105 locChildren[i].setOpacity(opa); 106 } 107 }, 108 109 /** 110 * return whether or not the menu will receive events 111 * @return {Boolean} 112 */ 113 isEnabled:function () { 114 return this._enabled; 115 }, 116 117 /** 118 * set whether or not the menu will receive events 119 * @param {Boolean} enabled 120 */ 121 setEnabled:function (enabled) { 122 this._enabled = enabled; 123 }, 124 125 /** 126 * initializes a cc.Menu with it's items 127 * @param {Array} args 128 * @return {Boolean} 129 */ 130 initWithItems:function (args) { 131 var pArray = []; 132 if (args) { 133 for (var i = 0; i < args.length; i++) { 134 if (args[i]) 135 pArray.push(args[i]); 136 } 137 } 138 139 return this.initWithArray(pArray); 140 }, 141 142 /** 143 * initializes a cc.Menu with a Array of cc.MenuItem objects 144 */ 145 initWithArray:function (arrayOfItems) { 146 if (this.init()) { 147 this.setTouchPriority(cc.MENU_HANDLER_PRIORITY); 148 this.setTouchMode(cc.TOUCH_ONE_BY_ONE); 149 this.setTouchEnabled(true); 150 this._enabled = true; 151 152 // menu in the center of the screen 153 var winSize = cc.Director.getInstance().getWinSize(); 154 this.ignoreAnchorPointForPosition(true); 155 this.setAnchorPoint(cc.p(0.5, 0.5)); 156 this.setContentSize(winSize); 157 this.setPosition(winSize.width / 2, winSize.height / 2); 158 159 if (arrayOfItems) { 160 for (var i = 0; i < arrayOfItems.length; i++) 161 this.addChild(arrayOfItems[i],i); 162 } 163 164 this._selectedItem = null; 165 this._state = cc.MENU_STATE_WAITING; 166 167 // enable cascade color and opacity on menus 168 this.setCascadeColorEnabled(true); 169 this.setCascadeOpacityEnabled(true); 170 return true; 171 } 172 return false; 173 }, 174 175 /** 176 * @param {cc.Node} child 177 * @param {Number|Null} [zOrder=] 178 * @param {Number|Null} [tag=] 179 */ 180 addChild:function (child, zOrder, tag) { 181 if(!(child instanceof cc.MenuItem)) 182 throw "cc.Menu.addChild() : Menu only supports MenuItem objects as children"; 183 cc.Layer.prototype.addChild.call(this, child, zOrder, tag); 184 }, 185 186 /** 187 * align items vertically with default padding 188 */ 189 alignItemsVertically:function () { 190 this.alignItemsVerticallyWithPadding(cc.DEFAULT_PADDING); 191 }, 192 193 /** 194 * align items vertically with specified padding 195 * @param {Number} padding 196 */ 197 alignItemsVerticallyWithPadding:function (padding) { 198 var height = -padding, locChildren = this._children, len, i, locScaleY, locHeight, locChild; 199 if (locChildren && locChildren.length > 0) { 200 for (i = 0, len = locChildren.length; i < len; i++) 201 height += locChildren[i].getContentSize().height * locChildren[i].getScaleY() + padding; 202 203 var y = height / 2.0; 204 205 for (i = 0, len = locChildren.length; i < len; i++) { 206 locChild = locChildren[i]; 207 locHeight = locChild.getContentSize().height; 208 locScaleY = locChild.getScaleY(); 209 locChild.setPosition(0, y - locHeight * locScaleY / 2); 210 y -= locHeight * locScaleY + padding; 211 } 212 } 213 }, 214 215 /** 216 * align items horizontally with default padding 217 */ 218 alignItemsHorizontally:function () { 219 this.alignItemsHorizontallyWithPadding(cc.DEFAULT_PADDING); 220 }, 221 222 /** 223 * align items horizontally with specified padding 224 * @param {Number} padding 225 */ 226 alignItemsHorizontallyWithPadding:function (padding) { 227 var width = -padding, locChildren = this._children, i, len, locScaleX, locWidth, locChild; 228 if (locChildren && locChildren.length > 0) { 229 for (i = 0, len = locChildren.length; i < len; i++) 230 width += locChildren[i].getContentSize().width * locChildren[i].getScaleX() + padding; 231 232 var x = -width / 2.0; 233 234 for (i = 0, len = locChildren.length; i < len; i++) { 235 locChild = locChildren[i]; 236 locScaleX = locChild.getScaleX(); 237 locWidth = locChildren[i].getContentSize().width; 238 locChild.setPosition(x + locWidth * locScaleX / 2, 0); 239 x += locWidth * locScaleX + padding; 240 } 241 } 242 }, 243 244 /** 245 * align items in columns 246 * @example 247 * // Example 248 * menu.alignItemsInColumns(3,2,3)// this will create 3 columns, with 3 items for first column, 2 items for second and 3 for third 249 * 250 * menu.alignItemsInColumns(3,3)//this creates 2 columns, each have 3 items 251 */ 252 alignItemsInColumns:function (/*Multiple Arguments*/) { 253 if((arguments.length > 0) && (arguments[arguments.length-1] == null)) 254 cc.log("parameters should not be ending with null in Javascript"); 255 256 var rows = []; 257 for (var i = 0; i < arguments.length; i++) { 258 rows.push(arguments[i]); 259 } 260 var height = -5; 261 var row = 0; 262 var rowHeight = 0; 263 var columnsOccupied = 0; 264 var rowColumns, tmp, len; 265 var locChildren = this._children; 266 if (locChildren && locChildren.length > 0) { 267 for (i = 0, len = locChildren.length; i < len; i++) { 268 if(row >= rows.length) 269 continue; 270 271 rowColumns = rows[row]; 272 // can not have zero columns on a row 273 if(!rowColumns) 274 continue; 275 276 tmp = locChildren[i].getContentSize().height; 277 rowHeight = ((rowHeight >= tmp || isNaN(tmp)) ? rowHeight : tmp); 278 279 ++columnsOccupied; 280 if (columnsOccupied >= rowColumns) { 281 height += rowHeight + 5; 282 283 columnsOccupied = 0; 284 rowHeight = 0; 285 ++row; 286 } 287 } 288 } 289 // check if too many rows/columns for available menu items 290 //cc.Assert(!columnsOccupied, ""); //? 291 var winSize = cc.Director.getInstance().getWinSize(); 292 293 row = 0; 294 rowHeight = 0; 295 rowColumns = 0; 296 var w = 0.0; 297 var x = 0.0; 298 var y = (height / 2); 299 300 if (locChildren && locChildren.length > 0) { 301 for (i = 0, len = locChildren.length; i < len; i++) { 302 var child = locChildren[i]; 303 if (rowColumns == 0) { 304 rowColumns = rows[row]; 305 w = winSize.width / (1 + rowColumns); 306 x = w; 307 } 308 309 tmp = child.getContentSize().height; 310 rowHeight = ((rowHeight >= tmp || isNaN(tmp)) ? rowHeight : tmp); 311 child.setPosition(x - winSize.width / 2, y - tmp / 2); 312 313 x += w; 314 ++columnsOccupied; 315 316 if (columnsOccupied >= rowColumns) { 317 y -= rowHeight + 5; 318 columnsOccupied = 0; 319 rowColumns = 0; 320 rowHeight = 0; 321 ++row; 322 } 323 } 324 } 325 }, 326 /** 327 * align menu items in rows 328 * @example 329 * // Example 330 * menu.alignItemsInRows(5,3)//this will align items to 2 rows, first row with 5 items, second row with 3 331 * 332 * menu.alignItemsInRows(4,4,4,4)//this creates 4 rows each have 4 items 333 */ 334 alignItemsInRows:function (/*Multiple arguments*/) { 335 if((arguments.length > 0) && (arguments[arguments.length-1] == null)) 336 cc.log("parameters should not be ending with null in Javascript"); 337 var columns = [], i; 338 for (i = 0; i < arguments.length; i++) { 339 columns.push(arguments[i]); 340 } 341 var columnWidths = []; 342 var columnHeights = []; 343 344 var width = -10; 345 var columnHeight = -5; 346 var column = 0; 347 var columnWidth = 0; 348 var rowsOccupied = 0; 349 var columnRows, child, len, tmp, locContentSize; 350 351 var locChildren = this._children; 352 if (locChildren && locChildren.length > 0) { 353 for (i = 0, len = locChildren.length; i < len; i++) { 354 child = locChildren[i]; 355 // check if too many menu items for the amount of rows/columns 356 if(column >= columns.length) 357 continue; 358 359 columnRows = columns[column]; 360 // can't have zero rows on a column 361 if(!columnRows) 362 continue; 363 364 // columnWidth = fmaxf(columnWidth, [item contentSize].width); 365 locContentSize = child.getContentSize(); 366 tmp = locContentSize.width; 367 columnWidth = ((columnWidth >= tmp || isNaN(tmp)) ? columnWidth : tmp); 368 369 columnHeight += (locContentSize.height + 5); 370 ++rowsOccupied; 371 372 if (rowsOccupied >= columnRows) { 373 columnWidths.push(columnWidth); 374 columnHeights.push(columnHeight); 375 width += columnWidth + 10; 376 377 rowsOccupied = 0; 378 columnWidth = 0; 379 columnHeight = -5; 380 ++column; 381 } 382 } 383 } 384 // check if too many rows/columns for available menu items. 385 //cc.Assert(!rowsOccupied, ""); 386 var winSize = cc.Director.getInstance().getWinSize(); 387 388 column = 0; 389 columnWidth = 0; 390 columnRows = 0; 391 var x = -width / 2; 392 var y = 0.0; 393 394 if (locChildren && locChildren.length > 0) { 395 for (i = 0, len = locChildren.length; i < len; i++) { 396 child = locChildren[i]; 397 if (columnRows == 0) { 398 columnRows = columns[column]; 399 y = columnHeights[column]; 400 } 401 402 // columnWidth = fmaxf(columnWidth, [item contentSize].width); 403 locContentSize = child.getContentSize(); 404 tmp = locContentSize.width; 405 columnWidth = ((columnWidth >= tmp || isNaN(tmp)) ? columnWidth : tmp); 406 407 child.setPosition(x + columnWidths[column] / 2, y - winSize.height / 2); 408 409 y -= locContentSize.height + 10; 410 ++rowsOccupied; 411 412 if (rowsOccupied >= columnRows) { 413 x += columnWidth + 5; 414 rowsOccupied = 0; 415 columnRows = 0; 416 columnWidth = 0; 417 ++column; 418 } 419 } 420 } 421 }, 422 423 /** 424 * make the menu clickable 425 */ 426 registerWithTouchDispatcher:function () { 427 cc.registerTargetedDelegate(this.getTouchPriority(), true, this); 428 }, 429 430 /** 431 * @param {cc.Node} child 432 * @param {boolean} cleanup 433 */ 434 removeChild:function(child, cleanup){ 435 if(child == null) 436 return; 437 if(!(child instanceof cc.MenuItem)){ 438 cc.log("cc.Menu.removeChild():Menu only supports MenuItem objects as children"); 439 return; 440 } 441 442 if (this._selectedItem == child) 443 this._selectedItem = null; 444 cc.Node.prototype.removeChild.call(this, child, cleanup); 445 }, 446 447 /** 448 * @param {cc.Touch} touch 449 * @param {Object} e 450 * @return {Boolean} 451 */ 452 onTouchBegan:function (touch, e) { 453 if (this._state != cc.MENU_STATE_WAITING || !this._visible || !this._enabled) 454 return false; 455 456 for (var c = this._parent; c != null; c = c.getParent()) { 457 if (!c.isVisible()) 458 return false; 459 } 460 461 this._selectedItem = this._itemForTouch(touch); 462 if (this._selectedItem) { 463 this._state = cc.MENU_STATE_TRACKING_TOUCH; 464 this._selectedItem.selected(); 465 return true; 466 } 467 return false; 468 }, 469 470 /** 471 * when a touch ended 472 */ 473 onTouchEnded:function (touch, e) { 474 if(this._state !== cc.MENU_STATE_TRACKING_TOUCH){ 475 cc.log("cc.Menu.onTouchEnded(): invalid state"); 476 return; 477 } 478 if (this._selectedItem) { 479 this._selectedItem.unselected(); 480 this._selectedItem.activate(); 481 } 482 this._state = cc.MENU_STATE_WAITING; 483 }, 484 485 /** 486 * touch cancelled 487 */ 488 onTouchCancelled:function (touch, e) { 489 if(this._state !== cc.MENU_STATE_TRACKING_TOUCH){ 490 cc.log("cc.Menu.onTouchCancelled(): invalid state"); 491 return; 492 } 493 if (this._selectedItem) 494 this._selectedItem.unselected(); 495 this._state = cc.MENU_STATE_WAITING; 496 }, 497 498 /** 499 * touch moved 500 * @param {cc.Touch} touch 501 * @param {Object} e 502 */ 503 onTouchMoved:function (touch, e) { 504 if(this._state !== cc.MENU_STATE_TRACKING_TOUCH){ 505 cc.log("cc.Menu.onTouchMoved(): invalid state"); 506 return; 507 } 508 var currentItem = this._itemForTouch(touch); 509 if (currentItem != this._selectedItem) { 510 if (this._selectedItem) 511 this._selectedItem.unselected(); 512 this._selectedItem = currentItem; 513 if (this._selectedItem) 514 this._selectedItem.selected(); 515 } 516 }, 517 518 /** 519 * custom on exit 520 */ 521 onExit:function () { 522 if (this._state == cc.MENU_STATE_TRACKING_TOUCH) { 523 if(this._selectedItem){ 524 this._selectedItem.unselected(); 525 this._selectedItem = null; 526 } 527 this._state = cc.MENU_STATE_WAITING; 528 } 529 cc.Layer.prototype.onExit.call(this); 530 }, 531 532 setOpacityModifyRGB:function (value) { 533 }, 534 535 isOpacityModifyRGB:function () { 536 return false; 537 }, 538 539 _itemForTouch:function (touch) { 540 var touchLocation = touch.getLocation(); 541 var itemChildren = this._children, locItemChild; 542 if (itemChildren && itemChildren.length > 0) { 543 for (var i = 0; i < itemChildren.length; i++) { 544 locItemChild = itemChildren[i]; 545 if (locItemChild.isVisible() && locItemChild.isEnabled()) { 546 var local = locItemChild.convertToNodeSpace(touchLocation); 547 var r = locItemChild.rect(); 548 r.x = 0; 549 r.y = 0; 550 if (cc.rectContainsPoint(r, local)) 551 return locItemChild; 552 } 553 } 554 } 555 return null; 556 }, 557 558 /** 559 * set event handler priority. By default it is: kCCMenuTouchPriority 560 * @param {Number} newPriority 561 */ 562 setHandlerPriority:function (newPriority) { 563 cc.Director.getInstance().getTouchDispatcher().setPriority(newPriority, this); 564 } 565 }); 566 567 /** 568 * create a new menu 569 * @return {cc.Menu} 570 * @example 571 * // Example 572 * //there is no limit on how many menu item you can pass in 573 * var myMenu = cc.Menu.create(menuitem1, menuitem2, menuitem3); 574 */ 575 cc.Menu.create = function (/*Multiple Arguments*/) { 576 if((arguments.length > 0) && (arguments[arguments.length-1] == null)) 577 cc.log("parameters should not be ending with null in Javascript"); 578 579 var ret = new cc.Menu(); 580 581 if (arguments.length == 0) { 582 ret.initWithItems(null, null); 583 } else if (arguments.length == 1) { 584 if (arguments[0] instanceof Array) { 585 ret.initWithArray(arguments[0]); 586 return ret; 587 } 588 } 589 ret.initWithItems(arguments); 590 return ret; 591 }; 592