1 /**
  2  * The MIT License (MIT)
  3  *
  4  * Copyright (c) 2016 DeNA Co., Ltd.
  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 THE
 22  * SOFTWARE.
 23  */
 24 
 25 /// <reference path="base.js"/>
 26 /// <reference path="display_object.js"/>
 27 /// <reference path="object_list.js"/>
 28 /// <reference path="bounding_box.js"/>
 29 
 30 /**
 31  * A class that contains multiple createjs.DisplayObject instances.
 32  * @extends {createjs.DisplayObject}
 33  * @constructor
 34  */
 35 createjs.Container = function() {
 36   createjs.DisplayObject.call(this);
 37 
 38   /**
 39    * The list of children of this container.
 40    * @type {createjs.ObjectList}
 41    * @private
 42    */
 43   this.children_ = new createjs.ObjectList();
 44 };
 45 createjs.inherits('Container', createjs.Container, createjs.DisplayObject);
 46 
 47 /**
 48  * A clone of the list of the children of this container.
 49  * @type {Array.<createjs.DisplayObject>}
 50  * @private
 51  */
 52 createjs.Container.prototype.clone_ = null;
 53 
 54 /**
 55  * A bit-mask representing CreateJS events listened either by this container or
 56  * by its children.
 57  * @type {number}
 58  * @private
 59  */
 60 createjs.Container.prototype.userEvents_ = 0;
 61 
 62 /**
 63  * Resets a child object having attached to this object.
 64  * @param {createjs.DisplayObject} child
 65  * @private
 66  */
 67 createjs.Container.prototype.resetChild_ = function(child) {
 68   /// <param type="createjs.DisplayObject" name="child"/>
 69   child.setParent(null);
 70   child.removeAllListeners();
 71 };
 72 
 73 /**
 74  * Detaches a display object from this container.
 75  * @param {createjs.DisplayObject} child
 76  * @private
 77  */
 78 createjs.Container.prototype.removeChild_ = function(child) {
 79   /// <param type="createjs.DisplayObject" name="child"/>
 80   this.resetChild_(child);
 81   this.children_.removeItem(child);
 82   child.handleDetach();
 83 };
 84 
 85 /**
 86  * Detaches a display object at the specified index.
 87  * @param {number} index
 88  * @private
 89  */
 90 createjs.Container.prototype.removeChildAt_ = function(index) {
 91   /// <param type="number" name="index"/>
 92   var child =
 93       /** @type {createjs.DisplayObject} */ (this.children_.getItemAt(index));
 94   this.resetChild_(child);
 95   this.children_.removeItemAt(index);
 96   child.handleDetach();
 97 };
 98 
 99 /**
100  * Adds the specified child to the specified location only if it is not a child
101  * of this container.
102  * @param {createjs.DisplayObject} child
103  * @private
104  */
105 createjs.Container.prototype.initializeChild_ = function(child) {
106   /// <param type="createjs.DisplayObject" name="child"/>
107   var parent = child.getParent();
108   createjs.assert(parent !== child);
109   if (parent) {
110     child.setParent(null);
111     parent.children_.removeItem(child);
112   } else {
113     child.handleAttach(1);
114   }
115   child.setDirty(createjs.DisplayObject.DIRTY_ALL);
116   child.setParent(this);
117 };
118 
119 /**
120  * Returns the child at the specified index.
121  * @param {number} index
122  * @return {createjs.DisplayObject}
123  */
124 createjs.Container.prototype.getChildAt = function(index) {
125   /// <param type="number" name="index"/>
126   /// <returns type="createjs.DisplayObject"/>
127   var child =
128       /** @type {createjs.DisplayObject} */ (this.children_.getItemAt(index));
129   return child;
130 };
131   
132 /**
133  * Returns the child with the specified name.
134  * @param {string} name
135  * @return {createjs.DisplayObject}
136  */
137 createjs.Container.prototype.getChildByName = function(name) {
138   /// <param type="string" name="name"/>
139   /// <returns type="createjs.DisplayObject"/>
140   var length = this.children_.getLength();
141   for (var i = length - 1; i >= 0; --i) {
142     var child =
143         /** @type {createjs.DisplayObject} */ (this.children_.getItemAt(i));
144     if (child['name'] == name) {
145       return child;
146     }
147   }
148   return null;
149 };
150 
151 /**
152  * Sorts the children of this container.
153  * @param {function(Object, Object): number|undefined} sortFunction
154  */
155 createjs.Container.prototype.sortChildren = function(sortFunction) {
156   /// <param type="Function" name="sortFunction"/>
157   createjs.notImplemented();
158 };
159 
160 /**
161  * Returns the index of the specified child in the display list, or -1 if it is
162  * not in the display list.
163  * @param {createjs.DisplayObject} child
164  * @return {number}
165  */
166 createjs.Container.prototype.getChildIndex = function(child) {
167   /// <param type="createjs.DisplayObject" name="child"/>
168   /// <returns type="number"/>
169   return this.children_.findItem(child);
170 };
171 
172 /**
173  * Returns the number of children in the display list.
174  * @return {number} The number of children in the display list.
175  */
176 createjs.Container.prototype.getNumChildren = function() {
177   /// <returns type="number"/>
178   return this.children_.getLength();
179 };
180   
181 /**
182  * Returns a clone of the child list of this container.
183  * @return {Array.<createjs.DisplayObject>}
184  */
185 createjs.Container.prototype.getChildren = function() {
186   /// <returns type="Array" elementType="createjs.DisplayObject"/>
187   if (!this.clone_) {
188     this.clone_ = this.children_.cloneItems();
189   }
190   return this.clone_;
191 };
192 
193 /**
194  * Swaps the children at the specified indexes. Fails silently if either index
195  * is out of range.
196  * @param {number} index1
197  * @param {number} index2
198  */
199 createjs.Container.prototype.swapChildrenAt = function(index1, index2) {
200   /// <param type="number" name="index1"/>
201   /// <param type="number" name="index2"/>
202   if (index1 < 0 || index2 < 0) {
203     return;
204   }
205   this.children_.swapItemsAt(index1, index2);
206 };
207   
208 /**
209  * Swaps the specified children's depth in the display list. Fails silently if
210  * either child is not a child of this object.
211  * @param {createjs.DisplayObject} child1
212  * @param {createjs.DisplayObject} child2
213  */
214 createjs.Container.prototype.swapChildren = function(child1, child2) {
215   /// <param type="createjs.DisplayObject" name="child1"/>
216   /// <param type="createjs.DisplayObject" name="child2"/>
217   if (child1.getParent() !== this || child2.getParent() !== this) {
218     return;
219   }
220   this.children_.swapItems(child1, child2);
221 };
222   
223 /**
224  * Changes the depth of the specified child. Fails silently if the child is not
225  * a child of this container, or the index is out of range.
226  * @param {createjs.DisplayObject} child
227  * @param {number} index  
228  */
229 createjs.Container.prototype.setChildIndex = function(child, index) {
230   /// <param type="createjs.DisplayObject" name="child"/>
231   /// <param type="number" name="index"/>
232   if (child.getParent() !== this || index >= this.children_.getLength()) {
233     return;
234   }
235   this.children_.removeItem(child);
236   this.children_.insertItem(index, child);
237 };
238 
239 /**
240  * Returns an array of all display objects under the specified coordinates that
241  * are in this container's display list. This routine ignores any display
242  * objects with mouseEnabled set to false. The array will be sorted in order of
243  * visual depth, with the top-most display object at index 0. This uses shape
244  * based hit detection, and can be an expensive operation to run, so it is best
245  * to use it carefully. For example, if testing for objects under the mouse,
246  * test on tick (instead of on mousemove), and only if the mouse's position has
247  * changed.
248  * @param {number} x
249  * @param {number} y
250  * @return {Array.<createjs.DisplayObject>}
251  */
252 createjs.Container.prototype.getObjectsUnderPoint = function(x, y) {
253   /// <param type="number" name="x"/>
254   /// <param type="number" name="y"/>
255   /// <returns type="createjs.DisplayObject"/>
256   var list = [];
257   this.hitTestObjects(this.localToGlobal(x, y), list, 0, 1);
258   return list;
259 };
260 
261 /**
262  * Similar to the Container.getObjectsUnderPoint() method, but returns only the
263  * top-most display object. This runs significantly faster than the
264  * Container.getObjectsUnderPoint() method, but is still an expensive operation.
265  * See the Container.getObjectsUnderPoint() for more information.
266  * @param {number} x
267  * @param {number} y
268  * @return {createjs.DisplayObject}
269  */
270 createjs.Container.prototype.getObjectUnderPoint = function(x, y) {
271   /// <param type="number" name="x"/>
272   /// <param type="number" name="y"/>
273   /// <returns type="createjs.DisplayObject"/>
274   return this.hitTestObject(new createjs.Point(x, y), 0, 1);
275 };
276   
277 /** @override */
278 createjs.Container.prototype.addChild = function(var_args) {
279   /// <var type="Array" elementType="createjs.DisplayObject" name="args"/>
280   var args = arguments;
281   var length = args.length;
282   if (length < 1) {
283     return null;
284   }
285   for (var i = 0; i < length; ++i) {
286     var child = /** @type {createjs.DisplayObject} */ (args[i]);
287     this.initializeChild_(child);
288     this.children_.pushItem(child);
289   }
290   this.clone_ = null;
291   return /** @type {createjs.DisplayObject} */ (args[length - 1]);
292 };
293 
294 /** @override */
295 createjs.Container.prototype.addChildAt = function(var_args) {
296   /// <var type="Array" elementType="createjs.DisplayObject" name="args"/>
297   var args = arguments;
298   var length = args.length;
299   if (length < 2) {
300     return null;
301   }
302   var index = createjs.getNumber(args[--length]);
303   for (var i = 0; i < length; ++i, ++index) {
304     var child = /** @type {createjs.DisplayObject} */ (args[i]);
305     this.initializeChild_(child);
306     this.children_.insertItem(index, child);
307   }
308   this.clone_ = null;
309   return /** @type {createjs.DisplayObject} */ (args[length - 1]);
310 };
311 
312 /** @override */
313 createjs.Container.prototype.removeChild = function(var_args) {
314   /// <var type="Array" elementType="createjs.DisplayObject" name="args"/>
315   var args = arguments;
316   var length = args.length;
317   if (length < 1) {
318     return false;
319   }
320   for (var i = 0; i < length; ++i) {
321     var child = /** @type {createjs.DisplayObject} */ (args[i]);
322     this.removeChild_(child);
323   }
324   this.clone_ = null;
325   return true;
326 };
327 
328 /** @override */
329 createjs.Container.prototype.removeChildAt = function(var_args) {
330   /// <var type="Array" elementType="number" name="args"/>
331   var args = arguments;
332   var length = args.length;
333   if (length < 1) {
334     return false;
335   }
336   for (var i = 0; i < length; ++i) {
337     var index = /** @type {number} */ (args[i]);
338     this.removeChildAt_(index);
339   }
340   this.clone_ = null;
341   return true;
342 };
343 
344 /** @override */
345 createjs.Container.prototype.removeAllChildren = function(opt_destroy) {
346   /// <param type="boolean" optional="true" name="opt_destroy"/>
347   if (this.children_) {
348     var length = this.children_.getLength();
349     for (var i = length - 1; i >= 0; --i) {
350       var child =
351           /** @type {createjs.DisplayObject} */ (this.children_.getItemAt(i));
352       if (child) {
353         child.removeAllChildren(true);
354         this.resetChild_(child);
355       }
356     }
357     this.children_.removeAllItems();
358   }
359   this.clone_ = null;
360   if (opt_destroy) {
361     this.children_ = null;
362     this.destroy();
363   }
364 };
365 
366 /** @override */
367 createjs.Container.prototype.handleDetach = function() {
368   if (this.children_) {
369     var length = this.children_.getLength();
370     for (var i = length - 1; i >= 0; --i) {
371       var child = this.children_.getItemAt(i);
372       child.setDirty(createjs.DisplayObject.DIRTY_ALL);
373       child.handleDetach();
374     }
375   }
376 };
377 
378 /** @override */
379 createjs.Container.prototype.hitTestObject = function(point, types, bubble) {
380   /// <param type="createjs.Point" name="point"/>
381   /// <param type="number" name="types"/>
382   /// <param type="number" name="bubble"/>
383   /// <returns type="createjs.DisplayObject"/>
384   bubble |= types & this.getEventTypes();
385   var testChildren = bubble | (types & this.userEvents_);
386   if (testChildren && this.getState().contain(point)) {
387     var length = this.children_.getLength();
388     for (var i = length - 1; i >= 0; --i) {
389       var child = this.children_.getItemAt(i);
390       if (!child.getOff() && child.isVisible()) {
391         var result = child.hitTestObject(point, types, bubble);
392         if (result) {
393           return result;
394         }
395       }
396     }
397   }
398   return null;
399 };
400 
401 /** @override */
402 createjs.Container.prototype.hitTestObjects =
403     function(point, list, types, bubble) {
404   /// <param type="createjs.Point" name="point"/>
405   /// <param type="Array" elementType="createjs.DisplayObject" name="list"/>
406   /// <param type="number" name="types"/>
407   /// <param type="number" name="bubble"/>
408   bubble |= types & this.getEventTypes();
409   var testChildren = bubble | (types & this.userEvents_);
410   if (testChildren && this.getState().contain(point)) {
411     var length = this.children_.getLength();
412     for (var i = length - 1; i >= 0; --i) {
413       var child = this.children_.getItemAt(i);
414       if (!child.getOff() && child.isVisible()) {
415         child.hitTestObjects(point, list, types);
416       }
417     }
418   }
419 };
420 
421 /** @override */
422 createjs.Container.prototype.layout =
423     function(renderer, parent, dirty, time, draw) {
424   /// <param type="createjs.Renderer" name="renderer"/>
425   /// <param type="createjs.DisplayObject" name="parent"/>
426   /// <param type="number" name="dirty"/>
427   /// <param type="number" name="time"/>
428   /// <param type="number" name="draw"/>
429   /// <returns type="numer"/>
430   // Return without traversing its children when it becomes invisible.
431   this.userEvents_ = this.getEventTypes();
432   if (!this.isVisible()) {
433     return 0;
434   }
435   // Updates the layout state of this object. The children of this object uses
436   // this layout state and this update must be executed BEFORE updating the
437   // layout of its children.
438   dirty |= this.getDirty();
439   this.updateLayout(parent, dirty);
440   this.getState().resetBox();
441 
442   // Update the tweens attached to all children of this container, and update
443   // their layouts. Tweens may add children to this container or remove them.
444   // When a tween adds a child, this code creates a clone of this children list
445   // and adds the child to the clone.
446   var children = this.children_.lock();
447   var length = children.length;
448   for (var i = 0; i < length; ++i) {
449     var child = children[i];
450     if (child.getOff()) {
451       child.setDirty(createjs.DisplayObject.DIRTY_ALL);
452       continue;
453     }
454     child.updateTweens(time);
455     this.userEvents_ |= child.layout(renderer, this, dirty, time, draw);
456   }
457   this.children_.unlock();
458 
459   // Inflate parent's bounding box so it contains the bounding box of this
460   // object and the ones of its children.
461   parent.getState().inflateBox(this.getState());
462   this.dirty = 0;
463   return this.userEvents_;
464 };
465 
466 /** @override */
467 createjs.Container.prototype.getBounds = function() {
468   // Calculate the bounding box of its children when this container has never
469   // been rendered.
470   if (this.getBoundingBox().isEmpty()) {
471     var children = this.children_.lock();
472     var minX = 10000;
473     var minY = 10000;
474     var maxX = -10000;
475     var maxY = -10000;
476     for (var i = children.length - 1; i >= 0; --i) {
477       var child = children[i];
478       var bounds = child.getBounds();
479       var x = bounds.x + child.getX();
480       var y = bounds.y + child.getY();
481       minX = createjs.min(minX, x);
482       minY = createjs.min(minY, y);
483       maxX = createjs.max(maxX, x + bounds.width);
484       maxY = createjs.max(maxY, y + bounds.height);
485     }
486     this.children_.unlock();
487     if (minX > maxX || minY > maxY) {
488       return null;
489     }
490     this.setBoundingBox(minX, minY, maxX, maxY);
491   }
492   return createjs.Container.superClass_.getBounds.call(this);
493 };
494 
495 // Add a getter for applications to access an internal variable.
496 Object.defineProperties(createjs.Container.prototype, {
497   'children': {
498     get: createjs.Container.prototype.getChildren
499   }
500 });
501 
502 // Export the createjs.Container object to the global namespace.
503 createjs.exportObject('createjs.Container', createjs.Container, {
504   // createjs.Container methods
505   'addChild': createjs.Container.prototype.addChild,
506   'addChildAt': createjs.Container.prototype.addChildAt,
507   'removeChild': createjs.Container.prototype.removeChild,
508   'removeChildAt': createjs.Container.prototype.removeChildAt,
509   'removeAllChildren': createjs.Container.prototype.removeAllChildren,
510   'getChildAt': createjs.Container.prototype.getChildAt,
511   'getChildByName': createjs.Container.prototype.getChildByName,
512   'sortChildren': createjs.Container.prototype.sortChildren,
513   'getChildIndex': createjs.Container.prototype.getChildIndex,
514   'getNumChildren': createjs.Container.prototype.getNumChildren,
515   'swapChildrenAt': createjs.Container.prototype.swapChildrenAt,
516   'swapChildren': createjs.Container.prototype.swapChildren,
517   'setChildIndex': createjs.Container.prototype.setChildIndex,
518   'getObjectsUnderPoint': createjs.Container.prototype.getObjectsUnderPoint,
519   'getObjectUnderPoint': createjs.Container.prototype.getObjectUnderPoint,
520 
521   // createjs.DisplayObject methods
522   'getBounds': createjs.Container.prototype.getBounds
523 
524   // createjs.EventDispatcher methods
525 
526   // createjs.Object methods.
527 });
528