//////////////////////////////////////////////////////////////////////////////// // // 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 mx.containers.utilityClasses { import mx.core.IChildList; import mx.core.IUIComponent; [ExcludeClass] /** * @private * The Flex class is for internal use only. */ public class Flex { include "../../core/Version.as"; //-------------------------------------------------------------------------- // // Class 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. * * @param parent The parent container of the children. * * @param spaceForChildren The space that is to be * distributed across all the children. * * @param h height for all children. * * @result Any extra space that's left over * after growing all children to their maxWidth. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public static function flexChildWidthsProportionally( parent:IChildList, spaceForChildren:Number, h:Number):Number { var spaceToDistribute:Number = spaceForChildren; var totalPercentWidth:Number = 0; var childInfoArray:Array = []; var childInfo:FlexChildInfo; var child:IUIComponent; var i:int; // 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 n:int = parent.numChildren; for (i = 0; i < n; i++) { child = IUIComponent(parent.getChildAt(i)); var percentWidth:Number = child.percentWidth; var percentHeight:Number = child.percentHeight; var height:Number; if (!isNaN(percentHeight) && child.includeInLayout) { height = Math.max(child.minHeight, Math.min(child.maxHeight, ((percentHeight >= 100) ? h : h * percentHeight / 100))); } else { height = child.getExplicitOrMeasuredHeight(); } if (!isNaN(percentWidth) && child.includeInLayout) { totalPercentWidth += percentWidth; childInfo = new FlexChildInfo(); childInfo.percent = percentWidth; childInfo.min = child.minWidth; childInfo.max = child.maxWidth; childInfo.height = height; childInfo.child = child; childInfoArray.push(childInfo); } else { var width:Number = child.getExplicitOrMeasuredWidth(); // 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.setActualSize(Math.floor(width), Math.floor(height)); } else { child.setActualSize(width, height); } if (child.includeInLayout) { // 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(spaceForChildren, spaceToDistribute, totalPercentWidth, childInfoArray); // Set the widths and heights of the flexible children n = childInfoArray.length; for (i = 0; i < n; i++) { 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.setActualSize(Math.floor(childInfo.size), Math.floor(childInfo.height)); } else { child.setActualSize(childInfo.size, childInfo.height); } } distributeExtraWidth(parent, spaceForChildren); } 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. * * @param parent The parent container of the children. * * @param spaceForChildren The space that is to be * distributed across all children . * * @param w width for all children. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public static function flexChildHeightsProportionally( parent:IChildList, spaceForChildren:Number, w:Number):Number { var spaceToDistribute:Number = spaceForChildren; var totalPercentHeight:Number = 0; var childInfoArray:Array = []; var childInfo:FlexChildInfo; var child:IUIComponent; var i:int; // 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 n:int = parent.numChildren; for (i = 0; i < n; i++) { child = IUIComponent(parent.getChildAt(i)); var percentWidth:Number = child.percentWidth; var percentHeight:Number = child.percentHeight; var width:Number; if (!isNaN(percentWidth) && child.includeInLayout) { width = Math.max(child.minWidth, Math.min(child.maxWidth, ((percentWidth >= 100) ? w : w * percentWidth / 100))); } else { width = child.getExplicitOrMeasuredWidth(); } if (!isNaN(percentHeight) && child.includeInLayout) { totalPercentHeight += percentHeight; childInfo = new FlexChildInfo(); childInfo.percent = percentHeight; childInfo.min = child.minHeight; childInfo.max = child.maxHeight; childInfo.width = width; childInfo.child = child; childInfoArray.push(childInfo); } else { var height:Number = child.getExplicitOrMeasuredHeight(); // 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.setActualSize(Math.floor(width), Math.floor(height)); } else { child.setActualSize(width, height); } if (child.includeInLayout) { // 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(spaceForChildren, spaceToDistribute, totalPercentHeight, childInfoArray); // Set the widths and heights of the flexible children n = childInfoArray.length; for (i = 0; i < n; i++) { 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.setActualSize(Math.floor(childInfo.width), Math.floor(childInfo.size)); } else { child.setActualSize(childInfo.width, childInfo.size); } } distributeExtraHeight(parent, spaceForChildren); } 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. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ 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; // 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 // 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. // This unused space can be used to fulfill mins and maxes. var unused:Number = spaceToDistribute - (spaceForChildren * totalPercent / 100); if (unused > 0) spaceToDistribute -= unused; else unused = 0; // 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:FlexChildInfo = 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; // Use unused space first before reducing flexible space. if (unused >= min) { unused -= min; } else { spaceToDistribute -= min - unused; unused = 0; } 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; // Use unused space first before reducing flexible space. if (unused >= max) { unused -= max; } else { spaceToDistribute -= max - unused; unused = 0; } 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 + unused) - 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 * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public static function distributeExtraHeight( parent:IChildList, 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 n:int = parent.numChildren; var wantToGrow:Boolean = false; var i:int; var percentHeight:Number; var spaceToDistribute:Number = spaceForChildren; var spaceUsed:Number = 0; var child:IUIComponent; var childHeight:Number; var wantSpace:Number; for (i = 0; i < n; i++) { child = IUIComponent(parent.getChildAt(i)); if (!child.includeInLayout) continue; childHeight = child.height; percentHeight = child.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 >= 1.0) { // Start optimistically stillFlexibleComponents = false; for (i = 0; i < n; i++) { child = IUIComponent(parent.getChildAt(i)); childHeight = child.height; percentHeight = child.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) && child.includeInLayout && childHeight < child.maxHeight) { wantSpace = Math.ceil(percentHeight/100 * spaceForChildren); if (wantSpace > childHeight) { child.setActualSize(child.width, 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 * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public static function distributeExtraWidth( parent:IChildList, 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 n:int = parent.numChildren; var wantToGrow:Boolean = false; var i:int; var percentWidth:Number; var spaceToDistribute:Number = spaceForChildren; var spaceUsed:Number = 0; var child:IUIComponent; var childWidth:Number; var wantSpace:Number; for (i = 0; i < n; i++) { child = IUIComponent(parent.getChildAt(i)); if (!child.includeInLayout) continue; childWidth = child.width; percentWidth = child.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 >= 1.0) { // Start optimistically stillFlexibleComponents = false; for (i = 0; i < n; i++) { child = IUIComponent(parent.getChildAt(i)); childWidth = child.width; percentWidth = child.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) && child.includeInLayout && childWidth < child.maxWidth) { wantSpace = Math.ceil(percentWidth / 100 * spaceForChildren); if (wantSpace > childWidth) { child.setActualSize(childWidth +1, child.height); spaceToDistribute--; stillFlexibleComponents = true; if (spaceToDistribute == 0) return; } } } } } } }