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