453 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			ActionScript
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			453 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			ActionScript
		
	
	
		
			Executable File
		
	
	
	
	
| /*
 | |
| 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;
 | |
| 		}
 | |
| 	}
 | |
| } |