Source: display/DisplayObjectContainer.js

//------------------------------------------------------------------------------
// Constructor scope
//------------------------------------------------------------------------------

/**
 * Creates a new DisplayObjectContainer.
 *
 * @constructor
 * @extends rune.display.Artboard
 *
 * @param {number} [x=0.0] The x coordinate of the top-left corner of the rectangle.
 * @param {number} [y=0.0] The y coordinate of the top-left corner of the rectangle.
 * @param {number} [width=0.0] The y coordinate of the top-left corner of the rectangle.
 * @param {number} [height=0.0] The height of the rectangle, in pixels.
 *
 * @class
 * @classdesc
 * 
 * The DisplayObjectContainer class is the base class for all objects that can 
 * serve as display object containers on the display list. The display list 
 * manages all objects displayed in the Rune runtimes. Use the 
 * DisplayObjectContainer class to arrange the display objects in the display 
 * list. Each DisplayObjectContainer object has its own child list for 
 * organizing the z-order of the objects. The z-order is the front-to-back 
 * order that determines which object is drawn in front, which is behind, 
 * and so on.
 */
rune.display.DisplayObjectContainer = function(x, y, width, height) {
    
    //--------------------------------------------------------------------------
    // Public properties
    //--------------------------------------------------------------------------
    
    /**
     * Refers to a function object that forms the basis for sorting child 
     * objects. When set, all children are sorted for each tick (update). Note 
     * that automatic sorting can be problematic when you want to preserve a 
     * specific display list structure.
     *
     * @type {Function}
     * @default null
     */
    this.sort = null;
    
    /**
     * Scale for how time is calculated within the DisplayObjectContainer 
     * object.
     *
     * @type {number}
     * @default 1.0
     */
    this.timeScale = 1.0;
    
    //--------------------------------------------------------------------------
    // Protected properties
    //--------------------------------------------------------------------------
    
    /**
     * List of current children.
     *
     * @type {Array.<rune.display.DisplayObject>}
     * @protected
     * @ignore
     */
    this.m_children = [];
    
    //--------------------------------------------------------------------------
    // Super call
    //--------------------------------------------------------------------------
    
    /**
     * Extend rune.display.Artboard.
     */
    rune.display.Artboard.call(this, x, y, width, height);
};

//------------------------------------------------------------------------------
// Inheritance
//------------------------------------------------------------------------------

rune.display.DisplayObjectContainer.prototype = Object.create(rune.display.Artboard.prototype);
rune.display.DisplayObjectContainer.prototype.constructor = rune.display.DisplayObjectContainer;

//------------------------------------------------------------------------------
// Public getter and setter methods
//------------------------------------------------------------------------------

/**
 * Returns the number of children of this object.
 *
 * @member {number} numChildren
 * @memberof rune.display.DisplayObjectContainer
 * @instance
 * @readonly
 */
Object.defineProperty(rune.display.DisplayObjectContainer.prototype, "numChildren", {
    /**
     * @this rune.display.DisplayObjectContainer
     * @ignore
     */
    get : function() {
        return this.m_children.length;
    }
});

//------------------------------------------------------------------------------
// Public prototype methods (API)
//------------------------------------------------------------------------------

/**
 * Adds a child DisplayObject instance to this DisplayObjectContainer instance. 
 * The child is added to the front (top) of all other children in this 
 * DisplayObjectContainer instance. To add a child to a specific index 
 * position, use the addChildAt() method.
 *
 * @param {rune.display.DisplayObject} child The DisplayObject instance to add as a child of this DisplayObjectContainer instance.
 *
 * @throws {Error} Throws if the child is the same as the parent.
 * @throws {TypeError} Throws if the child is not of type DisplayObject.
 *
 * @returns {rune.display.DisplayObject} The DisplayObject instance that you pass in the child parameter.
 */
rune.display.DisplayObjectContainer.prototype.addChild = function(child) {
    if (child instanceof rune.display.DisplayObject) {
        if (child != this) {
            if (child['parent'] != null) {
                var group = child['group'];
                child['parent'].removeChild(child, false);
                
                if (group != null && group['container'] == this) {
                    group.include(child);
                }
            }
            
            this.addChildAt(child, this['numChildren']);
        } else throw new Error();
    } else throw new TypeError();
    
    return child;
};

/**
 * Adds a child DisplayObject instance to this DisplayObjectContainer instance. 
 * The child is added at the index position specified. An index of 0 represents 
 * the back (bottom) of the display list for this DisplayObjectContainer object.
 *
 * @param {rune.display.DisplayObject} child The DisplayObject instance to add as a child of this DisplayObjectContainer instance.
 * @param {number} index The index position to which the child is added. If you specify a currently occupied index position, the child object that exists at that position and all higher positions are moved up one position in the child list.
 *
 * @throws {Error} Throws if the child is the same as the parent.
 * @throws {RangeError} Throws if the index position does not exist in the child list.
 * @throws {TypeError} Throws if the child is not of type DisplayObject.
 *
 * @returns {rune.display.DisplayObject} The DisplayObject instance that you pass in the child parameter.
 */
