1 /**************************************************************************** 2 Copyright (c) 2008-2010 Ricardo Quesada 3 Copyright (c) 2011-2012 cocos2d-x.org 4 Copyright (c) 2013-2014 Chukong Technologies 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.Layer 54 * @param {...cc.MenuItem|null} menuItems} 55 * @example 56 * var layer = new cc.Menu(menuitem1, menuitem2, menuitem3); 57 */ 58 cc.Menu = cc.Layer.extend(/** @lends cc.Menu# */{ 59 enabled: false, 60 61 _selectedItem: null, 62 _state: -1, 63 _touchListener: null, 64 _className: "Menu", 65 66 /** 67 * Constructor of cc.Menu override it to extend the construction behavior, remember to call "this._super()" in the extended "ctor" function. 68 * @param {...cc.MenuItem|null} menuItems 69 */ 70 ctor: function (menuItems) { 71 cc.Layer.prototype.ctor.call(this); 72 this._color = cc.color.WHITE; 73 this.enabled = false; 74 this._opacity = 255; 75 this._selectedItem = null; 76 this._state = -1; 77 78 this._touchListener = cc.EventListener.create({ 79 event: cc.EventListener.TOUCH_ONE_BY_ONE, 80 swallowTouches: true, 81 onTouchBegan: this._onTouchBegan, 82 onTouchMoved: this._onTouchMoved, 83 onTouchEnded: this._onTouchEnded, 84 onTouchCancelled: this._onTouchCancelled 85 }); 86 87 if ((arguments.length > 0) && (arguments[arguments.length - 1] == null)) 88 cc.log("parameters should not be ending with null in Javascript"); 89 90 var argc = arguments.length, items; 91 if (argc == 0) { 92 items = []; 93 } else if (argc == 1) { 94 if (menuItems instanceof Array) { 95 items = menuItems; 96 } 97 else items = [menuItems]; 98 } 99 else if (argc > 1) { 100 items = []; 101 for (var i = 0; i < argc; i++) { 102 if (arguments[i]) 103 items.push(arguments[i]); 104 } 105 } 106 this.initWithArray(items); 107 }, 108 /** 109 * <p> 110 * Event callback that is invoked every time when CCMenu enters the 'stage'. <br/> 111 * If the CCMenu enters the 'stage' with a transition, this event is called when the transition starts. <br/> 112 * During onEnter you can't access a "sister/brother" node. <br/> 113 * If you override onEnter, you must call its parent's onEnter function with this._super(). 114 * </p> 115 */ 116 onEnter: function () { 117 var locListener = this._touchListener; 118 if (!locListener._isRegistered()) 119 cc.eventManager.addListener(locListener, this); 120 cc.Node.prototype.onEnter.call(this); 121 }, 122 123 /** 124 * return whether or not the menu will receive events 125 * @return {Boolean} 126 */ 127 isEnabled: function () { 128 return this.enabled; 129 }, 130 131 /** 132 * set whether or not the menu will receive events 133 * @param {Boolean} enabled 134 */ 135 setEnabled: function (enabled) { 136 this.enabled = enabled; 137 }, 138 139 /** 140 * initializes a cc.Menu with it's items 141 * @param {Array} args 142 * @return {Boolean} 143 */ 144 initWithItems: function (args) { 145 var pArray = []; 146 if (args) { 147 for (var i = 0; i < args.length; i++) { 148 if (args[i]) 149 pArray.push(args[i]); 150 } 151 } 152 153 return this.initWithArray(pArray); 154 }, 155 156 /** 157 * initializes a cc.Menu with a Array of cc.MenuItem objects 158 * @param {Array} array Of cc.MenuItem Items 159 * @return {Boolean} 160 */ 161 initWithArray: function (arrayOfItems) { 162 if (cc.Layer.prototype.init.call(this)) { 163 this.enabled = true; 164 165 // menu in the center of the screen 166 var winSize = cc.winSize; 167 this.setPosition(winSize.width / 2, winSize.height / 2); 168 this.setContentSize(winSize); 169 this.setAnchorPoint(0.5, 0.5); 170 this.ignoreAnchorPointForPosition(true); 171 172 if (arrayOfItems) { 173 for (var i = 0; i < arrayOfItems.length; i++) 174 this.addChild(arrayOfItems[i], i); 175 } 176 177 this._selectedItem = null; 178 this._state = cc.MENU_STATE_WAITING; 179 180 // enable cascade color and opacity on menus 181 this.cascadeColor = true; 182 this.cascadeOpacity = true; 183 184 return true; 185 } 186 return false; 187 }, 188 189 /** 190 * add a child for cc.Menu 191 * @param {cc.Node} child 192 * @param {Number|Null} [zOrder=] zOrder for the child 193 * @param {Number|Null} [tag=] tag for the child 194 */ 195 addChild: function (child, zOrder, tag) { 196 if (!(child instanceof cc.MenuItem)) 197 throw "cc.Menu.addChild() : Menu only supports MenuItem objects as children"; 198 cc.Layer.prototype.addChild.call(this, child, zOrder, tag); 199 }, 200 201 /** 202 * align items vertically with default padding 203 */ 204 alignItemsVertically: function () { 205 this.alignItemsVerticallyWithPadding(cc.DEFAULT_PADDING); 206 }, 207 208 /** 209 * align items vertically with specified padding 210 * @param {Number} padding 211 */ 212 alignItemsVerticallyWithPadding: function (padding) { 213 var height = -padding, locChildren = this._children, len, i, locScaleY, locHeight, locChild; 214 if (locChildren && locChildren.length > 0) { 215 for (i = 0, len = locChildren.length; i < len; i++) 216 height += locChildren[i].height * locChildren[i].scaleY + padding; 217 218 var y = height / 2.0; 219 220 for (i = 0, len = locChildren.length; i < len; i++) { 221 locChild = locChildren[i]; 222 locHeight = locChild.height; 223 locScaleY = locChild.scaleY; 224 locChild.setPosition(0, y - locHeight * locScaleY / 2); 225 y -= locHeight * locScaleY + padding; 226 } 227 } 228 }, 229 230 /** 231 * align items horizontally with default padding 232 */ 233 alignItemsHorizontally: function () { 234 this.alignItemsHorizontallyWithPadding(cc.DEFAULT_PADDING); 235 }, 236 237 /** 238 * align items horizontally with specified padding 239 * @param {Number} padding 240 */ 241 alignItemsHorizontallyWithPadding: function (padding) { 242 var width = -padding, locChildren = this._children, i, len, locScaleX, locWidth, locChild; 243 if (locChildren && locChildren.length > 0) { 244 for (i = 0, len = locChildren.length; i < len; i++) 245 width += locChildren[i].width * locChildren[i].scaleX + padding; 246 247 var x = -width / 2.0; 248 249 for (i = 0, len = locChildren.length; i < len; i++) { 250 locChild = locChildren[i]; 251 locScaleX = locChild.scaleX; 252 locWidth = locChildren[i].width; 253 locChild.setPosition(x + locWidth * locScaleX / 2, 0); 254 x += locWidth * locScaleX + padding; 255 } 256 } 257 }, 258 259 /** 260 * align items in columns 261 * @example 262 * // Example 263 * menu.alignItemsInColumns(3,2,3)// this will create 3 columns, with 3 items for first column, 2 items for second and 3 for third 264 * 265 * menu.alignItemsInColumns(3,3)//this creates 2 columns, each have 3 items 266 */ 267 alignItemsInColumns: function (/*Multiple Arguments*/) { 268 if ((arguments.length > 0) && (arguments[arguments.length - 1] == null)) 269 cc.log("parameters should not be ending with null in Javascript"); 270 271 var rows = []; 272 for (var i = 0; i < arguments.length; i++) { 273 rows.push(arguments[i]); 274 } 275 var height = -5; 276 var row = 0; 277 var rowHeight = 0; 278 var columnsOccupied = 0; 279 var rowColumns, tmp, len; 280 var locChildren = this._children; 281 if (locChildren && locChildren.length > 0) { 282 for (i = 0, len = locChildren.length; i < len; i++) { 283 if (row >= rows.length) 284 continue; 285 286 rowColumns = rows[row]; 287 // can not have zero columns on a row 288 if (!rowColumns) 289 continue; 290 291 tmp = locChildren[i].height; 292 rowHeight = ((rowHeight >= tmp || isNaN(tmp)) ? rowHeight : tmp); 293 294 ++columnsOccupied; 295 if (columnsOccupied >= rowColumns) { 296 height += rowHeight + 5; 297 298 columnsOccupied = 0; 299 rowHeight = 0; 300 ++row; 301 } 302 } 303 } 304 // check if too many rows/columns for available menu items 305 //cc.assert(!columnsOccupied, ""); //? 306 var winSize = cc.director.getWinSize(); 307 308 row = 0; 309 rowHeight = 0; 310 rowColumns = 0; 311 var w = 0.0; 312 var x = 0.0; 313 var y = (height / 2); 314 315 if (locChildren && locChildren.length > 0) { 316 for (i = 0, len = locChildren.length; i < len; i++) { 317 var child = locChildren[i]; 318 if (rowColumns == 0) { 319 rowColumns = rows[row]; 320 w = winSize.width / (1 + rowColumns); 321 x = w; 322 } 323 324 tmp = child._getHeight(); 325 rowHeight = ((rowHeight >= tmp || isNaN(tmp)) ? rowHeight : tmp); 326 child.setPosition(x - winSize.width / 2, y - tmp / 2); 327 328 x += w; 329 ++columnsOccupied; 330 331 if (columnsOccupied >= rowColumns) { 332 y -= rowHeight + 5; 333 columnsOccupied = 0; 334 rowColumns = 0; 335 rowHeight = 0; 336 ++row; 337 } 338 } 339 } 340 }, 341 /** 342 * align menu items in rows 343 * @param {Number} 344 * @example 345 * // Example 346 * menu.alignItemsInRows(5,3)//this will align items to 2 rows, first row with 5 items, second row with 3 347 * 348 * menu.alignItemsInRows(4,4,4,4)//this creates 4 rows each have 4 items 349 */ 350 alignItemsInRows: function (/*Multiple arguments*/) { 351 if ((arguments.length > 0) && (arguments[arguments.length - 1] == null)) 352 cc.log("parameters should not be ending with null in Javascript"); 353 var columns = [], i; 354 for (i = 0; i < arguments.length; i++) { 355 columns.push(arguments[i]); 356 } 357 var columnWidths = []; 358 var columnHeights = []; 359 360 var width = -10; 361 var columnHeight = -5; 362 var column = 0; 363 var columnWidth = 0; 364 var rowsOccupied = 0; 365 var columnRows, child, len, tmp; 366 367 var locChildren = this._children; 368 if (locChildren && locChildren.length > 0) { 369 for (i = 0, len = locChildren.length; i < len; i++) { 370 child = locChildren[i]; 371 // check if too many menu items for the amount of rows/columns 372 if (column >= columns.length) 373 continue; 374 375 columnRows = columns[column]; 376 // can't have zero rows on a column 377 if (!columnRows) 378 continue; 379 380 // columnWidth = fmaxf(columnWidth, [item contentSize].width); 381 tmp = child.width; 382 columnWidth = ((columnWidth >= tmp || isNaN(tmp)) ? columnWidth : tmp); 383 384 columnHeight += (child.height + 5); 385 ++rowsOccupied; 386 387 if (rowsOccupied >= columnRows) { 388 columnWidths.push(columnWidth); 389 columnHeights.push(columnHeight); 390 width += columnWidth + 10; 391 392 rowsOccupied = 0; 393 columnWidth = 0; 394 columnHeight = -5; 395 ++column; 396 } 397 } 398 } 399 // check if too many rows/columns for available menu items. 400 //cc.assert(!rowsOccupied, ""); 401 var winSize = cc.director.getWinSize(); 402 403 column = 0; 404 columnWidth = 0; 405 columnRows = 0; 406 var x = -width / 2; 407 var y = 0.0; 408 409 if (locChildren && locChildren.length > 0) { 410 for (i = 0, len = locChildren.length; i < len; i++) { 411 child = locChildren[i]; 412 if (columnRows == 0) { 413 columnRows = columns[column]; 414 y = columnHeights[column]; 415 } 416 417 // columnWidth = fmaxf(columnWidth, [item contentSize].width); 418 tmp = child._getWidth(); 419 columnWidth = ((columnWidth >= tmp || isNaN(tmp)) ? columnWidth : tmp); 420 421 child.setPosition(x + columnWidths[column] / 2, y - winSize.height / 2); 422 423 y -= child.height + 10; 424 ++rowsOccupied; 425 426 if (rowsOccupied >= columnRows) { 427 x += columnWidth + 5; 428 rowsOccupied = 0; 429 columnRows = 0; 430 columnWidth = 0; 431 ++column; 432 } 433 } 434 } 435 }, 436 437 /** 438 * remove a child from cc.Menu 439 * @param {cc.Node} child the child you want to remove 440 * @param {boolean} cleanup whether to cleanup 441 */ 442 removeChild: function (child, cleanup) { 443 if (child == null) 444 return; 445 if (!(child instanceof cc.MenuItem)) { 446 cc.log("cc.Menu.removeChild():Menu only supports MenuItem objects as children"); 447 return; 448 } 449 450 if (this._selectedItem == child) 451 this._selectedItem = null; 452 cc.Node.prototype.removeChild.call(this, child, cleanup); 453 }, 454 455 _onTouchBegan: function (touch, event) { 456 var target = event.getCurrentTarget(); 457 if (target._state != cc.MENU_STATE_WAITING || !target._visible || !target.enabled) 458 return false; 459 460 for (var c = target.parent; c != null; c = c.parent) { 461 if (!c.isVisible()) 462 return false; 463 } 464 465 target._selectedItem = target._itemForTouch(touch); 466 if (target._selectedItem) { 467 target._state = cc.MENU_STATE_TRACKING_TOUCH; 468 target._selectedItem.selected(); 469 return true; 470 } 471 return false; 472 }, 473 474 _onTouchEnded: function (touch, event) { 475 var target = event.getCurrentTarget(); 476 if (target._state !== cc.MENU_STATE_TRACKING_TOUCH) { 477 cc.log("cc.Menu.onTouchEnded(): invalid state"); 478 return; 479 } 480 if (target._selectedItem) { 481 target._selectedItem.unselected(); 482 target._selectedItem.activate(); 483 } 484 target._state = cc.MENU_STATE_WAITING; 485 }, 486 487 _onTouchCancelled: function (touch, event) { 488 var target = event.getCurrentTarget(); 489 if (target._state !== cc.MENU_STATE_TRACKING_TOUCH) { 490 cc.log("cc.Menu.onTouchCancelled(): invalid state"); 491 return; 492 } 493 if (this._selectedItem) 494 target._selectedItem.unselected(); 495 target._state = cc.MENU_STATE_WAITING; 496 }, 497 498 _onTouchMoved: function (touch, event) { 499 var target = event.getCurrentTarget(); 500 if (target._state !== cc.MENU_STATE_TRACKING_TOUCH) { 501 cc.log("cc.Menu.onTouchMoved(): invalid state"); 502 return; 503 } 504 var currentItem = target._itemForTouch(touch); 505 if (currentItem != target._selectedItem) { 506 if (target._selectedItem) 507 target._selectedItem.unselected(); 508 target._selectedItem = currentItem; 509 if (target._selectedItem) 510 target._selectedItem.selected(); 511 } 512 }, 513 514 /** 515 * <p> 516 * callback that is called every time the cc.Menu leaves the 'stage'. <br/> 517 * If the cc.Menu leaves the 'stage' with a transition, this callback is called when the transition finishes. <br/> 518 * During onExit you can't access a sibling node. <br/> 519 * If you override onExit, you shall call its parent's onExit with this._super(). 520 * </p> 521 */ 522 onExit: function () { 523 if (this._state == cc.MENU_STATE_TRACKING_TOUCH) { 524 if (this._selectedItem) { 525 this._selectedItem.unselected(); 526 this._selectedItem = null; 527 } 528 this._state = cc.MENU_STATE_WAITING; 529 } 530 cc.Node.prototype.onExit.call(this); 531 }, 532 /** 533 * only use for jsbinding 534 * @param value 535 */ 536 setOpacityModifyRGB: function (value) { 537 }, 538 /** 539 * only use for jsbinding 540 * @returns {boolean} 541 */ 542 isOpacityModifyRGB: function () { 543 return false; 544 }, 545 546 _itemForTouch: function (touch) { 547 var touchLocation = touch.getLocation(); 548 var itemChildren = this._children, locItemChild; 549 if (itemChildren && itemChildren.length > 0) { 550 for (var i = itemChildren.length - 1; i >= 0; i--) { 551 locItemChild = itemChildren[i]; 552 if (locItemChild.isVisible() && locItemChild.isEnabled()) { 553 var local = locItemChild.convertToNodeSpace(touchLocation); 554 var r = locItemChild.rect(); 555 r.x = 0; 556 r.y = 0; 557 if (cc.rectContainsPoint(r, local)) 558 return locItemChild; 559 } 560 } 561 } 562 return null; 563 } 564 }); 565 566 var _p = cc.Menu.prototype; 567 568 // Extended properties 569 /** @expose */ 570 _p.enabled; 571 572 /** 573 * create a new menu 574 * @deprecated since v3.0, please use new cc.Menu(menuitem1, menuitem2, menuitem3) to create a new menu 575 * @param {...cc.MenuItem|null} menuItems 576 * @return {cc.Menu} 577 * @example 578 * // Example 579 * //there is no limit on how many menu item you can pass in 580 * var myMenu = cc.Menu.create(menuitem1, menuitem2, menuitem3); 581 */ 582 cc.Menu.create = function (menuItems) { 583 var argc = arguments.length; 584 if ((argc > 0) && (arguments[argc - 1] == null)) 585 cc.log("parameters should not be ending with null in Javascript"); 586 587 var ret; 588 if (argc == 0) 589 ret = new cc.Menu(); 590 else if (argc == 1) 591 ret = new cc.Menu(menuItems); 592 else 593 ret = new cc.Menu(Array.prototype.slice.call(arguments, 0)); 594 return ret; 595 }; 596