365 lines
15 KiB
ActionScript
365 lines
15 KiB
ActionScript
// =================================================================================================
|
|
//
|
|
// Starling Framework
|
|
// Copyright 2011 Gamua OG. All Rights Reserved.
|
|
//
|
|
// This program is free software. You can redistribute and/or modify it
|
|
// in accordance with the terms of the accompanying license agreement.
|
|
//
|
|
// =================================================================================================
|
|
|
|
package starling.display
|
|
{
|
|
import flash.geom.Matrix;
|
|
import flash.geom.Point;
|
|
import flash.geom.Rectangle;
|
|
import flash.utils.getQualifiedClassName;
|
|
|
|
import starling.core.RenderSupport;
|
|
import starling.errors.AbstractClassError;
|
|
import starling.events.Event;
|
|
import starling.utils.transformCoords;
|
|
|
|
/**
|
|
* A DisplayObjectContainer represents a collection of display objects.
|
|
* It is the base class of all display objects that act as a container for other objects. By
|
|
* maintaining an ordered list of children, it defines the back-to-front positioning of the
|
|
* children within the display tree.
|
|
*
|
|
* <p>A container does not a have size in itself. The width and height properties represent the
|
|
* extents of its children. Changing those properties will scale all children accordingly.</p>
|
|
*
|
|
* <p>As this is an abstract class, you can't instantiate it directly, but have to
|
|
* use a subclass instead. The most lightweight container class is "Sprite".</p>
|
|
*
|
|
* <strong>Adding and removing children</strong>
|
|
*
|
|
* <p>The class defines methods that allow you to add or remove children. When you add a child,
|
|
* it will be added at the frontmost position, possibly occluding a child that was added
|
|
* before. You can access the children via an index. The first child will have index 0, the
|
|
* second child index 1, etc.</p>
|
|
*
|
|
* Adding and removing objects from a container triggers non-bubbling events.
|
|
*
|
|
* <ul>
|
|
* <li><code>Event.ADDED</code>: the object was added to a parent.</li>
|
|
* <li><code>Event.ADDED_TO_STAGE</code>: the object was added to a parent that is
|
|
* connected to the stage, thus becoming visible now.</li>
|
|
* <li><code>Event.REMOVED</code>: the object was removed from a parent.</li>
|
|
* <li><code>Event.REMOVED_FROM_STAGE</code>: the object was removed from a parent that
|
|
* is connected to the stage, thus becoming invisible now.</li>
|
|
* </ul>
|
|
*
|
|
* Especially the <code>ADDED_TO_STAGE</code> event is very helpful, as it allows you to
|
|
* automatically execute some logic (e.g. start an animation) when an object is rendered the
|
|
* first time.
|
|
*
|
|
* @see Sprite
|
|
* @see DisplayObject
|
|
*/
|
|
public class DisplayObjectContainer extends DisplayObject
|
|
{
|
|
// members
|
|
|
|
private var mChildren:Vector.<DisplayObject>;
|
|
|
|
/** Helper objects. */
|
|
private static var sHelperMatrix:Matrix = new Matrix();
|
|
private static var sHelperPoint:Point = new Point();
|
|
|
|
// construction
|
|
|
|
/** @private */
|
|
public function DisplayObjectContainer()
|
|
{
|
|
if (getQualifiedClassName(this) == "starling.display::DisplayObjectContainer")
|
|
throw new AbstractClassError();
|
|
|
|
mChildren = new <DisplayObject>[];
|
|
}
|
|
|
|
/** Disposes the resources of all children. */
|
|
public override function dispose():void
|
|
{
|
|
var numChildren:int = mChildren.length;
|
|
|
|
for (var i:int=0; i<numChildren; ++i)
|
|
mChildren[i].dispose();
|
|
|
|
super.dispose();
|
|
}
|
|
|
|
// child management
|
|
|
|
/** Adds a child to the container. It will be at the frontmost position. */
|
|
public function addChild(child:DisplayObject):void
|
|
{
|
|
addChildAt(child, numChildren);
|
|
}
|
|
|
|
/** Adds a child to the container at a certain index. */
|
|
public function addChildAt(child:DisplayObject, index:int):void
|
|
{
|
|
if (index >= 0 && index <= numChildren)
|
|
{
|
|
child.removeFromParent();
|
|
mChildren.splice(index, 0, child);
|
|
child.setParent(this);
|
|
child.dispatchEvent(new Event(Event.ADDED, true));
|
|
if (stage) child.dispatchEventOnChildren(new Event(Event.ADDED_TO_STAGE));
|
|
}
|
|
else
|
|
{
|
|
throw new RangeError("Invalid child index");
|
|
}
|
|
}
|
|
|
|
/** Removes a child from the container. If the object is not a child, nothing happens.
|
|
* If requested, the child will be disposed right away. */
|
|
public function removeChild(child:DisplayObject, dispose:Boolean=false):void
|
|
{
|
|
var childIndex:int = getChildIndex(child);
|
|
if (childIndex != -1) removeChildAt(childIndex, dispose);
|
|
}
|
|
|
|
/** Removes a child at a certain index. Children above the child will move down. If
|
|
* requested, the child will be disposed right away. */
|
|
public function removeChildAt(index:int, dispose:Boolean=false):void
|
|
{
|
|
if (index >= 0 && index < numChildren)
|
|
{
|
|
var child:DisplayObject = mChildren[index];
|
|
child.dispatchEvent(new Event(Event.REMOVED, true));
|
|
if (stage) child.dispatchEventOnChildren(new Event(Event.REMOVED_FROM_STAGE));
|
|
child.setParent(null);
|
|
mChildren.splice(index, 1);
|
|
if (dispose) child.dispose();
|
|
}
|
|
else
|
|
{
|
|
throw new RangeError("Invalid child index");
|
|
}
|
|
}
|
|
|
|
/** Removes a range of children from the container (endIndex included).
|
|
* If no arguments are given, all children will be removed. */
|
|
public function removeChildren(beginIndex:int=0, endIndex:int=-1, dispose:Boolean=false):void
|
|
{
|
|
if (endIndex < 0 || endIndex >= numChildren)
|
|
endIndex = numChildren - 1;
|
|
|
|
for (var i:int=beginIndex; i<=endIndex; ++i)
|
|
removeChildAt(beginIndex, dispose);
|
|
}
|
|
|
|
/** Returns a child object at a certain index. */
|
|
public function getChildAt(index:int):DisplayObject
|
|
{
|
|
if (index >= 0 && index < numChildren)
|
|
return mChildren[index];
|
|
else
|
|
throw new RangeError("Invalid child index");
|
|
}
|
|
|
|
/** Returns a child object with a certain name (non-recursively). */
|
|
public function getChildByName(name:String):DisplayObject
|
|
{
|
|
var numChildren:int = mChildren.length;
|
|
for (var i:int=0; i<numChildren; ++i)
|
|
if (mChildren[i].name == name) return mChildren[i];
|
|
|
|
return null;
|
|
}
|
|
|
|
/** Returns the index of a child within the container, or "-1" if it is not found. */
|
|
public function getChildIndex(child:DisplayObject):int
|
|
{
|
|
return mChildren.indexOf(child);
|
|
}
|
|
|
|
/** Moves a child to a certain index. Children at and after the replaced position move up.*/
|
|
public function setChildIndex(child:DisplayObject, index:int):void
|
|
{
|
|
var oldIndex:int = getChildIndex(child);
|
|
if (oldIndex == -1) throw new ArgumentError("Not a child of this container");
|
|
mChildren.splice(oldIndex, 1);
|
|
mChildren.splice(index, 0, child);
|
|
}
|
|
|
|
/** Swaps the indexes of two children. */
|
|
public function swapChildren(child1:DisplayObject, child2:DisplayObject):void
|
|
{
|
|
var index1:int = getChildIndex(child1);
|
|
var index2:int = getChildIndex(child2);
|
|
if (index1 == -1 || index2 == -1) throw new ArgumentError("Not a child of this container");
|
|
swapChildrenAt(index1, index2);
|
|
}
|
|
|
|
/** Swaps the indexes of two children. */
|
|
public function swapChildrenAt(index1:int, index2:int):void
|
|
{
|
|
var child1:DisplayObject = getChildAt(index1);
|
|
var child2:DisplayObject = getChildAt(index2);
|
|
mChildren[index1] = child2;
|
|
mChildren[index2] = child1;
|
|
}
|
|
|
|
/** Sorts the children according to a given function (that works just like the sort function
|
|
* of the Vector class). */
|
|
public function sortChildren(compareFunction:Function):void
|
|
{
|
|
mChildren = mChildren.sort(compareFunction);
|
|
}
|
|
|
|
/** Determines if a certain object is a child of the container (recursively). */
|
|
public function contains(child:DisplayObject):Boolean
|
|
{
|
|
if (child == this) return true;
|
|
|
|
var numChildren:int = mChildren.length;
|
|
for (var i:int=0; i<numChildren; ++i)
|
|
{
|
|
var currentChild:DisplayObject = mChildren[i];
|
|
var currentChildContainer:DisplayObjectContainer = currentChild as DisplayObjectContainer;
|
|
|
|
if (currentChildContainer && currentChildContainer.contains(child)) return true;
|
|
else if (currentChild == child) return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/** @inheritDoc */
|
|
public override function getBounds(targetSpace:DisplayObject, resultRect:Rectangle=null):Rectangle
|
|
{
|
|
if (resultRect == null) resultRect = new Rectangle();
|
|
|
|
var numChildren:int = mChildren.length;
|
|
|
|
if (numChildren == 0)
|
|
{
|
|
getTransformationMatrix(targetSpace, sHelperMatrix);
|
|
transformCoords(sHelperMatrix, 0.0, 0.0, sHelperPoint);
|
|
|
|
resultRect.x = sHelperPoint.x;
|
|
resultRect.y = sHelperPoint.y;
|
|
resultRect.width = resultRect.height = 0;
|
|
|
|
return resultRect;
|
|
}
|
|
else if (numChildren == 1)
|
|
{
|
|
return mChildren[0].getBounds(targetSpace, resultRect);
|
|
}
|
|
else
|
|
{
|
|
var minX:Number = Number.MAX_VALUE, maxX:Number = -Number.MAX_VALUE;
|
|
var minY:Number = Number.MAX_VALUE, maxY:Number = -Number.MAX_VALUE;
|
|
|
|
for (var i:int=0; i<numChildren; ++i)
|
|
{
|
|
mChildren[i].getBounds(targetSpace, resultRect);
|
|
minX = minX < resultRect.x ? minX : resultRect.x;
|
|
maxX = maxX > resultRect.right ? maxX : resultRect.right;
|
|
minY = minY < resultRect.y ? minY : resultRect.y;
|
|
maxY = maxY > resultRect.bottom ? maxY : resultRect.bottom;
|
|
}
|
|
|
|
resultRect.x = minX;
|
|
resultRect.y = minY;
|
|
resultRect.width = maxX - minX;
|
|
resultRect.height = maxY - minY;
|
|
|
|
return resultRect;
|
|
}
|
|
}
|
|
|
|
/** @inheritDoc */
|
|
public override function hitTest(localPoint:Point, forTouch:Boolean=false):DisplayObject
|
|
{
|
|
if (forTouch && (!visible || !touchable))
|
|
return null;
|
|
|
|
var localX:Number = localPoint.x;
|
|
var localY:Number = localPoint.y;
|
|
|
|
var numChildren:int = mChildren.length;
|
|
for (var i:int=numChildren-1; i>=0; --i) // front to back!
|
|
{
|
|
var child:DisplayObject = mChildren[i];
|
|
getTransformationMatrix(child, sHelperMatrix);
|
|
|
|
transformCoords(sHelperMatrix, localX, localY, sHelperPoint);
|
|
var target:DisplayObject = child.hitTest(sHelperPoint, forTouch);
|
|
|
|
if (target) return target;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/** @inheritDoc */
|
|
public override function render(support:RenderSupport, alpha:Number):void
|
|
{
|
|
alpha *= this.alpha;
|
|
var numChildren:int = mChildren.length;
|
|
|
|
for (var i:int=0; i<numChildren; ++i)
|
|
{
|
|
var child:DisplayObject = mChildren[i];
|
|
if (child.alpha != 0.0 && child.visible && child.scaleX != 0.0 && child.scaleY != 0.0)
|
|
{
|
|
support.pushMatrix();
|
|
support.transformMatrix(child);
|
|
child.render(support, alpha);
|
|
support.popMatrix();
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Dispatches an event on all children (recursively). The event must not bubble. */
|
|
public function broadcastEvent(event:Event):void
|
|
{
|
|
if (event.bubbles)
|
|
throw new ArgumentError("Broadcast of bubbling events is prohibited");
|
|
|
|
dispatchEventOnChildren(event);
|
|
}
|
|
|
|
/** @private */
|
|
internal override function dispatchEventOnChildren(event:Event):void
|
|
{
|
|
// the event listeners might modify the display tree, which could make the loop crash.
|
|
// thus, we collect them in a list and iterate over that list instead.
|
|
|
|
var listeners:Vector.<DisplayObject> = new <DisplayObject>[];
|
|
getChildEventListeners(this, event.type, listeners);
|
|
var numListeners:int = listeners.length;
|
|
|
|
for (var i:int=0; i<numListeners; ++i)
|
|
listeners[i].dispatchEvent(event);
|
|
}
|
|
|
|
private function getChildEventListeners(object:DisplayObject, eventType:String,
|
|
listeners:Vector.<DisplayObject>):void
|
|
{
|
|
var container:DisplayObjectContainer = object as DisplayObjectContainer;
|
|
|
|
if (object.hasEventListener(eventType))
|
|
listeners.push(object);
|
|
|
|
if (container)
|
|
{
|
|
var children:Vector.<DisplayObject> = container.mChildren;
|
|
var numChildren:int = children.length;
|
|
|
|
for (var i:int=0; i<numChildren; ++i)
|
|
getChildEventListeners(children[i], eventType, listeners);
|
|
}
|
|
}
|
|
|
|
/** The number of children of this container. */
|
|
public function get numChildren():int { return mChildren.length; }
|
|
}
|
|
}
|