1 /**
  2  * Hilo
  3  * Copyright 2015 alibaba.com
  4  * Licensed under the MIT License
  5  */
  6 
  7 /**
  8  * @language=zh
  9  * @class Container是所有容器类的基类。每个Container都可以添加其他可视对象为子级。
 10  * @augments View
 11  * @param {Object} properties 创建对象的属性参数。可包含此类所有可写属性。
 12  * @module hilo/view/Container
 13  * @requires hilo/core/Hilo
 14  * @requires hilo/core/Class
 15  * @requires hilo/view/View
 16  * @property {Array} children 容器的子元素列表。只读。
 17  * @property {Boolean} pointerChildren 指示容器的子元素是否能响应用户交互事件。默认为true。
 18  * @property {Boolean} clipChildren 指示是否裁剪超出容器范围的子元素。默认为false。
 19  */
 20 var Container = Class.create(/** @lends Container.prototype */{
 21     Extends: View,
 22     constructor: function(properties){
 23         properties = properties || {};
 24         this.id = this.id || properties.id || Hilo.getUid("Container");
 25         Container.superclass.constructor.call(this, properties);
 26 
 27         if(this.children) this._updateChildren();
 28         else this.children = [];
 29     },
 30 
 31     children: null,
 32     pointerChildren: true,
 33     clipChildren: false,
 34 
 35     /**
 36      * @language=zh
 37      * 返回容器的子元素的数量。
 38      * @returns {Uint} 容器的子元素的数量。
 39      */
 40     getNumChildren: function(){
 41         return this.children.length;
 42     },
 43 
 44     /**
 45      * @language=zh
 46      * 在指定索引位置添加子元素。
 47      * @param {View} child 要添加的子元素。
 48      * @param {Number} index 指定的索引位置,从0开始。
 49      */
 50     addChildAt: function(child, index){
 51         var children = this.children,
 52             len = children.length,
 53             parent = child.parent;
 54 
 55         index = index < 0 ? 0 : index > len ? len : index;
 56         var childIndex = this.getChildIndex(child);
 57         if(childIndex == index){
 58             return this;
 59         }else if(childIndex >= 0){
 60             children.splice(childIndex, 1);
 61             index = index == len ? len - 1 : index;
 62         }else if(parent){
 63             parent.removeChild(child);
 64         }
 65 
 66         children.splice(index, 0, child);
 67 
 68         //直接插入,影响插入位置之后的深度
 69         //Insert directly, this will affect depth of elements after the index.
 70         if(childIndex < 0){
 71             this._updateChildren(index);
 72         }
 73         //只是移动时影响中间段的深度
 74         //Will affect depth of elements in the middle during moving
 75         else{
 76             var startIndex = childIndex < index ? childIndex : index;
 77             var endIndex = childIndex < index ? index : childIndex;;
 78             this._updateChildren(startIndex, endIndex + 1);
 79         }
 80 
 81         return this;
 82     },
 83 
 84     /**
 85      * @language=zh
 86      * 在最上面添加子元素。
 87      * @param {View} child 要添加的子元素。
 88      */
 89     addChild: function(child){
 90         var total = this.children.length,
 91             args = arguments;
 92 
 93         for(var i = 0, len = args.length; i < len; i++){
 94             this.addChildAt(args[i], total + i);
 95         }
 96         return this;
 97     },
 98 
 99     /**
100      * @language=zh
101      * 在指定索引位置删除子元素。
102      * @param {Int} index 指定删除元素的索引位置,从0开始。
103      * @returns {View} 被删除的对象。
104      */
105     removeChildAt: function(index){
106         var children = this.children;
107         if(index < 0 || index >= children.length) return null;
108 
109         var child = children[index];
110         if(child){
111             //NOTE: use `__renderer` for fixing child removal (DOMRenderer and FlashRenderer only).
112             //Do `not` use it in any other case.
113             if(!child.__renderer){
114                 var obj = child;
115                 while(obj = obj.parent){
116                     //obj is stage
117                     if(obj.renderer){
118                         child.__renderer = obj.renderer;
119                         break;
120                     }
121                     else if(obj.__renderer){
122                         child.__renderer = obj.__renderer;
123                         break;
124                     }
125                 }
126             }
127 
128             if(child.__renderer){
129                 child.__renderer.remove(child);
130             }
131 
132             child.parent = null;
133             child.depth = -1;
134         }
135 
136         children.splice(index, 1);
137         this._updateChildren(index);
138 
139         return child;
140     },
141 
142     /**
143      * @language=zh
144      * 删除指定的子元素。
145      * @param {View} child 指定要删除的子元素。
146      * @returns {View} 被删除的对象。
147      */
148     removeChild: function(child){
149         return this.removeChildAt(this.getChildIndex(child));
150     },
151 
152     /**
153      * @language=zh
154      * 删除指定id的子元素。
155      * @param {String} id 指定要删除的子元素的id。
156      * @returns {View} 被删除的对象。
157      */
158     removeChildById: function(id){
159         var children = this.children, child;
160         for(var i = 0, len = children.length; i < len; i++){
161             child = children[i];
162             if(child.id === id){
163                 this.removeChildAt(i);
164                 return child;
165             }
166         }
167         return null;
168     },
169 
170     /**
171      * @language=zh
172      * 删除所有的子元素。
173      * @returns {Container} 容器本身。
174      */
175     removeAllChildren: function(){
176         while(this.children.length) this.removeChildAt(0);
177         return this;
178     },
179 
180     /**
181      * @language=zh
182      * 返回指定索引位置的子元素。
183      * @param {Number} index 指定要返回的子元素的索引值,从0开始。
184      */
185     getChildAt: function(index){
186         var children = this.children;
187         if(index < 0 || index >= children.length) return null;
188         return children[index];
189     },
190 
191     /**
192      * @language=zh
193      * 返回指定id的子元素。
194      * @param {String} id 指定要返回的子元素的id。
195      */
196     getChildById: function(id){
197         var children = this.children, child;
198         for(var i = 0, len = children.length; i < len; i++){
199             child = children[i];
200             if(child.id === id) return child;
201         }
202         return null;
203     },
204 
205     /**
206      * @language=zh
207      * 返回指定子元素的索引值。
208      * @param {View} child 指定要返回索引值的子元素。
209      */
210     getChildIndex: function(child){
211         return this.children.indexOf(child);
212     },
213 
214     /**
215      * @language=zh
216      * 设置子元素的索引位置。
217      * @param {View} child 指定要设置的子元素。
218      * @param {Number} index 指定要设置的索引值。
219      */
220     setChildIndex: function(child, index){
221         var children = this.children,
222             oldIndex = children.indexOf(child);
223 
224         if(oldIndex >= 0 && oldIndex != index){
225             var len = children.length;
226             index = index < 0 ? 0 : index >= len ? len - 1 : index;
227             children.splice(oldIndex, 1);
228             children.splice(index, 0, child);
229             this._updateChildren();
230         }
231         return this;
232     },
233 
234     /**
235      * @language=zh
236      * 交换两个子元素的索引位置。
237      * @param {View} child1 指定要交换的子元素A。
238      * @param {View} child2 指定要交换的子元素B。
239      */
240     swapChildren: function(child1, child2){
241         var children = this.children,
242             index1 = this.getChildIndex(child1),
243             index2 = this.getChildIndex(child2);
244 
245         child1.depth = index2;
246         children[index2] = child1;
247         child2.depth = index1;
248         children[index1] = child2;
249     },
250 
251     /**
252      * @language=zh
253      * 交换两个指定索引位置的子元素。
254      * @param {Number} index1 指定要交换的索引位置A。
255      * @param {Number} index2 指定要交换的索引位置B。
256      */
257     swapChildrenAt: function(index1, index2){
258         var children = this.children,
259             child1 = this.getChildAt(index1),
260             child2 = this.getChildAt(index2);
261 
262         child1.depth = index2;
263         children[index2] = child1;
264         child2.depth = index1;
265         children[index1] = child2;
266     },
267 
268     /**
269      * @language=zh
270      * 根据指定键值或函数对子元素进行排序。
271      * @param {Object} keyOrFunction 如果此参数为String时,则根据子元素的某个属性值进行排序;如果此参数为Function时,则根据此函数进行排序。
272      */
273     sortChildren: function(keyOrFunction){
274         var fn = keyOrFunction,
275             children = this.children;
276         if(typeof fn == "string"){
277             var key = fn;
278             fn = function(a, b){
279                 return b[key] - a[key];
280             };
281         }
282         children.sort(fn);
283         this._updateChildren();
284     },
285 
286     /**
287      * @language=zh
288      * 更新子元素。
289      * @private
290      */
291     _updateChildren: function(start, end){
292         var children = this.children, child,
293             start = start || 0,
294             end = end || children.length;
295         for(var i = start; i < end; i++){
296             child = children[i];
297             child.depth = i + 1;
298             child.parent = this;
299         }
300     },
301 
302     /**
303      * @language=zh
304      * 返回是否包含参数指定的子元素。
305      * @param {View} child 指定要测试的子元素。
306      */
307     contains: function(child){
308         while(child = child.parent){
309             if(child === this){
310                 return true;
311             }
312         }
313         return false;
314     },
315 
316     /**
317      * @language=zh
318      * 返回由x和y指定的点下的对象。
319      * @param {Number} x 指定点的x轴坐标。
320      * @param {Number} y 指定点的y轴坐标。
321      * @param {Boolean} usePolyCollision 指定是否使用多边形碰撞检测。默认为false。
322      * @param {Boolean} global 使用此标志表明将查找所有符合的对象,而不仅仅是第一个,即全局匹配。默认为false。
323      * @param {Boolean} eventMode 使用此标志表明将在事件模式下查找对象。默认为false。
324      */
325     getViewAtPoint: function(x, y, usePolyCollision, global, eventMode){
326         var result = global ? [] : null,
327             children = this.children, child, obj;
328 
329         for(var i = children.length - 1; i >= 0; i--){
330             child = children[i];
331             //skip child which is not shown or pointer enabled
332             if(!child || !child.visible || child.alpha <= 0 || (eventMode && !child.pointerEnabled)) continue;
333             //find child recursively
334             if(child.children && child.children.length && !(eventMode && !child.pointerChildren)){
335                 obj = child.getViewAtPoint(x, y, usePolyCollision, global, eventMode);
336             }
337 
338             if(obj){
339                 if(!global) return obj;
340                 else if(obj.length) result = result.concat(obj);
341             }else if(child.hitTestPoint(x, y, usePolyCollision)){
342                 if(!global) return child;
343                 else result.push(child);
344             }
345         }
346 
347         return global && result.length ? result : null;
348     },
349 
350     /**
351      * @language=zh
352      * 覆盖渲染方法。
353      * @private
354      */
355     render: function(renderer, delta){
356         Container.superclass.render.call(this, renderer, delta);
357 
358         var children = this.children.slice(0), i, len, child;
359         for(i = 0, len = children.length; i < len; i++){
360             child = children[i];
361             //NOTE: the child could remove or change it's parent
362             if(child.parent === this) child._render(renderer, delta);
363         }
364     }
365 
366 });
367