first commit

This commit is contained in:
2020-10-20 00:58:15 +02:00
commit 7f1b9bfca5
222 changed files with 56918 additions and 0 deletions

View File

@ -0,0 +1,68 @@
/*
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.modes.ILayoutMode;
import flash.display.DisplayObject;
import flash.events.IEventDispatcher;
/**
* Defines properties and methods required for layout containers
* to work with LayoutManager.
*
* <p>Implementations must be a subclass of DisplayObjectContainer.</p>
*
* @see LayoutManager
* @see LayoutContainer
* @see flash.display.DisplayObjectContainer
*
* @author Josh Tynjala
*/
public interface ILayoutContainer extends IEventDispatcher
{
//--------------------------------------
// Properties
//--------------------------------------
/**
* The width of the content displayed by the layout container.
*/
function get contentWidth():Number;
/**
* The height of the content displayed by the layout container.
*/
function get contentHeight():Number;
/**
* The layout algorithm used to display children of the layout container.
*
* @see modes/package-detail.html Available Layout Modes (com.yahoo.astra.layout.modes)
*/
function get layoutMode():ILayoutMode;
/**
* @private
*/
function set layoutMode(value:ILayoutMode):void;
//--------------------------------------
// Methods
//--------------------------------------
/**
* Informs the layout container that it should update the layout of its
* children.
*/
function invalidateLayout():void;
/**
* Immediately updates the layout of the container's children.
*/
function validateLayout():void;
}
}

View File

@ -0,0 +1,453 @@
/*
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 com.yahoo.astra.layout.modes.BoxLayout;
import com.yahoo.astra.layout.modes.ILayoutMode;
import com.yahoo.astra.utils.NumberUtil;
import flash.display.DisplayObject;
import flash.display.Sprite;
import flash.events.Event;
import flash.geom.Rectangle;
//--------------------------------------
// Events
//--------------------------------------
/**
* Dispatched when this container's layout changes.
*
* @eventType com.yahoo.astra.layout.events.LayoutEvent.LAYOUT_CHANGE
*/
[Event(name="layoutChange", type="com.yahoo.astra.layout.events.LayoutEvent")]
/**
* Children of this display object are subject to being positioned, and
* possibly resized, based on a specified layout algorithm. LayoutContainer
* integrates with LayoutManager to refresh its the layout of its children
* when properties on the container itself change or when one of its
* children dispatches a registered invalidating event. This is the default
* implementation of ILayoutContainer.
*
* @example The following code demonstrates the usage of LayoutContainer:
* <listing version="3.0">
* // create an instance of a layout mode
* var mode:ILayoutMode = new BoxLayout();
* mode.direction = "horizontal";
* mode.horizontalGap = 10;
*
* // one may pass the mode to the constructor or the layoutMode property.
* // note: by default, a LayoutContainer will automatically determine
* // its size based on its content.
* var container:LayoutContainer = new LayoutContainer( mode );
* this.addChild(container);
*
* for( var i:int = 0; i < 5; i++ )
* {
* var square:Shape = new Shape();
* square.graphics.beginFill(0xcccccc);
* square.graphics.drawRect(0, 0, 25, 25);
* square.graphics.endFill();
* container.addChild(square);
* }
* </listing>
*
* <p><strong>Important Note:</strong> LayoutContainer leaves certain
* functionality to the implementor to complete. No scrollbars or other user
* interface controls will appear when the contents are larger than the
* LayoutContainer's dimensions.</p>
*
* <p>This limitation is deliberate and by design. The philosophy behind
* this choice centers on allowing an ActionScript developer to use these
* classes as a basis for implementing layout controls for nearly any user
* interface library available for Flash Player.</p>
*
* <p>For a reference implementation of full-featured UI controls that
* implement masking and scrolling, please take a look at the Layout
* Containers available in the <a href="http://developer.yahoo.com/flash/astra-flash/">Yahoo! Astra Components for Flash CS3</a>.</p>
*
* @see LayoutManager
* @see ILayoutContainer
* @see modes/package-detail.html Available Layout Modes (com.yahoo.astra.layout.modes)
* @author Josh Tynjala
*/
public class LayoutContainer extends Sprite implements ILayoutContainer
{
//--------------------------------------
// Static Properties
//--------------------------------------
/**
* @private
* Flag indicating whether we are in render mode.
*/
public static var isRendering:Boolean = false;
//--------------------------------------
// Constructor
//--------------------------------------
/**
* Constructor.
*
* @param mode The ILayoutMode implementation to use.
*/
public function LayoutContainer(mode:ILayoutMode = null)
{
super();
this.scrollRect = new Rectangle();
this.layoutMode = mode;
this.addEventListener(Event.ADDED_TO_STAGE, addedToStageHandler);
}
//--------------------------------------
// Properties
//--------------------------------------
/**
* @private
* Storage for the contentWidth property.
*/
protected var _contentWidth:Number = 0;
/**
* @inheritDoc
*/
public function get contentWidth():Number
{
return this._contentWidth;
}
/**
* @private
* Storage for the contentHeight property.
*/
protected var _contentHeight:Number = 0;
/**
* @inheritDoc
*/
public function get contentHeight():Number
{
return this._contentHeight;
}
/**
* @private
* Storage for width values explicitly set by a developer.
*/
protected var explicitWidth:Number = NaN;
/**
* @private
*/
override public function get width():Number
{
if(!isNaN(this.explicitWidth))
{
return this.explicitWidth;
}
return this.contentWidth;
}
/**
* @private
*/
override public function set width(value:Number):void
{
if(this.explicitWidth != value)
{
this.explicitWidth = value;
this.invalidateLayout();
}
}
/**
* @private
* Storage for height values explicitly set by a developer.
*/
protected var explicitHeight:Number = NaN;
/**
* @private
*/
override public function get height():Number
{
if(!isNaN(this.explicitHeight))
{
return this.explicitHeight;
}
return this.contentHeight;
}
/**
* @private
*/
override public function set height(value:Number):void
{
if(this.explicitHeight != value)
{
this.explicitHeight = value;
this.invalidateLayout();
}
}
/**
* @private
* Storage for the layoutMode property.
*/
private var _layoutMode:ILayoutMode = new BoxLayout();
/**
* @inheritDoc
*/
public function get layoutMode():ILayoutMode
{
return this._layoutMode;
}
/**
* @private
*/
public function set layoutMode(value:ILayoutMode):void
{
if(this._layoutMode)
{
this._layoutMode.removeEventListener(LayoutEvent.LAYOUT_CHANGE, layoutModeChangeHandler);
}
this._layoutMode = value;
if(this._layoutMode)
{
this._layoutMode.addEventListener(LayoutEvent.LAYOUT_CHANGE, layoutModeChangeHandler, false, 0, true);
}
this.invalidateLayout();
}
/**
* @private
* Storage for the autoMask property.
*/
private var _autoMask:Boolean = true;
/**
* If true, the conent will automatically update the scrollRect to fit
* the dimensions. Uses explicit dimensions if width or height is set by
* the developer. Otherwise, uses the content dimensions. If false, it
* is up to the implementor to set the mask or scrollRect.
*/
public function get autoMask():Boolean
{
return this._autoMask;
}
/**
* @private
*/
public function set autoMask(value:Boolean):void
{
this._autoMask = value;
if(this._autoMask)
{
this.scrollRect = new Rectangle(0, 0, this.width, this.height);
}
else
{
this.scrollRect = null;
}
}
/**
* @private
* Flag indicating if the layout is invalid.
*/
protected var invalid:Boolean = false;
/**
* @private
* Flag indicating if the LayoutContainer is currently validating.
*/
protected var isValidating:Boolean = false;
//--------------------------------------
// Public Methods
//--------------------------------------
/**
* @private
*/
override public function addChild(child:DisplayObject):DisplayObject
{
child = super.addChild(child);
if(child)
{
LayoutManager.registerContainerChild(child);
this.invalidateLayout();
}
return child;
}
/**
* @private
*/
override public function addChildAt(child:DisplayObject, index:int):DisplayObject
{
child = super.addChildAt(child, index);
if(child)
{
LayoutManager.registerContainerChild(child);
this.invalidateLayout();
}
return child;
}
/**
* @private
*/
override public function removeChild(child:DisplayObject):DisplayObject
{
child = super.removeChild(child);
if(child)
{
LayoutManager.unregisterContainerChild(child);
this.invalidateLayout();
}
return child;
}
/**
* @private
*/
override public function removeChildAt(index:int):DisplayObject
{
var child:DisplayObject = super.removeChildAt(index);
if(child)
{
LayoutManager.unregisterContainerChild(child);
this.invalidateLayout();
}
return child;
}
/**
* @inheritDoc
*/
public function invalidateLayout():void
{
//if we're validating, then this change is caused
//by an invalidating event from a child, and we can safely ignore it
if(this.isValidating)
{
return;
}
if(isRendering)
{
//force validation during the render phase. performance hit should be minimal.
this.validateLayout();
}
if(!this.invalid && this.stage)
{
this.invalid = true;
this.stage.addEventListener(Event.ENTER_FRAME, renderHandler);
}
}
/**
* @inheritDoc
*/
public function validateLayout():void
{
this.isValidating = true;
this.layout();
this.isValidating = false;
this.invalid = false;
}
//--------------------------------------
// Protected Methods
//--------------------------------------
/**
* @private
* Updates the layout algorithm and recalculates the content dimensions.
*/
protected function layout():void
{
var oldWidth:Number = this.contentWidth;
var oldHeight:Number = this.contentHeight;
this._contentWidth = Number.POSITIVE_INFINITY;
this._contentHeight = Number.POSITIVE_INFINITY;
//let the layout mode do all the work (strategy pattern)
var bounds:Rectangle = new Rectangle();
if(this.layoutMode)
{
var children:Array = [];
var childCount:int = this.numChildren;
for(var i:int = 0; i < childCount; i++)
{
children.push(this.getChildAt(i));
}
//width and height return the explicit values if available
//otherwise they return the content width and height values
bounds = this.layoutMode.layoutObjects(children, new Rectangle(0, 0, this.width, this.height));
}
this._contentWidth = bounds.x + bounds.width;
this._contentHeight = bounds.y + bounds.height;
if(this.autoMask)
{
var scrollRect:Rectangle = this.scrollRect;
scrollRect.width = this.width;
scrollRect.height = this.height;
this.scrollRect = scrollRect;
}
if(!NumberUtil.fuzzyEquals(this.contentWidth, oldWidth) || !NumberUtil.fuzzyEquals(this.contentHeight, oldHeight))
{
this.dispatchEvent(new LayoutEvent(LayoutEvent.LAYOUT_CHANGE));
}
}
//--------------------------------------
// Private Event Handlers
//--------------------------------------
/**
* @private
* Invalidates when the container is added to the stage.
*/
private function addedToStageHandler(event:Event):void
{
this.invalidateLayout();
}
/**
* @private
* Invalidates the layout if the layoutMode says that it is invalid.
*/
private function layoutModeChangeHandler(event:LayoutEvent):void
{
this.invalidateLayout();
}
/**
* @private
* Validates the layout on the next frame after invalidation.
*/
private function renderHandler(event:Event):void
{
isRendering = true;
event.target.removeEventListener(Event.ENTER_FRAME, renderHandler);
this.validateLayout();
isRendering = false;
}
}
}