rune.display.DisplayObjectContainer.prototype.addChildAt = function(child, index) {
    if (child instanceof rune.display.DisplayObject) {
        var group = child['group'];
        if (child['parent'] != null) {
            child['parent'].removeChild(child, false);
        }
        
        if (index > -1 && index <= this.m_children.length) {
            if (child != this) {
                this.m_children.splice(index, 0, child);
                
                if (group != null && group['container'] == this) {
                    group.include(child);
                }
                
                child.setParent(this);
                child.init();
                
                this.breakCache();
            } else throw new Error();
        } else throw new RangeError();
    } else throw new TypeError();
    
    return child;
};

/**
 * Calls a function for each child in the display object container.
 *
 * @param {Function} callback Call function.
 * @param {Object} scope Scope of execution.
 *
 * @returns {undefined}
 */
rune.display.DisplayObjectContainer.prototype.forEachChild = function(callback, scope) {
    for (var i = 0; i < this.m_children.length; i++) {
        callback.call(scope, this.m_children[i], i);
    }
};

/**
 * Returns the child display object instance that exists at the specified index.
 *
 * @param {number} index The index position of the child object.
 *
 * @throws {RangeError} Throws if the index does not exist in the child list.
 *
 * @returns {rune.display.DisplayObject} The child display object at the specified index position.
 */
rune.display.DisplayObjectContainer.prototype.getChildAt = function(index) {
    if (index > -1 && index < this.m_children.length) {
        return this.m_children[index];
    } else throw new RangeError();
};

/**
 * Returns the index position of a child DisplayObject instance.
 *
 * @param {rune.display.DisplayObject} child The DisplayObject instance to identify.
 *
 * @returns {number} The index position of the child display object to identify.
 */
rune.display.DisplayObjectContainer.prototype.getChildIndex = function(child) {
    return this.m_children.indexOf(child);
};

/**
 * Returns a list of all child objects. Note that this method is primarily 
 * intended for internal use.
 *
 * @returns {Array.<rune.display.DisplayObject>}
 */
rune.display.DisplayObjectContainer.prototype.getChildren = function() {
    return this.m_children;
};

/**
 * Checks whether a display object is a child of the DisplayObjectContainer 
 * instance.
 *
 * @param {rune.display.DisplayObject} child Child objects to check.
 *
 * @return {boolean}
 */
rune.display.DisplayObjectContainer.prototype.hasChild = function(child) {
    return (child) ? (this === child['parent']) : false;
};

/**
 * Removes the specified child DisplayObject instance from the child list of 
 * the DisplayObjectContainer instance. The parent property of the removed 
 * child is set to null , and the object is garbage collected if no other 
 * references to the child exist. The index positions of any display objects 
 * above the child in the DisplayObjectContainer are decreased by 1.
 *
 * @param {rune.display.DisplayObject} child The DisplayObject instance to remove.
 * @param {boolean} [dispose=false] If the child object is to be destroyed when it is removed.
 *
 * @returns {rune.display.DisplayObject}
 */
rune.display.DisplayObjectContainer.prototype.removeChild = function(child, dispose) {
    var i = this.m_children.indexOf(child);
    if (i != -1) {
        
        child.fini();
        child.setParent(null);
        
        if (child['group'] != null) {
            child['group'].exclude(child);
        }
        
        this.m_children.splice(i, 1);
        
        if (dispose == true) {
            child.dispose();
            child = null;
        }
        
        this.breakCache();
    }
    
    return (dispose) ? null : child;
};

/**
 * Removes a child DisplayObject from the specified index position in the child 
 * list of the DisplayObjectContainer.
 *
 * @param {number} index The child index of the DisplayObject to remove.
 * @param {boolean} [dispose=false] If the child object is to be destroyed when it is removed.
 *
 * @returns {rune.display.DisplayObject}
 */
rune.display.DisplayObjectContainer.prototype.removeChildAt = function(index, dispose) {
    var child = this.getChildAt(index);
    if (child) {
        child = this.removeChild(child, dispose);
    }
    
    return child;
};

/**
 * Removes all child DisplayObject instances from the child list of the 
 * DisplayObjectContainer instance. The parent property of the removed 
 * children is set to null.
 *
 * @param {boolean} [dispose=false] Whether the children's dispose process should be activated when they are removed.
 *
 * @returns {undefined}
 */
rune.display.DisplayObjectContainer.prototype.removeChildren = function(dispose) {
    while (this.m_children.length > 0) {
        this.removeChild(this.getChildAt(0), dispose);
    }
};

/**
 * Changes the position of an existing child in the display object container. 
 * This affects the layering of child objects.
 *
 * @param {rune.display.DisplayObject} child The child DisplayObject instance for which you want to change the index number.
 * @param {number} index The resulting index number for the child display object.
 *
 * @throws {RangeError} Throws if the index does not exist in the child list.
 * @throws {Error} Throws if the child parameter is not a child of this object.
 *
 * @returns {boolean}
 */
rune.display.DisplayObjectContainer.prototype.setChildIndex = function(child, index) {
    var sibling = this.getChildAt(index);
    if (this.getChildIndex(child) > -1) {
        return this.swapChildren(child, sibling);
    } else throw new Error();
};

