1 /****************************************************************************
  2  Copyright (c) 2013-2014 Chukong Technologies Inc.
  3 
  4  http://www.cocos2d-x.org
  5 
  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 
 13  The above copyright notice and this permission notice shall be included in
 14  all copies or substantial portions of the Software.
 15 
 16  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 17  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 18  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 19  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 20  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 21  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 22  THE SOFTWARE.
 23  ****************************************************************************/
 24 
 25 /**
 26  * A class inhert from cc.Node, use for saving some protected children in other list.
 27  * @class
 28  * @extends cc.Node
 29  */
 30 cc.ProtectedNode = cc.Node.extend(/** @lends cc.ProtectedNode# */{
 31     _protectedChildren: null,
 32     _reorderProtectedChildDirty: false,
 33 
 34     _insertProtectedChild: function(child, z){
 35         this._reorderProtectedChildDirty = true;
 36         this._protectedChildren.push(child);
 37         child._setLocalZOrder(z);
 38     },
 39 
 40     ctor: function(){
 41         cc.Node.prototype.ctor.call(this);
 42        this._protectedChildren = [];
 43     },
 44 
 45     /**
 46      *  Adds a child to the container with z order and tag
 47      *  If the child is added to a 'running' node, then 'onEnter' and 'onEnterTransitionDidFinish' will be called immediately.
 48      * @param {cc.Node} child  A child node
 49      * @param {Number} [localZOrder]  Z order for drawing priority. Please refer to `setLocalZOrder(int)`
 50      * @param {Number} [tag]  An integer to identify the node easily. Please refer to `setTag(int)`
 51      */
 52     addProtectedChild: function(child, localZOrder, tag){
 53          cc.assert(child != null, "child must be non-nil");
 54          cc.assert(!child.parent, "child already added. It can't be added again");
 55 
 56         localZOrder = localZOrder || child.getLocalZOrder();
 57         if(tag)
 58             child.setTag(tag);
 59 
 60         this._insertProtectedChild(child, localZOrder);
 61         child.setParent(this);
 62         child.setOrderOfArrival(cc.s_globalOrderOfArrival);
 63 
 64         //TODO USE PHYSICS
 65         if(this._running){
 66             child.onEnter();
 67             // prevent onEnterTransitionDidFinish to be called twice when a node is added in onEnter
 68             if(this._isTransitionFinished)
 69                 child.onEnterTransitionDidFinish();
 70         }
 71         if(this._cascadeColorEnabled)
 72             this._enableCascadeColor();
 73         if (this._cascadeOpacityEnabled)
 74             this._enableCascadeOpacity();
 75     },
 76 
 77     /**
 78      * Gets a child from the container with its tag
 79      * @param {Number} tag An identifier to find the child node.
 80      * @return {cc.Node} a Node object whose tag equals to the input parameter
 81      */
 82     getProtectedChildByTag: function(tag){
 83         cc.assert(tag != cc.NODE_TAG_INVALID, "Invalid tag");
 84         var locChildren = this._protectedChildren;
 85         for(var i = 0, len = locChildren.length; i < len; i++)
 86             if(locChildren.getTag() == tag)
 87                 return locChildren[i];
 88         return null;
 89     },
 90 
 91     /**
 92      * Removes a child from the container. It will also cleanup all running actions depending on the cleanup parameter.
 93      * @param {cc.Node} child  The child node which will be removed.
 94      * @param {Boolean} [cleanup=true] true if all running actions and callbacks on the child node will be cleanup, false otherwise.
 95      */
 96     removeProtectedChild: function(child,  cleanup){
 97         if(cleanup == null)
 98             cleanup = true;
 99          var locChildren = this._protectedChildren;
100         if(locChildren.length === 0)
101             return;
102         var idx = locChildren.indexOf(child);
103         if(idx > -1){
104              if(this._running){
105                  child.onExitTransitionDidStart();
106                  child.onExit();
107              }
108             //TODO  USE PHYSICS
109 
110             // If you don't do cleanup, the child's actions will not get removed and the
111             // its scheduledSelectors_ dict will not get released!
112             if (cleanup)
113                 child.cleanup();
114 
115             // set parent nil at the end
116             child.setParent(null);
117             locChildren.splice(idx, 1);
118         }
119     },
120 
121     /**
122      * Removes a child from the container by tag value. It will also cleanup all running actions depending on the cleanup parameter
123      * @param {Number} tag
124      * @param {Boolean} [cleanup=true]
125      */
126     removeProtectedChildByTag: function(tag, cleanup){
127         cc.assert( tag != cc.NODE_TAG_INVALID, "Invalid tag");
128 
129         if(cleanup == null)
130             cleanup = true;
131 
132         var child = this.getProtectedChildByTag(tag);
133 
134         if (child == null)
135             cc.log("cocos2d: removeChildByTag(tag = %d): child not found!", tag);
136         else
137             this.removeProtectedChild(child, cleanup);
138     },
139 
140     /**
141      * Removes all children from the container with a cleanup.
142      *  @see `removeAllChildrenWithCleanup(bool)`
143      */
144     removeAllProtectedChildren: function(){
145         this.removeAllProtectedChildrenWithCleanup(true);
146     },
147 
148     /**
149      * Removes all children from the container, and do a cleanup to all running actions depending on the cleanup parameter.
150      * @param {Boolean} [cleanup=true] true if all running actions on all children nodes should be cleanup, false otherwise.
151      */
152     removeAllProtectedChildrenWithCleanup: function(cleanup){
153         if(cleanup == null)
154             cleanup = true;
155         var locChildren = this._protectedChildren;
156         // not using detachChild improves speed here
157         for (var i = 0, len = locChildren.length; i< len; i++) {
158             var child = locChildren[i];
159             // IMPORTANT:
160             //  -1st do onExit
161             //  -2nd cleanup
162             if(this._running){
163                 child.onExitTransitionDidStart();
164                 child.onExit();
165             }
166 
167             //TODO USE PHYSICS
168             if (cleanup)
169                 child.cleanup();
170             // set parent nil at the end
171             child.setParent(null);
172         }
173         locChildren.length = 0;
174     },
175 
176     /**
177      * Reorders a child according to a new z value.
178      * @param {cc.Node} child An already added child node. It MUST be already added.
179      * @param {Number} localZOrder Z order for drawing priority. Please refer to setLocalZOrder(int)
180      */
181     reorderProtectedChild: function(child, localZOrder){
182         cc.assert( child != null, "Child must be non-nil");
183         this._reorderProtectedChildDirty = true;
184         child.setOrderOfArrival(cc.s_globalOrderOfArrival++);
185         child._setLocalZOrder(localZOrder);
186     },
187 
188     /**
189      * <p>
190      *     Sorts the children array once before drawing, instead of every time when a child is added or reordered.       <br/>
191      *     This approach can improves the performance massively.                                                         <br/>
192      *     @note Don't call this manually unless a child added needs to be removed in the same frame
193      * </p>
194      */
195     sortAllProtectedChildren: function(){
196         if (this._reorderProtectedChildDirty) {
197             var _children = this._protectedChildren;
198 
199             // insertion sort
200             var len = _children.length, i, j, tmp;
201             for(i=1; i<len; i++){
202                 tmp = _children[i];
203                 j = i - 1;
204 
205                 //continue moving element downwards while zOrder is smaller or when zOrder is the same but mutatedIndex is smaller
206                 while(j >= 0){
207                     if(tmp._localZOrder < _children[j]._localZOrder){
208                         _children[j+1] = _children[j];
209                     }else if(tmp._localZOrder === _children[j]._localZOrder && tmp.arrivalOrder < _children[j].arrivalOrder){
210                         _children[j+1] = _children[j];
211                     }else
212                         break;
213                     j--;
214                 }
215                 _children[j+1] = tmp;
216             }
217 
218             //don't need to check children recursively, that's done in visit of each child
219             this._reorderProtectedChildDirty = false;
220         }
221     },
222 
223     visit: null,
224 
225     _visitForCanvas: function(ctx){
226         var _t = this;
227         // quick return if not visible
228         if (!_t._visible)
229             return;
230 
231         //visit for canvas
232         var context = ctx || cc._renderContext, i, j;
233         var children = _t._children, child;
234         var locChildren = _t._children, locProtectedChildren = this._protectedChildren;
235         var childLen = locChildren.length, pLen = locProtectedChildren.length;
236         context.save();
237         _t.transform(context);
238 
239         _t.sortAllChildren();
240         _t.sortAllProtectedChildren();
241 
242         // draw children zOrder < 0
243         for (i = 0; i < childLen; i++) {
244             child = children[i];
245             if (child._localZOrder < 0)
246                 child.visit(context);
247             else
248                 break;
249         }
250         for (j = 0; j < pLen; j++) {
251             child = locProtectedChildren[j];
252             if (child._localZOrder < 0)
253                 child.visit(context);
254             else
255                 break;
256         }
257 
258         _t.draw(context);
259 
260         for (; i < childLen; i++) {
261             children[i] && children[i].visit(context);
262         }
263         for (; j < pLen; j++) {
264             locProtectedChildren[j] && locProtectedChildren[j].visit(context);
265         }
266 
267         this._cacheDirty = false;
268         _t.arrivalOrder = 0;
269         context.restore();
270     },
271 
272     _visitForWebGL: function(){
273         var _t = this;
274         // quick return if not visible
275         if (!_t._visible)
276             return;
277         var context = cc._renderContext, i, currentStack = cc.current_stack, j;
278 
279         //optimize performance for javascript
280         currentStack.stack.push(currentStack.top);
281         cc.kmMat4Assign(_t._stackMatrix, currentStack.top);
282         currentStack.top = _t._stackMatrix;
283 
284         var locGrid = _t.grid;
285         if (locGrid && locGrid._active)
286             locGrid.beforeDraw();
287 
288         _t.transform();
289 
290         var locChildren = _t._children, locProtectedChildren = this._protectedChildren;
291         var childLen = locChildren.length, pLen = locProtectedChildren.length;
292         _t.sortAllChildren();
293         _t.sortAllProtectedChildren();
294 
295         // draw children zOrder < 0
296         for (i = 0; i < childLen; i++) {
297             if (locChildren[i] && locChildren[i]._localZOrder < 0)
298                 locChildren[i].visit();
299             else
300                 break;
301         }
302         for(j = 0; j < pLen; j++){
303             if (locProtectedChildren[j] && locProtectedChildren[j]._localZOrder < 0)
304                 locProtectedChildren[j].visit();
305             else
306                 break;
307         }
308         _t.draw(context);
309         // draw children zOrder >= 0
310         for (; i < childLen; i++) {
311             locChildren[i] && locChildren[i].visit();
312         }
313         for (; j < pLen; j++) {
314             locProtectedChildren[j] && locProtectedChildren[j].visit();
315         }
316 
317         _t.arrivalOrder = 0;
318         if (locGrid && locGrid._active)
319             locGrid.afterDraw(_t);
320 
321         //optimize performance for javascript
322         currentStack.top = currentStack.stack.pop();
323     },
324 
325     cleanup: function(){
326        cc.Node.prototype.cleanup.call(this);
327        var locChildren = this._protectedChildren;
328         for(var i = 0 , len = locChildren.length; i  < len; i++)
329             locChildren[i].cleanup();
330     },
331 
332     onEnter: function(){
333         cc.Node.prototype.onEnter.call(this);
334         var locChildren = this._protectedChildren;
335         for(var i = 0, len = locChildren.length;i< len;i++)
336             locChildren[i].onEnter();
337     },
338 
339     /**
340      *  <p>
341      *     Event callback that is invoked when the Node enters in the 'stage'.                                          <br/>
342      *     If the Node enters the 'stage' with a transition, this event is called when the transition finishes.         <br/>
343      *     If you override onEnterTransitionDidFinish, you shall call its parent's one, e.g. Node::onEnterTransitionDidFinish()
344      *  </p>
345      */
346     onEnterTransitionDidFinish: function(){
347         cc.Node.prototype.onEnterTransitionDidFinish.call(this);
348         var locChildren = this._protectedChildren;
349         for(var i = 0, len = locChildren.length;i< len;i++)
350             locChildren[i].onEnterTransitionDidFinish();
351     },
352 
353     onExit:function(){
354         cc.Node.prototype.onExit.call(this);
355         var locChildren = this._protectedChildren;
356         for(var i = 0, len = locChildren.length;i< len;i++)
357             locChildren[i].onExit();
358     },
359 
360     /**
361      * <p>
362      *      Event callback that is called every time the Node leaves the 'stage'.                                      <br/>
363      *      If the Node leaves the 'stage' with a transition, this callback is called when the transition starts.
364      * </p>
365      */
366     onExitTransitionDidStart: function(){
367         cc.Node.prototype.onExitTransitionDidStart.call(this);
368         var locChildren = this._protectedChildren;
369         for(var i = 0, len = locChildren.length;i< len;i++)
370             locChildren[i].onExitTransitionDidStart();
371     },
372 
373     updateDisplayedOpacity: function(parentOpacity){
374         this._displayedOpacity = this._realOpacity * parentOpacity/255.0;
375         this._updateColor();
376 
377         var i,len, locChildren, _opacity = this._displayedOpacity;
378         if (this._cascadeOpacityEnabled){
379             locChildren = this._children;
380             for(i = 0, len = locChildren.length;i < len; i++){
381                 if(locChildren[i].updateDisplayedOpacity)
382                     locChildren[i].updateDisplayedOpacity(_opacity);
383             }
384         }
385         locChildren = this._protectedChildren;
386         for(i = 0, len = locChildren.length;i < len; i++){
387             var tmpOpacity = _opacity * locChildren[i].getOpacity() / 255;
388             if(locChildren[i])
389                 locChildren[i].setOpacity(tmpOpacity);
390         }
391     },
392 
393     updateDisplayedColor: function(parentColor){
394         var displayedColor = this._displayedColor, realColor = this._realColor;
395         displayedColor.r = realColor.r * parentColor.r/255.0;
396         displayedColor.g = realColor.g * parentColor.g/255.0;
397         displayedColor.b = realColor.b * parentColor.b/255.0;
398         this._updateColor();
399 
400         var i, len, locChildren;
401         if (this._cascadeColorEnabled){
402             locChildren = this._children;
403             for(i = 0, len = locChildren.length; i < len; i++){
404                 if(locChildren[i].updateDisplayedColor)
405                     locChildren[i].updateDisplayedColor(displayedColor);
406             }
407         }
408 
409         locChildren = this._protectedChildren;
410         for(i =0, len = locChildren.length; i < len; i++) {
411 
412             var tmpColor = locChildren[i]._realColor;
413             var tmpR = realColor.r * tmpColor.r / 255;
414             var tmpG = realColor.g * tmpColor.g / 255;
415             var tmpB = realColor.b * tmpColor.b / 255;
416 
417             if (locChildren[i])
418                 locChildren[i].setColor(cc.color(tmpR, tmpG,tmpB));
419         }
420     },
421 
422     disableCascadeColor: function(){
423         var white = cc.color.WHITE;
424         var i, len, locChildren = this._children;
425         for(i = 0, len = locChildren.length; i < len; i++)
426             locChildren[i].updateDisplayedColor(white);
427 
428         locChildren = this._protectedChildren;
429         for(i =0, len = locChildren.length; i < len; i++)
430             locChildren[i].setColor(white);
431     }
432 });
433 
434 if (cc._renderType === cc._RENDER_TYPE_CANVAS) {
435     cc.ProtectedNode.prototype.visit =  cc.ProtectedNode.prototype._visitForCanvas;
436 }else{
437     cc.ProtectedNode.prototype.visit =  cc.ProtectedNode.prototype._visitForWebGL;
438 }
439 
440 /**
441  * create a cc.ProtectedNode object;
442  * @deprecated
443  */
444 cc.ProtectedNode.create = function(){
445     return new cc.ProtectedNode();
446 };