View File

@ -0,0 +1,217 @@
/*
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.
*
* <p>For example, if <code>fl.core.UIComponent</code> fires the <code>resize</code>
* event when it is inside a layout container, the layout container will
* refresh its layout.</p>
*
* @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:
* <listing version="3.0">
* LayoutManager.registerInvalidatingEvents( UIComponent, [ComponentEvent.RESIZE, ComponentEvent.MOVE] );
* </listing>
*/
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:
* <listing version="3.0">
* LayoutManager.resize( mySprite, 100, 34 );
* </listing>
*/
public static function resize(target:DisplayObject, width:Number, height:Number):void
{
target.width = width;
target.height = height;
invalidateParentLayout(target);
}
/**
* Similar to <code>LayoutManager.resize()</code>, 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:
* <listing version="3.0">
* LayoutManager.update(mySprite, "transform", new Transform());
* </listing>
*/
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);
}
}
}

View File

@ -0,0 +1,55 @@
/*
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.events
{
import flash.events.Event;
/**
* Events associated with ILayoutContainer objects.
*
* @see ILayoutContainer
* @author Josh Tynjala
*/
public class LayoutEvent extends Event
{
//--------------------------------------
// Static Properties
//--------------------------------------
/**
* The <code>LayoutEvent.LAYOUT_CHANGE</code> event type constant indicates that
* the layout of an ILayoutContainer needs to be redrawn.
*
* @eventType layoutChange
*/
public static const LAYOUT_CHANGE:String = "layoutChange";
//--------------------------------------
// Constructor
//--------------------------------------
/**
* Constructor.
*/
public function LayoutEvent(type:String, bubbles:Boolean=false, cancelable:Boolean=false)
{
super(type, bubbles, cancelable);
}
//--------------------------------------
// Public Methods
//--------------------------------------
/**
* @private
*/
override public function clone():Event
{
return new LayoutEvent(this.type, this.bubbles, this.cancelable);
}
}
}

View File

@ -0,0 +1,267 @@
/*
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.modes
{
import com.yahoo.astra.layout.events.LayoutEvent;
import flash.display.DisplayObject;
import flash.events.EventDispatcher;
import flash.geom.Rectangle;
/**
* Some basic properties shared by multiple ILayoutMode implementations.
* Should be treated as an abstract class that is meant to be subclassed.
*
* @author Josh Tynjala
* @see ILayoutMode
* @see com.yahoo.astra.layout.ILayoutContainer
*/
public class BaseLayoutMode extends EventDispatcher implements IAdvancedLayoutMode
{
//--------------------------------------
// Constructor
//--------------------------------------
/**
* Constructor.
*/
public function BaseLayoutMode()
{
}
//--------------------------------------
// Properties
//--------------------------------------
/**
* @private
* Storage for the paddingLeft property.
*/
private var _paddingLeft:Number = 0;
/**
* The number of pixels displayed at the left of the target's children.
*/
public function get paddingLeft():Number
{
return this._paddingLeft;
}
/**
* @private
*/
public function set paddingLeft(value:Number):void
{
if(this._paddingLeft != value)
{
this._paddingLeft = value;
this.dispatchEvent(new LayoutEvent(LayoutEvent.LAYOUT_CHANGE));
}
}
/**
* @private
* Storage for the paddingRight property.
*/
private var _paddingRight:Number = 0;
/**
* The number of pixels displayed at the right of the target's children.
*/
public function get paddingRight():Number
{
return this._paddingRight;
}
/**
* @private
*/
public function set paddingRight(value:Number):void
{
if(this._paddingRight != value)
{
this._paddingRight = value;
this.dispatchEvent(new LayoutEvent(LayoutEvent.LAYOUT_CHANGE));
}
}
/**
* @private
* Storage for the paddingTop property.
*/
private var _paddingTop:Number = 0;
/**
* The number of pixels displayed at the top of the target's children.
*/
public function get paddingTop():Number
{
return this._paddingTop;
}
/**
* @private
*/
public function set paddingTop(value:Number):void
{
if(this._paddingTop != value)
{
this._paddingTop = value;
this.dispatchEvent(new LayoutEvent(LayoutEvent.LAYOUT_CHANGE));
}
}
/**
* @private
* Storage for the paddingBottom property.
*/
private var _paddingBottom:Number = 0;
/**
* The number of pixels displayed at the bottom of the target's children.
*/
public function get paddingBottom():Number
{
return this._paddingBottom;
}
/**
* @private
*/
public function set paddingBottom(value:Number):void
{
if(this._paddingBottom != value)
{
this._paddingBottom = value;
this.dispatchEvent(new LayoutEvent(LayoutEvent.LAYOUT_CHANGE));
}
}
/**
* @private
* Storage for the clients with advanced configuration options.
*/
protected var clients:Array = [];
/**
* @private
* Storage for the advanced configuration options. Indicies
* correspond to the indices in the clients Array.
*/
protected var configurations:Array = [];
//--------------------------------------
// Public Methods
//--------------------------------------
/**
* @inheritDoc
*/
public function addClient(target:DisplayObject, configuration:Object = null):void
{
if(!target)
{
throw new ArgumentError("Target must be a DisplayObject. Received " + target + ".");
}
configuration = configuration ? configuration : {};
var generatedConfig:Object = this.newConfiguration();
for(var prop:String in configuration)
{
generatedConfig[prop] = configuration[prop];
}
//reuse an existing client if we're re-adding it
var index:int = this.clients.indexOf(target);
if(index < 0)
{
index = this.clients.length;
this.clients[index] = target;
}
this.configurations[index] = generatedConfig;
this.dispatchEvent(new LayoutEvent(LayoutEvent.LAYOUT_CHANGE));
}
/**
* @inheritDoc
*/
public function removeClient(target:DisplayObject):void
{
var index:int = this.clients.indexOf(target);
if(index < 0)
{
throw new ArgumentError("Cannot call removeClient() with a DisplayObject that is not a client.");
}
this.clients.splice(index, 1);
this.configurations.splice(index, 1);
this.dispatchEvent(new LayoutEvent(LayoutEvent.LAYOUT_CHANGE));
}
/**
* @inheritDoc
*/
public function hasClient(target:DisplayObject):Boolean
{
return this.clients.indexOf(target) >= 0;
}
/**
* @inheritDoc
*/
public function layoutObjects(displayObjects:Array, bounds:Rectangle):Rectangle
{
//to be overridden
throw new Error("Method BaseLayoutMode.layoutChildren() must be overridden!");
return new Rectangle();
}
//--------------------------------------
// Protected Methods
//--------------------------------------
/**
* @private
* Ensures that every child of the target has a configuration and creates a list of children
* that are included in the layout.
*/
protected function configureChildren(targets:Array):Array
{
var childrenInLayout:Array = [];
var childCount:int = targets.length;
for(var i:int = 0; i < childCount; i++)
{
var child:DisplayObject = DisplayObject(targets[i]);
var index:int = this.clients.indexOf(child);
if(index < 0)
{
//set up a default configuration if the child hasn't been added as a client
index = this.clients.length;
this.clients.push(child);
this.configurations.push(this.newConfiguration());
}
//only return children that have includeInLayout specified
var config:Object = this.configurations[index];
if(config.includeInLayout)
{
childrenInLayout.push(child);
}
}
return childrenInLayout;
}
/**
* @private
* The default configuration properties for a client of the layout mode.
*/
protected function newConfiguration():Object
{
return { includeInLayout: true };
}
}
}

View File

@ -0,0 +1,96 @@
/*
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.modes
{
/**
* Constraint values available in BorderLayout.
*
* @see BorderLayout
* @author Josh Tynjala
*/
public class BorderConstraints
{
//--------------------------------------
// Static Properties
//--------------------------------------
/**
* The target will be constrained to the top edge. Its width will be
* altered to fit to the width of the container. Its height will
* remain unchanged.
*
* <p>Consider the <code>TOP</code> constraint to work like a page's
* header. It appears above all other constrained children, with no other
* children appearing to the left or right. Multiple <code>TOP</code> constraints
* will be arranged vertically from the top down in the order that they
* were registered as clients of the BorderLayout algorithm.</p>
*/
public static const TOP:String = "top";
/**
* The target will be constrained to the bottom edge. Its width will be
* altered to fit to the width of the container. Its height will
* remain unchanged.
*
* <p>Consider the <code>BOTTOM</code> constraint to work like a page's
* footer. It appears below all other constrained children, with no other
* children appearing to the left or right. Multiple <code>BOTTOM</code> constraints
* will be arranged vertically from the bottom up in the order that they
* were registered as clients of the BorderLayout algorithm.</p>
*/
public static const BOTTOM:String = "bottom";
/**
* The target will be constrained to the left edge. It will appear
* below any items constrained to the top and above items constrained to
* the bottom. Its height will be altered to fill the remaining height
* of the container (after the TOP and BOTTOM constraints are measured),
* and its width will remain unchanged.
*
* <p>Consinder the <code>LEFT</code> constraint to work like a page's
* sidebar that is aligned to the left. It appears to the left of all other
* children, with only the top and bottom constraints taking precendence.
* Multiple <code>LEFT</code> constraints will be arranged horizontally
* from left to right in the order that they were registered as clients
* of the BorderLayout algorithm.</p>
*/
public static const LEFT:String = "left";
/**
* The target will be constrained to the right edge. It will appear
* below any items constrained to the top and above items constrained to
* the bottom. Its height will be altered to fill the remaining height
* of the container (after the TOP and BOTTOM constraints are measured),
* and its width will remain unchanged.
*
* <p>Consinder the <code>RIGHT</code> constraint to work like a page's
* sidebar that is aligned to the right. It appears to the right of all other
* children, with only the top and bottom constraints taking precendence.
* Multiple <code>RIGHT</code> constraints will be arranged horizontally
* from right to left in the order that they were registered as clients
* of the BorderLayout algorithm.</p>
*/
public static const RIGHT:String = "right";
/**
* The target will be constrained to the center of the container. It
* will appear between all other constrained children. Its height will be
* altered to fill the remaining height of the container (after the TOP
* and BOTTOM constraints are measured) and its width will be altered to
* fill the remaining width of the container (after the LEFT and RIGHT
* constraints are measured).
*
* <p>Consider the <code>CENTER</code> constraint to work like a page's
* primary content. It appears in between all other constraints and changes
* size to fill the remaining area (after all other constraints are
* measured). Multiple <code>CENTER</code> constraints will be arranged
* vertically from top down starting from the bottom edge of the
* <code>TOP</code> constraints to the top edge of any <code>BOTTOM</code>
* constraints.</p>
*/
public static const CENTER:String = "center";
}
}

View File

