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         //Insert directly, this will affect depth of elements after the index.
 67         if(childIndex < 0){
 68             this._updateChildren(index);
 69         }
 70         //只是移动时影响中间段的深度
 71         //Will affect depth of elements in the middle during moving
 72         else{
 73             var startIndex = childIndex < index ? childIndex : index;
 74             var endIndex = childIndex < index ? index : childIndex;
 75             this._updateChildren(startIndex, endIndex + 1);
 76         }
 77 
 78         return this;
 79     },
 80 
 81     /**
 82      * 在最上面添加子元素。
 83      * @param {View} child 要添加的子元素。
 84      */
 85     addChild: function(child){
 86         var total = this.children.length,
 87             args = arguments;
 88 
 89         for(var i = 0, len = args.length; i < len; i++){
 90             this.addChildAt(args[i], total + i);
 91         }
 92         return this;
 93     },
 94 
 95     /**
 96      * 在指定索引位置删除子元素。
 97      * @param {Int} index 指定删除元素的索引位置,从0开始。
 98      * @returns {View} 被删除的对象。
 99      */
100     removeChildAt: function(index){
101         var children = this.children;
102         if(index < 0 || index >= children.length) return null;
103 
104         var child = children[index];
105         if(child){
106             //NOTE: use `__renderer` for fixing child removal (DOMRenderer and FlashRenderer only).
107             //Do `not` use it in any other case.
108             if(!child.__renderer){
109                 var obj = child;
110                 while(obj = obj.parent){
111                     //obj is stage
112                     if(obj.renderer){
113                         child.__renderer = obj.renderer;
114                         break;
115                     }
116                     else if(obj.__renderer){
117                         child.__renderer = obj.__renderer;
118                         break;
119                     }
120                 }
121             }
122 
123             if(child.__renderer){
124                 child.__renderer.remove(child);
125             }
126 
127             child.parent = null;
128             child.depth = -1;
129         }
130 
131         children.splice(index, 1);
132         this._updateChildren(index);
133 
134         return child;
135     },
136 
137     /**
138      * 删除指定的子元素。
139      * @param {View} child 指定要删除的子元素。
140      * @returns {View} 被删除的对象。
141      */
142     removeChild: function(child){
143         return this.removeChildAt(this.getChildIndex(child));
144     },
145 
146     /**
147      * 删除指定id的子元素。
148      * @param {String} id 指定要删除的子元素的id。
149      * @returns {View} 被删除的对象。
150      */
151     removeChildById: function(id){
152         var children = this.children, child;
153         for(var i = 0, len = children.length; i < len; i++){
154             child = children[i];
155             if(child.id === id){
156                 this.removeChildAt(i);
157                 return child;
158             }
159         }
160         return null;
161     },
162 
163     /**
164      * 删除所有的子元素。
165      * @returns {Container} 容器本身。
166      */
167     removeAllChildren: function(){
168         while(this.children.length) this.removeChildAt(0);
169         return this;
170     },
171 
172     /**
173      * 返回指定索引位置的子元素。
174      * @param {Number} index 指定要返回的子元素的索引值,从0开始。
175      */
176     getChildAt: function(index){
177         var children = this.children;
178         if(index < 0 || index >= children.length) return null;
179         return children[index];
180     },
181 
182     /**
183      * 返回指定id的子元素。
184      * @param {String} id 指定要返回的子元素的id。
185      */
186     getChildById: function(id){
187         var children = this.children, child;
188         for(var i = 0, len = children.length; i < len; i++){
189             child = children[i];
190             if(child.id === id) return child;
191         }
192         return null;
193     },
194 
195     /**
196      * 返回指定子元素的索引值。
197      * @param {View} child 指定要返回索引值的子元素。
198      */
199     getChildIndex: function(child){
200         return this.children.indexOf(child);
201     },
202 
203     /**
204      * 设置子元素的索引位置。
205      * @param {View} child 指定要设置的子元素。
206      * @param {Number} index 指定要设置的索引值。
207      */
208     setChildIndex: function(child, index){
209         var children = this.children,
210             oldIndex = children.indexOf(child);
211 
212         if(oldIndex >= 0 && oldIndex != index){
213             var len = children.length;
214             index = index < 0 ? 0 : index >= len ? len - 1 : index;
215             children.splice(oldIndex, 1);
216             children.splice(index, 0, child);
217             this._updateChildren();
218         }
219         return this;
220     },
221 
222     /**
223      * 交换两个子元素的索引位置。
224      * @param {View} child1 指定要交换的子元素A。
225      * @param {View} child2 指定要交换的子元素B。
226      */
227     swapChildren: function(child1, child2){
228         var children = this.children,
229             index1 = this.getChildIndex(child1),
230             index2 = this.getChildIndex(child2);
231 
232         child1.depth = index2;
233         children[index2] = child1;
234         child2.depth = index1;
235         children[index1] = child2;
236     },
237 
238     /**
239      * 交换两个指定索引位置的子元素。
240      * @param {Number} index1 指定要交换的索引位置A。
241      * @param {Number} index2 指定要交换的索引位置B。
242      */
243     swapChildrenAt: function(index1, index2){
244         var children = this.children,
245             child1 = this.getChildAt(index1),
246             child2 = this.getChildAt(index2);
247 
248         child1.depth = index2;
249         children[index2] = child1;
250         child2.depth = index1;
251         children[index1] = child2;
252     },
253 
254     /**
255      * 根据指定键值或函数对子元素进行排序。
256      * @param {Object} keyOrFunction 如果此参数为String时,则根据子元素的某个属性值进行排序;如果此参数为Function时,则根据此函数进行排序。
257      */
258     sortChildren: function(keyOrFunction){
259         var fn = keyOrFunction,
260             children = this.children;
261         if(typeof fn == "string"){
262             var key = fn;
263             fn = function(a, b){
264                 return b[key] - a[key];
265             };
266         }
267         children.sort(fn);
268         this._updateChildren();
269     },
270 
271     /**
272      * 更新子元素。
273      * @private
274      */
275     _updateChildren: function(start, end){
276         var children = this.children, child;
277         start = start || 0;
278         end = end || children.length;
279         for(var i = start; i < end; i++){
280             child = children[i];
281             child.depth = i + 1;
282             child.parent = this;
283         }
284     },
285 
286     /**
287      * 返回是否包含参数指定的子元素。
288      * @param {View} child 指定要测试的子元素。
289      */
290     contains: function(child){
291         while(child = child.parent){
292             if(child === this){
293                 return true;
294             }
295         }
296         return false;
297     },
298 
299     /**
300      * 返回由x和y指定的点下的对象。
301      * @param {Number} x 指定点的x轴坐标。
302      * @param {Number} y 指定点的y轴坐标。
303      * @param {Boolean} usePolyCollision 指定是否使用多边形碰撞检测。默认为false。
304      * @param {Boolean} global 使用此标志表明将查找所有符合的对象,而不仅仅是第一个,即全局匹配。默认为false。
305      * @param {Boolean} eventMode 使用此标志表明将在事件模式下查找对象。默认为false。
306      */
307     getViewAtPoint: function(x, y, usePolyCollision, global, eventMode){
308         var result = global ? [] : null,
309             children = this.children, child, obj;
310 
311         for(var i = children.length - 1; i >= 0; i--){
312             child = children[i];
313             //skip child which is not shown or pointer enabled
314             if(!child || !child.visible || child.alpha <= 0 || (eventMode && !child.pointerEnabled)) continue;
315             //find child recursively
316             if(child.children && child.children.length && !(eventMode && !child.pointerChildren)){
317                 obj = child.getViewAtPoint(x, y, usePolyCollision, global, eventMode);
318             }
319 
320             if(obj){
321                 if(!global) return obj;
322                 else if(obj.length) result = result.concat(obj);
323             }else if(child.hitTestPoint(x, y, usePolyCollision)){
324                 if(!global) return child;
325                 else result.push(child);
326             }
327         }
328 
329         return global && result.length ? result : null;
330     },
331 
332     /**
333      * 覆盖渲染方法。
334      * @private
335      */
336     render: function(renderer, delta){
337         Container.superclass.render.call(this, renderer, delta);
338 
339         var children = this.children.slice(0), i, len, child;
340         for(i = 0, len = children.length; i < len; i++){
341             child = children[i];
342             //NOTE: the child could remove or change it's parent
343             if(child.parent === this) child._render(renderer, delta);
344         }
345     }
346 
347 });
348