A few weeks ago Keith Peters posted a set of lightweight AS3 Components up on GoogleCode. They are really simple to use so I decided to take them apart to see how they are built. If you've got a minute pull down a copy so you can play along.
First unzip the contents to a folder. You'll find you have a set of Actionscript 3 classes in the com/bit101/components directory and a single font file in the assets directory. The font is an included asset for use with the text based components. These are lightweight components so the Actionscript structure is really simple. In the build I looked at I found classes for 18 components, a single base class and a single styes config class called Style.as.
The current list of MinimalComps include: CheckBox, ColorChooser, HSlider, HUISlider, IndicatorLight, InputText, Label, Meter, Panel, ProgressBar, PushButton, RadioButton, RotarySelector, Slider, Text, UISlider, VSlider, VUISlider
I'm not going to go into the specifics of each of the individual components. The key to understanding a component framework is to look at the common functionality - all the magic happens in the base class. This base class will usually extend Sprite or MovieClip and is likely to be called something like Component.as, UIComponent.as or AbstactComponent.as. In this case its...
Component.as
All the MinimalComps extend Component.as either directly or indirectly through another class. This is the base class that provides all the common functionality for the component set. Open up Component.as in your favourite editor to see how all these features have been implemented.
Feature: Shared Resources
Embedding common assets in the base class make them available to all their children. Having the code at this level centralises it, making it easy to change.
Feature: Common features
protected function getShadow(dist:Number , knockout:Boolean = false)
{
. . .
}
public static function initStage(stage:Stage):void
{
stage.align = StageAlign.TOP_LEFT;
stage.scaleMode = StageScaleMode.NO_SCALE;
}
Sharing functionality in a base class can save code repetition in child classes. At what level each piece of functionality should sit can be a matter for debate. For example, Component.as has two utility classes embedded in the base class. The first creates a Drop Shadow for the component and the second one configures the stage. I'm happy enough with the drop shadow but I don't think a Component base class is the right place for stage initialisation code.
Feature: Snap To Pixel
public function move(xpos:Number, ypos:Number):void
{
x = Math.round(xpos);
y = Math.round(ypos);
}
override public function set x(value:Number):void
{
super.x = Math.round(value);
}
override public function set y(value:Number):void
{
super.y = Math.round(value);
}
This is such a simple one to implement but a great little safety net for working with pixel fonts. Any time the x or y coordinates are set their value is rounded to the nearest whole number. This ensures the component always snaps to a pixel boundary, which in turn ensures that the pixel font is sharp and easy to read.
Feature: Deferred Rendering
protected var _width:Number = 0;
protected var _height:Number = 0;
protected function init():void
{
addChildren();
invalidate();
}
public function setSize(w:Number, h:Number):void
{
_width = w;
_height = h;
invalidate();
}
protected function invalidate():void
{
addEventListener(Event.ENTER_FRAME, onInvalidate);
}
private function onInvalidate(event:Event):void
{
removeEventListener(Event.ENTER_FRAME, onInvalidate);
draw();
}
public function draw():void
{
dispatchEvent(new Event(Component.DRAW));
}
Deferred Rendering is the key optimisation common to almost all component frameworks. The aim is to keep redraw operations to a minimum as they tend to be time consuming and processor intensive. A process known as "invalidation" ensures that no matter how often changes are made, a component is only ever redrawn once per frame.
Property changes are cached in internal variables and a redraw event is scheduled to take place at the next frame. There are two common ways of implementing the redraw request: onEnterFrame
and stage.invalidate
.
onEnterFrame
MinimalComps use onEnterFrame. This is the same invalidation technique used in the AS2 component sets. It's simple to implement and it's only real drawback is that the redraw happens on the frame after the update. This can cause some lag or flicker at slow frame rates.
stage.invalidate()
This is the preferred update technique in AS3 as it allows draw updates to be performed at the end of a frame. I say preferred - it would be if it wasn't buggy. Even the CS3 components don't rely on it to work as advertised. Jesse Warden has a fix for the CS3 components which replaces stage.invalidate with onEnterFrame.
Feature: Manage Children
public function Component(parent:DisplayObjectContainer = null, xpos:Number = 0, ypos:Number = 0):void
{
move(xpos, ypos);
if(parent != null)
{
parent.addChild(this);
}
init();
}
protected function init():void
{
addChildren();
invalidate();
}
protected function addChildren():void { }
Part of the power of a component framework is the ability to use components within other components. A simple Label can be embedded in a Button to get a LabelledButton. The LabelledButton can then be used as a ListItem inside a ListControl and so on. A unified system of child management and instantiation is the key enabler.