@ -0,0 +1,496 @@
/*
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.modes
{
import com.yahoo.astra.layout.events.LayoutEvent;
import com.yahoo.astra.utils.DisplayObjectUtil;
import flash.display.DisplayObject;
import flash.geom.Rectangle;
/**
* Arranges a DisplayObjectContainer's children using a page-like border
* algorithm. Children with TOP and BOTTOM constraints will be positioned
* and sized like page headers and footers. LEFT and RIGHT constrained
* children will be positioned and sized like sidebars and CENTER
* constrained children will be positioned and stretched to fill the
* remaining space.
*
* <p><strong>Advanced Client Options:</strong></p>
* <p>Client configuration parameters allow a developer to specify
* behaviors for individual children of the target container. To set these
* advanced options, one must call <code>addClient()</code> on the BorderLayout
* instance and pass the child to configure along with an object specifying
* the configuration parameters.</p>
*
* @example The following code adds clients to a BorderLayout instance:
* <listing version="3.0">
* var border:BorderLayout = new BorderLayout();
* border.addClient( headerSprite, { constraint: BorderConstraints.TOP } );
* border.addClient( contentSprite,
* {
* constraint: BorderConstraints.CENTER,
* maintainAspectRatio: true,
* horizontalAlign: HorizontalAlignment.CENTER,
* verticalAlign: VerticalAlignment.MIDDLE
* });
* border.addClient( footerSprite, { constraint: BorderConstraints.BOTTOM } );
*
* var container:LayoutContainer = new LayoutContainer();
* container.layoutMode = border;
* this.addChild( container );
* </listing>
*
* <p>Several client parameters are available with the BorderLayout algorithm:</p>
* <dl>
* <dt><strong><code>constraint</code></strong> : String</dt>
* <dd>The BorderConstraints value to be used on the target by the layout algorithm. The default
* value is <code>BorderConstraints.CENTER</code>.</dd>
* <dt><strong><code>maintainAspectRatio</code></strong> : Boolean</dt>
* <dd>If true, the aspect ratio of the target will be maintained if it is resized.</dd>
* <dt><strong><code>horizontalAlign</code></strong> : String</dt>
* <dd>The horizontal alignment used when positioning the target. Used in combination with
* <code>maintainAspectRatio</code>.</dd>
* <dt><strong><code>verticalAlign</code></strong> : String</dt>
* <dd>The vertical alignment used when positioning the target. Used in combination with
* <code>maintainAspectRatio</code>.</dd>
* <dt><strong><code>aspectRatio</code></strong> : Number</dt>
* <dd>The desired aspect ratio to use with <code>maintainAspectRatio</code>. This value is optional.
* If no aspect ratio is provided, it will be determined based on the target's original width and height.</dd>
* <dt><strong><code>includeInLayout</code></strong> : Boolean</dt>
* <dd>If <code>false</code>, the target will not be included in layout calculations. The default value is <code>true</code>.</dd>
* </dl>
*
* @see BorderConstraints
* @see HorizontalAlignment
* @see VerticalAlignment
* @see com.yahoo.astra.layout.LayoutContainer
*
* @author Josh Tynjala
*/
public class BorderLayout extends BaseLayoutMode implements IAdvancedLayoutMode
{
//--------------------------------------
// Constructor
//--------------------------------------
/**
* Constructor.
*/
public function BorderLayout()
{
}
//--------------------------------------
// Properties
//--------------------------------------
/**
* @private
* Storage for the verticalGap property.
*/
private var _verticalGap:Number = 0;
/**
* The number of vertical pixels between each item displayed by this
* container.
*/
public function get verticalGap():Number
{
return this._verticalGap;
}
/**
* @private
*/
public function set verticalGap(value:Number):void
{
this._verticalGap = value;
this.dispatchEvent(new LayoutEvent(LayoutEvent.LAYOUT_CHANGE));
}
/**
* @private
* Storage for the horizontalGap property.
*/
private var _horizontalGap:Number = 0;
/**
* The number of horizontal pixels between each item displayed by this
* container.
*/
public function get horizontalGap():Number
{
return this._horizontalGap;
}
/**
* @private
*/
public function set horizontalGap(value:Number):void
{
this._horizontalGap = value;
this.dispatchEvent(new LayoutEvent(LayoutEvent.LAYOUT_CHANGE));
}
//--------------------------------------
// Public Methods
//--------------------------------------
/**
* @private
*/
override public function addClient(target:DisplayObject, configuration:Object = null):void
{
//if horizontalAlign or verticalAlign is not specified, set some defaults
configuration.horizontalAlign = configuration.horizontalAlign ? configuration.horizontalAlign : "left";
configuration.verticalAlign = configuration.verticalAlign ? configuration.verticalAlign : "top";
//if no aspectRatio has been specified, use the aspect ratio
//calculated from the target's width and height
if(configuration.maintainAspectRatio && !configuration.aspectRatio)
{
configuration.aspectRatio = target.width / target.height;
}
super.addClient(target, configuration);
}
/**
* @inheritDoc
*/
override public function layoutObjects(displayObjects:Array, bounds:Rectangle):Rectangle
{
const START_X:Number = bounds.x + this.paddingLeft;
const START_Y:Number = bounds.y + this.paddingTop;
var width:Number = bounds.width;
if(bounds.width == Number.POSITIVE_INFINITY)
{
width = this.measureChildWidths();
}
width -= (this.paddingLeft + this.paddingRight);
var height:Number = bounds.height;
if(bounds.height == Number.POSITIVE_INFINITY)
{
height = this.measureChildHeights();
}
height -= (this.paddingTop + this.paddingBottom);
var remainingWidth:Number = width;
var remainingHeight:Number = height;
//position the top children
var topHeight:Number = 0;
var topChildren:Array = this.getChildrenByConstraint(BorderConstraints.TOP, true);
var topChildCount:int = topChildren.length;
for(var i:int = 0; i < topChildCount; i++)
{
var topChild:DisplayObject = DisplayObject(topChildren[i]);
var config:Object = this.configurations[this.clients.indexOf(topChild)];
var x:Number = START_X;
var y:Number = START_Y + topHeight;
if(config.maintainAspectRatio)
{
DisplayObjectUtil.resizeAndMaintainAspectRatio(topChild, width, topChild.height, config.aspectRatio);
}
else
{
topChild.width = width;
}
DisplayObjectUtil.align(topChild, new Rectangle(x, y, width, topChild.height), config.horizontalAlign, config.verticalAlign);
topHeight += topChild.height + this.verticalGap;
}
remainingHeight -= topHeight;
//position the bottom children
var bottomHeight:Number = 0;
var bottomChildren:Array = this.getChildrenByConstraint(BorderConstraints.BOTTOM, true);
var bottomChildCount:int = bottomChildren.length;
for(i = 0; i < bottomChildCount; i++)
{
var bottomChild:DisplayObject = DisplayObject(bottomChildren[i]);
config = this.configurations[this.clients.indexOf(bottomChild)];
bottomHeight += bottomChild.height;
x = START_X;
y = START_Y + height - bottomHeight;
if(config.maintainAspectRatio)
{
DisplayObjectUtil.resizeAndMaintainAspectRatio(bottomChild, width, bottomChild.height, config.aspectRatio);
}
else
{
bottomChild.width = width;
}
DisplayObjectUtil.align(bottomChild, new Rectangle(x, y, width, bottomChild.height), config.horizontalAlign, config.verticalAlign);
bottomHeight += this.verticalGap;
}
//if topHeight + bottomHeight < the total height, fix the overlap
var difference:Number = (START_Y + topHeight) - (START_Y + height - bottomHeight);
if(difference > 0)
{
for(i = 0; i < bottomChildCount; i++)
{
bottomChild = DisplayObject(bottomChildren[i]);
bottomChild.y += difference;
}
}
remainingHeight -= bottomHeight;
//the height of the center area affects the height of the left and right areas
var centerHeight:Number = Math.max(remainingHeight, 0);
//position the left children
var leftWidth:Number = 0;
var leftChildren:Array = this.getChildrenByConstraint(BorderConstraints.LEFT, true);
var leftChildCount:int = leftChildren.length;
for(i = 0; i < leftChildCount; i++)
{
var leftChild:DisplayObject = DisplayObject(leftChildren[i]);
config = this.configurations[this.clients.indexOf(leftChild)];
x = START_X + leftWidth;
y = START_Y + topHeight;
if(config.maintainAspectRatio)
{
DisplayObjectUtil.resizeAndMaintainAspectRatio(leftChild, leftChild.width, centerHeight, config.aspectRatio);
}
else
{
leftChild.height = centerHeight;
}
DisplayObjectUtil.align(leftChild, new Rectangle(x, y, leftChild.width, centerHeight), config.horizontalAlign, config.verticalAlign);
leftWidth += leftChild.width + this.horizontalGap;
}
remainingWidth -= leftWidth;
//position the right children
var rightWidth:Number = 0;
var rightChildren:Array = this.getChildrenByConstraint(BorderConstraints.RIGHT, true);
var rightChildCount:int = rightChildren.length;
for(i = 0; i < rightChildCount; i++)
{
var rightChild:DisplayObject = DisplayObject(rightChildren[i]);
config = this.configurations[this.clients.indexOf(rightChild)];
rightWidth += rightChild.width;
x = START_X + width - rightWidth;
y = START_Y + topHeight;
if(config.maintainAspectRatio)
{
DisplayObjectUtil.resizeAndMaintainAspectRatio(rightChild, rightChild.width, centerHeight, config.aspectRatio);
}
else
{
rightChild.height = centerHeight;
}
DisplayObjectUtil.align(rightChild, new Rectangle(x, y, rightChild.width, centerHeight), config.horizontalAlign, config.verticalAlign);
rightWidth += this.horizontalGap;
}
//if leftWidth + rightWidth < the total width, fix the overlap
difference = (START_X + leftWidth) - (START_X + width - rightWidth);
if(difference > 0)
{
for(i = 0; i < rightChildCount; i++)
{
rightChild = DisplayObject(rightChildren[i]);
rightChild.x += difference;
}
}
remainingWidth -= rightWidth;
//position the center children in the remaining width
var centerWidth:Number = Math.max(remainingWidth, 0);
var centerChildren:Array = this.getChildrenByConstraint(BorderConstraints.CENTER, true);
var centerChildCount:int = centerChildren.length;
var centerChildHeight:Number = centerHeight / centerChildCount;
for(i = 0; i < centerChildCount; i++)
{
var centerChild:DisplayObject = DisplayObject(centerChildren[i]);
config = this.configurations[this.clients.indexOf(centerChild)];
x = START_X + leftWidth;
y = START_Y + topHeight + (i * centerChildHeight);
if(config.maintainAspectRatio)
{
DisplayObjectUtil.resizeAndMaintainAspectRatio(centerChild, centerWidth, centerChildHeight, config.aspectRatio);
}
else
{
centerChild.width = centerWidth;
centerChild.height = centerChildHeight;
}
DisplayObjectUtil.align(centerChild, new Rectangle(x, y, centerWidth, centerChildHeight), config.horizontalAlign, config.verticalAlign);
}
if(remainingWidth < 0)
{
width -= remainingWidth;
}
if(remainingHeight < 0)
{
height -= remainingHeight;
}
bounds.width = width + this.paddingLeft + this.paddingRight;
bounds.height = height + this.paddingTop + this.paddingBottom;
return bounds;
}
/**
* @private
* Creates the default configuration for this layout mode.
*/
override protected function newConfiguration():Object
{
return {
includeInLayout: true,
constraint: BorderConstraints.CENTER
};
}
/**
* @private
* If no width is specified for the layout container, we need to
* measure the children to determine the best width.
*/
protected function measureChildWidths():Number
{
var totalWidth:Number = this.paddingLeft + this.paddingRight;
var leftChildren:Array = this.getChildrenByConstraint(BorderConstraints.LEFT, true);
var childCount:int = leftChildren.length;
for(var i:int = 0; i < childCount; i++)
{
var child:DisplayObject = DisplayObject(leftChildren[i]);
totalWidth += child.width + this.horizontalGap;
}
var rightChildren:Array = this.getChildrenByConstraint(BorderConstraints.RIGHT, true);
childCount = rightChildren.length;
for(i = 0; i < childCount; i++)
{
child = DisplayObject(rightChildren[i]);
totalWidth += child.width + this.horizontalGap;
}
var maxWidth:Number = 0;
var centerChildren:Array = this.getChildrenByConstraint(BorderConstraints.CENTER, true);
childCount = centerChildren.length;
for(i = 0; i < childCount; i++)
{
child = DisplayObject(centerChildren[i]);
maxWidth = Math.max(maxWidth, child.width);
}
totalWidth += maxWidth;
maxWidth = 0;
var topChildren:Array = this.getChildrenByConstraint(BorderConstraints.TOP, true);
childCount = topChildren.length;
for(i = 0; i < childCount; i++)
{
child = DisplayObject(topChildren[i]);
maxWidth = Math.max(maxWidth, child.width);
}
var bottomChildren:Array = this.getChildrenByConstraint(BorderConstraints.BOTTOM, true);
childCount = bottomChildren.length;
for(i = 0; i < childCount; i++)
{
child = DisplayObject(bottomChildren[i]);
maxWidth = Math.max(maxWidth, child.width);
}
totalWidth = Math.max(maxWidth, totalWidth);
return totalWidth;
}
/**
* @private
* If no height is specified for the layout container, we need to
* measure the children to determine the best height.
*/
protected function measureChildHeights():Number
{
var totalHeight:Number = this.paddingTop + this.paddingBottom;
var topChildren:Array = this.getChildrenByConstraint(BorderConstraints.TOP, true);
var childCount:int = topChildren.length;
for(var i:int = 0; i < childCount; i++)
{
var child:DisplayObject = DisplayObject(topChildren[i]);
totalHeight += child.height;
}
var bottomChildren:Array = this.getChildrenByConstraint(BorderConstraints.BOTTOM, true);
childCount = bottomChildren.length;
for(i = 0; i < childCount; i++)
{
child = DisplayObject(bottomChildren[i]);
totalHeight += child.height;
}
var centerTotalHeight:Number = 0;
var centerChildren:Array = this.getChildrenByConstraint(BorderConstraints.CENTER, true);
childCount = centerChildren.length;
for(i = 0; i < childCount; i++)
{
child = DisplayObject(centerChildren[i]);
centerTotalHeight += child.height;
}
var maxHeight:Number = centerTotalHeight;
var rightChildren:Array = this.getChildrenByConstraint(BorderConstraints.RIGHT, true);
childCount = rightChildren.length;
for(i = 0; i < childCount; i++)
{
child = DisplayObject(rightChildren[i]);
maxHeight = Math.max(maxHeight, child.height);
}
var leftChildren:Array = this.getChildrenByConstraint(BorderConstraints.LEFT, true);
childCount = leftChildren.length;
for(i = 0; i < childCount; i++)
{
child = DisplayObject(leftChildren[i]);
maxHeight = Math.max(maxHeight, child.height);
}
totalHeight += maxHeight;
return totalHeight;
}
/**
* @private
* A simple filter for getting all the clients with a specific constraint
* in their configuration.
*/
protected function getChildrenByConstraint(constraint:String, inLayoutOnly:Boolean = false):Array
{
return this.clients.filter(function(item:DisplayObject, index:int, source:Array):Boolean
{
var configuration:Object = this.configurations[index];
return configuration.constraint == constraint && (inLayoutOnly ? configuration.includeInLayout : true);
}, this);
}
}
}

