blob: dc49bd97d93bbdd8c1b38bc038d15cecff86d5aa [file] [log] [blame]
////////////////////////////////////////////////////////////////////////////////
//
// 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;
}
}
}
}
}
}
}