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