View File

@ -0,0 +1,444 @@
/*
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.modes
{
import com.yahoo.astra.layout.events.LayoutEvent;
import com.yahoo.astra.utils.DisplayObjectUtil;
import flash.display.DisplayObject;
import flash.display.DisplayObjectContainer;
import flash.geom.Rectangle;
/**
* Arranges a DisplayObjectContainer's children in a single column or row.
*
* @example The following code configures a BoxLayout instance and passes it to a container:
* <listing version="3.0">
* var box:BoxLayout = new BoxLayout();
* box.direction = "vertical";
* box.verticalGap = 4;
* box.verticalAlign = VerticalAlignment.MIDDLE;
*
* var container:LayoutContainer = new LayoutContainer();
* container.layoutMode = box;
* this.addChild( container );
* </listing>
*
* <p><strong>Advanced Client Options:</strong></p>
* <p>Optional client configuration parameters allow a developer to specify
* behaviors for individual children of the target container. To set these
* advanced options, one must call <code>addClient()</code> on the BoxLayout
* instance and pass the child to configure along with an object specifying
* the configuration parameters. Several client parameters are available to
* the BoxLayout algorithm:</p>
*
* <dl>
* <dt><strong><code>percentWidth</code></strong> : Number</dt>
* <dd>The target's width will be updated based on a percentage of the width specified in the layout bounds.</dd>
* <dt><strong><code>percentHeight</code></strong> : Number</dt>
* <dd>The target's width will be updated based on a percentage of the width specified in the layout bounds.</dd>
* <dt><strong><code>minWidth</code></strong> : Number</dt>
* <dd>The minimum width value to allow when resizing. The default value is <code>0</code>.</dd>
* <dt><strong><code>minHeight</code></strong> : Number</dt>
* <dd>The minimum height value to allow when resizing. The default value is <code>0</code>.</dd>
* <dt><strong><code>maxWidth</code></strong> : Number</dt>
* <dd>The maximum width value to allow when resizing. The default value is <code>10000</code>.</dd>
* <dt><strong><code>maxHeight</code></strong> : Number</dt>
* <dd>The maximum height value to allow when resizing. The default value is <code>10000</code>.</dd>
* <dt><strong><code>includeInLayout</code></strong> : Boolean</dt>
* <dd>If <code>false</code>, the target will not be included in layout calculations. The default value is <code>true</code>.</dd>
* </dl>
*
* @example The following code adds multiple clients to a BoxLayout instance:
* <listing version="3.0">
* var box:BoxLayout = new BoxLayout();
* box.direction = "vertical";
* box.addClient( headerSprite, { percentWidth: 100 } );
* box.addClient( contentSprite,
* {
* percentWidth: 100,
* percentHeight: 100,
* minWidth: 640,
* minHeight: 480
* });
* box.addClient( footerSprite, { percentWidth: 100 } );
*
* var container:LayoutContainer = new LayoutContainer( box );
* container.width = 1024;
* container.height = 768;
* container.addChild( headerSprite );
* container.addChild( contentSprite );
* container.addChild( footerSprite );
* this.addChild( container );
* </listing>
*
* @author Josh Tynjala
* @see com.yahoo.astra.layout.LayoutContainer
*/
public class BoxLayout extends BaseLayoutMode implements IAdvancedLayoutMode
{
//--------------------------------------
// Static Properties
//--------------------------------------
/**
* @private
* The default maximum number of pixels to calculate width sizing.
*/
private static const DEFAULT_MAX_WIDTH:Number = 10000;
/**
* @private
* The default maximum number of pixels to calculate height sizing.
*/
private static const DEFAULT_MAX_HEIGHT:Number = 10000;
//--------------------------------------
// Constructor
//--------------------------------------
/**
* Constructor.
*/
public function BoxLayout()
{
}
//--------------------------------------
// Properties
//--------------------------------------
/**
* @private
* Storage for the direction property.
*/
private var _direction:String = "horizontal";
/**
* The direction in which children of the target are laid out. Valid
* direction values include <code>"vertical"</code> or <code>"horizontal"</code>.
*/
public function get direction():String
{
return this._direction;
}
/**
* @private
*/
public function set direction(value:String):void
{
this._direction = value;
this.dispatchEvent(new LayoutEvent(LayoutEvent.LAYOUT_CHANGE));
}
/**
* @private
* Storage for the verticalGap property.
*/
private var _verticalGap:Number = 0;
/**
* The number of pixels appearing between the target's children
* vertically.
*/
public function get verticalGap():Number
{
return this._verticalGap;
}
/**
* @private
*/
public function set verticalGap(value:Number):void
{
this._verticalGap = value;
this.dispatchEvent(new LayoutEvent(LayoutEvent.LAYOUT_CHANGE));
}
/**
* @private
* Storage for the horizontalGap property.
*/
private var _horizontalGap:Number = 0;
/**
* The number of pixels appearing between the target's children
* horizontally.
*/
public function get horizontalGap():Number
{
return this._horizontalGap;
}
/**
* @private
*/
public function set horizontalGap(value:Number):void
{
this._horizontalGap = value;
this.dispatchEvent(new LayoutEvent(LayoutEvent.LAYOUT_CHANGE));
}
/**
* @private
* Storage for the verticalAlign property.
*/
private var _verticalAlign:String = "top";
/**
* The vertical alignment of children displayed in the target.
*
* @see VerticalAlignment
*/
public function get verticalAlign():String
{
return this._verticalAlign;
}
/**
* @private
*/
public function set verticalAlign(value:String):void
{
this._verticalAlign = value;
this.dispatchEvent(new LayoutEvent(LayoutEvent.LAYOUT_CHANGE));
}
/**
* @private
* Storage for the horizontalAlign property.
*/
private var _horizontalAlign:String = "left";
/**
* The horizontal alignment of children displayed in the target.
*
* @see HorizontalAlignment
*/
public function get horizontalAlign():String
{
return this._horizontalAlign;
}
/**
* @private
*/
public function set horizontalAlign(value:String):void
{
this._horizontalAlign = value;
this.dispatchEvent(new LayoutEvent(LayoutEvent.LAYOUT_CHANGE));
}
/**
* @private
* The maximum width value from among the current target's children.
*/
protected var maxChildWidth:Number;
/**
* @private
* The maximum height value from among the current target's children.
*/
protected var maxChildHeight:Number;
//--------------------------------------
// Public Methods
//--------------------------------------
/**
* @inheritDoc
*/
override public function layoutObjects(displayObjects:Array, bounds:Rectangle):Rectangle
{
var childrenInLayout:Array = this.configureChildren(displayObjects);
//determine the available horizontal space
var hSpaceForChildren:Number = bounds.width - this.paddingLeft - this.paddingRight;
if((hSpaceForChildren == Infinity)||(hSpaceForChildren >9000))
{
hSpaceForChildren = DEFAULT_MAX_WIDTH;
}
//determine the available vertical space
var vSpaceForChildren:Number = bounds.height - this.paddingTop - this.paddingBottom;
if((vSpaceForChildren == Infinity)||(vSpaceForChildren >9000))
{
vSpaceForChildren = DEFAULT_MAX_HEIGHT;
}
//resize the children based on the available space and the specified percentage width and height values.
if(this.direction == "vertical")
{
vSpaceForChildren -= (this.verticalGap * (childrenInLayout.length - 1));
PercentageSizeUtil.flexChildHeightsProportionally(this.clients, this.configurations, hSpaceForChildren, vSpaceForChildren);
}
else
{
hSpaceForChildren -= (this.horizontalGap * (childrenInLayout.length - 1));
PercentageSizeUtil.flexChildWidthsProportionally(this.clients, this.configurations, hSpaceForChildren, vSpaceForChildren);
}
this.maxChildWidth = 0;
this.maxChildHeight = 0;
var childCount:int = childrenInLayout.length;
for(var i:int = 0; i < childCount; i++)
{
var child:DisplayObject = DisplayObject(childrenInLayout[i]);
//measure the child's width
this.maxChildWidth = Math.max(this.maxChildWidth, child.width);
this.maxChildHeight = Math.max(this.maxChildHeight, child.height);
}
if(this.direction == "vertical")
{
this.layoutChildrenVertically(childrenInLayout, bounds);
}
else
{
this.layoutChildrenHorizontally(childrenInLayout, bounds);
}
bounds = LayoutModeUtil.calculateChildBounds(childrenInLayout);
bounds.width += this.paddingRight;
bounds.height += this.paddingBottom;
return bounds;
}
//--------------------------------------
// Protected Methods
//--------------------------------------
/**
* @private
* Positions the children when direction is vertical.
*/
protected function layoutChildrenVertically(children:Array, bounds:Rectangle):void
{
var maxXPosition:Number = bounds.width;
if(maxXPosition == Number.POSITIVE_INFINITY)
{
maxXPosition = this.maxChildWidth;
}
maxXPosition -= (this.paddingLeft + this.paddingRight);
var xPosition:Number = bounds.x + this.paddingLeft;
var yPosition:Number = bounds.y + this.paddingTop;
var childCount:int = children.length;
for(var i:int = 0; i < childCount; i++)
{
var child:DisplayObject = DisplayObject(children[i]);
child.x = xPosition;
child.y = yPosition;
DisplayObjectUtil.align(child, new Rectangle(child.x, child.y, maxXPosition, child.height), this.horizontalAlign, null);
yPosition += child.height + this.verticalGap;
}
//special case: if the combined height of the children
//is less than the total height specified in the bounds,
//then we can align vertically as well!
var totalHeight:Number = yPosition - this.verticalGap - bounds.y + this.paddingBottom;
if(totalHeight < bounds.height)
{
var middleStart:Number = (bounds.height - totalHeight) / 2;
var rightStart:Number = bounds.height - totalHeight - bounds.y;
rightStart = (rightStart == Infinity)?DEFAULT_MAX_HEIGHT:rightStart;
for(i = 0; i < childCount; i++)
{
child = DisplayObject(children[i]);
switch(this.verticalAlign)
{
case "middle":
child.y += middleStart;
break;
case "bottom":
child.y += rightStart;
break;
}
}
}
}
/**
* @private
* Positions the children when direction is horizontal.
*/
protected function layoutChildrenHorizontally(children:Array, bounds:Rectangle):void
{
var maxYPosition:Number = bounds.height;
if(maxYPosition == Number.POSITIVE_INFINITY)
{
maxYPosition = this.maxChildHeight;
}
maxYPosition -= (this.paddingBottom + this.paddingTop);
var xPosition:Number = bounds.x + this.paddingLeft;
var yPosition:Number = bounds.y + this.paddingTop
var childCount:int = children.length;
for(var i:int = 0; i < childCount; i++)
{
var child:DisplayObject = DisplayObject(children[i]);
child.x = xPosition;
child.y = yPosition;
DisplayObjectUtil.align(child, new Rectangle(child.x, child.y, child.width, maxYPosition), null, this.verticalAlign);
xPosition += child.width + this.horizontalGap;
}
//special case: if the combined width of the children
//is less than the total width specified in the bounds,
//then we can align horizontally as well!
var totalWidth:Number = xPosition - this.horizontalGap - bounds.x + this.paddingRight;
if(totalWidth < bounds.width)
{
var middleStart:Number = (bounds.width - totalWidth) / 2;
var rightStart:Number = bounds.width - totalWidth;
rightStart = (rightStart == Infinity)?DEFAULT_MAX_WIDTH:rightStart;
for(i = 0; i < childCount; i++)
{
child = DisplayObject(children[i]);
switch(this.horizontalAlign)
{
case "center":
child.x += middleStart;
break;
case "right":
child.x += rightStart;
break;
}
}
}
}
/**
* @private
* Creates the default configuration for this layout mode.
*/
override protected function newConfiguration():Object
{
return {
includeInLayout: true,
minWidth: 0,
maxWidth: 10000,
minHeight: 0,
maxHeight: 10000,
percentWidth: NaN,
percentHeight: NaN
};
}
}
}

