Widget – Better than a Sprite
- February 1st, 2009
- Posted in Actionscript 3
- Write comment
I’ve been ripping myself to shreds lately as to how I should design my ultimate extendable user interface classes. Today I found a solution that I like to call the Widget…
package djw.display {
import cru.interfaces.ISizeable;
import flash.display.DisplayObject;
import flash.display.Sprite;
import flash.events.Event;
import flash.geom.Rectangle;
/**
* The Widget class is a blank container designed to override inbuilt resizing
* methods. A Widget can be extended in place of a Sprite where redrawing is to
* be automatically handled.
*
* Widgets are practical for the creation of scalable user interface elements.
*
* Widgets also have stage management functions including automatic event
* listener population.
*
* Widgets can also size to targets, stages or rectangles.
*
* @author Dale J Williams / Cru Digital;
*/
public class Widget extends Sprite implements ISizeable {
public static const VERSION :String = "2.0.0";
protected var _width :Number = 0;
protected var _height :Number = 0;
private var _onDraw :Function;
private var _stageResizable :Boolean;
private var _onAddedToStage :Function;
private var _onRemovedFromStage :Function;
private var _managedListeners :Array;
private var _stageListeners :Array;
/**
* Widgets are designed to be blank on instanciation. Though a drawing
* function can be set at this stage, it is not required or recommended.
* @param on_draw Will set the drawing function on instanciation. This
* is not required and can set later using the onDraw setting.
* @param stage_resizeable Will set the drawing function to call
* whenever the stage is resized.
*/
public function Widget(on_draw:Function = null, stage_resizeable:Boolean=false) {
if(on_draw != null)
onDraw = on_draw;
stageResizable = stage_resizeable;
}
/**
* drawNextRender can be called whenever a change requires the Widget to need
* to redraw. This is the recommended drawing function.
* The drawNextRender function will call the onDraw function, if set, on next
* render.
*/
public function drawNextRender():void {
if (stage) {
super.addEventListener(Event.RENDER, _callDraw, false, 0, true);
super.addEventListener(Event.ENTER_FRAME, _callDraw, false, 0, true);
stage.invalidate();
}
}
/**
* Can be used to cancel a drawNextRender function before it is called.
*/
public function cancelNextRender():void {
if (hasEventListener(Event.RENDER)) super.removeEventListener(Event.RENDER, _callDraw);
if (hasEventListener(Event.ENTER_FRAME)) super.removeEventListener(Event.ENTER_FRAME, _callDraw);
}
/**
* Forces the onDraw function to be called immediately. Calling redraw instead
* of drawNextRender will force the visual update before the next render. This
* method will override stage management.
*/
public function redraw():void {
cancelNextRender();
if (_onDraw != null)
_onDraw();
}
/**
* Adds a stage managed event listener. Event listener is active when stage is present.
* @param type Event type
* @param listener Function to call
* @param target Optional target to assign listener to
*/
public function manageEventListener(type:String, listener:Function, target:Object = null, ...targets):void {
targets.push(target)
for each(target in targets){
if (!target) target = this;
if (!_managedListeners) _managedListeners = [];
_managedListeners.push([type, listener, target]);
if (stage) target.addEventListener(type, listener);
}
_manageStageFunctions();
}
/**
* Removes a stage managed event listener.
* @param type Event type
* @param listener Function to call
* @param target Optional target to assign listener to
*/
public function unmanageEventListener(type:String, listener:Function, target:Object = null, ...targets):void {
if (!_managedListeners) return;
targets.push(target);
for each(target in targets){
if (!target) target = this;
if (target.hasEventListener(type)) {
var managedListener:Object;
for (var i:int = 0; i < _managedListeners.length; i++) {
managedListener = _managedListeners[i];
if (managedListener[0] == type && managedListener[1] == listener)
_managedListeners.splice(i, 1);
target.removeEventListener(type, listener);
}
}
}
}
/**
* adds a managed event listener to the stage
* @param type
* @param listener
*/
public function manageStageEventListener(type:String, listener:Function):void {
if (!_stageListeners) _stageListeners = [];
_stageListeners.push([type, listener]);
if (stage) stage.addEventListener(type, listener);
}
/**
* removed a managed event listener from the stage
* @param type
* @param listener
*/
public function unmanageStageEventListener(type:String, listener:Function):void {
if (!_stageListeners) return;
var stageListener:Array;
for (var i:int = 0; i < _stageListeners.length; i++) {
stageListener = _stageListeners[i];
if (stageListener[0] == type && stageListener[1] == listener)
_stageListeners.splice(i, 1);
}
if (stage) stage.removeEventListener(type, listener);
}
/**
* Automatically sets width and height to target dimensions
*/
public function sizeTo(target:DisplayObject, call_draw:Boolean = true):void {
_width = target.width;
_height = target.height;
if (call_draw) redraw();
}
/**
* Automatically sets x and y to target position
*/
public function positionTo(target:DisplayObject):void {
x = target.x;
x = target.x;
}
/**
* Automatically sets width and height to target's bounds rectangle
*/
public function sizeToRect(rect:Rectangle, call_draw:Boolean = false):void {
x = rect.x;
y = rect.y;
width = rect.width;
height = rect.height;
if (call_draw) drawNextRender();
}
/**
* Automatically sets width and height to stage dimensions
*/
public function sizeToStage(redraw:Boolean=true):void {
_width = stage.stageWidth;
_height = stage.stageHeight;
if (redraw) drawNextRender();
}
/*--------------------------------------------------------------------------------------------
PRIVATE FUNCTIONS
---------------------------------------------------------------------------------------------*/
private function _callDraw(e:Event=null):void {
if (stage)
if (_onDraw != null)
_onDraw();
if (e) cancelNextRender();
}
private function _manageStageResize(e:Event=null):void {
if (stage && _stageResizable)
stage.addEventListener(Event.RESIZE, _callDraw);
else if (e) stage.removeEventListener(Event.RESIZE, _callDraw);
}
private function _callAddedToStage(e:Event = null):void {
for each(var managedListener:Array in _managedListeners)
managedListener[2].addEventListener(managedListener[0], managedListener[1]);
for each(var stageListener:Array in _stageListeners)
stage.addEventListener(stageListener[0], stageListener[1]);
if (_onAddedToStage != null)
_onAddedToStage();
}
private function _callRemovedFromStage(e:Event = null):void {
for each(var managedListener:Array in _managedListeners)
managedListener[2].removeEventListener(managedListener[0], managedListener[1]);
if (_onRemovedFromStage != null)
_onRemovedFromStage();
}
private function _manageStageFunctions(e:Event=null):void {
addEventListener(Event.ADDED_TO_STAGE, _callAddedToStage);
addEventListener(Event.REMOVED_FROM_STAGE, _callRemovedFromStage);
}
/*--------------------------------------------------------------------------------------------
GETTERS AND SETTERS
---------------------------------------------------------------------------------------------*/
/**
* If set to true, the Widget will automatically call the drawing function.
*/
public function set stageResizable(value:Boolean):void {
_stageResizable = value;
if(value){
super.addEventListener(Event.ADDED_TO_STAGE, _manageStageResize);
super.addEventListener(Event.REMOVED_FROM_STAGE, _manageStageResize);
} else {
super.removeEventListener(Event.ADDED_TO_STAGE, _manageStageResize);
super.removeEventListener(Event.REMOVED_FROM_STAGE, _manageStageResize);
}
_manageStageResize();
}
/**
* onDraw is the drawing function. The drawing function is called whenever
* Widget renders, provided it is set. It is good practice to set this value
* in your Widget's constructor.
*/
public function set onDraw(value:Function):void {
if(value != null)
super.addEventListener(Event.ADDED_TO_STAGE, _callDraw);
else super.removeEventListener(Event.ADDED_TO_STAGE, _callDraw);
_onDraw = value;
_callDraw();
}
/**
* A Widget's width value must be set by either itself or its container.
* Without a width and height a Widget is invisible. The behaviour of width
* overrides Sprite's default width behaviour.
* Setting width will cause callDrawNextRender to be called.
*/
override public function get width():Number { return _width; }
override public function set width(value:Number):void {
_width = value;
drawNextRender();
}
/**
* A Widget's height value must be set by either itself or its container.
* Without a height and width a Widget is invisible. The behaviour of height
* overrides Sprite's default height behaviour.
* Setting height will cause drawNextRender to be called.
*/
override public function get height():Number { return _height; }
override public function set height(value:Number):void {
_height = value;
drawNextRender();
}
/**
* The superWidth value can be used to access the Sprite's native width parameter.
* This will access the player's native scaling behaviour and should not be used at
* the same time as the overriden width value.
*/
public function get superWidth():Number { return super.width; }
public function set superWidth(value:Number):void {
super.width = value;
}
/**
* The superHeight value can be used to access the Sprite's native height parameter.
* This will access the player's native scaling behaviour and should not be used at
* the same time as the overriden height value.
*/
public function get superHeight():Number { return super.height; }
public function set superHeight(value:Number):void {
super.height = value;
}
/**
* The onAddedToStage method is called when the StageWidget is added to the
* stage, provided it is set. It is good practice to set this value
* in your StageWidget's constructor.
*/
public function set onAddedToStage(value:Function):void {
_onAddedToStage = value;
_manageStageFunctions();
}
/**
* The onRemovedFromStage method is called when the StageWidget is added to
* the stage, provided it is set. It is good practice to set this value
* in your StageWidget's constructor.
*/
public function set onRemovedFromStage(value:Function):void {
_onRemovedFromStage = value;
_manageStageFunctions();
}
}
}
All it takes to make sure this class will redraw itself on width or height calls or stage resize is to set the onDraw function to whichever function calls the graphics API or positions the elements. Snazzy!

No comments yet.