package flare.display {
import flash.display.DisplayObject;
import flash.display.Sprite;
import flash.display.Stage;
import flash.events.Event;
import flash.geom.Point;
import flash.geom.Rectangle;
/**
* A Sprite that redraws itself as needed when marked as "dirty".
* This allows multiple changes to be made to the sprite between frames
* without triggering a potentially costly redraw for each property update.
* Instead, the dirty()
method should be called whenever a
* change is made to the Sprite that would normally require a redraw. This
* class will ensure that the Sprite is redrawn only once before the next
* frame is rendered.
*
*
Subclasses should place drawing code within the render()
* method. For all properties used by the render
method to help
* draw this sprite, the corresponding "setter" method should call the
* dirty()
method to mark the sprite as dirty and thereby
* trigger a redraw for the next frame.
*
* Internally, the DirtySprite class maintains a static list of all
* "dirty" sprites, and redraws each sprite in the list when a
* Event.RENDER
event is issued. Typically, this process is
* performed automatically. In a few cases, erratic behavior has been
* observed due to a Flash Player bug that results in RENDER
* events not being properly issued. As a fallback, the static
* renderDirty()
method can be invoked to manually force
* each dirty sprite to be redrawn.
*/
public class DirtySprite extends Sprite
{
private static var __stage:Stage;
private static var __installed:Boolean = false;
private static var __dirtyList:Vector. = new Vector.();
/**
* Installs the frame render listener on the stage.
*/
private static function install(stage:Stage):void
{
__stage = stage;
__stage.addEventListener(Event.RENDER, renderDirty);
__installed = true;
}
/**
* Frame render callback that renders all sprites on the dirty list.
* Typically, this method is automatically triggered by stage
* RENDER events. It can also be manually invoked to redraw all
* dirty DirtySprites.
* @param evt the event that triggered the rendering (can be null)
*/
public static function renderDirty(evt:Event=null):void
{
while (__dirtyList.length > 0) {
var ds:DirtySprite = DirtySprite(__dirtyList.pop());
var db:Boolean = (ds._dirty == DIRTY);
ds._dirty = CLEAN;
if (db) ds.render();
}
// We need to remove and then re-add the listeners
// to work around Flash Player bugs (#139381?). Ugh.
// TODO: it seems this is not a complete solution, as in
// rare cases RENDER events are still omitted.
if (__stage != null) {
__stage.removeEventListener(Event.RENDER, renderDirty);
__installed = false;
}
}
// --------------------------------------------------------------------
private static const CLEAN:int = 0; // no changes
private static const DIRTY:int = 1; // re-rendering needed
private static const VISIT:int = 2; // was re-rendered, but on list
/** @private */
protected var _dirty:int = DIRTY; // dirty at birth
/**
* Creates a new DirtySprite. Registers this Sprite to receive
* added-to-stage events.
*/
public function DirtySprite() {
this.addEventListener(Event.ADDED_TO_STAGE, onAddToStage,
false, 0, true); // use weak reference
__dirtyList.push(this);
}
/**
* Makes sure that "dirtying" changes made to this Sprite
* while it is off the display list still result in a
* re-rendering if the Sprite is ever added to the list.
*/
private function onAddToStage(evt:Event):void
{
if (_dirty == DIRTY) {
if (!__installed) install(stage);
stage.invalidate();
}
}
/**
* Marks this sprite as "dirty" and in need of re-rendering.
* The next time that (a) a new frame is rendered, and
* (b) this Sprite is on the display list, the render method
* will automatically be called.
*/
public final function dirty():void
{
if (_dirty == DIRTY) return;
__dirtyList.push(this);
if (stage) {
if (!__installed) install(stage);
stage.invalidate();
}
_dirty = DIRTY;
}
/**
* If dirty, this sprite is re-rendered before returning the width.
*/
public override function get width():Number
{
if (_dirty == DIRTY) { _dirty = VISIT; render(); }
return super.width;
}
/**
* If dirty, this sprite is re-rendered before returning the height.
*/
public override function get height():Number
{
if (_dirty == DIRTY) { _dirty = VISIT; render(); }
return super.height;
}
/**
* If dirty, this sprite is re-rendered before returning the rect.
*/
public override function getRect(targetCoordinateSpace:DisplayObject):Rectangle
{
if (_dirty == DIRTY) { _dirty = VISIT; render(); }
return super.getRect(targetCoordinateSpace);
}
/**
* If dirty, this sprite is re-rendered returning the bounds.
*/
public override function getBounds(targetCoordinateSpace:DisplayObject):Rectangle
{
if (_dirty == DIRTY) { _dirty = VISIT; render(); }
return super.getBounds(targetCoordinateSpace);
}
/**
* If dirty, either sprite is re-rendered before hit-testing.
*/
public override function hitTestObject(obj:DisplayObject):Boolean
{
if (_dirty == DIRTY) { _dirty = VISIT; render(); }
var ds:DirtySprite = obj as DirtySprite;
if (ds && ds._dirty == DIRTY) { ds._dirty = VISIT; ds.render(); }
return super.hitTestObject(obj);
}
/**
* If dirty, this sprite is re-rendered before hit-testing.
*/
public override function hitTestPoint(x:Number, y:Number, shapeFlag:Boolean=false):Boolean
{
if (_dirty == DIRTY) { _dirty = VISIT; render(); }
return super.hitTestPoint(x, y, shapeFlag);
}
/**
* Draw this sprite's graphical content. Subclasses should
* override this method with custom drawing code.
*/
public function render():void
{
// for sub-classes to override...
}
/** @inheritDoc */
public override function toString():String
{
var s:String = super.toString();
return name==null ? s : s + " \""+name+"\"";
}
// -- Polar Coordinates -----------------------------------------------
/** A constant for the point (0,0). */
public static const ZERO:Point = new Point(0,0);
/** The radius value of this sprite's position in polar co-ordinates.
* Polar coordinate values are assume a circle center given by the
* origin
property. */
protected var _radius:Number;
/** The angle value of this sprite's position in polar co-ordinates.
* Polar coordinate values are assume a circle center given by the
* origin
property. */
protected var _angle:Number;
/** The origin point for polar coordinates. */
protected var _origin:Point = ZERO;
/** @inheritDoc */
public override function set x(v:Number):void {
super.x = v; _radius = NaN; _angle = NaN;
}
/** @inheritDoc */
public override function set y(v:Number):void {
super.y = v; _radius = NaN; _angle = NaN;
}
/** The radius value of this sprite's position in polar co-ordinates.
* Polar coordinate values are assume a circle center given by the
* origin
property. */
public function get radius():Number {
if (isNaN(_radius)) {
var cx:Number = x - _origin.x;
var cy:Number = y - _origin.y;
_radius = Math.sqrt(cx*cx + cy*cy);
}
return _radius;
}
public function set radius(r:Number):void {
var a:Number = angle;
super.x = r * Math.cos(a) + _origin.x;
super.y = -r * Math.sin(a) + _origin.y;
_radius = r;
}
/** The angle value of this sprite's position in polar co-ordinates.
* Polar coordinate values are assume a circle center given by the
* origin
property. */
public function get angle():Number {
if (isNaN(_angle)) {
_angle = Math.atan2(-(y-_origin.y),(x-_origin.x));
}
return _angle;
}
public function set angle(a:Number):void {
var r:Number = radius;
super.x = r * Math.cos(a) + _origin.x;
super.y = -r * Math.sin(a) + _origin.y;
_angle = a;
}
/** The origin point for polar coordinates. */
public function get origin():Point { return _origin; }
public function set origin(p:Point):void {
if (p.x != _origin.x || p.y != _origin.y) {
_radius = NaN; _angle = NaN;
}
_origin = p;
}
} // end of class DirtySprite
}