package com.senocular.display {
import flash.display.DisplayObject;
import flash.display.DisplayObjectContainer;
import flash.display.Stage;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.geom.Rectangle;
import flash.utils.Dictionary;
/*
* Dispatched when any registered layout changes.
*/
[Event("change")] // [Event(name="change", type="flash.events.Event")]
/**
* The LayoutManager class is used to help layout instances
* keep up to date. By registering a diaplay object with a
* LayoutManager instance, a layout object is created and
* associated with that display object. This layout object can
* then be recognized when a parent display object within the
* display list with a layout is updated and update
* itself with that parent's changes. Additionally, by using
* LayoutManager.initializeAutoUpdate(), with a reference to stage
* the RENDER event will be used to automatically update registered
* layouts at the end of a frame when they've changed. When
* registered layouts change, the LayoutManager will dispatch a
* CHANGE event.
*
* The LayoutManager works through instances as well as a singleton.
* If you do not want to manage multiple instances of LayoutManager,
* you can call LayoutManager methods directly from the LayoutManager
* class (meaning the class has two sets of methods, one set for
* instances and one set static).
*
* Use of the LayoutManager is optional for layouts. If you do not
* use the LayoutManager class, then you will need to make sure to
* call draw() for all of your layouts to make sure they are
* properly updated.
*
* @author Trevor McCauley, www.senocular.com
* @date August 22, 2008
* @version 1.0.1
*/
public class LayoutManager extends EventDispatcher {
private static var _instance:LayoutManager = new LayoutManager();
/**
* Returns the LayoutManager instance associated with
* the LayoutManager class if the LayoutManager is
* being used as a singleton.
*/
public static function getInstance():LayoutManager {
return _instance;
}
/**
* Registers a display object with a layout. As a
* registered layout, it will be available for updates
* if the Layout class is initialized with auto updates
* and for propagation of changes from parent layouts. If the
* target display object already has a registered layout
* for this same LayoutManager, that layout is returned. If the
* target is registered to another layout manager, it will
* continue to be registered to that layout manager with a
* separate layout instance.
* @param target The display object to get a layout for.
* @param changeHandler If a new Layout instance is created, this
* handler will be used to update the target during the CHANGE
* event [optional].
*/
public static function registerNewLayout(target:DisplayObject, useDefaultChangeHandler:Boolean = true):Layout {
return _instance.registerNewLayout(target, useDefaultChangeHandler);
}
/**
* Returns the current layout object associated with
* the passed display object. If no layout has been
* registered for that object, null is returned.
* @param target The display object to get a layout for.
*/
public static function getLayout(target:DisplayObject):Layout {
return _instance.getLayout(target);
}
/**
* Unregisters a display object's layout. As a
* registered layout, it will be available for updates
* if the LayoutManager class is initialized with auto updates
* and for propagation of changes from parent layouts. When
* unregistered, updates will have to be made manually.
* @param target The display object to unregister from the manager.
*/
public static function unregisterLayout(target:DisplayObject):Layout {
return _instance.unregisterLayout(target);
}
/**
* Determines if the display object has a registered layout.
* @param target A display object to check if registered
* to this LayoutManager instance.
*/
public static function isRegisteredLayout(target:DisplayObject):Boolean {
return _instance.isRegisteredLayout(target);
}
/**
* Initializes the Layout class to perform automatic updates
* for all registered layouts. Updates happen during the RENDER
* event and only occur if there was a change in a layout. If
* already initialized the Layout class for auto updates and
* want to stop the auto updates, call initializeAutoUpdate
* again but pass null instead of a reference to the stage.
* @param stage A reference to the stage to be used to
* allow for updates in the RENDER event.
*/
public static function initializeAutoUpdate(stage:Stage):void {
_instance.initializeAutoUpdate(stage);
}
/**
* Draws and updates all layouts in the layout manager
*/
public static function draw():void {
_instance.validate(null);
}
/**
* @private
*/
internal var invalidList:Dictionary = new Dictionary(true);
/**
* @private
*/
internal var registeredList:Dictionary = new Dictionary(true);
private var stage:Stage;
private var invalid:Boolean;
/**
* Constructor. Creates a new LayoutManager instance from which
* you can register layouts for diaply objects. As an alternative
* to making your own LayoutManager instances, you can also use
* the static methods from the LayoutManager class to handle all
* layouts.
*/
public function LayoutManager() {}
/**
* Registers a display object with a layout. As a
* registered layout, it will be available for updates
* if the Layout class is initialized with auto updates
* and for propagation of changes from parent layouts. If the
* target display object already has a registered layout
* for this same LayoutManager, that layout is returned. If the
* target is registered to another layout manager, it will
* continue to be registered to that layout manager with a
* separate layout instance.
* @param target The display object to get a layout for.
* @param changeHandler If a new Layout instance is created, this
* handler will be used to update the target during the CHANGE
* event [optional].
*/
public function registerNewLayout(target:DisplayObject, useDefaultChangeHandler:Boolean = true):Layout {
// create new layout and associate with target
// if doesn't already exist in registeredList
if (!(target in registeredList)) {
var layout:Layout = new Layout(target, useDefaultChangeHandler);
layout._manager = this;
registeredList[target] = layout;
}
return Layout(registeredList[target]);
}
/**
* Returns the current layout object associated with
* the passed display object. If no layout has been
* registered for that object, null is returned.
* @param target The display object to get a layout for.
*/
public function getLayout(target:DisplayObject):Layout {
if (target in registeredList) {
return Layout(registeredList[target]);
}
return null;
}
/**
* Unregisters a display object's layout. As a
* registered layout, it will be available for updates
* if the LayoutManager class is initialized with auto updates
* and for propagation of changes from parent layouts. When
* unregistered, updates will have to be made manually.
* @param target The display object to unregister from the manager.
*/
public function unregisterLayout(target:DisplayObject):Layout {
if (target in registeredList) {
var layout:Layout = Layout(registeredList[target]);
layout._manager = null;
delete registeredList[target];
return layout;
}
return null;
}
/**
* Determines if the display object has a registered layout.
* @param target A display object to check if registered
* to this LayoutManager instance.
*/
public function isRegisteredLayout(target:DisplayObject):Boolean {
return Boolean(target in registeredList);
}
/**
* Initializes the Layout class to perform automatic updates
* for all registered layouts. Updates happen during the RENDER
* event and only occur if there was a change in a layout. If
* already initialized the Layout class for auto updates and
* want to stop the auto updates, call initializeAutoUpdate
* again but pass null instead of a reference to the stage.
* @param stage A reference to the stage to be used to
* allow for updates in the RENDER event.
*/
public function initializeAutoUpdate(stage:Stage):void {
if (this.stage){
this.stage.removeEventListener(Event.RENDER, validate, false);
}
this.stage = stage;
if (this.stage){
this.stage.addEventListener(Event.RENDER, validate, false, 1, true);
}
}
/**
* Draws and updates all layouts in the layout manager
*/
public function draw():void {
validate(null);
}
/**
* Adds the passed layout to the invalid list of the manager
* and invalidates the stage (if available) to ensure that
* validate will be called in the next RENDER event
* @private
*/
internal function invalidate(layout:Layout):void {
invalidList[layout] = true;
if (stage){
if (!invalid) {
stage.invalidate();
invalid = true;
}
// WORKAROUND: needed to retain render listeners
// in case another action uses
// removeEventListener(Event.RENDER, ... )
// which removes all RENDER listeners (bug)
initializeAutoUpdate(stage);
}
}
/*
* Called in the RENDER event, updates all invalid
* layouts in the manager
*/
private function validate(event:Event):void {
removeInvalidRedundancies();
// draw each layout in invalid list
var changedList:Dictionary = new Dictionary(true);
for (var element:* in invalidList) {
Layout(element)._draw(changedList);
}
Layout.updateChanged(changedList);
// dispatch manager CHANGE if
// changedList has any layouts
for (element in changedList) {
dispatchEvent(new Event(Event.CHANGE));
break;
}
invalid = false;
}
/*
* Each invalid layout will also update it's children to fit
* within its new contraints/bounds. If these children are
* also invalid, they can be removed from the invalid list since
* they will automatically be drawn when their parent layout is
*/
private function removeInvalidRedundancies():void {
for (var element:* in invalidList) {
removeRedundantChildren(Layout(element));
}
}
private function removeRedundantChildren(layout:Layout):void {
// first check any children within the childList
for (var element:* in layout.childList){
if (element in invalidList) {
delete invalidList[element];
}
}
// second check registered layouts in the target container
if (layout._target is DisplayObjectContainer) {
var targetContainer:DisplayObjectContainer = DisplayObjectContainer(layout._target);
var i:int = targetContainer.numChildren;
var child:DisplayObject;
while(i--) {
child = targetContainer.getChildAt(i);
if (child in registeredList) {
if (registeredList[child] in invalidList) {
delete invalidList[registeredList[child]];
}
}
}
}
}
}
}