View File

@ -0,0 +1,366 @@
/*
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.modes
{
import com.yahoo.astra.layout.events.LayoutEvent;
import com.yahoo.astra.utils.DisplayObjectUtil;
import flash.display.DisplayObject;
import flash.display.DisplayObjectContainer;
import flash.geom.Rectangle;
/**
* Arranges a DisplayObjectContainer's children using a flow algorithm. When
* a child is too large for a row or column, a new row or column is created.
* Similar to the flow of text in a document.
*
* @example The following code configures a FlowLayout instance and passes it to a container:
* <listing version="3.0">
* var flow:FlowLayout = new FlowLayout();
* flow.direction = "horizontal";
* flow.horizontalGap = 1;
* flow.verticalGap = 4;
* flow.verticalAlign = VerticalAlignment.BOTTOM;
*
* var container:LayoutContainer = new LayoutContainer();
* container.layoutMode = flow;
* this.addChild( container );
* </listing>
*
* <p><strong>Advanced Client Options:</strong></p>
* <p>Optional client configuration parameters allow a developer to specify
* behaviors for individual children of the target container. To set these
* advanced options, one must call <code>addClient()</code> on the FlowLayout
* instance and pass the child to configure along with an object specifying
* the configuration parameters. The following client parameters are available to
* the FlowLayout algorithm:</p>
*
* <dl>
* <dt><strong><code>includeInLayout</code></strong> : Boolean</dt>
* <dd>If <code>false</code>, the target will not be included in layout calculations. The default value is <code>true</code>.</dd>
* </dl>
*
* @author Josh Tynjala
* @see com.yahoo.astra.layout.LayoutContainer
*/
public class FlowLayout extends BaseLayoutMode implements IAdvancedLayoutMode
{
//--------------------------------------
// Constructor
//--------------------------------------
/**
* Constructor.
*/
public function FlowLayout()
{
super();
}
//--------------------------------------
// Properties
//--------------------------------------
/**
* @private
* Storage for the direction property.
*/
private var _direction:String = "horizontal";
/**
* The direction in which children of the target are laid out. Once
* the edge of the container is reached, the children will begin
* appearing on the next row or column. Valid direction values include
* <code>"vertical"</code> or <code>"horizontal"</code>.
*/
public function get direction():String
{
return this._direction;
}
/**
* @private
*/
public function set direction(value:String):void
{
this._direction = value;
this.dispatchEvent(new LayoutEvent(LayoutEvent.LAYOUT_CHANGE));
}
/**
* @private
* Storage for the verticalGap property.
*/
private var _verticalGap:Number = 0;
/**
* The number of pixels appearing between the target's children
* vertically.
*/
public function get verticalGap():Number
{
return this._verticalGap;
}
/**
* @private
*/
public function set verticalGap(value:Number):void
{
this._verticalGap = value;
this.dispatchEvent(new LayoutEvent(LayoutEvent.LAYOUT_CHANGE));
}
/**
* @private
* Storage for the horizontalGap property.
*/
private var _horizontalGap:Number = 0;
/**
* The number of pixels appearing between the target's children
* horizontally.
*/
public function get horizontalGap():Number
{
return this._horizontalGap;
}
/**
* @private
*/
public function set horizontalGap(value:Number):void
{
this._horizontalGap = value;
this.dispatchEvent(new LayoutEvent(LayoutEvent.LAYOUT_CHANGE));
}
/**
* @private
* Storage for the verticalAlign property.
*/
private var _verticalAlign:String = "top";
/**
* The vertical alignment of children displayed in the target.
*
* @see VerticalAlignment
*/
public function get verticalAlign():String
{
return this._verticalAlign;
}
/**
* @private
*/
public function set verticalAlign(value:String):void
{
this._verticalAlign = value;
this.dispatchEvent(new LayoutEvent(LayoutEvent.LAYOUT_CHANGE));
}
/**
* @private
* Storage for the horizontalAlign property.
*/
private var _horizontalAlign:String = "left";
/**
* The horizontal alignment of children displayed in the target.
*
* @see HorizontalAlignment
*/
public function get horizontalAlign():String
{
return this._horizontalAlign;
}
/**
* @private
*/
public function set horizontalAlign(value:String):void
{
this._horizontalAlign = value;
this.dispatchEvent(new LayoutEvent(LayoutEvent.LAYOUT_CHANGE));
}
//--------------------------------------
// Public Methods
//--------------------------------------
/**
* @inheritDoc
*/
override public function layoutObjects(displayObjects:Array, bounds:Rectangle):Rectangle
{
var childrenInLayout:Array = this.configureChildren(displayObjects);
if(this.direction == "vertical")
{
this.layoutChildrenVertically(childrenInLayout, bounds);
}
else
{
this.layoutChildrenHorizontally(childrenInLayout, bounds);
}
bounds = LayoutModeUtil.calculateChildBounds(childrenInLayout);
bounds.width += this.paddingRight;
bounds.height += this.paddingBottom;
return bounds;
}
//--------------------------------------
// Protected Methods
//--------------------------------------
/**
* @private
* Positions the children when direction is vertical.
*/
protected function layoutChildrenVertically(children:Array, bounds:Rectangle):void
{
const START_Y:Number = bounds.y + this.paddingTop;
var xPosition:Number = bounds.x + this.paddingLeft;
var yPosition:Number = START_Y;
var maxChildWidth:Number = 0;
var column:Array = [];;
var childCount:int = children.length;
for(var i:int = 0; i < childCount; i++)
{
var child:DisplayObject = DisplayObject(children[i]);
//next column if we're over the height, but not if we're at yposition == bounds.y
var endOfColumn:Number = yPosition + child.height + this.paddingBottom;
if(endOfColumn - bounds.y >= bounds.height && yPosition != START_Y)
{
//update alignment
this.alignColumn(column, maxChildWidth, bounds);
xPosition += maxChildWidth + this.horizontalGap;
yPosition = START_Y;
maxChildWidth = 0;
column = [];
}
child.x = xPosition;
child.y = yPosition;
column.push(child);
maxChildWidth = Math.max(maxChildWidth, child.width);
yPosition += child.height + this.verticalGap;
}
this.alignColumn(column, maxChildWidth, bounds);
}
/**
* @private
* Positions the children when direction is horizontal.
*/
protected function layoutChildrenHorizontally(children:Array, bounds:Rectangle):void
{
const START_X:Number = bounds.x + this.paddingLeft;
var xPosition:Number = START_X;
var yPosition:Number = bounds.y + this.paddingTop;
var maxChildHeight:Number = 0;
var row:Array = [];
var childCount:int = children.length;
for(var i:int = 0; i < childCount; i++)
{
var child:DisplayObject = DisplayObject(children[i]);
var childWidth:Number = child.width;
var childHeight:Number = child.height;
//next row if we're over the width, but not if we're at xposition == bounds.x
var endOfRow:Number = xPosition + child.width + this.paddingRight;
if(endOfRow - bounds.x >= bounds.width && xPosition != START_X)
{
//update alignment
this.alignRow(row, maxChildHeight, bounds);
xPosition = START_X;
yPosition += maxChildHeight + this.verticalGap;
maxChildHeight = 0;
row = [];
}
child.x = xPosition;
child.y = yPosition;
row.push(child);
maxChildHeight = Math.max(maxChildHeight, childHeight);
xPosition += child.width + this.horizontalGap;
}
this.alignRow(row, maxChildHeight, bounds);
}
/**
* @private
* Repositions a column of children based on the alignment values.
*/
protected function alignColumn(column:Array, maxChildWidth:Number, bounds:Rectangle):void
{
if(column.length == 0)
{
return;
}
var lastChild:DisplayObject = DisplayObject(column[column.length - 1]);
var columnHeight:Number = (lastChild.y + lastChild.height) - bounds.y + this.paddingBottom;
var difference:Number = bounds.height - columnHeight;
var columnCount:int = column.length;
for(var i:int = 0; i < columnCount; i++)
{
var child:DisplayObject = DisplayObject(column[i]);
DisplayObjectUtil.align(child, new Rectangle(child.x, child.y, maxChildWidth, child.height), this.horizontalAlign, null);
switch(this.verticalAlign)
{
case "middle":
child.y += difference / 2;
break;
case "bottom":
child.y += difference;
break;
}
}
}
/**
* @private
* Repositions a row of children based on the alignment values.
*/
protected function alignRow(row:Array, maxChildHeight:Number, bounds:Rectangle):void
{
if(row.length == 0)
{
return;
}
var lastChild:DisplayObject = DisplayObject(row[row.length - 1]);
var rowWidth:Number = (lastChild.x + lastChild.width) - bounds.x + this.paddingRight;
var difference:Number = bounds.width - rowWidth;
var rowCount:int = row.length;
for(var i:int = 0; i < rowCount; i++)
{
var child:DisplayObject = DisplayObject(row[i]);
DisplayObjectUtil.align(child, new Rectangle(child.x, child.y, child.width, maxChildHeight), null, this.verticalAlign);
switch(this.horizontalAlign)
{
case "center":
child.x += difference / 2;
break;
case "right":
child.x += difference;
break;
}
}
}
}
}

