/***************************************************** * * Copyright 2009 Adobe Systems Incorporated. All Rights Reserved. * ***************************************************** * 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 Initial Developer of the Original Code is Adobe Systems Incorporated. * Portions created by Adobe Systems Incorporated are Copyright (C) 2009 Adobe Systems * Incorporated. All Rights Reserved. * *****************************************************/ package org.osmf.layout { import __AS3__.vec.Vector; import flash.geom.Point; import flash.geom.Rectangle; import flash.utils.Dictionary; import org.osmf.metadata.MetadataNamespaces; import org.osmf.metadata.MetadataWatcher; CONFIG::LOGGING { import org.osmf.logging.Logger; } /** * A layout renderer that sizes and positions its targets using the LayoutMetadata * that it looks for on its targets. * * @see org.osmf.layout.LayoutMetadata * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 1.5 * @productversion OSMF 1.0 */ public class LayoutRenderer extends LayoutRendererBase { // Overrides // /** * @private */ override protected function get usedMetadatas():Vector. { return USED_METADATAS; } /** * @private */ override protected function processContainerChange(oldContainer:ILayoutTarget, newContainer:ILayoutTarget):void { if (oldContainer) { containerAbsoluteWatcher.unwatch(); containerAttributesWatcher.unwatch(); } if (newContainer) { containerAbsoluteWatcher = new MetadataWatcher ( newContainer.layoutMetadata , MetadataNamespaces.ABSOLUTE_LAYOUT_PARAMETERS , null , function (..._):void { invalidate(); } ); containerAbsoluteWatcher.watch(); containerAttributesWatcher = new MetadataWatcher ( newContainer.layoutMetadata , MetadataNamespaces.LAYOUT_ATTRIBUTES , null , function (facet:LayoutAttributesMetadata):void { layoutMode = facet ? facet.layoutMode : LayoutMode.NONE invalidate(); } ); containerAttributesWatcher.watch(); } invalidate(); } /** * @private */ override protected function processUpdateMediaDisplayBegin(targets:Vector.):void { lastCalculatedBounds = null; } /** * @private */ override protected function processUpdateMediaDisplayEnd():void { lastCalculatedBounds = null; } /** * @private */ override protected function processTargetAdded(target:ILayoutTarget):void { var attributes:LayoutAttributesMetadata = target.layoutMetadata.getValue(MetadataNamespaces.LAYOUT_ATTRIBUTES) as LayoutAttributesMetadata; // If no layout properties are set on the target ... var relative:RelativeLayoutMetadata = target.layoutMetadata.getValue(MetadataNamespaces.RELATIVE_LAYOUT_PARAMETERS) as RelativeLayoutMetadata; if ( layoutMode == LayoutMode.NONE && relative == null && attributes == null && target.layoutMetadata.getValue(MetadataNamespaces.ABSOLUTE_LAYOUT_PARAMETERS) == null && target.layoutMetadata.getValue(MetadataNamespaces.ANCHOR_LAYOUT_PARAMETERS) == null ) { // Set target to take 100% of their container's width and height relative = new RelativeLayoutMetadata(); relative.width = 100; relative.height = 100; target.layoutMetadata.addValue(MetadataNamespaces.RELATIVE_LAYOUT_PARAMETERS, relative); // Set target to scale letter box layoutMode, centered, by default: attributes = new LayoutAttributesMetadata(); attributes.scaleMode ||= ScaleMode.LETTERBOX; attributes.verticalAlign ||= VerticalAlign.MIDDLE; attributes.horizontalAlign ||= HorizontalAlign.CENTER; target.layoutMetadata.addValue(MetadataNamespaces.LAYOUT_ATTRIBUTES, attributes); } // Watch the index metadata attribute for change: // var watcher:MetadataWatcher = new MetadataWatcher ( target.layoutMetadata , MetadataNamespaces.LAYOUT_ATTRIBUTES , LayoutAttributesMetadata.INDEX , function(..._):void { updateTargetOrder(target); } ); watcher.watch(); targetMetadataWatchers[target] = watcher; } /** * @private */ override protected function processTargetRemoved(target:ILayoutTarget):void { var watcher:MetadataWatcher = targetMetadataWatchers[target]; delete targetMetadataWatchers[target]; watcher.unwatch(); watcher = null; } /** * @private * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 1.5 * @productversion OSMF 1.0 */ override protected function compareTargets(x:ILayoutTarget, y:ILayoutTarget):Number { var attributesX:LayoutAttributesMetadata = x.layoutMetadata.getValue(MetadataNamespaces.LAYOUT_ATTRIBUTES) as LayoutAttributesMetadata; var attributesY:LayoutAttributesMetadata = y.layoutMetadata.getValue(MetadataNamespaces.LAYOUT_ATTRIBUTES) as LayoutAttributesMetadata; var indexX:Number = attributesX ? attributesX.index : NaN; var indexY:Number = attributesY ? attributesY.index : NaN; if (isNaN(indexX) && isNaN(indexY)) { return 1; } else { indexX ||= 0; indexY ||= 0; return indexX < indexY ? -1 : indexX > indexY ? 1 : 0; } } /** * @private * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 1.5 * @productversion OSMF 1.0 */ override protected function calculateTargetBounds(target:ILayoutTarget, availableWidth:Number, availableHeight:Number):Rectangle { var attributes:LayoutAttributesMetadata = target.layoutMetadata.getValue(MetadataNamespaces.LAYOUT_ATTRIBUTES) as LayoutAttributesMetadata || new LayoutAttributesMetadata(); if (attributes.includeInLayout == false) { return new Rectangle(); } var rect:Rectangle = new Rectangle(0, 0, target.measuredWidth, target.measuredHeight); var absolute:AbsoluteLayoutMetadata = target.layoutMetadata.getValue(MetadataNamespaces.ABSOLUTE_LAYOUT_PARAMETERS) as AbsoluteLayoutMetadata; var deltaX:Number; var deltaXExclude:Number = 0; var deltaY:Number; var deltaYExclude:Number = 0; var toDo:int = ALL; // Next, get all absolute layout values, if available: if (absolute) { if (!isNaN(absolute.x)) { rect.x = absolute.x; toDo ^= X; } if (!isNaN(absolute.y)) { rect.y = absolute.y; toDo ^= Y; } if (!isNaN(absolute.width)) { rect.width = absolute.width; toDo ^= WIDTH; } if (!isNaN(absolute.height)) { rect.height = absolute.height; toDo ^= HEIGHT; } } // If not all position and size fieds have been set yet, then continue // processing relative parameters: if (toDo != 0) { var box:BoxAttributesMetadata; var relative:RelativeLayoutMetadata = target.layoutMetadata.getValue(MetadataNamespaces.RELATIVE_LAYOUT_PARAMETERS) as RelativeLayoutMetadata; if (relative) { if ((toDo & X) && !isNaN(relative.x)) { rect.x = (availableWidth * relative.x) / 100 || 0; toDo ^= X; } if ((toDo & WIDTH) && !isNaN(relative.width)) { if (layoutMode == LayoutMode.HORIZONTAL) { box = container.layoutMetadata.getValue(MetadataNamespaces.BOX_LAYOUT_ATTRIBUTES) as BoxAttributesMetadata || new BoxAttributesMetadata(); rect.width = ( Math.max(0, availableWidth - box.absoluteSum) * relative.width ) / box.relativeSum; } else { rect.width = (availableWidth * relative.width) / 100; } toDo ^= WIDTH; } if ((toDo & Y) && !isNaN(relative.y)) { rect.y = (availableHeight * relative.y) / 100 || 0; toDo ^= Y; } if ((toDo & HEIGHT) && !isNaN(relative.height)) { if (layoutMode == LayoutMode.VERTICAL) { box = container.layoutMetadata.getValue(MetadataNamespaces.BOX_LAYOUT_ATTRIBUTES) as BoxAttributesMetadata || new BoxAttributesMetadata(); rect.height = ( Math.max(0, availableHeight - box.absoluteSum) * relative.height ) / box.relativeSum; } else { rect.height = (availableHeight * relative.height) / 100; } toDo ^= HEIGHT; } } } // Last, do anchors: (doing them last is a natural order because we require // a set width and x to do 'right', as well as a set height and y to do // 'bottom') if (toDo != 0) { var anchors:AnchorLayoutMetadata = target.layoutMetadata.getValue(MetadataNamespaces.ANCHOR_LAYOUT_PARAMETERS) as AnchorLayoutMetadata; // Process the anchor parameters: if (anchors) { if ((toDo & X) && !isNaN(anchors.left)) { rect.x = anchors.left; toDo ^= X; } if ((toDo & Y) && !isNaN(anchors.top)) { rect.y = anchors.top; toDo ^= Y; } if (!isNaN(anchors.right) && availableWidth) { if ((toDo & X) && !(toDo & WIDTH)) { rect.x = Math.max(0, availableWidth - rect.width - anchors.right); toDo ^= X; } else if ((toDo & WIDTH) && !(toDo & X)) { rect.width = Math.max(0, availableWidth - anchors.right - rect.x); toDo ^= WIDTH; } else { rect.x = Math.max(0, availableWidth - target.measuredWidth - anchors.right); toDo ^= X; } deltaXExclude += anchors.right; } if (!isNaN(anchors.bottom) && availableHeight) { if ((toDo & Y) && !(toDo & HEIGHT)) { rect.y = Math.max(0, availableHeight - rect.height - anchors.bottom); toDo ^= Y; } else if ((toDo & HEIGHT) && !(toDo & Y)) { rect.height = Math.max(0, availableHeight - anchors.bottom - rect.y); toDo ^= HEIGHT; } else { rect.y = Math.max(0, availableHeight - target.measuredHeight - anchors.bottom); toDo ^= Y; } deltaYExclude += anchors.bottom; } } } // Apply padding, if set. Note the bottom and right padding can only be // applied when a height and width value are available! var padding:PaddingLayoutMetadata = target.layoutMetadata.getValue(MetadataNamespaces.PADDING_LAYOUT_PARAMETERS) as PaddingLayoutMetadata; if (padding) { if (!isNaN(padding.left)) { rect.x += padding.left; } if (!isNaN(padding.top)) { rect.y += padding.top; } if (!isNaN(padding.right) && !(toDo & WIDTH)) { rect.width -= padding.right + (padding.left || 0); } if (!isNaN(padding.bottom) && !(toDo & HEIGHT)) { rect.height -= padding.bottom + (padding.top || 0); } } // Apply scaling layoutMode: if (attributes.scaleMode) { if (! ( toDo & WIDTH || toDo & HEIGHT) && target.measuredWidth && target.measuredHeight ) { var size:Point = ScaleModeUtils.getScaledSize ( attributes.scaleMode , rect.width , rect.height , target.measuredWidth , target.measuredHeight ); deltaX = rect.width - size.x; deltaY = rect.height - size.y; rect.width = size.x; rect.height = size.y; } } // Set deltas: if (layoutMode != LayoutMode.HORIZONTAL) { deltaX ||= availableWidth - (rect.x || 0) - (rect.width || 0) - deltaXExclude; } if (layoutMode != LayoutMode.VERTICAL) { deltaY ||= availableHeight - (rect.y || 0) - (rect.height || 0) - deltaYExclude; } // Apply alignment (if there's surpluss space reported:) if (deltaY) { switch (attributes.verticalAlign) { case null: case VerticalAlign.TOP: // all set. break; case VerticalAlign.MIDDLE: rect.y += deltaY / 2; break; case VerticalAlign.BOTTOM: rect.y += deltaY; break; } } if (deltaX) { switch (attributes.horizontalAlign) { case null: case HorizontalAlign.LEFT: // all set. break; case HorizontalAlign.CENTER: rect.x += deltaX / 2; break; case HorizontalAlign.RIGHT: rect.x += deltaX; break; } } // Apply pixel snapping: if (attributes.snapToPixel) { rect.x = Math.round(rect.x); rect.y = Math.round(rect.y); rect.width = Math.round(rect.width); rect.height = Math.round(rect.height); } if (layoutMode == LayoutMode.HORIZONTAL || layoutMode == LayoutMode.VERTICAL) { if (lastCalculatedBounds != null) { // Apply either the x or y coordinate to apply the desired boxing // behavior: if (layoutMode == LayoutMode.HORIZONTAL) { rect.x = lastCalculatedBounds.x + lastCalculatedBounds.width; } else // layoutMode == VERTICAL { rect.y = lastCalculatedBounds.y + lastCalculatedBounds.height; } } lastCalculatedBounds = rect; } CONFIG::LOGGING { logger.debug ( "dimensions: {0} available: ({1}, {2}), media: ({3},{4})" , rect , availableWidth, availableHeight , target.measuredWidth, target.measuredHeight ); } return rect; } /** * @private */ override protected function calculateContainerSize(targets:Vector.):Point { var size:Point = new Point(NaN, NaN); var absolute:AbsoluteLayoutMetadata = container.layoutMetadata.getValue(MetadataNamespaces.ABSOLUTE_LAYOUT_PARAMETERS) as AbsoluteLayoutMetadata; if (absolute) { size.x = absolute.width; size.y = absolute.height; } if (layoutMode != LayoutMode.NONE) { var boxAttributes:BoxAttributesMetadata = new BoxAttributesMetadata(); container.layoutMetadata.addValue(MetadataNamespaces.BOX_LAYOUT_ATTRIBUTES, boxAttributes); } if (isNaN(size.x) || isNaN(size.y) || layoutMode != LayoutMode.NONE) { // Iterrate over all targets, calculating their bounds, combining the results // into a bounds rectangle: var containerBounds:Rectangle = new Rectangle(); var targetBounds:Rectangle; var lastBounds:Rectangle; for each (var target:ILayoutTarget in targets) { if (target.layoutMetadata.includeInLayout) { targetBounds = calculateTargetBounds(target, size.x, size.y); targetBounds.x ||= 0; targetBounds.y ||= 0; targetBounds.width ||= target.measuredWidth || 0; targetBounds.height ||= target.measuredHeight || 0; if (layoutMode == LayoutMode.HORIZONTAL) { if (!isNaN(target.layoutMetadata.percentWidth)) { boxAttributes.relativeSum += target.layoutMetadata.percentWidth; } else { boxAttributes.absoluteSum += targetBounds.width; } if (lastBounds) { targetBounds.x = lastBounds.x + lastBounds.width; } lastBounds = targetBounds; } else if (layoutMode == LayoutMode.VERTICAL) { if (!isNaN(target.layoutMetadata.percentHeight)) { boxAttributes.relativeSum += target.layoutMetadata.percentHeight; } else { boxAttributes.absoluteSum += targetBounds.height; } if (lastBounds) { targetBounds.y = lastBounds.y + lastBounds.height; } lastBounds = targetBounds; } containerBounds = containerBounds.union(targetBounds); } } size.x ||= (absolute == null || isNaN(absolute.width)) ? containerBounds.width : absolute.width; size.y ||= (absolute == null || isNaN(absolute.height)) ? containerBounds.height : absolute.height; } CONFIG::LOGGING { logger.debug ( "calculated container size ({0}, {1}) (bounds: {2})" , size.x, size.y , containerBounds ); } return size; } // Internals // private static const USED_METADATAS:Vector. = new Vector.(5, true); /* static */ { USED_METADATAS[0] = MetadataNamespaces.ABSOLUTE_LAYOUT_PARAMETERS; USED_METADATAS[1] = MetadataNamespaces.RELATIVE_LAYOUT_PARAMETERS; USED_METADATAS[2] = MetadataNamespaces.ANCHOR_LAYOUT_PARAMETERS; USED_METADATAS[3] = MetadataNamespaces.PADDING_LAYOUT_PARAMETERS; USED_METADATAS[4] = MetadataNamespaces.LAYOUT_ATTRIBUTES; } private static const X:int = 0x1; private static const Y:int = 0x2; private static const WIDTH:int = 0x4; private static const HEIGHT:int = 0x8; private static const POSITION:int = X + Y; private static const DIMENSIONS:int = WIDTH + HEIGHT; private static const ALL:int = POSITION + DIMENSIONS; private var layoutMode:String = LayoutMode.NONE; private var lastCalculatedBounds:Rectangle; private var targetMetadataWatchers:Dictionary = new Dictionary(); private var containerAbsoluteWatcher:MetadataWatcher; private var containerAttributesWatcher:MetadataWatcher; CONFIG::LOGGING private static const logger:org.osmf.logging.Logger = org.osmf.logging.Log.getLogger("org.osmf.layout.LayoutRenderer"); } }