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