View File

@ -0,0 +1,35 @@
/*
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.modes
{
/**
* A set of constants for horizontal alignment values used by
* the layout engine.
*
* @author Josh Tynjala
*/
public class HorizontalAlignment
{
//--------------------------------------
// Static Properties
//--------------------------------------
/**
* Items will be aligned to the left of the available bounds.
*/
public static const LEFT:String = "left";
/**
* Items will be aligned to the center of the available bounds.
*/
public static const CENTER:String = "center";
/**
* Items will be aligned to the right of the available bounds.
*/
public static const RIGHT:String = "right";
}
}

View File

@ -0,0 +1,41 @@
/*
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.modes
{
import flash.display.DisplayObject;
/**
* Defines the methods required for layout modes that have
* advanced configuration settings for individual children.
*
* @author Josh Tynjala
*/
public interface IAdvancedLayoutMode extends ILayoutMode
{
/**
* Registers a specific display object with the layout algorithm. If certain
* settings need to be specified for individual display objects, they
* should be passed to the layout algorithm here.
*
* @param target The client to add
* @param configuration An optional set of name-value pairs for the client's configuration.
*/
function addClient(target:DisplayObject, configuration:Object = null):void;
/**
* Unregisters a specific display object from the layout algorithm.
*
* @param target The client to remove
*/
function removeClient(target:DisplayObject):void;
/**
* Returns true if a display object has been registered as a client.
*
* @param The display object that may be registered
*/
function hasClient(target:DisplayObject):Boolean;
}
}

View File

@ -0,0 +1,40 @@
/*
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.modes
{
import flash.events.IEventDispatcher;
import flash.geom.Rectangle;
/**
* Defines the properties and functions required
* for layout modes used by ILayoutContainer.
*
* @author Josh Tynjala
*/
public interface ILayoutMode extends IEventDispatcher
{
//--------------------------------------
// Methods
//--------------------------------------
/**
* The DisplayObjects in the input parameter will be positioned and sized
* based on a specified rectangle. There is no requirement that the
* display objects remain entirely within the rectangle.
*
* <p>Returns the actual rectangular region in which the laid out
* children will appear. This may be larger or smaller than the
* suggested rectangle. This returned value is expected to be used by
* container components to determine if scrollbars or other navigation
* controls are needed.</p>
*
* @param displayObjects An Array of DisplayObjects to be laid out.
* @param bounds The rectangular region in which the display objects should be placed.
* @return The actual region in which the display objects are contained.
*/
function layoutObjects(displayObjects:Array, bounds:Rectangle):Rectangle;
}
}

View File

@ -0,0 +1,50 @@
/*
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.modes
{
import flash.display.DisplayObject;
import flash.geom.Rectangle;
/**
* Utility functions shared by implementations of ILayoutMode.
*
* @author Josh Tynjala
* @see ILayoutMode
*/
public class LayoutModeUtil
{
//--------------------------------------
// Static Methods
//--------------------------------------
/**
* Calculates the rectangular bounds occupied by the target's children.
*
* @param children The set of children to use to calculate the maximum bounds
*/
public static function calculateChildBounds(children:Array):Rectangle
{
var minX:Number = 0;
var maxX:Number = 0;
var minY:Number = 0;
var maxY:Number = 0;
var childCount:int = children.length;
for(var i:int = 0; i < childCount; i++)
{
var child:DisplayObject = DisplayObject(children[i]);
var childMaxX:Number = child.x + child.width;
var childMaxY:Number = child.y + child.height;
minX = Math.min(minX, child.x);
minY = Math.min(minY, child.y);
maxX = Math.max(maxX, childMaxX);
maxY = Math.max(maxY, childMaxY);
}
return new Rectangle(minX, minY, maxX - minX, maxY - minY);
}
}
}

View File

