/* Copyright (c) 2009 Yahoo! Inc. All rights reserved. The copyrights embodied in the content of this file are licensed under the BSD (revised) open source license */ package com.yahoo.astra.layout { import com.yahoo.astra.layout.events.LayoutEvent; import flash.display.DisplayObject; import flash.events.Event; import flash.events.TextEvent; import flash.text.TextField; import flash.utils.Dictionary; import flash.utils.getDefinitionByName; import flash.utils.getQualifiedClassName; /** * Generic layout manager for DisplayObjects. * * @see flash.display.DisplayObject * @see com.yahoo.astra.layout.ILayoutContainer * * @author Josh Tynjala */ public class LayoutManager { //-------------------------------------- // Static Properties //-------------------------------------- /** * @private * The classes registered with invalidating events. */ private static var classes:Array = []; /** * @private * A hash of class references to an Array of invalidating events for each class. */ private static var classToEvents:Dictionary = new Dictionary(true); //-------------------------------------- // Static Methods //-------------------------------------- /** * @private */ private static function initialize():void { //ILayoutContainer will always be available. registerInvalidatingEvents(ILayoutContainer, [LayoutEvent.LAYOUT_CHANGE]); //catch when a TextField's text changes (in case it has autoSize enabled) registerInvalidatingEvents(TextField, [Event.CHANGE]); } initialize(); /** * Allows users to specify events that invalidate the parent layout container * when fired by instances of a specific class. * *

For example, if fl.core.UIComponent fires the resize * event when it is inside a layout container, the layout container will * refresh its layout.

* * @param source The class that will fire invalidating events (ie. TextField) * @param events An Array of event constants (ie. Event.CHANGE) * * @example The following code demonstrates how to specify a set of invalidating events: * * LayoutManager.registerInvalidatingEvents( UIComponent, [ComponentEvent.RESIZE, ComponentEvent.MOVE] ); * */ public static function registerInvalidatingEvents(source:Class, events:Array):void { if(classes.indexOf(source) >= 0) { var savedEvents:Array = classToEvents[source]; events = events.concat(savedEvents); } else { classes.push(source); } classToEvents[source] = events; } /** * Determines if a particular DisplayObject's type has been registered * with invalidating events. */ public static function hasInvalidatingEvents(target:DisplayObject):Boolean { var targetType:Class = getDefinitionByName(getQualifiedClassName(target)) as Class; return classes.indexOf(targetType) >= 0; } /** * Called by an ILayoutContainer implementation when a child is added. * If the child is an instance of a class with registered events, the * layout system will listen for those events. * * @see ILayoutContainer */ public static function registerContainerChild(child:DisplayObject):void { for each(var registeredClass:Class in classes) { if(child is registeredClass) { var events:Array = classToEvents[registeredClass]; for each(var eventName:String in events) { //weak listener so that the layout system won't stop GC. child.addEventListener(eventName, invalidatingEventHandler, false, 0, true); } } } } /** * Called by a layout container when a child is removed. If the child is * an instance of a class with registered events, the layout system will * stop listening to those events. * * @see ILayoutContainer */ public static function unregisterContainerChild(child:DisplayObject):void { for each(var registeredClass:Class in classes) { if(child is registeredClass) { var events:Array = classToEvents[registeredClass]; for each(var eventName:String in events) { child.removeEventListener(eventName, invalidatingEventHandler); } } } } /** * If a DisplayObject that is placed into a layout container doesn't * fire events for size changes, calling this function will allow * its size to properly affect its parent layout. * * @param target The display object to resize * @param width The new width of the display object * @param height The new height of the display object * * @example The following code demonstrates how to resize a * DisplayObject that doesn't fire an event when it resizes: * * LayoutManager.resize( mySprite, 100, 34 ); * */ public static function resize(target:DisplayObject, width:Number, height:Number):void { target.width = width; target.height = height; invalidateParentLayout(target); } /** * Similar to LayoutManager.resize(), this function may be called * to update any property of a DisplayObject and notify its parent layout * container to refresh if no event normally indicates this is needed. * * @param target The display object whose property will be changed * @param property The name of the property. * @param value The value of the property * * @example The following code demonstrates how to change a * DisplayObject's property when that DisplayObject doesn't fire an * event when the property changes: * * LayoutManager.update(mySprite, "transform", new Transform()); * */ public static function update(target:DisplayObject, property:String, value:Object):void { if(!target.hasOwnProperty(property)) return; target[property] = value; invalidateParentLayout(target); } /** * If the target's parent is a layout container, that parent will be * informed that it needs to update the layout. */ public static function invalidateParentLayout(target:DisplayObject):void { var parent:ILayoutContainer = target.parent as ILayoutContainer; if(!parent) return; parent.invalidateLayout(); } /** * @private * * Generic event handler for invalidating events. If the target's parent * is a layout container, that parent will be informed that its layout * needs to be updated. Any standard event is supported. */ private static function invalidatingEventHandler(event:Event):void { var child:DisplayObject = DisplayObject(event.currentTarget); invalidateParentLayout(child); } } }