CCMenu.js 18 KB

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