package com.senocular.display { import flash.display.DisplayObject; import flash.display.DisplayObjectContainer; import flash.display.Stage; import flash.events.Event; import flash.geom.Rectangle; import flash.utils.Dictionary; /* * Dispatched when the layout is updated and its contents * (the target display object) should be redrawn to fit in * the layout's new rect. */ [Event("change")] // [Event(name="change", type="flash.events.Event")] // vers 1.0.1: // useDefaultChangeHandler now defaults to true // prevented defaultChangeHandler from erroring with Stage target // corrected ambiguous error for CS4 compiler // added maintainAspectRatioPolicy + related constants // added @private tags to internal data members to keep // them out of documentation // added LayoutConstraint.init() /** * The Layout class is used to create dynamically sized and positioned * layouts that adjust content to fit within a containing boundary. Layout * instances are (usually) associated with single target DisplayObject * instances and dispatch a CHANGE event when it's layout has changed * allowing that target to fit itself to the layout's rectangle boundary * (rect). *

* Using the optional LayoutManager class, Layout instances can be automatically * updated when they are modified and propagate their changes to child * display objects or layouts that are also using the same LayoutManager * when contained within the same display hierarchy. Without the using * LayoutManager, updates can be forced using the draw() method. Updates to * children will automatically be propagated. *

* If LayoutManager is not being used, you would need to update your layout * using the draw() method (this may sometimes still needed when the * LayoutManager class is being used). This method dispatches the CHANGE * event for the layout and all of its children also affected by * the change in the layout if there was a change. It is generally the * responsibility of the target instance to physically position and size * itself to a layout's rect during the CHANGE event in an event handler, * though the Layout class does provide an automatic handler that will fit * a target to the layout's rect by setting x, y, width and height directly. * When the stage is used as a target for a layout, that layout will * automatically update during the stage's RESIZE event and fit itself * to match the size of the stage. *

* Each layout instance is also an instance of a LayoutConstraint. The * LayoutConstraint class defines the constraints specific to each layout * that affect the direct contents of the target display object associated * with that layout. Normally you would not create instances of * LayoutConstraint. For laying out your objects you would use a Layout * instance. The exception is for Layout children. Each layout will * arrange child layouts within its rect area. An optional children * property can be set (to either a Layout or a LayoutConstraint) to fit * all children within a specific layout of the target layout. Basically * it serves as an intermediary child layout to contain all other child * layouts. * * @author Trevor McCauley, www.senocular.com * @date August 22, 2008 * @version 1.0.1 */ public class Layout extends LayoutConstraint { /** * A predefined change handler for generic objects * being fit within a contraint's bounds. This * handler maps the display objects x, y, width and * height properties directly to the respective properties * within the layout rect. * @param event The CHANGE event object dispatched to this * handler when a layout has changed. */ public static function defaultChangeHandler(event:Event):void { var layout:Layout = Layout(event.target); var layoutTarget:DisplayObject = layout.target; if (!layoutTarget || layoutTarget is Stage) return; var layoutRect:Rectangle = layout.rect; layoutTarget.x = layoutRect.x; layoutTarget.y = layoutRect.y; layoutTarget.width = layoutRect.width; layoutTarget.height = layoutRect.height; } /** * Dispatches the CHANGE event for each layout with changes * @private */ internal static function updateChanged(changedList:Dictionary):void { var layout:Layout; for (var element:* in changedList) { layout = Layout(element); if (layout.lastRect.x != layout._rect.x || layout.lastRect.y != layout._rect.y || layout.lastRect.width != layout._rect.width || layout.lastRect.height != layout._rect.height) { // dispatch the change event so // instances will know their bounding // boxes (layout.rect) have changed layout.dispatchEvent(new Event(Event.CHANGE)); // update last rect values layout.lastRect = layout._rect.clone(); } } } /** * The change handler for updating a display object * when its layout has changed. This relates to the * changeHandler passed into the Layout constructor * and is optional to using addEventListener with * the layout's Event.CHANGE event yourself. */ public function get useDefaultChangeHandler():Boolean { return _useDefaultChangeHandler; } public function set useDefaultChangeHandler(value:Boolean):void { if (value == _useDefaultChangeHandler) return; // remove the current change handler if exists if (_useDefaultChangeHandler) { removeEventListener(Event.CHANGE, defaultChangeHandler, false); } _useDefaultChangeHandler = value; // add the new change handler if not null if (_useDefaultChangeHandler) { addEventListener(Event.CHANGE, defaultChangeHandler, false, 0, true); } } private var _useDefaultChangeHandler:Boolean; /** * The parent layout in which the current layout is * contained. This property will return the parent layout * if that parent had this layout added to it using addChild(), * otherwise if the layout within the parent of the layout's * target display object has been registered to the same * LayoutManager instance, it will return that layout. If * neither are the case, null is returned [read-only]. */ public function get parent():Layout { // return explicit parent if set if (_parent) return _parent; // if _parent not defined, look in // the target's parent for a registered // layout which targets this instance // as a child layout if (_manager && _target && _target.parent in _manager.registeredList) { return Layout(_manager.registeredList[_target.parent]); } // no parent found, return null return null; } private var _parent:Layout; /** * An optional constraint used for layouts contained * within the current layout. By default, layouts within * other layouts fit to the extends of the containing layout. * By defining a children layout constraint layouts that * are children of the current layout can be contained to * a separate set of boundaries. The children property is * null by default. To use the children layout, you must * first assign it to a Layout or LayoutConstraint instance. */ public function get children():LayoutConstraint { return _children; } public function set children(value:LayoutConstraint):void { if (_children) { // remove any owner reference in the // current _children object _children.owner = null; } // assign children constraint; it cannot be 'this' _children = value != this ? value : null; if (_children) { // set up owner Layout instance. This will // be the Layout instance if this constraint // is for Layout.children, otherwise as a // Layout constraint, it will reference itself _children.owner = this; // child rectangles (for children constraints) // do not inherit positioning and are based on // the coordinate space (starting at 0,0) // of the current layout rectangle _children.setIn(new Rectangle(0, 0, _rect.width, _rect.height)); } } private var _children:LayoutConstraint; /** * The LayoutManager instance managing this layout [read-only]. */ public function get manager():LayoutManager { return _manager; } /** * @private */ internal var _manager:LayoutManager; /** * The target display object this layout affects. */ public function get target():DisplayObject { return _target; } public function set target(value:DisplayObject):void { if (value == _target) return; // remove resize listener if current target is stage if (_target is Stage) { _target.removeEventListener(Event.RESIZE, stageResized, false); } _target = value; // add resize listener if new target is stage if (_target is Stage) { _target.addEventListener(Event.RESIZE, stageResized, false, 0, true); } } /** * @private */ internal var _target:DisplayObject; /** * Used in determining whether or not the layout * has changed since it was last updated * @private */ internal var lastRect:Rectangle = new Rectangle(); /** * A list for child references when any layout * defines this layout as its parent * @private */ internal var childList:Dictionary = new Dictionary(true); /** * Constructor for creating new Layout instances. If you want * your instances to automatically update, you would generally * favor LayoutManager.registerNewLayout() in favor of creating * layout instances with the Layout constructor. * @param target The target display object this layout instance * will be associated. Of the target is stage, an automatic * listner for the RESIZE event will be added to update the * layout's size to match the stage's [optional]. * @param changeHandler A change handler function that will * automatically be set to listen to the CHANGE event for * the new layout instance [optional]. */ public function Layout(target:DisplayObject = null, useDefaultChangeHandler:Boolean = true) { // default target rectangle var targetRect:Rectangle = new Rectangle(0, 0, 100, 100); if (target) { // assign the target (display object) // using this layout this.target = target; // set up the constraint for the target using // this Layout instance. A special case is made // if target is the stage instance where the // initial rect is based on the stage size. // Otherwise its based on the target location and size. if (_target is Stage) { targetRect = new Rectangle(0, 0, Stage(_target).stageWidth, Stage(_target).stageHeight); // don't default to the target size // if it has no size }else if (_target.width && _target.height) { targetRect = new Rectangle(_target.x, _target.y, _target.width, _target.height); } } // call constraint super super(targetRect); // assign the owner of the constraint // to be this layout instance owner = this; // assign changeHandler listener this.useDefaultChangeHandler = useDefaultChangeHandler; } /** * Creates a new copy of the current Layout instance. * Cloned layouts do do not retain an association with any * LayoutManager used with the original. */ override public function clone():LayoutConstraint { var layout:Layout = new Layout(_target, _useDefaultChangeHandler); layout.match(this); if (_children) { layout.children = _children.clone(); } if (_parent) { _parent.addChild(layout); } return layout; } /** * Adds a layout to be the child of the current layout. * As a child, changes from this layout will be propagated * to that layout to assure that it is properly * constrained within this layout's boundaries. * @param childLayout The layout to be made a * child of the current layout. */ public function addChild(childLayout:Layout):Layout { if (childLayout._parent) { childLayout._parent.removeChild(childLayout); } childList[childLayout] = true; childLayout._parent = this; return childLayout; } /** * Removes a layout as a child of this layout. * @param childLayout The layout to be removed as a * child of the current layout. */ public function removeChild(childLayout:Layout):Layout { if (childLayout in childList) { delete childList[childLayout]; childLayout._parent = null; } return childLayout; } /** * Updates the layout (current and children) dimensions * dispatching the Layout.CHANGE event for this layout or * any child layouts affected. */ public function draw():void { var changedList:Dictionary = new Dictionary(true); _draw(changedList); updateChanged(changedList); } /** * Invalidates the instance so that it * will be validated during the next update * @private */ internal override function invalidate():void { // make sure instance is invalid // using inherited invalidate super.invalidate(); if (_manager) { _manager.invalidate(this); } } /** * Internal version of draw which does the actual drawing * @private */ internal function _draw(changedList:Dictionary):void { // mark layout as changed // only layouts in changed list will // dispatch the CHANGE event and // only if their rect has changed changedList[this] = true; // if there is a specified parent, use it // to fit this layout to fit within // the parent children contraint if (_parent) { if (_parent._children){ setIn(_parent._children._rect); }else{ setIn(new Rectangle(0, 0, _parent._rect.width, _parent._rect.height)); } // if no explicit parent, rely on the parent // of the target display object and check to // see if it is registered with a layout }else if (_manager && _target && _target.parent in _manager.registeredList) { var parentLayout:Layout = Layout(_manager.registeredList[_target.parent]); if (parentLayout._children){ setIn(parentLayout._children._rect); }else{ setIn(new Rectangle(0, 0, parentLayout._rect.width, parentLayout._rect.height)); } // if no parent found, set in a null area this // allows x, y, width, and height to still // be observed with the appropriate limits }else{ setIn(null); } if (_children) { // update the children constraint // to fit within the current layout // child rectangles (for children constraints) // do not inherit positioning and are based on // the coordinate space (starting at 0,0) // of the current layout rectangle _children.setIn(new Rectangle(0, 0, _rect.width, _rect.height)); // children no longer invalid _children.invalid = false; } // loop through all the children of the // parent layout and draw all the // children which are registered // first check any children within the childList for (var element:* in childList) { Layout(element)._draw(changedList); } // second check registered layouts in the target container if (_manager && _target is DisplayObjectContainer) { var targetContainer:DisplayObjectContainer = DisplayObjectContainer(_target); var i:int = targetContainer.numChildren; var child:DisplayObject; while(i--) { child = targetContainer.getChildAt(i); // draw the child if it is registered // under the same manager as this instance if (child in _manager.registeredList) { Layout(_manager.registeredList[child])._draw(changedList); } } } // clear layout invalid flags invalid = false; if (_manager) { delete _manager.invalidList[this]; } } /* * Event handler for stage resizing if target is the stage */ private function stageResized(event:Event):void { // remove resize listener if target // finds itself not being the stage if (!(_target is Stage)) { removeEventListener(Event.RESIZE, stageResized); return; } // set the width and height to be // the width and height of the stage width = Stage(_target).stageWidth; height = Stage(_target).stageHeight; // update draw(); } } }