/* * Container * Visit http://createjs.com/ for documentation, updates and examples. * * Copyright (c) 2010 gskinner.com, inc. * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ // namespace: this.createjs = this.createjs||{}; (function() { "use strict"; // constructor: /** * A Container is a nestable display list that allows you to work with compound display elements. For example you could * group arm, leg, torso and head {{#crossLink "Bitmap"}}{{/crossLink}} instances together into a Person Container, and * transform them as a group, while still being able to move the individual parts relative to each other. Children of * containers have their `transform` and `alpha` properties concatenated with their parent Container. * * For example, a {{#crossLink "Shape"}}{{/crossLink}} with `x=100` and `alpha=0.5`, placed in a Container with `x=50` * and `alpha=0.7` will be rendered to the canvas at `x=150` and `alpha=0.35`. Containers have some overhead, so you * generally shouldn't create a Container to hold a single child. * * <h4>Example</h4> * * var container = new createjs.Container(); * container.addChild(bitmapInstance, shapeInstance); * container.x = 100; * * @class Container * @extends DisplayObject * @constructor **/ function Container() { this.DisplayObject_constructor(); // public properties: /** * The array of children in the display list. You should usually use the child management methods such as * {{#crossLink "Container/addChild"}}{{/crossLink}}, {{#crossLink "Container/removeChild"}}{{/crossLink}}, * {{#crossLink "Container/swapChildren"}}{{/crossLink}}, etc, rather than accessing this directly, but it is * included for advanced uses. * @property children * @type Array * @default null **/ this.children = []; /** * Indicates whether the children of this container are independently enabled for mouse/pointer interaction. * If false, the children will be aggregated under the container - for example, a click on a child shape would * trigger a click event on the container. * @property mouseChildren * @type Boolean * @default true **/ this.mouseChildren = true; /** * If false, the tick will not be propagated to children of this Container. This can provide some performance benefits. * In addition to preventing the "tick" event from being dispatched, it will also prevent tick related updates * on some display objects (ex. Sprite & MovieClip frame advancing, DOMElement visibility handling). * @property tickChildren * @type Boolean * @default true **/ this.tickChildren = true; } var p = createjs.extend(Container, createjs.DisplayObject); // getter / setters: /** * Use the {{#crossLink "Container/numChildren:property"}}{{/crossLink}} property instead. * @method _getNumChildren * @protected * @return {Number} **/ p._getNumChildren = function() { return this.children.length; }; /** * Use the {{#crossLink "Container/numChildren:property"}}{{/crossLink}} property instead. * @method getNumChildren * @deprecated */ // Container.getNumChildren is @deprecated. Remove for 1.1+ p.getNumChildren = createjs.deprecate(p._getNumChildren, "Container.getNumChildren"); /** * Returns the number of children in the container. * @property numChildren * @type {Number} * @readonly **/ try { Object.defineProperties(p, { numChildren: { get: p._getNumChildren } }); } catch (e) {} // public methods: /** * Constructor alias for backwards compatibility. This method will be removed in future versions. * Subclasses should be updated to use {{#crossLink "Utility Methods/extends"}}{{/crossLink}}. * @method initialize * @deprecated in favour of `createjs.promote()` **/ p.initialize = Container; // TODO: deprecated. /** * Returns true or false indicating whether the display object would be visible if drawn to a canvas. * This does not account for whether it would be visible within the boundaries of the stage. * * NOTE: This method is mainly for internal use, though it may be useful for advanced uses. * @method isVisible * @return {Boolean} Boolean indicating whether the display object would be visible if drawn to a canvas **/ p.isVisible = function() { var hasContent = this.cacheCanvas || this.children.length; return !!(this.visible && this.alpha > 0 && this.scaleX != 0 && this.scaleY != 0 && hasContent); }; /** * Draws the display object into the specified context ignoring its visible, alpha, shadow, and transform. * Returns true if the draw was handled (useful for overriding functionality). * * NOTE: This method is mainly for internal use, though it may be useful for advanced uses. * @method draw * @param {CanvasRenderingContext2D} ctx The canvas 2D context object to draw into. * @param {Boolean} [ignoreCache=false] Indicates whether the draw operation should ignore any current cache. * For example, used for drawing the cache (to prevent it from simply drawing an existing cache back * into itself). **/ p.draw = function(ctx, ignoreCache) { if (this.DisplayObject_draw(ctx, ignoreCache)) { return true; } // this ensures we don't have issues with display list changes that occur during a draw: var list = this.children.slice(); for (var i=0,l=list.length; i<l; i++) { var child = list[i]; if (!child.isVisible()) { continue; } // draw the child: ctx.save(); child.updateContext(ctx); child.draw(ctx); ctx.restore(); } return true; }; /** * Adds a child to the top of the display list. * * <h4>Example</h4> * * container.addChild(bitmapInstance); * * You can also add multiple children at once: * * container.addChild(bitmapInstance, shapeInstance, textInstance); * * @method addChild * @param {DisplayObject} child The display object to add. * @return {DisplayObject} The child that was added, or the last child if multiple children were added. **/ p.addChild = function(child) { if (child == null) { return child; } var l = arguments.length; if (l > 1) { for (var i=0; i<l; i++) { this.addChild(arguments[i]); } return arguments[l-1]; } // Note: a lot of duplication with addChildAt, but push is WAY faster than splice. var par=child.parent, silent = par === this; par&&par._removeChildAt(createjs.indexOf(par.children, child), silent); child.parent = this; this.children.push(child); if (!silent) { child.dispatchEvent("added"); } return child; }; /** * Adds a child to the display list at the specified index, bumping children at equal or greater indexes up one, and * setting its parent to this Container. * * <h4>Example</h4> * * addChildAt(child1, index); * * You can also add multiple children, such as: * * addChildAt(child1, child2, ..., index); * * The index must be between 0 and numChildren. For example, to add myShape under otherShape in the display list, * you could use: * * container.addChildAt(myShape, container.getChildIndex(otherShape)); * * This would also bump otherShape's index up by one. Fails silently if the index is out of range. * * @method addChildAt * @param {DisplayObject} child The display object to add. * @param {Number} index The index to add the child at. * @return {DisplayObject} Returns the last child that was added, or the last child if multiple children were added. **/ p.addChildAt = function(child, index) { var l = arguments.length; var indx = arguments[l-1]; // can't use the same name as the index param or it replaces arguments[1] if (indx < 0 || indx > this.children.length) { return arguments[l-2]; } if (l > 2) { for (var i=0; i<l-1; i++) { this.addChildAt(arguments[i], indx+i); } return arguments[l-2]; } var par=child.parent, silent = par === this; par&&par._removeChildAt(createjs.indexOf(par.children, child), silent); child.parent = this; this.children.splice(index, 0, child); if (!silent) { child.dispatchEvent("added"); } return child; }; /** * Removes the specified child from the display list. Note that it is faster to use removeChildAt() if the index is * already known. * * <h4>Example</h4> * * container.removeChild(child); * * You can also remove multiple children: * * removeChild(child1, child2, ...); * * Returns true if the child (or children) was removed, or false if it was not in the display list. * @method removeChild * @param {DisplayObject} child The child to remove. * @return {Boolean} true if the child (or children) was removed, or false if it was not in the display list. **/ p.removeChild = function(child) { var l = arguments.length; if (l > 1) { var good = true; for (var i=0; i<l; i++) { good = good && this.removeChild(arguments[i]); } return good; } return this._removeChildAt(createjs.indexOf(this.children, child)); }; /** * Removes the child at the specified index from the display list, and sets its parent to null. * * <h4>Example</h4> * * container.removeChildAt(2); * * You can also remove multiple children: * * container.removeChild(2, 7, ...) * * Returns true if the child (or children) was removed, or false if any index was out of range. * @method removeChildAt * @param {Number} index The index of the child to remove. * @return {Boolean} true if the child (or children) was removed, or false if any index was out of range. **/ p.removeChildAt = function(index) { var l = arguments.length; if (l > 1) { var a = []; for (var i=0; i<l; i++) { a[i] = arguments[i]; } a.sort(function(a, b) { return b-a; }); var good = true; for (var i=0; i<l; i++) { good = good && this._removeChildAt(a[i]); } return good; } return this._removeChildAt(index); }; /** * Removes all children from the display list. * * <h4>Example</h4> * * container.removeAllChildren(); * * @method removeAllChildren **/ p.removeAllChildren = function() { var kids = this.children; while (kids.length) { this._removeChildAt(0); } }; /** * Returns the child at the specified index. * * <h4>Example</h4> * * container.getChildAt(2); * * @method getChildAt * @param {Number} index The index of the child to return. * @return {DisplayObject} The child at the specified index. Returns null if there is no child at the index. **/ p.getChildAt = function(index) { return this.children[index]; }; /** * Returns the child with the specified name. * @method getChildByName * @param {String} name The name of the child to return. * @return {DisplayObject} The child with the specified name. **/ p.getChildByName = function(name) { var kids = this.children; for (var i=0,l=kids.length;i<l;i++) { if(kids[i].name == name) { return kids[i]; } } return null; }; /** * Performs an array sort operation on the child list. * * <h4>Example: Display children with a higher y in front.</h4> * * var sortFunction = function(obj1, obj2, options) { * if (obj1.y > obj2.y) { return 1; } * if (obj1.y < obj2.y) { return -1; } * return 0; * } * container.sortChildren(sortFunction); * * @method sortChildren * @param {Function} sortFunction the function to use to sort the child list. See JavaScript's `Array.sort` * documentation for details. **/ p.sortChildren = function(sortFunction) { this.children.sort(sortFunction); }; /** * Returns the index of the specified child in the display list, or -1 if it is not in the display list. * * <h4>Example</h4> * * var index = container.getChildIndex(child); * * @method getChildIndex * @param {DisplayObject} child The child to return the index of. * @return {Number} The index of the specified child. -1 if the child is not found. **/ p.getChildIndex = function(child) { return createjs.indexOf(this.children, child); }; /** * Swaps the children at the specified indexes. Fails silently if either index is out of range. * @method swapChildrenAt * @param {Number} index1 * @param {Number} index2 **/ p.swapChildrenAt = function(index1, index2) { var kids = this.children; var o1 = kids[index1]; var o2 = kids[index2]; if (!o1 || !o2) { return; } kids[index1] = o2; kids[index2] = o1; }; /** * Swaps the specified children's depth in the display list. Fails silently if either child is not a child of this * Container. * @method swapChildren * @param {DisplayObject} child1 * @param {DisplayObject} child2 **/ p.swapChildren = function(child1, child2) { var kids = this.children; var index1,index2; for (var i=0,l=kids.length;i<l;i++) { if (kids[i] == child1) { index1 = i; } if (kids[i] == child2) { index2 = i; } if (index1 != null && index2 != null) { break; } } if (i==l) { return; } // TODO: throw error? kids[index1] = child2; kids[index2] = child1; }; /** * Changes the depth of the specified child. Fails silently if the child is not a child of this container, or the index is out of range. * @param {DisplayObject} child * @param {Number} index * @method setChildIndex **/ p.setChildIndex = function(child, index) { var kids = this.children, l=kids.length; if (child.parent != this || index < 0 || index >= l) { return; } for (var i=0;i<l;i++) { if (kids[i] == child) { break; } } if (i==l || i == index) { return; } kids.splice(i,1); kids.splice(index,0,child); }; /** * Returns true if the specified display object either is this container or is a descendent (child, grandchild, etc) * of this container. * @method contains * @param {DisplayObject} child The DisplayObject to be checked. * @return {Boolean} true if the specified display object either is this container or is a descendent. **/ p.contains = function(child) { while (child) { if (child == this) { return true; } child = child.parent; } return false; }; /** * Tests whether the display object intersects the specified local point (ie. draws a pixel with alpha > 0 at the * specified position). This ignores the alpha, shadow and compositeOperation of the display object, and all * transform properties including regX/Y. * @method hitTest * @param {Number} x The x position to check in the display object's local coordinates. * @param {Number} y The y position to check in the display object's local coordinates. * @return {Boolean} A Boolean indicating whether there is a visible section of a DisplayObject that overlaps the specified * coordinates. **/ p.hitTest = function(x, y) { // TODO: optimize to use the fast cache check where possible. return (this.getObjectUnderPoint(x, y) != null); }; /** * Returns an array of all display objects under the specified coordinates that are in this container's display * list. This routine ignores any display objects with {{#crossLink "DisplayObject/mouseEnabled:property"}}{{/crossLink}} * set to `false`. The array will be sorted in order of visual depth, with the top-most display object at index 0. * This uses shape based hit detection, and can be an expensive operation to run, so it is best to use it carefully. * For example, if testing for objects under the mouse, test on tick (instead of on {{#crossLink "Stage/stagemousemove:event"}}{{/crossLink}}), * and only if the mouse's position has changed. * * <ul> * <li>By default (mode=0) this method evaluates all display objects.</li> * <li>By setting the `mode` parameter to `1`, the {{#crossLink "DisplayObject/mouseEnabled:property"}}{{/crossLink}} * and {{#crossLink "mouseChildren:property"}}{{/crossLink}} properties will be respected.</li> * <li>Setting the `mode` to `2` additionally excludes display objects that do not have active mouse event * listeners or a {{#crossLink "DisplayObject:cursor:property"}}{{/crossLink}} property. That is, only objects * that would normally intercept mouse interaction will be included. This can significantly improve performance * in some cases by reducing the number of display objects that need to be tested.</li> * </ul> * * This method accounts for both {{#crossLink "DisplayObject/hitArea:property"}}{{/crossLink}} and {{#crossLink "DisplayObject/mask:property"}}{{/crossLink}}. * @method getObjectsUnderPoint * @param {Number} x The x position in the container to test. * @param {Number} y The y position in the container to test. * @param {Number} [mode=0] The mode to use to determine which display objects to include. 0-all, 1-respect mouseEnabled/mouseChildren, 2-only mouse opaque objects. * @return {Array} An Array of DisplayObjects under the specified coordinates. **/ p.getObjectsUnderPoint = function(x, y, mode) { var arr = []; var pt = this.localToGlobal(x, y); this._getObjectsUnderPoint(pt.x, pt.y, arr, mode>0, mode==1); return arr; }; /** * Similar to {{#crossLink "Container/getObjectsUnderPoint"}}{{/crossLink}}, but returns only the top-most display * object. This runs significantly faster than `getObjectsUnderPoint()`, but is still potentially an expensive * operation. See {{#crossLink "Container/getObjectsUnderPoint"}}{{/crossLink}} for more information. * @method getObjectUnderPoint * @param {Number} x The x position in the container to test. * @param {Number} y The y position in the container to test. * @param {Number} mode The mode to use to determine which display objects to include. 0-all, 1-respect mouseEnabled/mouseChildren, 2-only mouse opaque objects. * @return {DisplayObject} The top-most display object under the specified coordinates. **/ p.getObjectUnderPoint = function(x, y, mode) { var pt = this.localToGlobal(x, y); return this._getObjectsUnderPoint(pt.x, pt.y, null, mode>0, mode==1); }; // Docced in superclass p.getBounds = function() { return this._getBounds(null, true); }; // Docced in superclass p.getTransformedBounds = function() { return this._getBounds(); }; /** * Returns a clone of this Container. Some properties that are specific to this instance's current context are * reverted to their defaults (for example .parent). * @method clone * @param {Boolean} [recursive=false] If true, all of the descendants of this container will be cloned recursively. If false, the * properties of the container will be cloned, but the new instance will not have any children. * @return {Container} A clone of the current Container instance. **/ p.clone = function(recursive) { var o = this._cloneProps(new Container()); if (recursive) { this._cloneChildren(o); } return o; }; /** * Returns a string representation of this object. * @method toString * @return {String} a string representation of the instance. **/ p.toString = function() { return "[Container (name="+ this.name +")]"; }; // private methods: /** * @method _tick * @param {Object} evtObj An event object that will be dispatched to all tick listeners. This object is reused between dispatchers to reduce construction & GC costs. * @protected **/ p._tick = function(evtObj) { if (this.tickChildren) { for (var i=this.children.length-1; i>=0; i--) { var child = this.children[i]; if (child.tickEnabled && child._tick) { child._tick(evtObj); } } } this.DisplayObject__tick(evtObj); }; /** * Recursively clones all children of this container, and adds them to the target container. * @method cloneChildren * @protected * @param {Container} o The target container. **/ p._cloneChildren = function(o) { if (o.children.length) { o.removeAllChildren(); } var arr = o.children; for (var i=0, l=this.children.length; i<l; i++) { var clone = this.children[i].clone(true); clone.parent = o; arr.push(clone); } }; /** * Removes the child at the specified index from the display list, and sets its parent to null. * Used by `removeChildAt`, `addChild`, and `addChildAt`. * @method removeChildAt * @protected * @param {Number} index The index of the child to remove. * @param {Boolean} [silent] Prevents dispatch of `removed` event if true. * @return {Boolean} true if the child (or children) was removed, or false if any index was out of range. **/ p._removeChildAt = function(index, silent) { if (index < 0 || index > this.children.length-1) { return false; } var child = this.children[index]; if (child) { child.parent = null; } this.children.splice(index, 1); if (!silent) { child.dispatchEvent("removed"); } return true; }; /** * @method _getObjectsUnderPoint * @param {Number} x * @param {Number} y * @param {Array} arr * @param {Boolean} mouse If true, it will respect mouse interaction properties like mouseEnabled, mouseChildren, and active listeners. * @param {Boolean} activeListener If true, there is an active mouse event listener on a parent object. * @param {Number} currentDepth Indicates the current depth of the search. * @return {DisplayObject} * @protected **/ p._getObjectsUnderPoint = function(x, y, arr, mouse, activeListener, currentDepth) { currentDepth = currentDepth || 0; if (!currentDepth && !this._testMask(this, x, y)) { return null; } var mtx, ctx = createjs.DisplayObject._hitTestContext; activeListener = activeListener || (mouse&&this._hasMouseEventListener()); // draw children one at a time, and check if we get a hit: var children = this.children, l = children.length; for (var i=l-1; i>=0; i--) { var child = children[i]; var hitArea = child.hitArea; if (!child.visible || (!hitArea && !child.isVisible()) || (mouse && !child.mouseEnabled)) { continue; } if (!hitArea && !this._testMask(child, x, y)) { continue; } // if a child container has a hitArea then we only need to check its hitAre2a, so we can treat it as a normal DO: if (!hitArea && child instanceof Container) { var result = child._getObjectsUnderPoint(x, y, arr, mouse, activeListener, currentDepth+1); if (!arr && result) { return (mouse && !this.mouseChildren) ? this : result; } } else { if (mouse && !activeListener && !child._hasMouseEventListener()) { continue; } // TODO: can we pass displayProps forward, to avoid having to calculate this backwards every time? It's kind of a mixed bag. When we're only hunting for DOs with event listeners, it may not make sense. var props = child.getConcatenatedDisplayProps(child._props); mtx = props.matrix; if (hitArea) { mtx.appendMatrix(hitArea.getMatrix(hitArea._props.matrix)); props.alpha = hitArea.alpha; } ctx.globalAlpha = props.alpha; ctx.setTransform(mtx.a, mtx.b, mtx.c, mtx.d, mtx.tx-x, mtx.ty-y); (hitArea||child).draw(ctx); if (!this._testHit(ctx)) { continue; } ctx.setTransform(1, 0, 0, 1, 0, 0); ctx.clearRect(0, 0, 2, 2); if (arr) { arr.push(child); } else { return (mouse && !this.mouseChildren) ? this : child; } } } return null; }; /** * @method _testMask * @param {DisplayObject} target * @param {Number} x * @param {Number} y * @return {Boolean} Indicates whether the x/y is within the masked region. * @protected **/ p._testMask = function(target, x, y) { var mask = target.mask; if (!mask || !mask.graphics || mask.graphics.isEmpty()) { return true; } var mtx = this._props.matrix, parent = target.parent; mtx = parent ? parent.getConcatenatedMatrix(mtx) : mtx.identity(); mtx = mask.getMatrix(mask._props.matrix).prependMatrix(mtx); var ctx = createjs.DisplayObject._hitTestContext; ctx.setTransform(mtx.a, mtx.b, mtx.c, mtx.d, mtx.tx-x, mtx.ty-y); // draw the mask as a solid fill: mask.graphics.drawAsPath(ctx); ctx.fillStyle = "#000"; ctx.fill(); if (!this._testHit(ctx)) { return false; } ctx.setTransform(1, 0, 0, 1, 0, 0); ctx.clearRect(0, 0, 2, 2); return true; }; /** * @method _getBounds * @param {Matrix2D} matrix * @param {Boolean} ignoreTransform If true, does not apply this object's transform. * @return {Rectangle} * @protected **/ p._getBounds = function(matrix, ignoreTransform) { var bounds = this.DisplayObject_getBounds(); if (bounds) { return this._transformBounds(bounds, matrix, ignoreTransform); } var mtx = this._props.matrix; mtx = ignoreTransform ? mtx.identity() : this.getMatrix(mtx); if (matrix) { mtx.prependMatrix(matrix); } var l = this.children.length, rect=null; for (var i=0; i<l; i++) { var child = this.children[i]; if (!child.visible || !(bounds = child._getBounds(mtx))) { continue; } if (rect) { rect.extend(bounds.x, bounds.y, bounds.width, bounds.height); } else { rect = bounds.clone(); } } return rect; }; createjs.Container = createjs.promote(Container, "DisplayObject"); }());