| //////////////////////////////////////////////////////////////////////////////// |
| // |
| // Licensed to the Apache Software Foundation (ASF) under one or more |
| // contributor license agreements. See the NOTICE file distributed with |
| // this work for additional information regarding copyright ownership. |
| // The ASF licenses this file to You under the Apache License, Version 2.0 |
| // (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.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| // |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| package spark.layouts |
| { |
| import flash.geom.Point; |
| import flash.utils.Dictionary; |
| |
| import mx.containers.errors.ConstraintError; |
| import mx.containers.utilityClasses.ConstraintColumn; |
| import mx.containers.utilityClasses.ConstraintRow; |
| import mx.containers.utilityClasses.Flex; |
| import mx.core.ILayoutElement; |
| import mx.core.mx_internal; |
| import mx.resources.ResourceManager; |
| |
| import spark.components.supportClasses.GroupBase; |
| import spark.layouts.supportClasses.LayoutBase; |
| import spark.layouts.supportClasses.LayoutElementHelper; |
| |
| use namespace mx_internal; |
| |
| [ResourceBundle("layout")] |
| |
| /** |
| * The ConstraintLayout class arranges the layout elements based on their individual |
| * settings and a set of constraint regions defined by constraint columns and |
| * constraint rows. Although you can use all of the properties and constraints from |
| * BasicLayout to position and size elements, ConstraintLayout gives you the ability |
| * to create sibling-relative layouts by constraining elements to the specified |
| * columns and rows. |
| * |
| * <p><b>Note: </b>The Spark list-based controls (the Spark List control and its subclasses |
| * such as ButtonBar, ComboBox, DropDownList, and TabBar) do not support the ConstraintLayout class. |
| * Do not use ConstraintLayout with the Spark list-based controls.</p> |
| * |
| * <p>Per-element supported constraints are <code>left</code>, <code>right</code>, |
| * <code>top</code>, <code>bottom</code>, <code>baseline</code>, |
| * <code>percentWidth</code>, and <code>percentHeight</code>. |
| * Element's minimum and maximum sizes will always be respected.</p> |
| * |
| * <p>Columns and rows may have an explicit size or content size (no explicit size). |
| * Explicit size regions will be fixed at their specified size, while content size |
| * regions will stretch to fit only the elements constrained to them. If multiple |
| * content size regions are spanned by an element, the space will be divided |
| * equally among the content size regions.</p> |
| * |
| * <p>The measured size of the container is calculated from the elements, their |
| * constraints, their preferred sizes, and the sizes of the rows and columns. |
| * The size of each row and column is just big enough to hold all of the elements |
| * constrained to it at their preferred sizes with constraints satisfied. The measured |
| * size of the container is big enough to hold all of the columns and rows as well as |
| * any other elements left at their preferred sizes with constraints satisfied. </p> |
| * |
| * <p>During a call to the <code>updateDisplayList()</code> method, |
| * the element's size is determined according to |
| * the rules in the following order of precedence (the element's minimum and |
| * maximum sizes are always respected):</p> |
| * <ul> |
| * <li>If the element has <code>percentWidth</code> or <code>percentHeight</code> set, |
| * then its size is calculated as a percentage of the available size, where the available |
| * size is the region or container size minus any <code>left</code>, <code>right</code>, |
| * <code>top</code>, or <code>bottom</code> constraints.</li> |
| * |
| * <li>If the element has both left and right constraints, it's width is |
| * set to be the region's or container's width minus the <code>left</code> |
| * and <code>right</code> constraints.</li> |
| * |
| * <li>If the element has both <code>top</code> and <code>bottom</code> constraints, |
| * it's height is set to be the container's height minus the <code>top</code> |
| * and <code>bottom</code> constraints.</li> |
| * |
| * <li>The element is set to its preferred width and/or height.</li> |
| * </ul> |
| * |
| * <p>The element's position is determined according to the rules in the following |
| * order of precedence:</p> |
| * <ul> |
| * <li>If element's baseline is specified, then the element is positioned in |
| * the vertical direction such that its <code>baselinePosition</code> (usually the base line |
| * of its first line of text) is aligned with <code>baseline</code> constraint.</li> |
| * |
| * <li>If element's <code>top</code> or <code>left</code> constraints |
| * are specified, then the element is |
| * positioned such that the top-left corner of the element's layout bounds is |
| * offset from the top-left corner of the container by the specified values.</li> |
| * |
| * <li>If element's <code>bottom</code> or <code>right</code> constraints are specified, |
| * then the element is positioned such that the bottom-right corner |
| * of the element's layout bounds is |
| * offset from the bottom-right corner of the container by the specified values.</li> |
| * |
| * <li>When no constraints determine the position in the horizontal or vertical |
| * direction, the element is positioned according to its x and y coordinates.</li> |
| * </ul> |
| * |
| * <p>The content size of the container is calculated as the maximum of the |
| * coordinates of the bottom-right corner of all the layout elements and |
| * constraint regions.</p> |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4.5 |
| */ |
| public class ConstraintLayout extends LayoutBase |
| { |
| |
| //See FLEX-33311 for more details on why this is used |
| private var constraintCacheNeeded:int = 0; |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Class methods |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| * |
| * @return true if the constraints determine the element's width; |
| */ |
| private static function constraintsDetermineWidth(elementInfo:ElementConstraintInfo):Boolean |
| { |
| return !isNaN(elementInfo.left) && !isNaN(elementInfo.right); |
| } |
| |
| /** |
| * @private |
| * |
| * @return true if the constraints determine the element's height; |
| */ |
| private static function constraintsDetermineHeight(elementInfo:ElementConstraintInfo):Boolean |
| { |
| return !isNaN(elementInfo.top) && !isNaN(elementInfo.bottom); |
| } |
| |
| /** |
| * @private |
| * @return Returns the maximum value for an element's dimension so that the component doesn't |
| * spill out of the container size. Calculations are based on the layout rules. |
| * Pass in unscaledWidth, left, right, childX to get a maxWidth value. |
| * Pass in unscaledHeight, top, bottom, childY to get a maxHeight value. |
| */ |
| private static function maxSizeToFitIn(totalSize:Number, |
| lowConstraint:Number, |
| highConstraint:Number, |
| position:Number):Number |
| { |
| if (!isNaN(lowConstraint)) |
| { |
| // childWidth + left <= totalSize |
| return totalSize - lowConstraint; |
| } |
| else if (!isNaN(highConstraint)) |
| { |
| // childWidth + right <= totalSize |
| return totalSize - highConstraint; |
| } |
| else |
| { |
| // childWidth + childX <= totalSize |
| return totalSize - position; |
| } |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Constructor |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * Constructor. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4.5 |
| */ |
| public function ConstraintLayout() |
| { |
| super(); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Variables |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| * Vectors that keep track of children spanning |
| * content size columns or rows or whether the |
| * elements don't use columns or rows at all. |
| */ |
| private var colSpanElements:Vector.<ElementConstraintInfo> = null; |
| private var rowSpanElements:Vector.<ElementConstraintInfo> = null; |
| private var otherElements:Vector.<ElementConstraintInfo> = null; |
| |
| /** |
| * @private |
| * Vectors to store the baseline property of the rows, and |
| * the maximum ascent of the elements in each row. |
| * |
| * In rowBaselines, the value is stored as |
| * [value, maxAscent] if the baseline is maxAscent:value, |
| * and [value, null] if the baseline is just a value. |
| */ |
| private var rowBaselines:Vector.<Array> = null; |
| private var rowMaxAscents:Vector.<Number> = null; |
| |
| /** |
| * @private |
| * Hashtable that maps elements to their constraint |
| * information. The mapping has type: |
| * ILayoutElement -> ElementConstraintInfo |
| * |
| * This cache is always discarded after measure() or |
| * updateDisplayList() because the constraints may have changed. |
| */ |
| private var constraintCache:Dictionary = null; |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Properties |
| // |
| //-------------------------------------------------------------------------- |
| |
| //---------------------------------- |
| // constraintColumns |
| //---------------------------------- |
| |
| private var _constraintColumns:Vector.<ConstraintColumn> = new Vector.<ConstraintColumn>(0, true); |
| // An associative array of column id --> column index |
| private var columnsObject:Object = {}; |
| |
| /** |
| * A Vector of ConstraintColumn instances that partition the target container. |
| * The ConstraintColumn instance at index 0 is the left-most column; |
| * indices increase from left to right. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4.5 |
| */ |
| public function get constraintColumns():Vector.<ConstraintColumn> |
| { |
| // make defensive copy |
| return _constraintColumns.slice(); |
| } |
| |
| /** |
| * @private |
| */ |
| public function set constraintColumns(value:Vector.<ConstraintColumn>):void |
| { |
| // clear constraintColumns |
| if (value == null) |
| { |
| _constraintColumns = new Vector.<ConstraintColumn>(0, true); |
| columnsObject = {}; |
| return; |
| } |
| |
| var n:int = value.length; |
| var col:ConstraintColumn; |
| var temp:Vector.<ConstraintColumn> = value.slice(); |
| var obj:Object = {}; |
| |
| for (var i:int = 0; i < n; i++) |
| { |
| col = temp[i]; |
| col.container = this.target; |
| obj[col.id] = i; |
| } |
| |
| _constraintColumns = temp; |
| columnsObject = obj; |
| |
| if (target) |
| { |
| target.invalidateSize(); |
| target.invalidateDisplayList(); |
| } |
| } |
| |
| //---------------------------------- |
| // constraintRows |
| //---------------------------------- |
| |
| private var _constraintRows:Vector.<ConstraintRow> = new Vector.<ConstraintRow>(0, true); |
| // An associative array of row id --> row index |
| private var rowsObject:Object = {}; |
| |
| /** |
| * A Vector of ConstraintRow instances that partition the target container. |
| * The ConstraintRow instance at index 0 is the top-most column; |
| * indices increase from top to bottom. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4.5 |
| */ |
| public function get constraintRows():Vector.<ConstraintRow> |
| { |
| return _constraintRows.slice(); |
| } |
| |
| /** |
| * @private |
| */ |
| public function set constraintRows(value:Vector.<ConstraintRow>):void |
| { |
| // clear constraintRows |
| if (value == null) |
| { |
| _constraintRows = new Vector.<ConstraintRow>(0, true); |
| rowsObject = {}; |
| return; |
| } |
| |
| var n:int = value.length; |
| var row:ConstraintRow; |
| var temp:Vector.<ConstraintRow> = value.slice(); |
| var obj:Object = {}; |
| rowBaselines = new Vector.<Array>(); |
| |
| for (var i:int = 0; i < n; i++) |
| { |
| row = temp[i]; |
| row.container = this.target; |
| obj[row.id] = i; |
| rowBaselines[i] = LayoutElementHelper.parseConstraintExp(row.baseline); |
| |
| var maxAscentStr:String = rowBaselines[i][1]; |
| if (maxAscentStr && maxAscentStr != "maxAscent") |
| throw new Error(ResourceManager.getInstance().getString("layout", "invalidBaselineOnRow", |
| [ row.id, row.baseline ])); |
| } |
| |
| _constraintRows = temp; |
| rowsObject = obj; |
| |
| if (target) |
| { |
| target.invalidateSize(); |
| target.invalidateDisplayList(); |
| } |
| } |
| |
| /** |
| * @private |
| * Resets the target on the constraintColumns and constraintRows. |
| */ |
| override public function set target(value:GroupBase):void |
| { |
| super.target = value; |
| |
| // setting a new target means we need to reset the targets of |
| // our columns and rows |
| var i:int; |
| var n:int = _constraintColumns.length; |
| |
| for (i = 0; i < n; i++) |
| { |
| _constraintColumns[i].container = value; |
| } |
| |
| n = _constraintRows.length; |
| for (i = 0; i < n; i++) |
| { |
| _constraintRows[i].container = value; |
| } |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Overridden Methods: LayoutBase |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| * |
| * 1) Parse each element constraint and populate the constraintCache |
| * 2) Measure the columns and rows based on only the elements that use them |
| * and get the sum of the column widths and row heights. |
| * 3) Measure the size of this container based on elements that don't use |
| * either columns or rows or both. |
| * 4) Take the max of 2 and 3 to find the measuredWidth. |
| */ |
| override public function measure():void |
| { |
| checkUseVirtualLayout(); |
| super.measure(); |
| |
| var layoutTarget:GroupBase = target; |
| if (!layoutTarget) |
| return; |
| |
| var width:Number = 0; |
| var height:Number = 0; |
| var minWidth:Number = 0; |
| var minHeight:Number = 0; |
| |
| parseConstraints(); |
| |
| // Find preferred column widths and row heights. |
| var colWidths:Vector.<Number> = measureColumns(); |
| var rowHeights:Vector.<Number> = measureRows(); |
| var n:Number; |
| |
| for each (n in colWidths) |
| { |
| width += n; |
| } |
| |
| for each (n in rowHeights) |
| { |
| height += n; |
| } |
| |
| // Find minimum measured width/height by passing in 0 for the constrained size. |
| // This means that percent size regions will be set to their min size. |
| constrainPercentRegionSizes(colWidths, 0, true); |
| for each (n in colWidths) |
| { |
| minWidth += n; |
| } |
| |
| constrainPercentRegionSizes(rowHeights, 0, false); |
| for each (n in rowHeights) |
| { |
| minHeight += n; |
| } |
| |
| if (otherElements) |
| { |
| var vec:Vector.<Number> = measureOtherContent(); |
| |
| width = Math.max(width, vec[0]); |
| height = Math.max(height, vec[1]); |
| minWidth = Math.max(minWidth, vec[2]); |
| minHeight = Math.max(minHeight, vec[3]); |
| } |
| |
| layoutTarget.measuredWidth = Math.ceil(width); |
| layoutTarget.measuredHeight = Math.ceil(height); |
| layoutTarget.measuredMinWidth = Math.ceil(minWidth); |
| layoutTarget.measuredMinHeight = Math.ceil(minHeight); |
| |
| // clear out cache |
| clearConstraintCache(); |
| } |
| |
| /** |
| * @private |
| * |
| * 1) Re-parse element constraints because they may have changed. |
| * 2) Resize and reposition the columns and rows based on new constraints. |
| * 3) Size and position the elements in the available space. |
| */ |
| override public function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void |
| { |
| checkUseVirtualLayout(); |
| super.updateDisplayList(unscaledWidth, unscaledHeight); |
| |
| var layoutTarget:GroupBase = target; |
| if (!layoutTarget) |
| return; |
| |
| // Need to measure in case of explicit width and height on target. |
| // Also need to reparse constraints in case of something changing. |
| measureAndPositionColumnsAndRows(unscaledWidth, unscaledHeight); |
| |
| layoutContent(unscaledWidth, unscaledHeight); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Methods: Used by FormItemLayout |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * Lays out the elements of the layout target using the current |
| * widths and heights of the columns and rows. Used by FormItemLayout |
| * after setting new column widths to lay elements using those new widths. |
| * |
| * @param unscaledWidth Specifies the width of the component, in pixels, |
| * in the component's coordinates, regardless of the value of the |
| * <code>scaleX</code> property of the component. |
| * |
| * @param unscaledHeight Specifies the height of the component, in pixels, |
| * in the component's coordinates, regardless of the value of the |
| * <code>scaleY</code> property of the component. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4.5 |
| */ |
| protected function layoutContent(unscaledWidth:Number, unscaledHeight:Number):void |
| { |
| var layoutTarget:GroupBase = target; |
| if (!layoutTarget) |
| return; |
| |
| var count:int = layoutTarget.numElements; |
| var layoutElement:ILayoutElement; |
| |
| var maxX:Number = 0; |
| var maxY:Number = 0; |
| |
| // update children |
| for (var i:int = 0; i < count; i++) |
| { |
| layoutElement = layoutTarget.getElementAt(i); |
| if (!layoutElement || !layoutElement.includeInLayout) |
| continue; |
| |
| applyConstraintsToElement(unscaledWidth, unscaledHeight, layoutElement); |
| |
| // update content limits |
| maxX = Math.max(maxX, layoutElement.getLayoutBoundsX() + |
| layoutElement.getLayoutBoundsWidth()); |
| maxY = Math.max(maxY, layoutElement.getLayoutBoundsY() + |
| layoutElement.getLayoutBoundsHeight()); |
| } |
| |
| // Make sure that if the content spans partially over a pixel to the right/bottom, |
| // the content size includes the whole pixel. |
| layoutTarget.setContentSize(Math.ceil(maxX), Math.ceil(maxY)); |
| |
| // clear out cache |
| clearConstraintCache(); |
| } |
| |
| /** |
| * Used by FormItemLayout to measure and set new column widths |
| * and row heights before laying out the elements. |
| * |
| * @param constrainedWidth The total width available for columns to stretch |
| * or shrink their percent width columns. If NaN, percent width columns |
| * are unconstrained and fit to their content. |
| * @param constrainedHeight The total height available for rows to stretch |
| * or shrink their percent height rows. If NaN, percent height rows |
| * are unconstrained and fit to their content. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4.5 |
| */ |
| protected function measureAndPositionColumnsAndRows(constrainedWidth:Number = NaN, constrainedHeight:Number = NaN):void |
| { |
| parseConstraints(); |
| setColumnWidths(measureColumns(constrainedWidth)); |
| setRowHeights(measureRows(constrainedHeight)); |
| } |
| |
| /** |
| * @private |
| * This function is mx_internal so that FormItemLayout can use it |
| * in its updateDisplayList. |
| */ |
| mx_internal function checkUseVirtualLayout():void |
| { |
| if (useVirtualLayout) |
| throw new Error(ResourceManager.getInstance().getString("layout", "constraintLayoutNotVirtualized")); |
| } |
| |
| /** |
| * @private |
| */ |
| override mx_internal function get virtualLayoutSupported():Boolean |
| { |
| return false; |
| } |
| |
| /** |
| * @private |
| * Used to set new column widths before laying out the elements. |
| * Used by FormItemLayout to set column widths provided by the |
| * Form. |
| */ |
| mx_internal function setColumnWidths(value:Vector.<Number>):void |
| { |
| if (value == null) |
| return; |
| |
| var constraintColumns:Vector.<ConstraintColumn> = this._constraintColumns; |
| var numCols:int = constraintColumns.length; |
| var totalWidth:Number = 0; |
| |
| for (var i:int = 0; i < numCols; i++) |
| { |
| constraintColumns[i].setActualWidth(value[i]); |
| constraintColumns[i].x = totalWidth; |
| totalWidth += value[i]; |
| } |
| } |
| |
| /** |
| * @private |
| * Used to set new row heights before laying out the elements. |
| */ |
| mx_internal function setRowHeights(value:Vector.<Number>):void |
| { |
| if (value == null) |
| return; |
| |
| var constraintRows:Vector.<ConstraintRow> = this._constraintRows; |
| var numRows:int = constraintRows.length; |
| var totalHeight:Number = 0; |
| |
| for (var i:int = 0; i < numRows; i++) |
| { |
| constraintRows[i].setActualHeight(value[i]); |
| constraintRows[i].y = totalHeight; |
| totalHeight += value[i]; |
| } |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Methods |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| * Sizes and positions the element based on the given size of the container |
| * and the element's constraints. |
| * |
| * 1) Retrieves element constraints from the constraint cache. |
| * 2) Determines the x and y boundaries of each side. |
| * 3) Sizes the element based on constraints and its preferred |
| * size. The precedence for sizing is as follows: percent, |
| * top and bottom constraints, preferred size. |
| * 4) Positions the element based on its constraints. The precedence |
| * for positioning is as follows: baseline, left and top, right and bottom, |
| * x and y. |
| */ |
| private function applyConstraintsToElement(unscaledWidth:Number, |
| unscaledHeight:Number, |
| layoutElement:ILayoutElement):void |
| { |
| var elementInfo:ElementConstraintInfo = constraintCache[layoutElement]; |
| |
| var left:Number = elementInfo.left; |
| var right:Number = elementInfo.right; |
| var top:Number = elementInfo.top; |
| var bottom:Number = elementInfo.bottom; |
| var baseline:Number = elementInfo.baseline; |
| |
| var leftBoundary:String = elementInfo.leftBoundary; |
| var rightBoundary:String = elementInfo.rightBoundary; |
| var topBoundary:String = elementInfo.topBoundary; |
| var bottomBoundary:String = elementInfo.bottomBoundary; |
| var baselineBoundary:String = elementInfo.baselineBoundary; |
| |
| var percentWidth:Number = layoutElement.percentWidth; |
| var percentHeight:Number = layoutElement.percentHeight; |
| |
| var availableWidth:Number; |
| var availableHeight:Number; |
| |
| var elementWidth:Number = NaN; |
| var elementHeight:Number = NaN; |
| var elementMaxWidth:Number = NaN; |
| var elementMaxHeight:Number = NaN; |
| var elementX:Number = 0; |
| var elementY:Number = 0; |
| |
| var leftHolder:Number = 0; |
| var rightHolder:Number = unscaledWidth; |
| var topHolder:Number = 0; |
| var bottomHolder:Number = unscaledHeight; |
| var baselineHolder:Number = 0; |
| |
| var i:Number; |
| |
| var col:ConstraintColumn; |
| var row:ConstraintRow; |
| |
| if (leftBoundary) |
| { |
| col = _constraintColumns[elementInfo.colSpanLeftIndex]; |
| leftHolder = col.x; |
| } |
| |
| if (rightBoundary) |
| { |
| col = _constraintColumns[elementInfo.colSpanRightIndex]; |
| rightHolder = col.x + col.width; |
| } |
| |
| if (topBoundary) |
| { |
| row = _constraintRows[elementInfo.rowSpanTopIndex]; |
| topHolder = row.y; |
| } |
| |
| if (bottomBoundary) |
| { |
| row = _constraintRows[elementInfo.rowSpanBottomIndex]; |
| bottomHolder = row.y + row.height; |
| } |
| |
| if (baselineBoundary) |
| { |
| var baselineIndex:Number = elementInfo.baselineIndex; |
| var rowBaseline:Array = rowBaselines[baselineIndex]; |
| row = _constraintRows[baselineIndex]; |
| |
| // add baseline offset from row. |
| baselineHolder = row.y + Number(rowBaseline[0]); |
| |
| // add maxAscent. maxAscent defaults to 0 if not specified. |
| if (rowMaxAscents) |
| baselineHolder += rowMaxAscents[baselineIndex]; |
| |
| // If bottom doesn't exist, then the bottom should be restricted to the |
| // baseline row. |
| if (isNaN(bottom)) |
| bottomHolder = row.y + row.height; |
| } |
| |
| // available width |
| availableWidth = Math.round(rightHolder - leftHolder); |
| |
| // cases are baseline with top and bottom, |
| // baseline with top, baseline with bottom, no baseline |
| if (!isNaN(baseline) && (isNaN(top) || isNaN(bottom))) |
| availableHeight = Math.round(bottomHolder - baselineHolder); |
| else |
| availableHeight = Math.round(bottomHolder - topHolder); |
| |
| // set width |
| if (!isNaN(percentWidth)) |
| { |
| if (!isNaN(left)) |
| availableWidth -= left; |
| if (!isNaN(right)) |
| availableWidth -= right; |
| |
| elementWidth = Math.round(availableWidth * Math.min(percentWidth * 0.01, 1)); |
| elementMaxWidth = Math.min(layoutElement.getMaxBoundsWidth(), |
| maxSizeToFitIn(unscaledWidth, left, right, layoutElement.getLayoutBoundsX())); |
| } |
| else if (!isNaN(left) && !isNaN(right)) |
| { |
| elementWidth = availableWidth - left - right; |
| } |
| |
| // set height |
| if (!isNaN(percentHeight)) |
| { |
| if (!isNaN(top)) |
| availableHeight -= top; |
| if (!isNaN(bottom)) |
| availableHeight -= bottom; |
| |
| elementHeight = Math.round(availableHeight * Math.min(percentHeight * 0.01, 1)); |
| elementMaxHeight = Math.min(layoutElement.getMaxBoundsHeight(), |
| maxSizeToFitIn(unscaledHeight, top, bottom, layoutElement.getLayoutBoundsY())); |
| } |
| else if (!isNaN(top) && !isNaN(bottom)) |
| { |
| elementHeight = availableHeight - top - bottom; |
| } |
| |
| // Apply min and max constraints, make sure min is applied last. In the cases |
| // where elementWidth and elementHeight are NaN, setLayoutBoundsSize will use preferredSize |
| // which is already constrained between min and max. |
| if (!isNaN(elementWidth)) |
| { |
| if (isNaN(elementMaxWidth)) |
| elementMaxWidth = layoutElement.getMaxBoundsWidth(); |
| elementWidth = Math.max(layoutElement.getMinBoundsWidth(), Math.min(elementMaxWidth, elementWidth)); |
| } |
| |
| if (!isNaN(elementHeight)) |
| { |
| if (isNaN(elementMaxHeight)) |
| elementMaxHeight = layoutElement.getMaxBoundsHeight(); |
| elementHeight = Math.max(layoutElement.getMinBoundsHeight(), Math.min(elementMaxHeight, elementHeight)); |
| } |
| |
| layoutElement.setLayoutBoundsSize(elementWidth, elementHeight); |
| // update temp variables |
| elementWidth = layoutElement.getLayoutBoundsWidth(); |
| elementHeight = layoutElement.getLayoutBoundsHeight(); |
| |
| // Horizontal Position |
| if (!isNaN(left)) |
| elementX = leftHolder + left; |
| else if (!isNaN(right)) |
| elementX = rightHolder - right - elementWidth; |
| else |
| elementX = layoutElement.getLayoutBoundsX(); |
| |
| // Vertical Position |
| if (!isNaN(baseline)) |
| elementY = baselineHolder + baseline - layoutElement.baselinePosition; |
| else if (!isNaN(top)) |
| elementY = topHolder + top; |
| else if (!isNaN(bottom)) |
| elementY = bottomHolder - bottom - elementHeight; |
| else |
| elementY = layoutElement.getLayoutBoundsY(); |
| |
| layoutElement.setLayoutBoundsPosition(elementX, elementY); |
| } |
| |
| /** |
| * @private |
| * Updates the widths of content size and percent size columns that are spanned |
| * by the specified element. This method updates the provided column widths |
| * vector in place. The algorithm is as follows: |
| * |
| * 1) Determine the space needed by the element to satisfy its constraints |
| * and be at its preferred size. |
| * |
| * 2) Calculate the number of columns the element will span. |
| * |
| * 3) If the element causes a column to expand, update the column width to match. |
| * a) For the single spanning case, we only need to check if this element's |
| * required width is larger than the column's current width. |
| * b) For the multiple spanning case, we distribute the remaining width after |
| * subtracting the fixed size columns across the content/percent size columns. |
| * Then, we only update a column's width if the new divided width would cause |
| * the column's width to expand. |
| * |
| * @param elementInfo The constraint information of the element. |
| * @param colWidths The vector of column widths to update. |
| */ |
| private function updateColumnWidthsForElement(colWidths:Vector.<Number>, elementInfo:ElementConstraintInfo):void |
| { |
| var layoutElement:ILayoutElement = elementInfo.layoutElement; |
| var numCols:int = _constraintColumns.length; |
| var col:ConstraintColumn; |
| |
| var leftIndex:int = -1; |
| var rightIndex:int = -1; |
| var span:int; |
| |
| var extX:Number = 0; |
| var preferredWidth:Number = layoutElement.getPreferredBoundsWidth(); |
| var maxExtent:Number; |
| var remainingWidth:Number; |
| |
| var j:int; |
| var colWidth:Number = 0; |
| |
| // 1) Determine how much space the element needs to satisfy its |
| // constraints and be at its preferred width. |
| if (!isNaN(elementInfo.left)) |
| { |
| extX += elementInfo.left; |
| if (elementInfo.leftBoundary) |
| leftIndex = elementInfo.colSpanLeftIndex; |
| else |
| leftIndex = 0; // constrained to parent |
| } |
| |
| if (!isNaN(elementInfo.right)) |
| { |
| extX += elementInfo.right; |
| if (elementInfo.rightBoundary) |
| rightIndex = elementInfo.colSpanRightIndex; |
| else |
| rightIndex = numCols - 1; // constrained to parent |
| } |
| |
| maxExtent = extX + preferredWidth; |
| remainingWidth = maxExtent; |
| |
| // 2) If either the left or the right constraint doesn't exist, |
| // we must find the span of the element. We do this by |
| // determining the index of the last column that the element |
| // occupies in the unconstrained direction. |
| if (leftIndex < 0 || rightIndex < 0) |
| { |
| var isLeft:Boolean = leftIndex < 0; |
| var startIndex:int = isLeft ? rightIndex : leftIndex; |
| var endIndex:int = isLeft ? -1 : numCols; |
| var increment:int = isLeft ? -1 : 1; |
| |
| if (isLeft) // defaults to 0 |
| leftIndex = 0; |
| else // defaults to numCols - 1 |
| rightIndex = numCols - 1; |
| |
| for (j = startIndex; j != endIndex ; j += increment) |
| { |
| col = _constraintColumns[j]; |
| |
| // subtract fixed columns |
| if (!isNaN(col.explicitWidth)) |
| remainingWidth -= col.explicitWidth; |
| |
| if ((col.contentSize || !isNaN(col.percentWidth)) || remainingWidth < 0) |
| { |
| if (isLeft) |
| leftIndex = j; |
| else |
| rightIndex = j; |
| break; |
| } |
| } |
| } |
| // always 1 or positive. |
| span = rightIndex - leftIndex + 1; |
| |
| // 3) If the element causes a column to expand, update the column width to match. |
| if (span == 1) |
| { |
| // a) For the single spanning case, we only need to check if this element's |
| // required width is larger than the column's current width. |
| col = _constraintColumns[leftIndex]; |
| |
| if (col.contentSize || !isNaN(col.percentWidth)) |
| { |
| colWidth = Math.max(colWidths[leftIndex], extX + preferredWidth); |
| |
| if (constraintsDetermineWidth(elementInfo)) |
| colWidth = Math.max(colWidth, extX + layoutElement.getMinBoundsWidth()); |
| |
| // bound with max width of column |
| if (!isNaN(col.maxWidth)) |
| colWidth = Math.min(colWidth, col.maxWidth); |
| |
| colWidths[leftIndex] = Math.ceil(colWidth); |
| } |
| } |
| else |
| { |
| // b) multiple spanning case when span >= 2. |
| // 1) start from leftIndex and subtract fixed columns. |
| // 2) divide space evenly into content/percent size columns. |
| var contentCols:Vector.<ConstraintColumn> = new Vector.<ConstraintColumn>(); |
| var contentColsIndices:Vector.<int> = new Vector.<int>(); |
| |
| remainingWidth = maxExtent; |
| for (j = leftIndex; j <= rightIndex; j++) |
| { |
| col = _constraintColumns[j]; |
| |
| if (!isNaN(col.explicitWidth)) |
| { |
| if (remainingWidth < col.width) |
| break; |
| |
| remainingWidth -= col.width; |
| } |
| else if (col.contentSize || !isNaN(col.percentWidth)) |
| { |
| contentCols.push(col); |
| contentColsIndices.push(j); |
| } |
| } |
| |
| var numContentCols:Number = contentCols.length; |
| if (numContentCols > 0) |
| { |
| var splitWidth:Number = remainingWidth / numContentCols; |
| |
| for (j = 0; j < numContentCols; j++) |
| { |
| col = contentCols[j]; |
| |
| colWidth = Math.max(colWidths[contentColsIndices[j]], splitWidth); |
| if (!isNaN(col.maxWidth)) |
| colWidth = Math.min(colWidth, col.maxWidth); |
| |
| colWidths[contentColsIndices[j]] = Math.ceil(colWidth); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Adjusts the sizes of percent size columns or rows to fill the constrainedSize. |
| * This method updates the provided column widths or row heights vector in place. |
| * |
| * The term, "region", refers to a column or row. |
| * The percent size region sizes are first reset to their minimum size. |
| * If the given region sizes from the content and fixed size regions already |
| * fill the available space, then the percent size region sizes stay at their |
| * minimum size. Otherwise, the remaining space is distributed to the percent |
| * size regions based on the ratio of its percent size to the sum of all the |
| * percent sizes. |
| */ |
| private function constrainPercentRegionSizes(sizes:Vector.<Number>, constrainedSize:Number, isColumns:Boolean):void |
| { |
| var col:ConstraintColumn; |
| var row:ConstraintRow; |
| var numSizes:int = isColumns ? _constraintColumns.length : _constraintRows.length; |
| |
| var childInfoArray:Array /* of ConstraintRegionFlexChildInfo */ = []; |
| var childInfo:ConstraintRegionFlexChildInfo; |
| var remainingSpace:Number = constrainedSize; |
| var percentMinSizes:Number = 0; |
| var totalPercent:Number = 0; |
| |
| // Set percent size regions back to minSize and |
| // find the remaining space. |
| for (var i:int = 0; i < numSizes; i++) |
| { |
| var percentSize:Number; |
| var minSize:Number; |
| var maxSize:Number; |
| |
| if (isColumns) |
| { |
| col = _constraintColumns[i]; |
| percentSize = col.percentWidth; |
| minSize = col.minWidth; |
| maxSize = col.maxWidth; |
| } |
| else |
| { |
| row = _constraintRows[i]; |
| percentSize = row.percentHeight; |
| minSize = row.minHeight; |
| maxSize = row.maxHeight; |
| } |
| |
| if (!isNaN(percentSize)) |
| { |
| sizes[i] = (!isNaN(minSize)) ? Math.ceil(Math.max(minSize, 0)) : 0; |
| percentMinSizes += sizes[i]; |
| totalPercent += percentSize; |
| |
| // Fill childInfoArray for distributing the width. |
| childInfo = new ConstraintRegionFlexChildInfo(); |
| childInfo.index = i; |
| childInfo.percent = percentSize; |
| childInfo.min = minSize; |
| childInfo.max = maxSize; |
| childInfoArray.push(childInfo); |
| } |
| else |
| { |
| remainingSpace -= sizes[i]; |
| } |
| } |
| |
| // If there's space remaining, distribute the width to the percent size |
| // columns based on their ratio of percentWidth to sum of all the percentWidths. |
| if (remainingSpace > percentMinSizes) |
| { |
| Flex.flexChildrenProportionally(constrainedSize, |
| remainingSpace, |
| totalPercent, |
| childInfoArray); |
| |
| var roundOff:Number = 0; |
| for each (childInfo in childInfoArray) |
| { |
| // Make sure the calculated widths are rounded to pixel boundaries |
| var size:Number = Math.round(childInfo.size + roundOff); |
| roundOff += childInfo.size - size; |
| |
| sizes[childInfo.index] = size; |
| |
| // remainingSpace -= size; |
| } |
| // TODO (klin): What do we do if there's remainingSpace after all this? |
| } |
| } |
| |
| /** |
| * @private |
| * This function measures the ConstraintColumns partitioning |
| * the target and returns their new widths. The calculations |
| * are based on the current constraintCache and other derived |
| * data structures. To update the constraintCache, one needs to |
| * call the parseConstraints() method. |
| * |
| * The widths are measured with the following requirements: |
| * 1. Fixed size columns honor their pixel values. |
| * |
| * 2. Content size columns whose children only span that column |
| * assumes the width of the widest child. |
| * |
| * 3. Content size columns whose children span more than one column |
| * assumes the widest width possible when the child's size is divided |
| * among the spanned columns. |
| * a. Each child divides its preferred width among the content size |
| * columns that it spans. A child also always honors fixed size |
| * columns that it spans. |
| * b. The column takes the widest width given by its children. |
| * |
| * 4. Percent size columns measure exactly like content size columns |
| * at first, but after measurement, if availableWidth is provided, |
| * the percent size columns are remeasured to allow the columns to |
| * fit exactly in the remaining width in accordance with their given |
| * percentage. |
| * a. The percentages given are treated as ratios for how the width |
| * should be divided among the percent size columns. |
| * b. If no remaining space is available or the measured size of all |
| * the content and fixed size columns are greater than the |
| * constrainedWidth, percent size columns are set to their minimum. |
| * |
| * 5. Columns always honor their max and min widths. |
| * |
| * 6. If constrainedWidth is not specified, sum the column widths to find |
| * the total measured width of the target. |
| * |
| * @param constrainedWidth The constraining width to be used when measuring |
| * percent size columns. The default is NaN. |
| * |
| * @return A vector of the new column widths. |
| */ |
| mx_internal function measureColumns(constrainedWidth:Number = NaN):Vector.<Number> |
| { |
| // TODO (klin): Parameterize this to work for both columns and rows. |
| // This may mean we need to add some mx_internal properties to |
| // the columns for "major size", etc... Question is, what about |
| // 1-D properties like baseline? What parts can we parameterize and |
| // what parts aren't possible. |
| |
| // Parse constraints if it hasn't been done yet, make sure to clear |
| // the cache afterwards. |
| var clearCache:Boolean = false; |
| if (!constraintCache) |
| { |
| parseConstraints(); |
| clearCache = true; |
| } |
| |
| if (_constraintColumns.length <= 0) |
| return new Vector.<Number>(); |
| |
| var measuredWidth:Number = 0; |
| var i:Number; |
| var numCols:Number = _constraintColumns.length; |
| var col:ConstraintColumn; |
| var hasContentSize:Boolean = false; |
| var hasPercentSize:Boolean = false; |
| var colWidths:Vector.<Number> = new Vector.<Number>(numCols); |
| |
| // Start column widths at the minWidth of each column or |
| // its explicit width. |
| for (i = 0; i < numCols; i++) |
| { |
| col = _constraintColumns[i]; |
| |
| if (col.contentSize || !isNaN(col.percentWidth)) |
| { |
| hasContentSize ||= col.contentSize; |
| hasPercentSize ||= !isNaN(col.percentWidth); |
| |
| if (!isNaN(col.minWidth)) |
| colWidths[i] = Math.ceil(Math.max(col.minWidth, 0)); |
| else |
| colWidths[i] = 0; |
| } |
| else if (!isNaN(col.explicitWidth)) |
| { |
| var w:Number = col.explicitWidth; |
| |
| if (!isNaN(col.minWidth)) |
| w = Math.max(w, col.minWidth); |
| |
| if (!isNaN(col.maxWidth)) |
| w = Math.min(w, col.maxWidth); |
| |
| colWidths[i] = Math.ceil(w); |
| } |
| } |
| |
| // Assumption: elements in colSpanElements have one or more constraints touching a column. |
| // This is enforced in parseElementConstraints(). |
| if (colSpanElements && (hasContentSize || hasPercentSize)) |
| { |
| // Measure content/percent size columns. |
| for each (var elementInfo:ElementConstraintInfo in colSpanElements) |
| { |
| updateColumnWidthsForElement(colWidths, elementInfo); |
| } |
| } |
| |
| // Adjust percent size columns to account for constraining width. |
| if (!isNaN(constrainedWidth) && hasPercentSize) |
| { |
| constrainPercentRegionSizes(colWidths, constrainedWidth, true); |
| } |
| |
| // Clear the cache only if we created it just for this method call. |
| if (clearCache) |
| clearConstraintCache(); |
| |
| return colWidths; |
| } |
| |
| /** |
| * @private |
| * Updates the heights of content size and percent size rows that are spanned |
| * by the specified element. This method updates the provided row heights |
| * vector in place. The algorithm is as follows: |
| * |
| * 1) Determine the space needed by the element to satisfy its constraints |
| * and be at its preferred size. |
| * |
| * 2) Calculate the number of rows the element will span. |
| * |
| * 3) If the element causes a row to expand, update the row height to match. |
| * a) For the single spanning case, we only need to check if this element's |
| * required height is larger than the row's current height. |
| * b) For the multiple spanning case, we distribute the remaining height after |
| * subtracting the fixed size rows across the content/percent size rows. |
| * Then, we only update a row's height if the new divided height would cause |
| * the row's height to expand. |
| * |
| * @param elementInfo The constraint information of the element. |
| * @param colWidths The vector of column widths to update. |
| */ |
| private function updateRowHeightsForElement(rowHeights:Vector.<Number>, elementInfo:ElementConstraintInfo):void |
| { |
| var layoutElement:ILayoutElement = elementInfo.layoutElement; |
| var numRows:int = _constraintRows.length; |
| var row:ConstraintRow; |
| |
| var topIndex:int = -1; |
| var bottomIndex:int = -1; |
| var span:int; |
| |
| var extY:Number = 0; |
| var preferredHeight:Number = layoutElement.getPreferredBoundsHeight(); |
| var maxExtent:Number; |
| var remainingHeight:Number; |
| |
| var j:int; |
| var rowHeight:Number = 0; |
| |
| // 1) Determine how much space the element needs to satisfy its |
| // constraints and be at its preferred height. |
| if (!isNaN(elementInfo.top)) |
| { |
| extY += elementInfo.top; |
| if (elementInfo.topBoundary) |
| topIndex = elementInfo.rowSpanTopIndex; |
| else |
| topIndex = 0; // constrained to parent |
| } |
| |
| if (!isNaN(elementInfo.bottom)) |
| { |
| extY += elementInfo.bottom; |
| if (elementInfo.bottomBoundary) |
| bottomIndex = elementInfo.rowSpanBottomIndex; |
| else |
| bottomIndex = numRows - 1; // constrained to parent |
| } |
| |
| // Only include baseline if at least one of top or bottom don't |
| // exist. |
| if (!isNaN(elementInfo.baseline) && (topIndex < 0 || bottomIndex < 0)) |
| { |
| extY += elementInfo.baseline - layoutElement.baselinePosition; |
| |
| if (!isNaN(elementInfo.top)) |
| extY -= elementInfo.top; |
| |
| if (elementInfo.baselineBoundary) |
| { |
| topIndex = elementInfo.baselineIndex; |
| |
| // add baseline offset. |
| extY += Number(rowBaselines[topIndex][0]); |
| |
| // add maxAscent. maxAscent is 0 if not specified on the row. |
| if (rowMaxAscents) |
| extY += rowMaxAscents[topIndex]; |
| } |
| else |
| { |
| topIndex = 0; |
| } |
| } |
| |
| maxExtent = extY + preferredHeight; |
| remainingHeight = maxExtent; |
| |
| // 2) If either the top or the bottom constraint doesn't exist, |
| // we must find the span of the element. We do this by |
| // determining the index of the last row that the element |
| // occupies in the unconstrained direction. |
| if (topIndex < 0 || bottomIndex < 0) |
| { |
| var isTop:Boolean = topIndex < 0; |
| var startIndex:int = isTop ? bottomIndex : topIndex; |
| var endIndex:int = isTop ? -1 : numRows; |
| var increment:int = isTop ? -1 : 1; |
| |
| if (isTop) // defaults to 0 |
| topIndex = 0; |
| else // defaults to numRows - 1 |
| bottomIndex = numRows - 1; |
| |
| for (j = startIndex; j != endIndex ; j += increment) |
| { |
| row = _constraintRows[j]; |
| |
| // subtract fixed rows |
| if (!isNaN(row.explicitHeight)) |
| remainingHeight -= row.explicitHeight; |
| |
| if ((row.contentSize || !isNaN(row.percentHeight)) || remainingHeight < 0) |
| { |
| if (isTop) |
| topIndex = j; |
| else |
| bottomIndex = j; |
| break; |
| } |
| } |
| } |
| // always 1 or positive. |
| span = bottomIndex - topIndex + 1; |
| |
| // 3) If the element causes a row to expand, update the row height to match. |
| if (span == 1) |
| { |
| // a) For the single spanning case, we only need to check if this element's |
| // required height is larger than the row's current height. |
| row = _constraintRows[topIndex]; |
| |
| if (row.contentSize || !isNaN(row.percentHeight)) |
| { |
| rowHeight = Math.max(rowHeights[topIndex], extY + preferredHeight); |
| |
| if (constraintsDetermineHeight(elementInfo)) |
| rowHeight = Math.max(rowHeight, extY + layoutElement.getMinBoundsHeight()); |
| |
| // bound with max height of row |
| if (!isNaN(row.maxHeight)) |
| rowHeight = Math.min(rowHeight, row.maxHeight); |
| |
| rowHeights[topIndex] = Math.ceil(rowHeight); |
| } |
| } |
| else |
| { |
| // b) multiple spanning case. span >= 2. |
| // 1) start from topIndex and subtract fixed rows |
| // 2) divide space evenly into content/percent size rows. |
| var contentRows:Vector.<ConstraintRow> = new Vector.<ConstraintRow>(); |
| var contentRowsIndices:Vector.<int> = new Vector.<int>(); |
| |
| remainingHeight = maxExtent; |
| for (j = topIndex; j <= bottomIndex; j++) |
| { |
| row = _constraintRows[j]; |
| |
| if (!isNaN(row.explicitHeight)) |
| { |
| if (remainingHeight < row.height) |
| break; |
| |
| remainingHeight -= row.height; |
| } |
| else if (row.contentSize || !isNaN(row.percentHeight)) |
| { |
| contentRows.push(row); |
| contentRowsIndices.push(j); |
| } |
| } |
| |
| var numContentRows:Number = contentRows.length; |
| if (numContentRows > 0) |
| { |
| var splitHeight:Number = remainingHeight / numContentRows; |
| |
| for (j = 0; j < numContentRows; j++) |
| { |
| row = contentRows[j]; |
| |
| rowHeight = Math.max(rowHeights[contentRowsIndices[j]], splitHeight); |
| if (!isNaN(row.maxHeight)) |
| rowHeight = Math.min(rowHeight, row.maxHeight); |
| |
| rowHeights[contentRowsIndices[j]] = Math.ceil(rowHeight); |
| } |
| } |
| } |
| } |
| |
| /** |
| * @private |
| * Synonymous to measureColumns(), but with added baseline constraint. |
| * Baseline is only included in the measurement if at least one of the element's |
| * top or bottom constraint doesn't exist. The calculations are based on the |
| * current constraintCache. To update the constraintCache, one needs to call |
| * the parseConstraints() method. |
| */ |
| private function measureRows(constrainedHeight:Number = NaN):Vector.<Number> |
| { |
| if (_constraintRows.length <= 0) |
| return new Vector.<Number>(); |
| |
| var measuredHeight:Number = 0; |
| var i:Number; |
| var numRows:Number = _constraintRows.length; |
| var row:ConstraintRow; |
| var hasContentSize:Boolean = false; |
| var hasPercentSize:Boolean = false; |
| var rowHeights:Vector.<Number> = new Vector.<Number>(numRows); |
| |
| // Start row heights at the minHeight of each row or |
| // its explicit height. |
| for (i = 0; i < numRows; i++) |
| { |
| row = _constraintRows[i]; |
| |
| if (row.contentSize || !isNaN(row.percentHeight)) |
| { |
| hasContentSize ||= row.contentSize; |
| hasPercentSize ||= !isNaN(row.percentHeight); |
| |
| if (!isNaN(row.minHeight)) |
| rowHeights[i] = Math.ceil(Math.max(row.minHeight, 0)); |
| else |
| rowHeights[i] = 0; |
| } |
| else if (!isNaN(row.explicitHeight)) |
| { |
| var h:Number = row.explicitHeight; |
| |
| if (!isNaN(row.minHeight)) |
| h = Math.max(h, row.minHeight); |
| |
| if (!isNaN(row.maxHeight)) |
| h = Math.min(h, row.maxHeight); |
| |
| rowHeights[i] = Math.ceil(h); |
| } |
| } |
| |
| // Assumption: elements in rowSpanElements have one or more constraints touching a row. |
| // This is enforced in parseElementConstraints(). |
| if (rowSpanElements && (hasContentSize || hasPercentSize)) |
| { |
| // Measure content/percent size columns. |
| for each (var elementInfo:ElementConstraintInfo in rowSpanElements) |
| { |
| updateRowHeightsForElement(rowHeights, elementInfo); |
| } |
| } |
| |
| // Adjust percent size rows to account for constraining height. |
| if (!isNaN(constrainedHeight) && hasPercentSize) |
| { |
| constrainPercentRegionSizes(rowHeights, constrainedHeight, false); |
| } |
| |
| return rowHeights; |
| } |
| |
| /** |
| * @private |
| * Measures the size of target based on content not included in the columns and rows. |
| * Basically, applies BasicLayout to other content to determine measured size. |
| * Returns a vector with the measured [width, height, minWidth, minHeight]. |
| */ |
| private function measureOtherContent():Vector.<Number> |
| { |
| var width:Number = 0; |
| var height:Number = 0; |
| var minWidth:Number = 0; |
| var minHeight:Number = 0; |
| var count:int = otherElements.length; |
| |
| for (var i:int = 0; i < count; i++) |
| { |
| var elementInfo:ElementConstraintInfo = otherElements[i]; |
| var layoutElement:ILayoutElement = elementInfo.layoutElement; |
| |
| // Only measure width if not constrained to columns. |
| if (!elementInfo.leftBoundary && !elementInfo.rightBoundary) |
| { |
| var left:Number = elementInfo.left; |
| var right:Number = elementInfo.right; |
| var extX:Number; |
| |
| if (!isNaN(left) && !isNaN(right)) |
| { |
| // If both left & right are set, then the extents is always |
| // left + right so that the element is resized to its preferred |
| // size (if it's the one that pushes out the default size of the container). |
| extX = left + right; |
| } |
| else if (!isNaN(left) || !isNaN(right)) |
| { |
| extX = isNaN(left) ? 0 : left; |
| extX += isNaN(right) ? 0 : right; |
| } |
| else |
| { |
| extX = layoutElement.getBoundsXAtSize(NaN, NaN); |
| } |
| |
| var preferredWidth:Number = layoutElement.getPreferredBoundsWidth(); |
| width = Math.max(width, extX + preferredWidth); |
| |
| // Find the minimum default extents, we take the minimum height only |
| // when the element size is determined by the parent size |
| var elementMinWidth:Number = |
| constraintsDetermineWidth(elementInfo) ? layoutElement.getMinBoundsWidth() : |
| preferredWidth; |
| |
| minWidth = Math.max(minWidth, extX + elementMinWidth); |
| } |
| |
| // only measure height if not constrained to rows. |
| var noVerticalBoundaries:Boolean = !elementInfo.topBoundary && !elementInfo.bottomBoundary; |
| var noBaselineBoundary:Boolean = !elementInfo.baselineBoundary; |
| |
| if (noVerticalBoundaries || noBaselineBoundary) |
| { |
| var top:Number; |
| var bottom:Number; |
| var baseline:Number; |
| var extY:Number; |
| |
| if (noVerticalBoundaries) |
| { |
| top = elementInfo.top; |
| bottom = elementInfo.bottom; |
| } |
| |
| if (noBaselineBoundary) |
| baseline = elementInfo.baseline; |
| |
| if (!isNaN(top) && !isNaN(bottom)) |
| { |
| // If both top & bottom are set, then the extents is always |
| // top + bottom so that the element is resized to its preferred |
| // size (if it's the one that pushes out the default size of the container). |
| extY = top + bottom; |
| } |
| else if (!isNaN(baseline)) |
| { |
| extY = Math.round(baseline - layoutElement.baselinePosition); |
| } |
| else if (!isNaN(top) || !isNaN(bottom)) |
| { |
| extY = isNaN(top) ? 0 : top; |
| extY += isNaN(bottom) ? 0 : bottom; |
| } |
| else |
| { |
| extY = layoutElement.getBoundsYAtSize(NaN, NaN); |
| } |
| |
| var preferredHeight:Number = layoutElement.getPreferredBoundsHeight(); |
| height = Math.max(height, extY + preferredHeight); |
| |
| // Find the minimum default extents, we take the minimum height only |
| // when the element size is determined by the parent size |
| var elementMinHeight:Number = |
| constraintsDetermineHeight(elementInfo) ? layoutElement.getMinBoundsHeight() : |
| preferredHeight; |
| |
| minHeight = Math.max(minHeight, extY + elementMinHeight); |
| } |
| } |
| |
| var vec:Vector.<Number> = new Vector.<Number>(4, true); |
| vec[0] = Math.max(width, minWidth); |
| vec[1] = Math.max(height, minHeight); |
| vec[2] = minWidth; |
| vec[3] = minHeight; |
| |
| return vec; |
| } |
| |
| /** |
| * @private |
| * Iterates over elements and calls parseElementConstraints on each. |
| */ |
| private function parseConstraints():void |
| { |
| var layoutTarget:GroupBase = target; |
| if (!layoutTarget) |
| return; |
| |
| constraintCacheNeeded++; |
| |
| var count:Number = layoutTarget.numElements; |
| var layoutElement:ILayoutElement; |
| |
| var cache:Dictionary = new Dictionary(true); |
| var i:int; |
| |
| // Populate rowBaselines with baseline information from rows. |
| var n:int = _constraintRows.length; |
| var row:ConstraintRow; |
| |
| if (rowBaselines == null) |
| rowBaselines = new Vector.<Array>(); |
| else |
| rowBaselines.length = 0; |
| |
| for (i = 0; i < n; i++) |
| { |
| row = _constraintRows[i]; |
| rowBaselines[i] = LayoutElementHelper.parseConstraintExp(row.baseline); |
| |
| var maxAscentStr:String = rowBaselines[i][1]; |
| if (maxAscentStr && maxAscentStr != "maxAscent") |
| throw new Error(ResourceManager.getInstance().getString("layout", "invalidBaselineOnRow", |
| [ row.id, row.baseline ])); |
| } |
| |
| for (i = 0; i < count; i++) |
| { |
| layoutElement = layoutTarget.getElementAt(i); |
| if (!layoutElement || !layoutElement.includeInLayout) |
| continue; |
| |
| parseElementConstraints(layoutElement, cache); |
| } |
| |
| this.constraintCache = cache; |
| constraintCacheNeeded--; |
| } |
| |
| /** |
| * @private |
| * This function parses the constraints of a single element, creates an |
| * ElementConstraintInfo object for the element, and throws errors if the |
| * columns or rows are not found for each constraint. |
| */ |
| private function parseElementConstraints(layoutElement:ILayoutElement, constraintCache:Dictionary):void |
| { |
| // Variables to track the offsets |
| var left:Number; |
| var right:Number; |
| var top:Number; |
| var bottom:Number; |
| var baseline:Number; |
| |
| // Variables to track the boundaries from which |
| // the offsets are calculated from. If null, the |
| // boundary is the parent container edge. |
| var leftBoundary:String; |
| var rightBoundary:String; |
| var topBoundary:String; |
| var bottomBoundary:String; |
| var baselineBoundary:String; |
| |
| var message:String; |
| |
| var temp:Array = LayoutElementHelper.parseConstraintExp(layoutElement.left); |
| left = temp[0]; |
| leftBoundary = temp[1]; |
| |
| temp = LayoutElementHelper.parseConstraintExp(layoutElement.right, temp); |
| right = temp[0]; |
| rightBoundary = temp[1]; |
| |
| temp = LayoutElementHelper.parseConstraintExp(layoutElement.top, temp); |
| top = temp[0]; |
| topBoundary = temp[1]; |
| |
| temp = LayoutElementHelper.parseConstraintExp(layoutElement.bottom, temp); |
| bottom = temp[0]; |
| bottomBoundary = temp[1]; |
| |
| temp = LayoutElementHelper.parseConstraintExp(layoutElement.baseline, temp); |
| baseline = temp[0]; |
| baselineBoundary = temp[1]; |
| |
| // save values into a Dictionary based on element name. |
| var elementInfo:ElementConstraintInfo = new ElementConstraintInfo(layoutElement, |
| left, right, top, bottom, baseline, |
| leftBoundary, rightBoundary, |
| topBoundary, bottomBoundary, baselineBoundary); |
| constraintCache[layoutElement] = elementInfo; |
| |
| // If some pair of boundaries don't exist, we will need to measure |
| // the container size based on the element's other properties like |
| // x, y, width, height. |
| var i:Number; |
| if ((!leftBoundary && !rightBoundary) || |
| (!topBoundary && !bottomBoundary) || |
| !baselineBoundary) |
| { |
| if (!otherElements) |
| otherElements = new Vector.<ElementConstraintInfo>(); |
| |
| otherElements.push(elementInfo); |
| } |
| |
| // match columns |
| if (leftBoundary || rightBoundary) |
| { |
| var numColumns:Number = _constraintColumns.length; |
| var colIndex:Object; |
| |
| if (!colSpanElements) |
| colSpanElements = new Vector.<ElementConstraintInfo>(); |
| |
| colSpanElements.push(elementInfo); |
| |
| if (leftBoundary) |
| { |
| colIndex = columnsObject[leftBoundary]; |
| |
| if (colIndex != null) |
| elementInfo.colSpanLeftIndex = int(colIndex); |
| |
| // throw error if no match. |
| if (elementInfo.colSpanLeftIndex < 0) |
| { |
| message = ResourceManager.getInstance().getString( |
| "layout", "columnNotFound", [ leftBoundary ]); |
| throw new ConstraintError(message); |
| } |
| } |
| |
| // can we assume rightIndex >= leftIndex? |
| if (rightBoundary) |
| { |
| colIndex = columnsObject[rightBoundary]; |
| |
| if (colIndex != null) |
| elementInfo.colSpanRightIndex = int(colIndex); |
| |
| // throw error if no match. |
| if (elementInfo.colSpanRightIndex < 0) |
| { |
| message = ResourceManager.getInstance().getString( |
| "layout", "columnNotFound", [ rightBoundary ]); |
| throw new ConstraintError(message); |
| } |
| } |
| } |
| |
| // match rows. |
| if (topBoundary || bottomBoundary || baselineBoundary) |
| { |
| var rowIndex:Object; |
| |
| if (!rowSpanElements) |
| rowSpanElements = new Vector.<ElementConstraintInfo>(); |
| |
| rowSpanElements.push(elementInfo); |
| |
| if (topBoundary) |
| { |
| rowIndex = rowsObject[topBoundary]; |
| |
| if (rowIndex != null) |
| elementInfo.rowSpanTopIndex = int(rowIndex); |
| |
| // throw error if no match. |
| if (elementInfo.rowSpanTopIndex < 0) |
| { |
| message = ResourceManager.getInstance().getString( |
| "layout", "rowNotFound", [ topBoundary ]); |
| throw new ConstraintError(message); |
| } |
| } |
| |
| if (bottomBoundary) |
| { |
| rowIndex = rowsObject[bottomBoundary]; |
| |
| if (rowIndex != null) |
| elementInfo.rowSpanBottomIndex = int(rowIndex); |
| |
| // throw error if no match. |
| if (elementInfo.rowSpanBottomIndex < 0) |
| { |
| message = ResourceManager.getInstance().getString( |
| "layout", "rowNotFound", [ bottomBoundary ]); |
| throw new ConstraintError(message); |
| } |
| } |
| |
| if (baselineBoundary) |
| { |
| rowIndex = rowsObject[baselineBoundary]; |
| |
| if (rowIndex != null) |
| elementInfo.baselineIndex = int(rowIndex); |
| |
| // throw error if no match. |
| if (elementInfo.baselineIndex < 0) |
| { |
| message = ResourceManager.getInstance().getString( |
| "layout", "rowNotFound", [ baselineBoundary ]); |
| throw new ConstraintError(message); |
| } |
| |
| // when using maxAscent, calculate maximum baselinePosition for this row. |
| var bIndex:int = elementInfo.baselineIndex; |
| var numRows:Number = _constraintRows.length; |
| |
| if (rowBaselines[bIndex][1]) |
| { |
| // maxAscents will all default to 0. |
| if (!rowMaxAscents) |
| rowMaxAscents = new Vector.<Number>(numRows, true); |
| |
| rowMaxAscents[bIndex] = Math.max(rowMaxAscents[bIndex], layoutElement.baselinePosition); |
| } |
| } |
| } |
| } |
| |
| /** |
| * @private |
| */ |
| private function clearConstraintCache():void |
| { |
| if(!constraintCacheNeeded) |
| { |
| colSpanElements = null; |
| rowSpanElements = null; |
| otherElements = null; |
| rowBaselines = null; |
| rowMaxAscents = null; |
| constraintCache = null; |
| } |
| } |
| } |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // |
| // Helper class: ElementConstraintInfo |
| // |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| import mx.core.ILayoutElement; |
| |
| class ElementConstraintInfo |
| { |
| //-------------------------------------------------------------------------- |
| // |
| // Constructor |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| */ |
| public function ElementConstraintInfo( |
| layoutElement:ILayoutElement, |
| left:Number, right:Number, |
| top:Number, bottom:Number, |
| baseline:Number, leftBoundary:String = null, |
| rightBoundary:String = null, |
| topBoundary:String = null, bottomBoundary:String = null, |
| baselineBoundary:String = null, |
| colSpanLeftIndex:int = -1, colSpanRightIndex:int = -1, |
| rowSpanTopIndex:int = -1, rowSpanBottomIndex:int = -1, |
| baselineIndex:int = -1):void |
| { |
| super(); |
| |
| // pointer to element |
| this.layoutElement = layoutElement; |
| |
| // offsets |
| this.left = left; |
| this.right = right; |
| this.top = top; |
| this.bottom = bottom; |
| this.baseline = baseline; |
| |
| // boundaries (ie: parent, column or row edge) |
| this.leftBoundary = leftBoundary; |
| this.rightBoundary = rightBoundary; |
| this.topBoundary = topBoundary; |
| this.bottomBoundary = bottomBoundary; |
| this.baselineBoundary = baselineBoundary; |
| |
| this.colSpanLeftIndex = colSpanLeftIndex; |
| this.colSpanRightIndex = colSpanRightIndex; |
| this.rowSpanTopIndex = rowSpanTopIndex; |
| this.rowSpanBottomIndex = rowSpanBottomIndex; |
| this.baselineIndex = baselineIndex; |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Properties |
| // |
| //-------------------------------------------------------------------------- |
| |
| public var layoutElement:ILayoutElement; |
| |
| public var left:Number; |
| public var right:Number; |
| public var top:Number; |
| public var bottom:Number; |
| public var baseline:Number; |
| public var leftBoundary:String; |
| public var rightBoundary:String; |
| public var topBoundary:String; |
| public var bottomBoundary:String; |
| public var baselineBoundary:String; |
| |
| public var colSpanLeftIndex:int; |
| public var colSpanRightIndex:int; |
| public var rowSpanTopIndex:int; |
| public var rowSpanBottomIndex:int; |
| public var baselineIndex:int; |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // |
| // Helper class: ConstraintRegionFlexChildInfo |
| // |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| import mx.containers.utilityClasses.FlexChildInfo; |
| |
| class ConstraintRegionFlexChildInfo extends FlexChildInfo |
| { |
| public var index:int |
| } |