/**
 * Sorts the instance's child objects based on the specified function. Any 
 * kind of sorting breaks the instance's pixel buffer cache.
 *
 * @param {Function} func Sort function
 *
 * @throws {TypeError} if the specified argument is not of type Function.
 *
 * @returns {undefined}
 */
rune.display.DisplayObjectContainer.prototype.sortChildren = function(func) {
    if (typeof func === "function") {
        this.m_children.sort(func);
        this.breakCache();
    } else throw new TypeError();
};

/**
 * Swaps the z-order (front-to-back order) of the two specified child objects. 
 * All other child objects in the display object container remain in the same 
 * index positions.
 *
 * @param {rune.display.DisplayObject} a The first child object.
 * @param {rune.display.DisplayObject} b The second child object.
 *
 * @returns {boolean}
 */
rune.display.DisplayObjectContainer.prototype.swapChildren = function(a, b) {
    var ai = this.getChildIndex(a);
    var bi = this.getChildIndex(b);

    if ((ai !== -1) && (bi !== -1)) {
        var tz = a.z;
        
        a.z = b.z;
        b.z = tz;
        
        this.m_children[ai] = b;
        this.m_children[bi] = a;
        
        this.breakCache();
        
        return true;
    }

    return false;
};

//------------------------------------------------------------------------------
// Override public prototype methods (ENGINE)
//------------------------------------------------------------------------------

/**
 * @inheritDoc
 */
rune.display.DisplayObjectContainer.prototype.update = function(step) {
    rune.display.Artboard.prototype.update.call(this, step);
    this.m_updateChildren(step);
};

/**
 * @inheritDoc
 */
rune.display.DisplayObjectContainer.prototype.render = function() {
    if (this.m_cached == false) {
        this.m_renderBackgroundColor();
        this.m_renderChildren();
        this.m_renderGraphics();
        this.m_renderStates();
        
        this.restoreCache();
    }
};

/**
 * @inheritDoc
 */
rune.display.DisplayObjectContainer.prototype.dispose = function() {
    this.m_disposeChildren();
    rune.display.Artboard.prototype.dispose.call(this);
};

//------------------------------------------------------------------------------
// Protected prototype methods (ENGINE)
//------------------------------------------------------------------------------

/**
 * Updates the internal logic for all child objects.
 *
 * @param {number} step Current time step.
 *
 * @returns {undefined}
 * @protected
 * @ignore
 */
rune.display.DisplayObjectContainer.prototype.m_updateChildren = function(step) {
    var c = this.m_children;
    var i = c.length;
    
    if (this.sort != null) {
        this.sortChildren(this.sort);
    }
    
    while (i--) {
        this.m_updateChild(c[i], step);
    }
};

/**
 * Updates the internal logic for a specific child object.
 *
 * @param {rune.display.DisplayObject} child The child object to update.
 * @param {number} step Current time step.
 *
 * @returns {undefined}
 * @ignore
 */
rune.display.DisplayObjectContainer.prototype.m_updateChild = function(child, step) {
    if (child != null) {
        if (child["active"] == true) {
            var s = step * this.timeScale;
            child.preUpdate(s);
            child.update(s);
            child.postUpdate(s);
        }
    }
};

/**
 * Draws all the child objects in the DisplayObjectContainer instance.
 *
 * @returns {undefined}
 * @protected
 * @ignore
 */
rune.display.DisplayObjectContainer.prototype.m_renderChildren = function() {
    var children = this.m_children;
    for (var i = 0, l = children.length; i < l; i++) {
        this.m_renderChild(children[i]);
    } 
};

/**
 * Draws a specific child object.
 *
 * @param {rune.display.DisplayObject} child The child object to draw.
 *
 * @returns {undefined}
 * @protected
 * @ignore
 */
rune.display.DisplayObjectContainer.prototype.m_renderChild = function(child) {
    if (child['visible'] == true) {
        this.m_canvas.renderDisplayObject(child);   
    }
    
    this.m_renderChildDebug(child);
};

/**
 * Draws a specific child object.
 *
 * @param {rune.display.DisplayObject} child The child object to draw.
 *
 * @returns {undefined}
 * @protected
 * @ignore
 */
rune.display.DisplayObjectContainer.prototype.m_renderChildDebug = function(child) {
    if (child['debug'] == true) {
        this.m_canvas.drawRect(
            child['x'],
            child['y'],
            child['width'],
            child['height'],
            child.debugColor,
            1
        );   
    }
    
    if (child['hitbox'].debug == true) {
        this.m_canvas.drawRect(
            child['hitbox']['x'],
            child['hitbox']['y'],
            child['hitbox']['width'],
            child['hitbox']['height'],
            child['hitbox'].debugColor,
            1
        );   
    }
};

/**
 * Removes all child objects.
 *
 * @returns {undefined}
 * @protected
 * @ignore
 */
rune.display.DisplayObjectContainer.prototype.m_disposeChildren = function() {
    this.removeChildren(true);
    this.m_children = null;
};