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