@ -0,0 +1,639 @@
/*
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
*/
/**
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at http://www.mozilla.org/MPL/.
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
* the specific language governing rights and limitations under the License.
*
* The Original Code is part of the Open Source Flex 3 SDK Downloads
* (http://opensource.adobe.com/wiki/display/flexsdk/Downloads)
*
* The Initial Developer of the Original Code is Adobe Systems Incorporated
* (see original files for appropriate copyright notices)
*
* Contributor(s): Yahoo! Inc.
*
* Copyright (c) 2008 Yahoo! Inc. All Rights Reserved.
*/
package com.yahoo.astra.layout.modes
{
import flash.display.DisplayObject;
/**
* Utility functions used for determining pixel-based sizes from percentage-based sizes.
*
* @author Josh Tynjala and Adobe Systems Inc.
*/
public class PercentageSizeUtil
{
//--------------------------------------
// Static Methods
//--------------------------------------
/**
* This function sets the width of each child so that the widths add up
* to spaceForChildren. Each child is set to its preferred width if its
* percentWidth is zero. If it's percentWidth is a positive number the
* child grows depending on the size of its parent. The height of each
* child is set to its preferred height. The return value is any extra
* space that's left over after growing all children to their maxWidth.
*/
public static function flexChildWidthsProportionally(children:Array, configurations:Array, totalWidth:Number, totalHeight:Number):Number
{
var spaceToDistribute:Number = totalWidth;
var totalPercentWidth:Number = 0;
var childInfoArray:Array = [];
// If the child is flexible, store information about it in the
// childInfoArray. For non-flexible children, just set the child's
// width and height immediately.
//
// Also calculate the sum of all widthFlexes, and calculate the
// sum of the width of all non-flexible children.
var childCount:int = children.length;
for(var i:int = 0; i < childCount; i++)
{
var child:DisplayObject = DisplayObject(children[i]);
var config:Object = configurations[i];
if(!config.includeInLayout)
{
//skip non-configuration children
continue;
}
var percentWidth:Number = config.percentWidth;
var percentHeight:Number = config.percentHeight;
var height:Number = NaN;
if(!isNaN(percentHeight))
{
height = Math.max(config.minHeight,
Math.min(config.maxHeight, ((percentHeight >= 100) ? totalHeight : totalHeight * percentHeight / 100)));
}
if(!isNaN(percentWidth))
{
totalPercentWidth += percentWidth;
var childInfo:ChildInfo = new ChildInfo();
childInfo.percent = percentWidth;
childInfo.min = config.minWidth;
childInfo.max = config.maxWidth;
childInfo.opposite = height;
childInfo.child = child;
childInfoArray.push(childInfo);
}
else
{
//var width:Number = child.width;
// if scaled and zoom is playing, best to let the sizes be non-integer
// otherwise the rounding creates an error that accumulates in some components like List
if(child.scaleX == 1 && child.scaleY == 1)
{
if(!isNaN(height))
{
child.height = Math.floor(height);
}
}
else
{
if(!isNaN(height))
{
child.height = height;
}
}
// Need to account for the actual child width since
// setActualSize may trigger a Resize effect, which
// could change the size of the component.
spaceToDistribute -= child.width;
}
}
// Distribute the extra space among the children.
if(totalPercentWidth)
{
spaceToDistribute = flexChildrenProportionally(totalWidth, spaceToDistribute, totalPercentWidth, childInfoArray);
// Set the widths and heights of the flexible children
childCount = childInfoArray.length;
for(i = 0; i < childCount; i++)
{
childInfo = ChildInfo(childInfoArray[i]);
child = childInfo.child;
// if scaled and zoom is playing, best to let the sizes be non-integer
// otherwise the rounding creates an error that accumulates in some components like List
if(child.scaleX == 1 && child.scaleY == 1)
{
child.width = Math.floor(childInfo.size);
if(!isNaN(childInfo.opposite))
{
child.height = Math.floor(childInfo.opposite);
}
}
else
{
child.width = childInfo.size;
if(!isNaN(childInfo.opposite))
{
child.height = childInfo.opposite;
}
}
}
distributeExtraWidth(children, configurations, totalWidth);
}
return spaceToDistribute;
}
/**
* This function sets the height of each child
* so that the heights add up to spaceForChildren.
* Each child is set to its preferred height
* if its percentHeight is zero.
* If its percentHeight is a positive number,
* the child grows (or shrinks) to consume its share of extra space.
* The width of each child is set to its preferred width.
* The return value is any extra space that's left over
* after growing all children to their maxHeight.
*/
public static function flexChildHeightsProportionally(children:Array, configurations:Array,
totalWidth:Number, totalHeight:Number):Number
{
var spaceToDistribute:Number = totalHeight;
var totalPercentHeight:Number = 0;
var childInfoArray:Array = [];
// If the child is flexible, store information about it in the
// childInfoArray. For non-flexible children, just set the child's
// width and height immediately.
//
// Also calculate the sum of all percentHeights, and calculate the
// sum of the height of all non-flexible children.
var childCount:int = children.length;
for(var i:int = 0; i < childCount; i++)
{
var child:DisplayObject = DisplayObject(children[i]);
var config:Object = configurations[i];
if(!config.includeInLayout)
{
//skip children that aren't in the layout
continue;
}
var percentWidth:Number = config.percentWidth;
var percentHeight:Number = config.percentHeight;
var width:Number = NaN;
if(!isNaN(percentWidth))
{
width = Math.max(config.minWidth, Math.min(config.maxWidth,
((percentWidth >= 100) ? totalWidth : totalWidth * percentWidth / 100)));
}
if(!isNaN(percentHeight))
{
totalPercentHeight += percentHeight;
var childInfo:ChildInfo = new ChildInfo();
childInfo.percent = percentHeight;
childInfo.min = config.minHeight;
childInfo.max = config.maxHeight;
childInfo.opposite = width;
childInfo.child = child;
childInfoArray.push(childInfo);
}
else
{
if(child.scaleX == 1 && child.scaleY == 1)
{
if(!isNaN(width))
{
child.width = Math.floor(width);
}
}
else
{
if(!isNaN(width))
{
child.width = width;
}
}
// Need to account for the actual child height since
// setActualSize may trigger a Resize effect, which
// could change the size of the component.
spaceToDistribute -= child.height;
}
}
// Distribute the extra space among the children.
if(totalPercentHeight)
{
spaceToDistribute = flexChildrenProportionally(totalHeight, spaceToDistribute, totalPercentHeight, childInfoArray);
// Set the widths and heights of the flexible children
childCount = childInfoArray.length;
for(i = 0; i < childCount; i++)
{
childInfo = ChildInfo(childInfoArray[i]);
child = childInfo.child;
// if scaled and zoom is playing, best to let the sizes be non-integer
// otherwise the rounding creates an error that accumulates in some components like List
if(child.scaleX == 1 && child.scaleY == 1)
{
if(!isNaN(childInfo.opposite))
{
child.width = Math.floor(childInfo.opposite);
}
child.height = Math.floor(childInfo.size);
}
else
{
if(!isNaN(childInfo.opposite))
{
child.width = childInfo.opposite;
}
child.height = childInfo.size;
}
}
distributeExtraHeight(children, configurations, totalHeight);
}
return spaceToDistribute;
}
/**
* This function distributes excess space among the flexible children.
* It does so with a view to keep the children's overall size
* close the ratios specified by their percent.
*
* @param spaceForChildren The total space for all children
*
* @param spaceToDistribute The space that needs to be distributed
* among the flexible children.
*
* @param childInfoArray An array of Objects. When this function
* is called, each object should define the following properties:
* - percent: the percentWidth or percentHeight of the child (depending
* on whether we're growing in a horizontal or vertical direction)
* - min: the minimum width (or height) for that child
* - max: the maximum width (or height) for that child
*
* @return When this function finishes executing, a "size" property
* will be defined for each child object. The size property contains
* the portion of the spaceToDistribute to be distributed to the child.
* Ideally, the sum of all size properties is spaceToDistribute.
* If all the children hit their minWidth/maxWidth/minHeight/maxHeight
* before the space was distributed, then the remaining unused space
* is returned. Otherwise, the return value is zero.
*/
public static function flexChildrenProportionally(spaceForChildren:Number, spaceToDistribute:Number,
totalPercent:Number, childInfoArray:Array):Number
{
// The algorithm iterivately attempts to break down the space that
// is consumed by "flexible" containers into ratios that are related
// to the percentWidth/percentHeight of the participating containers.
var numChildren:int = childInfoArray.length;
var flexConsumed:Number; // space consumed by flexible compontents
var done:Boolean;
// We now do something a little tricky so that we can
// support partial filling of the space. If our total
// percent < 100% then we can trim off some space.
var unused:Number = spaceToDistribute - (spaceForChildren * totalPercent / 100);
if(unused > 0)
{
spaceToDistribute -= unused;
}
// Continue as long as there are some remaining flexible children.
// The "done" flag isn't strictly necessary, except that it catches
// cases where round-off error causes totalPercent to not exactly
// equal zero.
do
{
flexConsumed = 0; // space consumed by flexible compontents
done = true; // we are optimistic
// Space for flexible children is the total amount of space
// available minus the amount of space consumed by non-flexible
// components.Divide that space in proportion to the percent
// of the child
var spacePerPercent:Number = spaceToDistribute / totalPercent;
// Attempt to divide out the space using our percent amounts,
// if we hit its limit then that control becomes 'non-flexible'
// and we run the whole space to distribute calculation again.
for(var i:int = 0; i < numChildren; i++)
{
var childInfo:ChildInfo = ChildInfo(childInfoArray[i]);
// Set its size in proportion to its percent.
var size:Number = childInfo.percent * spacePerPercent;
// If our flexiblity calc say grow/shrink more than we are
// allowed, then we grow/shrink whatever we can, remove
// ourselves from the array for the next pass, and start
// the loop over again so that the space that we weren't
// able to consume / release can be re-used by others.
if(size < childInfo.min)
{
var min:Number = childInfo.min;
childInfo.size = min;
// Move this object to the end of the array
// and decrement the length of the array.
// This is slightly expensive, but we don't expect
// to hit these min/max limits very often.
childInfoArray[i] = childInfoArray[--numChildren];
childInfoArray[numChildren] = childInfo;
totalPercent -= childInfo.percent;
spaceToDistribute -= min;
done = false;
break;
}
else if(size > childInfo.max)
{
var max:Number = childInfo.max;
childInfo.size = max;
childInfoArray[i] = childInfoArray[--numChildren];
childInfoArray[numChildren] = childInfo;
totalPercent -= childInfo.percent;
spaceToDistribute -= max;
done = false;
break;
}
else
{
// All is well, let's carry on...
childInfo.size = size;
flexConsumed += size;
}
}
}
while(!done);
return Math.max(0, Math.floor(spaceToDistribute - flexConsumed))
}
/**
* This function distributes excess space among the flexible children
* because of rounding errors where we want to keep children's dimensions
* full pixel amounts. This only distributes the extra space
* if there was some rounding down and there are still
* flexible children.
*
* @param parent The parent container of the children.
*
* @param spaceForChildren The total space for all children
*/
public static function distributeExtraHeight(children:Array, configurations:Array, spaceForChildren:Number):void
{
// We should only get here after distributing the majority of the
// space already. This is done in flexChildHeightsProportionally.
// Strategy here is to keep adding 1 pixel at a time to each
// component that's flexible (percentHeight defined and hasn't
// reached maxHeight yet). We could use another approach where
// we add more than a pixel at a time, but we'd have to first
// calculate exactly how many flexible components we have first
// and see how much space we can add to them without hitting
// their maxHeight. Since we're just dealing with rounding
// issues, we should only make one pass here (if we hit maxHeight
// problems, we might make more than one, but not many more).
// We just distribute from the top-down and don't care about
// who was "rounded down the most"
// First check if we should distribute any extra space. To do
// this, we check to see if someone suffers from rounding error.
var wantToGrow:Boolean = false;
var percentHeight:Number;
var spaceToDistribute:Number = spaceForChildren;
var spaceUsed:Number = 0;
var childHeight:Number;
var wantSpace:Number;
var childCount:int = children.length;
for(var i:int = 0; i < childCount; i++)
{
var child:DisplayObject = DisplayObject(children[i]);
var config:Object = configurations[i];
if(!config.includeInLayout)
{
continue;
}
childHeight = child.height;
percentHeight = config.percentHeight;
spaceUsed += childHeight;
if(!isNaN(percentHeight))
{
wantSpace = Math.ceil(percentHeight/100 * spaceForChildren);
if(wantSpace > childHeight)
{
wantToGrow = true;
}
}
}
// No need to distribute extra size
if(!wantToGrow)
{
return;
}
// Start distributing...
spaceToDistribute -= spaceUsed;
// If we still have components that will let us
// distribute to them
var stillFlexibleComponents:Boolean = true;
while(stillFlexibleComponents && spaceToDistribute > 0)
{
// Start optimistically
stillFlexibleComponents = false;
for(i = 0; i < childCount; i++)
{
child = DisplayObject(children[i]);
config = configurations[i];
childHeight = child.height;
percentHeight = config.percentHeight;
// if they have a percentHeight, and we won't reach their
// maxHeight by giving them one more pixel, then
// give them a pixel
if(!isNaN(percentHeight) &&
config.includeInLayout &&
childHeight < config.maxHeight)
{
wantSpace = Math.ceil(percentHeight/100 * spaceForChildren);
if(wantSpace > childHeight)
{
child.height = childHeight + 1;
spaceToDistribute--;
stillFlexibleComponents = true;
if(spaceToDistribute == 0)
{
return;
}
}
}
}
}
}
/**
* This function distributes excess space among the flexible children
* because of rounding errors where we want to keep children's dimensions
* full pixel amounts. This only distributes the extra space
* if there was some rounding down and there are still
* flexible children.
*
* @param parent The parent container of the children.
*
* @param spaceForChildren The total space for all children
*/
public static function distributeExtraWidth(children:Array, configurations:Array, spaceForChildren:Number):void
{
// We should only get here after distributing the majority of the
// space already. This is done in flexChildWidthsProportionally.
// Strategy here is to keep adding 1 pixel at a time to each
// component that's flexible (percentWidth defined and hasn't
// reached maxWidth yet). We could use another approach where
// we add more than a pixel at a time, but we'd have to first
// calculate exactly how many flexible components we have first
// and see how much space we can add to them without hitting
// their maxWidth. Since we're just dealing with rounding
// issues, we should only make one pass here (if we hit maxWidth
// problems, we might make more than one, but not many more).
// We just distribute from the top-down and don't care about
// who was "rounded down the most"
// First check if we should distribute any extra space. To do
// this, we check to see if someone suffers from rounding error.
var childCount:int = children.length;
var wantToGrow:Boolean = false;
var percentWidth:Number;
var spaceToDistribute:Number = spaceForChildren;
var spaceUsed:Number = 0;
var childWidth:Number;
var wantSpace:Number;
for(var i:int = 0; i < childCount; i++)
{
var child:DisplayObject = DisplayObject(children[i]);
var config:Object = configurations[i];
if(!config.includeInLayout)
{
continue;
}
childWidth = child.width;
percentWidth = config.percentWidth;
spaceUsed += childWidth;
if(!isNaN(percentWidth))
{
wantSpace = Math.ceil(percentWidth / 100 * spaceForChildren);
if(wantSpace > childWidth)
{
wantToGrow = true;
}
}
}
// No need to distribute extra size
if(!wantToGrow)
{
return;
}
// Start distributing...
spaceToDistribute -= spaceUsed;
// If we still have components that will let us
// distribute to them
var stillFlexibleComponents:Boolean = true;
while(stillFlexibleComponents && spaceToDistribute > 0)
{
// Start optimistically
stillFlexibleComponents = false;
for(i = 0; i < childCount; i++)
{
child = DisplayObject(children[i]);
config = configurations[i];
childWidth = child.width;
percentWidth = config.percentWidth;
// if they have a percentWidth, and we won't reach their
// maxWidth by giving them one more pixel, then
// give them a pixel
if(!isNaN(percentWidth) && config.includeInLayout && childWidth < config.maxWidth)
{
wantSpace = Math.ceil(percentWidth / 100 * spaceForChildren);
if(wantSpace > childWidth)
{
child.width = childWidth + 1;
spaceToDistribute--;
stillFlexibleComponents = true;
if(spaceToDistribute == 0)
{
return;
}
}
}
}
}
}
}
}
import flash.display.DisplayObject;
class ChildInfo
{
public var max:Number;
public var min:Number;
public var child:DisplayObject;
public var percent:Number;
public var size:Number;
public var opposite:Number;
}

