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 accepted 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 * @param {Array} arrayOfItems 145 * @return {Boolean} 146 */ 147 initWithArray:function (arrayOfItems) { 148 if (this.init()) { 149 this.setTouchPriority(cc.MENU_HANDLER_PRIORITY); 150 this.setTouchMode(cc.TOUCH_ONE_BY_ONE); 151 this.setTouchEnabled(true); 152 this._enabled = true; 153 154 // menu in the center of the screen 155 var winSize = cc.Director.getInstance().getWinSize(); 156 this.ignoreAnchorPointForPosition(true); 157 this.setAnchorPoint(0.5, 0.5); 158 this.setContentSize(winSize); 159 this.setPosition(winSize.width / 2, winSize.height / 2); 160 161 if (arrayOfItems) { 162 for (var i = 0; i < arrayOfItems.length; i++) 163 this.addChild(arrayOfItems[i],i); 164 } 165 166 this._selectedItem = null; 167 this._state = cc.MENU_STATE_WAITING; 168 169 // enable cascade color and opacity on menus 170 this.setCascadeColorEnabled(true); 171 this.setCascadeOpacityEnabled(true); 172 return true; 173 } 174 return false; 175 }, 176 177 /** 178 * @param {cc.Node} child 179 * @param {Number|Null} [zOrder=] 180 * @param {Number|Null} [tag=] 181 */ 182 addChild:function (child, zOrder, tag) { 183 if(!(child instanceof cc.MenuItem)) 184 throw "cc.Menu.addChild() : Menu only supports MenuItem objects as children"; 185 cc.Layer.prototype.addChild.call(this, child, zOrder, tag); 186 }, 187 188 /** 189 * align items vertically with default padding 190 */ 191 alignItemsVertically:function () { 192 this.alignItemsVerticallyWithPadding(cc.DEFAULT_PADDING); 193 }, 194 195 /** 196 * align items vertically with specified padding 197 * @param {Number} padding 198 */ 199 alignItemsVerticallyWithPadding:function (padding) { 200 var height = -padding, locChildren = this._children, len, i, locScaleY, locHeight, locChild; 201 if (locChildren && locChildren.length > 0) { 202 for (i = 0, len = locChildren.length; i < len; i++) 203 height += locChildren[i].getContentSize().height * locChildren[i].getScaleY() + padding; 204 205 var y = height / 2.0; 206 207 for (i = 0, len = locChildren.length; i < len; i++) { 208 locChild = locChildren[i]; 209 locHeight = locChild.getContentSize().height; 210 locScaleY = locChild.getScaleY(); 211 locChild.setPosition(0, y - locHeight * locScaleY / 2); 212 y -= locHeight * locScaleY + padding; 213 } 214 } 215 }, 216 217 /** 218 * align items horizontally with default padding 219 */ 220 alignItemsHorizontally:function () { 221 this.alignItemsHorizontallyWithPadding(cc.DEFAULT_PADDING); 222 }, 223 224 /** 225 * align items horizontally with specified padding 226 * @param {Number} padding 227 */ 228 alignItemsHorizontallyWithPadding:function (padding) { 229 var width = -padding, locChildren = this._children, i, len, locScaleX, locWidth, locChild; 230 if (locChildren && locChildren.length > 0) { 231 for (i = 0, len = locChildren.length; i < len; i++) 232 width += locChildren[i].getContentSize().width * locChildren[i].getScaleX() + padding; 233 234 var x = -width / 2.0; 235 236 for (i = 0, len = locChildren.length; i < len; i++) { 237 locChild = locChildren[i]; 238 locScaleX = locChild.getScaleX(); 239 locWidth = locChildren[i].getContentSize().width; 240 locChild.setPosition(x + locWidth * locScaleX / 2, 0); 241 x += locWidth * locScaleX + padding; 242 } 243 } 244 }, 245 246 /** 247 * align items in columns 248 * @example 249 * // Example 250 * menu.alignItemsInColumns(3,2,3)// this will create 3 columns, with 3 items for first column, 2 items for second and 3 for third 251 * 252 * menu.alignItemsInColumns(3,3)//this creates 2 columns, each have 3 items 253 */ 254 alignItemsInColumns:function (/*Multiple Arguments*/) { 255 if((arguments.length > 0) && (arguments[arguments.length-1] == null)) 256 cc.log("parameters should not be ending with null in Javascript"); 257 258 var rows = []; 259 for (var i = 0; i < arguments.length; i++) { 260 rows.push(arguments[i]); 261 } 262 var height = -5; 263 var row = 0; 264 var rowHeight = 0; 265 var columnsOccupied = 0; 266 var rowColumns, tmp, len; 267 var locChildren = this._children; 268 if (locChildren && locChildren.length > 0) { 269 for (i = 0, len = locChildren.length; i < len; i++) { 270 if(row >= rows.length) 271 continue; 272 273 rowColumns = rows[row]; 274 // can not have zero columns on a row 275 if(!rowColumns) 276 continue; 277 278 tmp = locChildren[i].getContentSize().height; 279 rowHeight = ((rowHeight >= tmp || isNaN(tmp)) ? rowHeight : tmp); 280 281 ++columnsOccupied; 282 if (columnsOccupied >= rowColumns) { 283 height += rowHeight + 5; 284 285 columnsOccupied = 0; 286 rowHeight = 0; 287 ++row; 288 } 289 } 290 } 291 // check if too many rows/columns for available menu items 292 //cc.Assert(!columnsOccupied, ""); //? 293 var winSize = cc.Director.getInstance().getWinSize(); 294 295 row = 0; 296 rowHeight = 0; 297 rowColumns = 0; 298 var w = 0.0; 299 var x = 0.0; 300 var y = (height / 2); 301 302 if (locChildren && locChildren.length > 0) { 303 for (i = 0, len = locChildren.length; i < len; i++) { 304 var child = locChildren[i]; 305 if (rowColumns == 0) { 306 rowColumns = rows[row]; 307 w = winSize.width / (1 + rowColumns); 308 x = w; 309 } 310 311 tmp = child.getContentSize().height; 312 rowHeight = ((rowHeight >= tmp || isNaN(tmp)) ? rowHeight : tmp); 313 child.setPosition(x - winSize.width / 2, y - tmp / 2); 314 315 x += w; 316 ++columnsOccupied; 317 318 if (columnsOccupied >= rowColumns) { 319 y -= rowHeight + 5; 320 columnsOccupied = 0; 321 rowColumns = 0; 322 rowHeight = 0; 323 ++row; 324 } 325 } 326 } 327 }, 328 /** 329 * align menu items in rows 330 * @example 331 * // Example 332 * menu.alignItemsInRows(5,3)//this will align items to 2 rows, first row with 5 items, second row with 3 333 * 334 * menu.alignItemsInRows(4,4,4,4)//this creates 4 rows each have 4 items 335 */ 336 alignItemsInRows:function (/*Multiple arguments*/) { 337 if((arguments.length > 0) && (arguments[arguments.length-1] == null)) 338 cc.log("parameters should not be ending with null in Javascript"); 339 var columns = [], i; 340 for (i = 0; i < arguments.length; i++) { 341 columns.push(arguments[i]); 342 } 343 var columnWidths = []; 344 var columnHeights = []; 345 346 var width = -10; 347 var columnHeight = -5; 348 var column = 0; 349 var columnWidth = 0; 350 var rowsOccupied = 0; 351 var columnRows, child, len, tmp, locContentSize; 352 353 var locChildren = this._children; 354 if (locChildren && locChildren.length > 0) { 355 for (i = 0, len = locChildren.length; i < len; i++) { 356 child = locChildren[i]; 357 // check if too many menu items for the amount of rows/columns 358 if(column >= columns.length) 359 continue; 360 361 columnRows = columns[column]; 362 // can't have zero rows on a column 363 if(!columnRows) 364 continue; 365 366 // columnWidth = fmaxf(columnWidth, [item contentSize].width); 367 locContentSize = child.getContentSize(); 368 tmp = locContentSize.width; 369 columnWidth = ((columnWidth >= tmp || isNaN(tmp)) ? columnWidth : tmp); 370 371 columnHeight += (locContentSize.height + 5); 372 ++rowsOccupied; 373 374 if (rowsOccupied >= columnRows) { 375 columnWidths.push(columnWidth); 376 columnHeights.push(columnHeight); 377 width += columnWidth + 10; 378 379 rowsOccupied = 0; 380 columnWidth = 0; 381 columnHeight = -5; 382 ++column; 383 } 384 } 385 } 386 // check if too many rows/columns for available menu items. 387 //cc.Assert(!rowsOccupied, ""); 388 var winSize = cc.Director.getInstance().getWinSize(); 389 390 column = 0; 391 columnWidth = 0; 392 columnRows = 0; 393 var x = -width / 2; 394 var y = 0.0; 395 396 if (locChildren && locChildren.length > 0) { 397 for (i = 0, len = locChildren.length; i < len; i++) { 398 child = locChildren[i]; 399 if (columnRows == 0) { 400 columnRows = columns[column]; 401 y = columnHeights[column]; 402 } 403 404 // columnWidth = fmaxf(columnWidth, [item contentSize].width); 405 locContentSize = child.getContentSize(); 406 tmp = locContentSize.width; 407 columnWidth = ((columnWidth >= tmp || isNaN(tmp)) ? columnWidth : tmp); 408 409 child.setPosition(x + columnWidths[column] / 2, y - winSize.height / 2); 410 411 y -= locContentSize.height + 10; 412 ++rowsOccupied; 413 414 if (rowsOccupied >= columnRows) { 415 x += columnWidth + 5; 416 rowsOccupied = 0; 417 columnRows = 0; 418 columnWidth = 0; 419 ++column; 420 } 421 } 422 } 423 }, 424 425 /** 426 * make the menu clickable 427 */ 428 registerWithTouchDispatcher:function () { 429 cc.registerTargetedDelegate(this.getTouchPriority(), true, this); 430 }, 431 432 /** 433 * @param {cc.Node} child 434 * @param {boolean} cleanup 435 */ 436 removeChild:function(child, cleanup){ 437 if(child == null) 438 return; 439 if(!(child instanceof cc.MenuItem)){ 440 cc.log("cc.Menu.removeChild():Menu only supports MenuItem objects as children"); 441 return; 442 } 443 444 if (this._selectedItem == child) 445 this._selectedItem = null; 446 cc.Node.prototype.removeChild.call(this, child, cleanup); 447 }, 448 449 /** 450 * @param {cc.Touch} touch 451 * @param {Object} e 452 * @return {Boolean} 453 */ 454 onTouchBegan:function (touch, e) { 455 if (this._state != cc.MENU_STATE_WAITING || !this._visible || !this._enabled) 456 return false; 457 458 for (var c = this._parent; c != null; c = c.getParent()) { 459 if (!c.isVisible()) 460 return false; 461 } 462 463 this._selectedItem = this._itemForTouch(touch); 464 if (this._selectedItem) { 465 this._state = cc.MENU_STATE_TRACKING_TOUCH; 466 this._selectedItem.selected(); 467 return true; 468 } 469 return false; 470 }, 471 472 /** 473 * when a touch ended 474 */ 475 onTouchEnded:function (touch, e) { 476 if(this._state !== cc.MENU_STATE_TRACKING_TOUCH){ 477 cc.log("cc.Menu.onTouchEnded(): invalid state"); 478 return; 479 } 480 if (this._selectedItem) { 481 this._selectedItem.unselected(); 482 this._selectedItem.activate(); 483 } 484 this._state = cc.MENU_STATE_WAITING; 485 }, 486 487 /** 488 * touch cancelled 489 */ 490 onTouchCancelled:function (touch, e) { 491 if(this._state !== cc.MENU_STATE_TRACKING_TOUCH){ 492 cc.log("cc.Menu.onTouchCancelled(): invalid state"); 493 return; 494 } 495 if (this._selectedItem) 496 this._selectedItem.unselected(); 497 this._state = cc.MENU_STATE_WAITING; 498 }, 499 500 /** 501 * touch moved 502 * @param {cc.Touch} touch 503 * @param {Object} e 504 */ 505 onTouchMoved:function (touch, e) { 506 if(this._state !== cc.MENU_STATE_TRACKING_TOUCH){ 507 cc.log("cc.Menu.onTouchMoved(): invalid state"); 508 return; 509 } 510 var currentItem = this._itemForTouch(touch); 511 if (currentItem != this._selectedItem) { 512 if (this._selectedItem) 513 this._selectedItem.unselected(); 514 this._selectedItem = currentItem; 515 if (this._selectedItem) 516 this._selectedItem.selected(); 517 } 518 }, 519 520 /** 521 * custom on exit 522 */ 523 onExit:function () { 524 if (this._state == cc.MENU_STATE_TRACKING_TOUCH) { 525 if(this._selectedItem){ 526 this._selectedItem.unselected(); 527 this._selectedItem = null; 528 } 529 this._state = cc.MENU_STATE_WAITING; 530 } 531 cc.Layer.prototype.onExit.call(this); 532 }, 533 534 setOpacityModifyRGB:function (value) { 535 }, 536 537 isOpacityModifyRGB:function () { 538 return false; 539 }, 540 541 _itemForTouch:function (touch) { 542 var touchLocation = touch.getLocation(); 543 var itemChildren = this._children, locItemChild; 544 if (itemChildren && itemChildren.length > 0) { 545 for (var i = 0; i < itemChildren.length; i++) { 546 locItemChild = itemChildren[i]; 547 if (locItemChild.isVisible() && locItemChild.isEnabled()) { 548 var local = locItemChild.convertToNodeSpace(touchLocation); 549 var r = locItemChild.rect(); 550 r.x = 0; 551 r.y = 0; 552 if (cc.rectContainsPoint(r, local)) 553 return locItemChild; 554 } 555 } 556 } 557 return null; 558 }, 559 560 /** 561 * set event handler priority. By default it is: kCCMenuTouchPriority 562 * @param {Number} newPriority 563 */ 564 setHandlerPriority:function (newPriority) { 565 cc.Director.getInstance().getTouchDispatcher().setPriority(newPriority, this); 566 } 567 }); 568 569 /** 570 * create a new menu 571 * @param {...cc.MenuItem|null} menuItems 572 * @return {cc.Menu} 573 * @example 574 * // Example 575 * //there is no limit on how many menu item you can pass in 576 * var myMenu = cc.Menu.create(menuitem1, menuitem2, menuitem3); 577 */ 578 cc.Menu.create = function (menuItems) { 579 if((arguments.length > 0) && (arguments[arguments.length-1] == null)) 580 cc.log("parameters should not be ending with null in Javascript"); 581 582 var ret = new cc.Menu(); 583 584 if (arguments.length == 0) { 585 ret.initWithItems(null, null); 586 } else if (arguments.length == 1) { 587 if (arguments[0] instanceof Array) { 588 ret.initWithArray(arguments[0]); 589 return ret; 590 } 591 } 592 ret.initWithItems(arguments); 593 return ret; 594 }; 595