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.Layer 54 */ 55 cc.Menu = cc.Layer.extend(/** @lends cc.Menu# */{ 56 RGBAProtocol:true, 57 _color:new cc.Color3B(), 58 59 /** 60 * @return {cc.Color3B} 61 */ 62 getColor:function () { 63 return this._color; 64 }, 65 66 /** 67 * @param {cc.Color3B} color 68 */ 69 setColor:function (color) { 70 this._color = color; 71 72 if (this._children && this._children.length > 0) { 73 for (var i = 0; i < this._children.length; i++) { 74 this._children[i].setColor(this._color); 75 } 76 } 77 }, 78 79 _opacity:0, 80 81 /** 82 * @return {Number} 83 */ 84 getOpacity:function () { 85 return this._opacity; 86 }, 87 88 /** 89 * @param {Number} opa 90 */ 91 setOpacity:function (opa) { 92 this._opacity = opa; 93 if (this._children && this._children.length > 0) { 94 for (var i = 0; i < this._children.length; i++) { 95 this._children[i].setOpacity(this._opacity); 96 } 97 } 98 }, 99 100 _enabled:false, 101 102 /** 103 * return whether or not the menu will receive events 104 * @return {Boolean} 105 */ 106 isEnabled:function () { 107 return this._enabled; 108 }, 109 110 /** 111 * set whether or not the menu will receive events 112 * @param {Boolean} enabled 113 */ 114 setEnabled:function (enabled) { 115 this._enabled = enabled; 116 }, 117 118 _selectedItem:null, 119 120 /** 121 * initializes a cc.Menu with it's items 122 * @param {Array} args 123 * @return {Boolean} 124 */ 125 initWithItems:function (args) { 126 var pArray = []; 127 if (args) { 128 for (var i = 0; i < args.length; i++) { 129 if (args[i]) { 130 pArray.push(args[i]); 131 } 132 } 133 } 134 135 return this.initWithArray(pArray); 136 }, 137 138 /** 139 * initializes a cc.Menu with a Array of cc.MenuItem objects 140 */ 141 initWithArray:function (arrayOfItems) { 142 if(this.init()){ 143 this.setTouchEnabled(true); 144 this._enabled = true; 145 146 // menu in the center of the screen 147 var winSize = cc.Director.getInstance().getWinSize(); 148 this.ignoreAnchorPointForPosition(true); 149 this.setAnchorPoint(cc.p(0.5, 0.5)); 150 this.setContentSize(winSize); 151 152 this.setPosition(cc.p(winSize.width / 2, winSize.height / 2)); 153 154 if(arrayOfItems){ 155 for(var i = 0; i< arrayOfItems.length; i++){ 156 this.addChild(arrayOfItems[i],i); 157 } 158 } 159 160 this._selectedItem = null; 161 this._state = cc.MENU_STATE_WAITING; 162 return true; 163 } 164 return false; 165 }, 166 167 /** 168 * @param {cc.Node} child 169 * @param {Number|Null} zOrder 170 * @param {Number|Null} tag 171 */ 172 addChild:function (child, zOrder, tag) { 173 cc.Assert((child instanceof cc.MenuItem), "Menu only supports MenuItem objects as children"); 174 this._super(child, zOrder, tag); 175 }, 176 177 /** 178 * align items vertically with default padding 179 */ 180 alignItemsVertically:function () { 181 this.alignItemsVerticallyWithPadding(cc.DEFAULT_PADDING); 182 }, 183 184 /** 185 * align items vertically with specified padding 186 * @param {Number} padding 187 */ 188 alignItemsVerticallyWithPadding:function (padding) { 189 var height = -padding; 190 if (this._children && this._children.length > 0) { 191 for (var i = 0; i < this._children.length; i++) { 192 height += this._children[i].getContentSize().height * this._children[i].getScaleY() + padding; 193 } 194 } 195 196 var y = height / 2.0; 197 if (this._children && this._children.length > 0) { 198 for (i = 0; i < this._children.length; i++) { 199 this._children[i].setPosition(cc.p(0, y - this._children[i].getContentSize().height * this._children[i].getScaleY() / 2)); 200 y -= this._children[i].getContentSize().height * this._children[i].getScaleY() + padding; 201 } 202 } 203 }, 204 205 /** 206 * align items horizontally with default padding 207 */ 208 alignItemsHorizontally:function () { 209 this.alignItemsHorizontallyWithPadding(cc.DEFAULT_PADDING); 210 }, 211 212 /** 213 * align items horizontally with specified padding 214 * @param {Number} padding 215 */ 216 alignItemsHorizontallyWithPadding:function (padding) { 217 var width = -padding; 218 if (this._children && this._children.length > 0) { 219 for (var i = 0; i < this._children.length; i++) { 220 width += this._children[i].getContentSize().width * this._children[i].getScaleX() + padding; 221 } 222 } 223 224 var x = -width / 2.0; 225 if (this._children && this._children.length > 0) { 226 for (i = 0; i < this._children.length; i++) { 227 this._children[i].setPosition(cc.p(x + this._children[i].getContentSize().width * this._children[i].getScaleX() / 2, 0)); 228 x += this._children[i].getContentSize().width * this._children[i].getScaleX() + padding; 229 } 230 } 231 }, 232 233 /** 234 * align items in columns 235 * @example 236 * // Example 237 * menu.alignItemsInColumns(3,2,3)// this will create 3 columns, with 3 items for first column, 2 items for second and 3 for third 238 * 239 * menu.alignItemsInColumns(3,3)//this creates 2 columns, each have 3 items 240 */ 241 alignItemsInColumns:function (/*Multiple Arguments*/) { 242 var rows = []; 243 for (var i = 0; i < arguments.length; i++) { 244 rows.push(arguments[i]); 245 } 246 var height = -5; 247 var row = 0; 248 var rowHeight = 0; 249 var columnsOccupied = 0; 250 var rowColumns; 251 if (this._children && this._children.length > 0) { 252 for (i = 0; i < this._children.length; i++) { 253 cc.Assert(row < rows.length, ""); 254 255 rowColumns = rows[row]; 256 // can not have zero columns on a row 257 cc.Assert(rowColumns, ""); 258 259 var tmp = this._children[i].getContentSize().height; 260 rowHeight = ((rowHeight >= tmp || isNaN(tmp)) ? rowHeight : tmp); 261 262 ++columnsOccupied; 263 if (columnsOccupied >= rowColumns) { 264 height += rowHeight + 5; 265 266 columnsOccupied = 0; 267 rowHeight = 0; 268 ++row; 269 } 270 } 271 } 272 // check if too many rows/columns for available menu items 273 cc.Assert(!columnsOccupied, ""); 274 var winSize = cc.Director.getInstance().getWinSize(); 275 276 row = 0; 277 rowHeight = 0; 278 rowColumns = 0; 279 var w = 0.0; 280 var x = 0.0; 281 var y = (height / 2); 282 283 if (this._children && this._children.length > 0) { 284 for (i = 0; i < this._children.length; i++) { 285 var child = this._children[i]; 286 if (rowColumns == 0) { 287 rowColumns = rows[row]; 288 w = winSize.width / (1 + rowColumns); 289 x = w; 290 } 291 292 var tmp = child.getContentSize().height; 293 rowHeight = ((rowHeight >= tmp || isNaN(tmp)) ? rowHeight : tmp); 294 295 child.setPosition(cc.p(x - winSize.width / 2, 296 y - child.getContentSize().height / 2)); 297 298 x += w; 299 ++columnsOccupied; 300 301 if (columnsOccupied >= rowColumns) { 302 y -= rowHeight + 5; 303 304 columnsOccupied = 0; 305 rowColumns = 0; 306 rowHeight = 0; 307 ++row; 308 } 309 } 310 } 311 }, 312 /** 313 * align menu items in rows 314 * @example 315 * // Example 316 * menu.alignItemsInRows(5,3)//this will align items to 2 rows, first row with 5 items, second row with 3 317 * 318 * menu.alignItemsInRows(4,4,4,4)//this creates 4 rows each have 4 items 319 */ 320 alignItemsInRows:function (/*Multiple arguments*/) { 321 var columns = []; 322 for (var i = 0; i < arguments.length; i++) { 323 columns.push(arguments[i]); 324 } 325 var columnWidths = []; 326 var columnHeights = []; 327 328 var width = -10; 329 var columnHeight = -5; 330 var column = 0; 331 var columnWidth = 0; 332 var rowsOccupied = 0; 333 var columnRows; 334 335 if (this._children && this._children.length > 0) { 336 for (var i = 0; i < this._children.length; i++) { 337 var child = this._children[i]; 338 // check if too many menu items for the amount of rows/columns 339 cc.Assert(column < columns.length, ""); 340 341 columnRows = columns[column]; 342 // can't have zero rows on a column 343 cc.Assert(columnRows, ""); 344 345 // columnWidth = fmaxf(columnWidth, [item contentSize].width); 346 var tmp = child.getContentSize().width; 347 columnWidth = ((columnWidth >= tmp || isNaN(tmp)) ? columnWidth : tmp); 348 349 columnHeight += (child.getContentSize().height + 5); 350 ++rowsOccupied; 351 352 if (rowsOccupied >= columnRows) { 353 columnWidths.push(columnWidth); 354 columnHeights.push(columnHeight); 355 width += columnWidth + 10; 356 357 rowsOccupied = 0; 358 columnWidth = 0; 359 columnHeight = -5; 360 ++column; 361 } 362 } 363 } 364 // check if too many rows/columns for available menu items. 365 cc.Assert(!rowsOccupied, ""); 366 367 var winSize = cc.Director.getInstance().getWinSize(); 368 369 column = 0; 370 columnWidth = 0; 371 columnRows = 0; 372 var x = -width / 2; 373 var y = 0.0; 374 375 if (this._children && this._children.length > 0) { 376 for (var i = 0; i < this._children.length; i++) { 377 var child = this._children[i]; 378 if (columnRows == 0) { 379 columnRows = columns[column]; 380 y = columnHeights[column]; 381 } 382 383 // columnWidth = fmaxf(columnWidth, [item contentSize].width); 384 var tmp = child.getContentSize().width; 385 columnWidth = ((columnWidth >= tmp || isNaN(tmp)) ? columnWidth : tmp); 386 387 child.setPosition(cc.p(x + columnWidths[column] / 2, 388 y - winSize.height / 2)); 389 390 y -= child.getContentSize().height + 10; 391 ++rowsOccupied; 392 393 if (rowsOccupied >= columnRows) { 394 x += columnWidth + 5; 395 rowsOccupied = 0; 396 columnRows = 0; 397 columnWidth = 0; 398 ++column; 399 } 400 } 401 } 402 }, 403 404 /** 405 * make the menu clickable 406 */ 407 registerWithTouchDispatcher:function () { 408 cc.Director.getInstance().getTouchDispatcher().addTargetedDelegate(this, cc.MENU_HANDLER_PRIORITY, true); 409 }, 410 411 /** 412 * @param {cc.Touch} touch 413 * @return {Boolean} 414 */ 415 onTouchBegan:function (touch, e) { 416 if (this._state != cc.MENU_STATE_WAITING || !this._visible || !this._enabled) { 417 return false; 418 } 419 420 for (var c = this._parent; c != null; c = c.getParent()) { 421 if (!c.isVisible()) { 422 return false; 423 } 424 } 425 426 this._selectedItem = this._itemForTouch(touch); 427 if (this._selectedItem) { 428 this._state = cc.MENU_STATE_TRACKING_TOUCH; 429 this._selectedItem.selected(); 430 return true; 431 } 432 return false; 433 }, 434 435 /** 436 * when a touch ended 437 */ 438 onTouchEnded:function (touch, e) { 439 cc.Assert(this._state == cc.MENU_STATE_TRACKING_TOUCH, "[Menu onTouchEnded] -- invalid state"); 440 if (this._selectedItem) { 441 this._selectedItem.unselected(); 442 this._selectedItem.activate(); 443 } 444 this._state = cc.MENU_STATE_WAITING; 445 }, 446 447 /** 448 * touch cancelled 449 */ 450 onTouchCancelled:function (touch, e) { 451 cc.Assert(this._state == cc.MENU_STATE_TRACKING_TOUCH, "[Menu onTouchCancelled] -- invalid state"); 452 if (this._selectedItem) { 453 this._selectedItem.unselected(); 454 } 455 this._state = cc.MENU_STATE_WAITING; 456 }, 457 458 /** 459 * touch moved 460 * @param {cc.Touch} touch 461 */ 462 onTouchMoved:function (touch, e) { 463 cc.Assert(this._state == cc.MENU_STATE_TRACKING_TOUCH, "[Menu onTouchMoved] -- invalid state"); 464 var currentItem = this._itemForTouch(touch); 465 if (currentItem != this._selectedItem) { 466 if (this._selectedItem) { 467 this._selectedItem.unselected(); 468 } 469 this._selectedItem = currentItem; 470 if (this._selectedItem) { 471 this._selectedItem.selected(); 472 } 473 } 474 }, 475 476 /** 477 * custom on exit 478 */ 479 onExit:function () { 480 if (this._state == cc.MENU_STATE_TRACKING_TOUCH) { 481 this._selectedItem.unselected(); 482 this._state = cc.MENU_STATE_WAITING; 483 this._selectedItem = null; 484 } 485 486 this._super(); 487 }, 488 489 setOpacityModifyRGB:function (value) { 490 }, 491 492 isOpacityModifyRGB:function () { 493 return false; 494 }, 495 496 _itemForTouch:function (touch) { 497 var touchLocation = touch.getLocation(); 498 499 if (this._children && this._children.length > 0) { 500 for (var i = 0; i < this._children.length; i++) { 501 if (this._children[i].isVisible() && this._children[i].isEnabled()) { 502 var local = this._children[i].convertToNodeSpace(touchLocation); 503 var r = this._children[i].rect(); 504 r.origin = cc.p(0,0); 505 if (cc.Rect.CCRectContainsPoint(r, local)) { 506 return this._children[i]; 507 } 508 } 509 } 510 } 511 512 return null; 513 }, 514 _state:-1, 515 516 /** 517 * set event handler priority. By default it is: kCCMenuTouchPriority 518 * @param {Number} newPriority 519 */ 520 setHandlerPriority:function (newPriority) { 521 cc.Director.getInstance().getTouchDispatcher().setPriority(newPriority, this); 522 } 523 }); 524 525 /** 526 * create a new menu 527 * @return {cc.Menu} 528 * @example 529 * // Example 530 * //there is no limit on how many menu item you can pass in 531 * var myMenu = cc.Menu.create(menuitem1, menuitem2, menuitem3); 532 */ 533 cc.Menu.create = function (/*Multiple Arguments*/) { 534 var ret = new cc.Menu(); 535 536 if (arguments.length == 0) { 537 ret.initWithItems(null, null); 538 } else if (arguments.length == 1) { 539 if (arguments[0] instanceof Array) { 540 ret.initWithArray(arguments[0]); 541 return ret; 542 } 543 } 544 ret.initWithItems(arguments); 545 return ret; 546 }; 547