@awayjs/scene
Version:
AwayJS scene classes
549 lines (481 loc) • 21.2 kB
text/typescript
import { Point, ArgumentError, RangeError } from '@awayjs/core';
import { IAnimator, IRenderContainer } from '@awayjs/renderer';
import { ContainerNode } from '@awayjs/view';
import { DisplayObject } from './DisplayObject';
/**
* 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 Flash 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.
*
* <p>DisplayObject is an abstract base class; therefore, you cannot call
* DisplayObject directly. Invoking <code>new DisplayObject()</code> throws an
* <code>ArgumentError</code> exception.</p>
* The DisplayObjectContainer class is an abstract base class for all objects
* that can contain child objects. It cannot be instantiated directly; calling
* the <code>new DisplayObjectContainer()</code> constructor throws an
* <code>ArgumentError</code> exception.
*
* <p>For more information, see the "Display Programming" chapter of the
* <i>ActionScript 3.0 Developer's Guide</i>.</p>
*/
export class DisplayObjectContainer extends DisplayObject implements IRenderContainer {
public static assetType: string = '[asset DisplayObjectContainer]';
private static NO_CHILD_ERROR = new ArgumentError('Child parameter is not a child of the caller');
private _animator: IAnimator;
protected _children: DisplayObject[] = [];
public doingSwap: boolean = false;
/**
*
*/
public get assetType(): string {
return DisplayObjectContainer.assetType;
}
/**
* Defines the animator of the display object. Default value is <code>null</code>.
*/
public get animator(): IAnimator {
return this._animator;
}
public set animator(value: IAnimator) {
if (this._animator)
this._animator.removeOwner(this);
this._animator = value;
if (this._animator)
this._animator.addOwner(this);
}
/**
* Returns the number of children of this object.
*/
public get numChildren(): number {
return this._children.length;
}
/**
* Determines whether the children of the object are tab enabled. Enables or
* disables tabbing for the children of the object. The default is
* <code>true</code>.
*
* <p><b>Note:</b> Do not use the <code>tabChildren</code> property with
* Flex. Instead, use the
* <code>mx.core.UIComponent.hasFocusableChildren</code> property.</p>
*
* @throws IllegalOperationError Calling this property of the Stage object
* throws an exception. The Stage object does
* not implement this property.
*/
public tabChildren: boolean;
/**
* Calling the <code>new DisplayObjectContainer()</code> constructor throws
* an <code>ArgumentError</code> exception. You <i>can</i>, however, call
* constructors for the following subclasses of DisplayObjectContainer:
* <ul>
* <li><code>new Loader()</code></li>
* <li><code>new Sprite()</code></li>
* <li><code>new MovieClip()</code></li>
* </ul>
*/
constructor() {
super();
this.tabChildren = false;
}
public advanceFrame(): void {
for (let i: number = 0; i < this._children.length; i++)
this._children[i].advanceFrame();
}
/**
* 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 <code>addChildAt()</code> method.)
*
* <p>If you add a child object that already has a different display object
* container as a parent, the object is removed from the child list of the
* other display object container. </p>
*
* <p><b>Note:</b> The command <code>stage.addChild()</code> can cause
* problems with a published SWF file, including security problems and
* conflicts with other loaded SWF files. There is only one Stage within a
* Flash runtime instance, no matter how many SWF files you load into the
* runtime. So, generally, objects should not be added to the Stage,
* directly, at all. The only object the Stage should contain is the root
* object. Create a DisplayObjectContainer to contain all of the items on the
* display list. Then, if necessary, add that DisplayObjectContainer instance
* to the Stage.</p>
*
* @param child The DisplayObject instance to add as a child of this
* DisplayObjectContainer instance.
* @return The DisplayObject instance that you pass in the <code>child</code>
* parameter.
* @throws ArgumentError Throws if the child is the same as the parent. Also
* throws if the caller is a child(or grandchild etc.)
* of the child being added.
* @event added Dispatched when a display object is added to the display
* list.
*/
public addChild(child: DisplayObject): DisplayObject {
return this.addChildAt(child, this._children.length);
}
/**
* 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.
*
* <p>For example, the following example shows three display objects, labeled
* a, b, and c, at index positions 0, 2, and 1, respectively:</p>
*
* <p>If you add a child object that already has a different display object
* container as a parent, the object is removed from the child list of the
* other display object container. </p>
*
* @param child The DisplayObject instance to add as a child of this
* DisplayObjectContainer instance.
* @param 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.
* @return The DisplayObject instance that you pass in the <code>child</code>
* parameter.
* @throws ArgumentError Throws if the child is the same as the parent. Also
* throws if the caller is a child(or grandchild etc.)
* of the child being added.
* @throws RangeError Throws if the index position does not exist in the
* child list.
* @event added Dispatched when a display object is added to the display
* list.
*/
public addChildAt(child: DisplayObject, index: number): DisplayObject {
if (child.parent)
child.parent.removeChild(child);
this._children.splice(index, 0, child);
child._setParent(this);
for (const key in this._abstractionPool)
if ((<ContainerNode> this._abstractionPool[key]).addChildAt)
(<ContainerNode> this._abstractionPool[key]).addChildAt(child, index);
return child;
}
public addChildren(...childarray: Array<DisplayObject>): void {
const len: number = childarray.length;
for (let i: number = 0; i < len; i++)
this.addChild(childarray[i]);
}
/**
*
*/
public clone(): DisplayObjectContainer {
const newInstance: DisplayObjectContainer = new DisplayObjectContainer();
this.copyTo(newInstance);
return newInstance;
}
public copyTo(newInstance: DisplayObjectContainer): void {
super.copyTo(newInstance);
if (this._animator)
newInstance.animator = this._animator.clone();
const len: number = this._children.length;
for (let i: number = 0; i < len; ++i) {
const newChild = (<any> this._children[i].adapter).clone().adaptee;
newInstance.addChild(newChild);
}
}
/**
* Determines whether the specified display object is a child of the
* DisplayObjectContainer instance or the instance itself. The search
* includes the entire display list including this DisplayObjectContainer
* instance. Grandchildren, great-grandchildren, and so on each return
* <code>true</code>.
*
* @param child The child object to test.
* @return <code>true</code> if the <code>child</code> object is a child of
* the DisplayObjectContainer or the container itself; otherwise
* <code>false</code>.
*/
public contains(child: DisplayObject): boolean {
return this._children.indexOf(child) >= 0;
}
/**
*
*/
public disposeValues(): void {
if (this._animator)
this._animator.dispose();
for (let i: number = this._children.length - 1; i >= 0; i--)
this.removeChild(this._children[i]);
super.disposeValues();
}
/**
* Returns the child display object instance that exists at the specified
* index.
*
* @param index The index position of the child object.
* @return The child display object at the specified index position.
* @throws RangeError Throws if the index does not exist in the child
* list.
*/
public getChildAt(index: number): DisplayObject {
const child: DisplayObject = this._children[index];
if (child == null)
throw new RangeError('Index does not exist in the child list of the caller');
return child;
}
/**
* Returns the child display object that exists with the specified name.
* If more that one child display object has the specified name,
* the method returns the first object in the child list. *
* <p>The <code>getChildAt()</code> method is faster than the
* <code>getChildByName()</code> method. The <code>getChildAt()</code> method
* accesses a child from a cached array, whereas the
* <code>getChildByName()</code> method has to traverse a linked list to
* access a child.</p>
*
* @param name The name of the child to return.
* @return The child display object with the specified name.
*/
public getChildByName(name: string): DisplayObject {
const len: number = this._children.length;
for (let i: number = 0; i < len; ++i)
if (this._children[i].name == name)
return this._children[i];
return null;
}
/**
* Returns the index position of a <code>child</code> DisplayObject instance.
*
* @param child The DisplayObject instance to identify.
* @return The index position of the child display object to identify.
* @throws ArgumentError Throws if the child parameter is not a child of this object.
*/
public getChildIndex(child: DisplayObject): number {
const childIndex: number = this._children.indexOf(child);
if (childIndex == -1) {
throw DisplayObjectContainer.NO_CHILD_ERROR;
}
return childIndex;
}
/**
* Returns an array of objects that lie under the specified point and are
* children(or grandchildren, and so on) of this DisplayObjectContainer
* instance. Any child objects that are inaccessible for security reasons are
* omitted from the returned array. To determine whether this security
* restriction affects the returned array, call the
* <code>areInaccessibleObjectsUnderPoint()</code> method.
*
* <p>The <code>point</code> parameter is in the coordinate space of the
* Stage, which may differ from the coordinate space of the display object
* container(unless the display object container is the Stage). You can use
* the <code>globalToLocal()</code> and the <code>localToGlobal()</code>
* methods to convert points between these coordinate spaces.</p>
*
* @param point The point under which to look.
* @return An array of objects that lie under the specified point and are
* children (or grandchildren, and so on) of this
* DisplayObjectContainer instance.
*/
public getObjectsUnderPoint(point: Point): Array<DisplayObject> {
return new Array<DisplayObject>();
}
/**
* Removes the specified <code>child</code> DisplayObject instance from the
* child list of the DisplayObjectContainer instance. The <code>parent</code>
* property of the removed child is set to <code>null</code> , 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.
*
* <p>The garbage collector reallocates unused memory space. When a variable
* or object is no longer actively referenced or stored somewhere, the
* garbage collector sweeps through and wipes out the memory space it used to
* occupy if no other references to it exist.</p>
*
* @param child The DisplayObject instance to remove.
* @return The DisplayObject instance that you pass in the <code>child</code>
* parameter.
* @throws ArgumentError Throws if the child parameter is not a child of this
* object.
*/
public removeChild(child: DisplayObject): DisplayObject {
if (child == null)
throw new ArgumentError('Parameter child cannot be null');
this.removeChildAt(this.getChildIndex(child));
return child;
}
/**
* Removes a child DisplayObject from the specified <code>index</code>
* position in the child list of the DisplayObjectContainer. The
* <code>parent</code> property of the removed child is set to
* <code>null</code>, 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.
*
* <p>The garbage collector reallocates unused memory space. When a variable
* or object is no longer actively referenced or stored somewhere, the
* garbage collector sweeps through and wipes out the memory space it used to
* occupy if no other references to it exist.</p>
*
* @param index The child index of the DisplayObject to remove.
* @return The DisplayObject instance that was removed.
* @throws RangeError Throws if the index does not exist in the child
* list.
* @throws SecurityError This child display object belongs to a sandbox to
* which the calling object does not have access. You
* can avoid this situation by having the child movie
* call the <code>Security.allowDomain()</code> method.
*/
public removeChildAt(index: number): DisplayObject {
return this.removeChildAtInternal(index);
}
/**
* Removes all <code>child</code> DisplayObject instances from the child list
* of the DisplayObjectContainer instance. The <code>parent</code> property
* of the removed children is set to <code>null</code>, and the objects are
* garbage collected if no other references to the children exist.
*
* The garbage collector reallocates unused memory space. When a variable or
* object is no longer actively referenced or stored somewhere, the garbage
* collector sweeps through and wipes out the memory space it used to occupy
* if no other references to it exist.
*
* @param beginIndex The beginning position. A value smaller than 0 throws a RangeError.
* @param endIndex The ending position. A value smaller than 0 throws a RangeError.
* @throws RangeError Throws if the beginIndex or endIndex positions do
* not exist in the child list.
*/
public removeChildren(beginIndex: number = 0, endIndex: number = 2147483647): void {
if (beginIndex < 0)
throw new RangeError('beginIndex is out of range of the child list');
if (endIndex > this._children.length)
throw new RangeError('endIndex is out of range of the child list');
for (let i: number = endIndex - 1;i >= beginIndex; i--)
this.removeChildAtInternal(i);
}
/**
* Changes the position of an existing child in the display object container.
* This affects the layering of child objects. For example, the following
* example shows three display objects, labeled a, b, and c, at index
* positions 0, 1, and 2, respectively:
*
* <p>When you use the <code>setChildIndex()</code> method and specify an
* index position that is already occupied, the only positions that change
* are those in between the display object's former and new position. All
* others will stay the same. If a child is moved to an index LOWER than its
* current index, all children in between will INCREASE by 1 for their index
* reference. If a child is moved to an index HIGHER than its current index,
* all children in between will DECREASE by 1 for their index reference. For
* example, if the display object container in the previous example is named
* <code>container</code>, you can swap the position of the display objects
* labeled a and b by calling the following code:</p>
*
* <p>This code results in the following arrangement of objects:</p>
*
* @param child The child DisplayObject instance for which you want to change
* the index number.
* @param index The resulting index number for the <code>child</code> display
* object.
* @throws ArgumentError Throws if the child parameter is not a child of this
* object.
* @throws RangeError Throws if the index does not exist in the child
* list.
*/
public setChildIndex(child: DisplayObject, index: number): void {
const original_idx = this.getChildIndex(child);
if (original_idx < 0)
throw new ArgumentError('Parameter child must be child of this object');
if (index > this._children.length - 1)
throw new RangeError('Parameter index is out of range of the child list');
if (original_idx == index)
return;
this.removeChildAt(original_idx);
this.addChildAt(child, index);
if (child._sessionID >= 0 && (<any> this)._sessionID_childs) {
delete (<any> this)._sessionID_childs[child._sessionID];
child._sessionID = -1;
}
}
/**
* 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 child1 The first child object.
* @param child2 The second child object.
* @throws ArgumentError Throws if either child parameter is not a child of
* this object.
*/
public swapChildren(child1: DisplayObject, child2: DisplayObject): void {
if (child1.parent != this || child2.parent != this)
return;
this.swapChildrenAt(this.getChildIndex(child1), this.getChildIndex(child2));
}
/**
* Swaps the z-order(front-to-back order) of the child objects at the two
* specified index positions in the child list. All other child objects in
* the display object container remain in the same index positions.
*
* @param index1 The index position of the first child object.
* @param index2 The index position of the second child object.
* @throws RangeError If either index does not exist in the child list.
*/
public swapChildrenAt(index1: number, index2: number): void {
if (index1 == index2)
return;
if (index1 >= this._children.length || index2 >= this._children.length)
throw ('[scene/DisplayobjectContainer] - swapChildrenAt - Range Error');
let child1: DisplayObject;
let child2: DisplayObject;
if (index1 < index2) {
child2 = this.removeChildAt(index2);
child1 = this.removeChildAt(index1);
this.addChildAt(child2, index1);
this.addChildAt(child1, index2);
} else {
child1 = this.removeChildAt(index1);
child2 = this.removeChildAt(index2);
this.addChildAt(child1, index2);
this.addChildAt(child2, index1);
}
// dirty code to check if this is a movieclip, and if so handle the sessionID_childs:
if ((<any> this)._sessionID_childs) {
if (this._children[index1]._sessionID >= 0) {
delete (<any> this)._sessionID_childs[this._children[index1]._sessionID];
this._children[index1]._sessionID == -1;
this._children[index1]._avmDepthID == -1;
}
if (this._children[index2]._sessionID >= 0) {
delete (<any> this)._sessionID_childs[this._children[index2]._sessionID];
this._children[index2]._sessionID == -1;
this._children[index1]._avmDepthID == -1;
}
}
}
public _initNode(node: ContainerNode): void {
for (let i: number = 0; i < this._children.length; ++i)
node.addChildAt(this._children[i], i);
}
/**
* @private
*
* @param child
*/
protected removeChildAtInternal(index: number): DisplayObject {
const child: DisplayObject = this._children.splice(index, 1)[0];
if (child.adapter && (<any>child.adapter).dispatchStaticEvent) {
(<any>child.adapter).dispatchStaticEvent('removed', child.adapter);
}
if (this.isOnDisplayList() && (<any>child.adapter).dispatch_REMOVED_FROM_STAGE) {
(<any>child.adapter).dispatch_REMOVED_FROM_STAGE(<DisplayObjectContainer>child);
}
child._setParent(null);
for (const key in this._abstractionPool)
if ((<ContainerNode> this._abstractionPool[key]).removeChildAt)
(<ContainerNode> this._abstractionPool[key]).removeChildAt(index);
return child;
}
protected _updateMaskMode(): void {
if (this.maskMode)
this.mouseChildren = false;
super._updateMaskMode();
}
}