View File

@ -0,0 +1,362 @@
/*
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.modes
{
import com.yahoo.astra.layout.events.LayoutEvent;
import com.yahoo.astra.utils.DisplayObjectUtil;
import flash.display.DisplayObject;
import flash.geom.Rectangle;
/**
* Arranges a DisplayObjectContainer's children using a tiling algorithm.
* All tiles are the same size and tile dimensions are determined from the
* maximum width or height values of the available children.
*
* @example The following code configures a TileLayout instance and passes it to a container:
* <listing version="3.0">
* var tile:TileLayout = new TileLayout();
* tile.direction = "horizontal";
* tile.horizontalGap = 1;
* tile.verticalGap = 4;
* tile.horizontalAlign = HorizontalAlignment.CENTER;
* tile.verticalAlign = VerticalAlignment.MIDDLE;
*
* var container:LayoutContainer = new LayoutContainer();
* container.layoutMode = tile;
* this.addChild( container );
* </listing>
*
* <p><strong>Advanced Client Options:</strong></p>
* <p>Optional client configuration parameters allow a developer to specify
* behaviors for individual children of the target container. To set these
* advanced options, one must call <code>addClient()</code> on the TileLayout
* instance and pass the child to configure along with an object specifying
* the configuration parameters. The following client parameters are available to
* the TileLayout algorithm:</p>
*
* <dl>
* <dt><strong><code>includeInLayout</code></strong> : Boolean</dt>
* <dd>If <code>false</code>, the target will not be included in layout calculations. The default value is <code>true</code>.</dd>
* </dl>
*
* @author Josh Tynjala
* @see com.yahoo.astra.layout.LayoutContainer
*/
public class TileLayout extends BaseLayoutMode implements IAdvancedLayoutMode
{
//--------------------------------------
// Constructor
//--------------------------------------
/**
* Constructor.
*/
public function TileLayout()
{
}
//--------------------------------------
// Properties
//--------------------------------------
/**
* @private
* Storage for the direction property.
*/
private var _direction:String = "horizontal";
/**
* The direction in which children of the target are laid out. Once
* the edge of the container is reached, the children will begin
* appearing on the next row or column. Valid direction values include
* <code>"vertical"</code> or <code>"horizontal"</code>.
*/
public function get direction():String
{
return this._direction;
}
/**
* @private
*/
public function set direction(value:String):void
{
this._direction = value;
this.dispatchEvent(new LayoutEvent(LayoutEvent.LAYOUT_CHANGE));
}
/**
* @private
* Storage for the verticalGap property.
*/
private var _verticalGap:Number = 0;
/**
* The number of pixels appearing between the target's children
* vertically.
*/
public function get verticalGap():Number
{
return this._verticalGap;
}
/**
* @private
*/
public function set verticalGap(value:Number):void
{
this._verticalGap = value;
this.dispatchEvent(new LayoutEvent(LayoutEvent.LAYOUT_CHANGE));
}
/**
* @private
* Storage for the horizontalGap property.
*/
private var _horizontalGap:Number = 0;
/**
* The number of pixels appearing between the target's children
* horizontally.
*/
public function get horizontalGap():Number
{
return this._horizontalGap;
}
/**
* @private
*/
public function set horizontalGap(value:Number):void
{
this._horizontalGap = value;
this.dispatchEvent(new LayoutEvent(LayoutEvent.LAYOUT_CHANGE));
}
/**
* @private
* Storage for the verticalAlign property.
*/
private var _verticalAlign:String = "top";
/**
* The children of the target may be aligned vertically within their
* respective tiles.
*
* @see VerticalAlignment
*/
public function get verticalAlign():String
{
return this._verticalAlign;
}
/**
* @private
*/
public function set verticalAlign(value:String):void
{
this._verticalAlign = value;
this.dispatchEvent(new LayoutEvent(LayoutEvent.LAYOUT_CHANGE));
}
/**
* @private
* Storage for the horizontalAlign property.
*/
private var _horizontalAlign:String = "left";
/**
* The children of the target may be aligned horizontally within their
* respective tiles.
*
* @see HorizontalAlignment
*/
public function get horizontalAlign():String
{
return this._horizontalAlign;
}
/**
* @private
*/
public function set horizontalAlign(value:String):void
{
this._horizontalAlign = value;
this.dispatchEvent(new LayoutEvent(LayoutEvent.LAYOUT_CHANGE));
}
/**
* @private
* Storage for the tileWidth property.
*/
private var _tileWidth:Number = NaN;
/**
* The width of tiles displayed in the target. If NaN, the tile width
* will be calculated based on the maximum width among the target's children.
*/
public function get tileWidth():Number
{
return this._tileWidth;
}
/**
* @private
*/
public function set tileWidth(value:Number):void
{
this._tileWidth = value;
this.dispatchEvent(new LayoutEvent(LayoutEvent.LAYOUT_CHANGE));
}
/**
* @private
* Storage for the tileHeight property.
*/
private var _tileHeight:Number = NaN;
/**
* The height of tiles displayed in the target. If NaN, the tile height
* will be calculated based on the maximum height among the target's children.
*/
public function get tileHeight():Number
{
return this._tileHeight;
}
/**
* @private
*/
public function set tileHeight(value:Number):void
{
this._tileHeight = value;
this.dispatchEvent(new LayoutEvent(LayoutEvent.LAYOUT_CHANGE));
}
/**
* @private
* The maximum width value from among the current target's children.
*/
protected var maxChildWidth:Number;
/**
* @private
* The maximum height value from among the current target's children.
*/
protected var maxChildHeight:Number;
//--------------------------------------
// Public Methods
//--------------------------------------
/**
* @inheritDoc
*/
override public function layoutObjects(displayObjects:Array, bounds:Rectangle):Rectangle
{
var childrenInLayout:Array = this.configureChildren(displayObjects);
this.maxChildWidth = this.maxChildHeight = 0;
var childCount:int = displayObjects.length;
for(var i:int = 0; i < childCount; i++)
{
var child:DisplayObject = DisplayObject(displayObjects[i]);
this.maxChildWidth = Math.max(this.maxChildWidth, child.width);
this.maxChildHeight = Math.max(this.maxChildHeight, child.height);
}
if(!isNaN(this.tileWidth))
{
this.maxChildWidth = this.tileWidth;
}
if(!isNaN(this.tileHeight))
{
this.maxChildHeight = this.tileHeight;
}
if(this.direction == "vertical")
{
this.layoutChildrenVertically(childrenInLayout, bounds);
}
else
{
this.layoutChildrenHorizontally(childrenInLayout, bounds);
}
var bounds:Rectangle = LayoutModeUtil.calculateChildBounds(childrenInLayout);
bounds.width += this.paddingRight;
bounds.height += this.paddingBottom;
return bounds;
}
//--------------------------------------
// Protected Methods
//--------------------------------------
/**
* @private
* Positions the children when direction is vertical.
*/
protected function layoutChildrenVertically(children:Array, bounds:Rectangle):void
{
const START_Y:Number = bounds.y + this.paddingTop;
var xPosition:Number = bounds.x + this.paddingLeft;
var yPosition:Number = START_Y;
var childCount:int = children.length;
for(var i:int = 0; i < childCount; i++)
{
var child:DisplayObject = DisplayObject(children[i]);
var endOfColumn:Number = yPosition + this.maxChildHeight + this.paddingBottom;
if(endOfColumn - bounds.y >= bounds.height && yPosition != START_Y)
{
//next column if we're over the height,
//but not if we're at yposition == START_Y
yPosition = START_Y;
xPosition += this.maxChildWidth + this.horizontalGap;
}
DisplayObjectUtil.align(child, new Rectangle(xPosition, yPosition, this.maxChildWidth, this.maxChildHeight), this.horizontalAlign, this.verticalAlign);
yPosition += this.maxChildHeight + this.verticalGap;
}
}
/**
* @private
* Positions the children when direction is horizontal.
*/
protected function layoutChildrenHorizontally(children:Array, bounds:Rectangle):void
{
const START_X:Number = bounds.x + this.paddingLeft;
var xPosition:Number = START_X;
var yPosition:Number = bounds.y + this.paddingTop;
var childCount:int = children.length;
for(var i:int = 0; i < childCount; i++)
{
var child:DisplayObject = DisplayObject(children[i]);
var endOfRow:Number = xPosition + this.maxChildWidth + this.paddingRight;
if(endOfRow - bounds.x >= bounds.width && xPosition != START_X)
{
//next row if we're over the width,
//but not if we're at xposition == START_X
xPosition = START_X;
yPosition += this.maxChildHeight + this.verticalGap;
}
DisplayObjectUtil.align(child, new Rectangle(xPosition, yPosition, this.maxChildWidth, this.maxChildHeight), this.horizontalAlign, this.verticalAlign);
xPosition += this.maxChildWidth + this.horizontalGap;
}
}
}
}

View File

@ -0,0 +1,35 @@
/*
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.modes
{
/**
* A set of constants for vertical alignment values used by
* the layout engine.
*
* @author Josh Tynjala
*/
public class VerticalAlignment
{
//--------------------------------------
// Static Properties
//--------------------------------------
/**
* Items will be aligned to the top of the available bounds.
*/
public static const TOP:String = "top";
/**
* Items will be aligned to the middle of the available bounds.
*/
public static const MIDDLE:String = "middle";
/**
* Items will be aligned to the bottom of the available bounds.
*/
public static const BOTTOM:String = "bottom";
}
}