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