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