Thursday, December 18, 2008

ActionScript 3 - A Smart Way To Save Display List Objects


So here's a pretty common problem you'll run across that has a not too obvious solution. I'm building an application that draws vector graphics. So I have written 3 classes that draws either a rectangle, line, or curve


import com.blogspot.hairydoodle.draw.Rect;
import com.blogspot.hairydoodle.draw.Line;
import com.blogspot.hairydoodle.draw.Curve;

So I've drawn some shapes using these classes. All of the drawn shapes are children of one display object, which is the digital equivalent of a canvas.


So I've drawn my masterpiece with the help of these classes. How do I save it for later on?

It should be easy right? Just loop through the canvas object's children and save each one.


for ( var i:uint=0; i < canvas.numChildren; i++ ) {
save( canvas.getChildAt( i ) );
}


But here's the tricky part. canvas.getChildAt( i ) returns objects as only DisplayObjects. It doesn't know what class drew the shape.

All of these shapes have different parameters we need to save. Curves are defined by more than two sets of coordinates. Lines don't have a fill color like rectangles do. So I really need to know what class an object belongs to so I can save it appropriately. What's the easiest way to do this?

At first I tried writing a class that associated a display list index to a class name but I had this feeling that there was an easier way. And I found out there was. There's a class you already have called ObjectUtils that can do most of the work for you, but it requires just a little hack to make it work for you. Take a look at the code snippet below.


import mx.utils.ObjectUtil;

for ( var i:uint=0; i < canvas.numChildren; i++ ) {
var type:String = ObjectUtil.getClassInfo( canvas.getChildAt(i) ).name;
type = type.slice( type.lastIndexOf( "::" ) );
switch ( type ) {
case "::Rect":
var r:Rect = Rect( canvas.getChildAt( i ) );
saveRect( r );
break;
case "::Line":
var l:Line = Line( canvas.getChildAt( i ) );
saveLine( l );
break;
case "::Curve":
var c:Curve = Curve( canvas.getChildAt( i ) );
saveCurve( c );
break;
case default:
throwError( "Unknown class : " + type );
break;
}
}


So this is the magic line.


var type:String = ObjectUtil.getClassInfo( canvas.getChildAt(i) ).name;


It returns the class an object belongs to with full package path ( aka
com::blogspot::hairydoodle::draw::Rect ).

Usually you don't need to know the whole package name.::Rect is just as good as com::blogspot::hairydoodle::draw::Rect to uniquely identify the class. So this line clips everything in the package path except the name of the class.


type = type.slice( type.lastIndexOf( "::" ) );


The final bit is easy. I have a switch case statement to handle the three class types and a default case to handle exceptions. See easy as pie!


switch ( type ) {
case "::Rect":
var r:Rect = Rect( canvas.getChildAt( i ) );
saveRect( r );
break;
case "::Line":
var l:Line = Line( canvas.getChildAt( i ) );
saveLine( l );
break;
case "::Curve":
var c:Curve = Curve( canvas.getChildAt( i ) );
saveCurve( c );
break;
case default:
throwError( "Unknown class : " + type );
break;
}


Enjoy!