<mx:constraintcolumn id="nav" width="212"></mx:constraintcolumn>
. Percentage size means that the space taken up by that region is a percentage of the overall space available to the parent Canvas, ie: <mx:constraintrow id="footer" height="20%"></mx:constraintrow>
. Content size means that the space taken up by that region is dictated by the content contained in that space. As the size of the content changes, so does the size of the region. An example of a content sized constraint row is: <mx:constraintrow id="footer"></mx:constraintrow>
- one can infer this is a content sized constraint row since the height property is not set. As space is added or removed from the parent Canvas, space is distributed to each ConstraintColumn and ConstraintRow instance according to its sizing option.
Constraint columns/rows will be able to set minimum and maximum sizes to control how much they grow or shrink. This is useful in percentage-sized constraint regions. As a constraint region shrinks below its minimum size, the parent Canvas will sprout scrollbars to show hidden content.
### Constraints
Controls are placed within, or can span across, the constraint regions which partition the main Canvas. A control identifies its content area by specifying the constraint region or parent border which it is bounded by on the left, right, top and bottom. This identification happens by the value passed into the original constraint styles defined in UIComponent: `left`, `right`, `top` and `bottom`. A micro-language is used to specify the constraint region and the pixel offset. The pixel offset is evaluated relative to that constraint region. For example, this mxml:
<mx:canvas>
<mx:constraintcolumns>
<mx:constraintcolumn id="nav" width="212">
<mx:constraintcolumn id="column1" width="100%">
</mx:constraintcolumn></mx:constraintcolumn></mx:constraintcolumns>
<mx:constraintrows>
<mx:constraintrow id="row1" height="80%">
<mx:constraintrow id="footer" height="20%">
</mx:constraintrow></mx:constraintrow></mx:constraintrows>
<mx:list left="nav:10" right="nav:10" top="row1:10" bottom="row1:10">
</mx:list></mx:canvas>
defines a List control which is bounded by the ConstraintColumn on the left with id="nav", the ConstraintColumn on the right with id="nav", the ConstraintRow on top with id="row1" and the ConstraintRow below with id="row2". The List is placed such that it is 10 pixels offset from the left and right edges of the "nav" ConstraintColumn and 10 pixels offset from the top and bottom of the "row1" ConstraintRow. The List will stretch to consume the space defined. If the main Canvas is resized horizontally, the List will not grow since it lives in the fixed "nav" ConstraintColumn. If the main Canvas is resized vertically, the List will grow to always consume 80% of the total height of the Canvas.
To specify that a control's content area is bounded by a parent Canvas border, the original syntax is used. An pffset without a ConstraintColumn or ConstraintRow id implies that the offset is relative to the parent border. For example, in this mxml:
<mx:canvas id="main">
<mx:constraintcolumns>
<mx:constraintcolumn id="nav" width="212">
<mx:constraintcolumn id="column1" width="100%">
</mx:constraintcolumn></mx:constraintcolumn></mx:constraintcolumns>
<mx:constraintrows>
<mx:constraintrow id="row1" height="80%">
<mx:constraintrow id="footer" height="20%">
</mx:constraintrow></mx:constraintrow></mx:constraintrows>
<mx:list left="10" right="nav:10" top="row1:10" bottom="row1:10">
</mx:list></mx:canvas>
There are some rules, which are hopefully obvious, that dictate how a control behaves based on what constraint regions are set to define its content area. When using DesignView to create constraint regions and apply constraints, we will auto-generate mxml that follows these rules.
*Unset constraint regions default to the appropriate Canvas edge border. IE: `<mx:list left="10" right="nav:10" top="row1:10" bottom="row1:10">` the left constraint is evaluated relative to the Canvas' left edge.
*If neither of the paired constraint regions are set (ie: left and right are evaluated relative to the Canvas' left and right edges), and a control is defined that spans multiple existing constraint columns, those constraint columns do not take that control into account when evaluating their reflow behavior. (ie: if those constraint columns are content sized columns, they do not count that control as content they care about).
*If a control's boundary is set to an invalid constraint region (ie: left references a ConstraintRow id, left references a non-existant or misspelled constraint region), we will throw an error. This is the downside of not having databinding in specifying the constraint boundary expressions. Additionally, we may chose to emit RTE's for other errors in the micro-language used to specify a constraint boundary and offset.
Below are some simple examples showing a variety of constraint regions and constraint permutations and the expected behavior in those situations.
<mx:button id="btn" left="col1:20" right="col1">
<mx:button id="btn" left="col1:20" right="col4:0">
<mx:button id="btn" left="20" right="20">
<mx:button id="btn" left="col1:20" right="col2:20">
#### Center Constraints
If the horizontalCenter or verticalCenter constraint is set, that constraint is evaluated relative to the constraint region specified, or the center of the parent container if no constraint region is specified. In specifying constraint regions, horizontalCenter constraints require a ConstraintColumn to evaluate off of and verticalCenter constraints require a ConstraintRow to evaluate off of. Examples are included below.
<mx:button horizontalcenter="col1:0">
<mx:button verticalcenter="row1:0">
<mx:button horizontalcenter="col1:20">
<mx:button verticalcenter="20">
#### Baseline Constraint
We would like to add the ability to constrain controls along a baseline. A new `baseline` constraint style will be added to UIComponent to accomplish this.
Setting the baseline constraint means that the user is specifying an offset from the top edge of constraint region or parent border and the control's baseline. By offering a baseline constraint style, we can now allow multiple components to be constrained along a single shared baseline. Examples are included below.
<mx:button baseline="row2:0">
<mx:button baseline="20">
<mx:button baseline="row1:0">
<mx:label baseline="row1:0">
<mx:linkbutton baseline="row1:0">
#### Solving Constraints
We will write a new layout algorithm that a Canvas uses whenever it has constraint regions specified. If no constraint regions are specified, the old layout algorithm will be used. The new layout algorithm is very similar to the old layout algorithm. The description below is written with respect to horizontal coordinates.
**Step 1. Given the current size of the parent Canvas, decipher pixel values for all the ConstraintColumns.**
1a. Fixed ConstraintColumns honor their pixel values.
1b. Content sized ConstraintColumns figure out if they have content that spans multiple columns or not (ie: all the content in that ConstraintColumn is contained solely in that ConstraintColumn or spans that ConstraintColumn plus other ConstraintColumns).
*Content is contained solely in that ConstraintColumn means the ConstraintColumn assumes the width of the widest child.
*Content spans multiple columns:
* (1) Sort the children by order of how many columns they are spanning (least to most)
* (2) For children spanning a single column, make each column as wide as the preferred size of the child
* (3) Work through the next set of children (ie: those spanning 2 columns) and divide the remainder space equally between shared columns
* We might want to consider sorting the children in each column-span set by size and evaluate remainder space in order of smallest to largest.
Note: This means content-sized column widths are decided in order of children encountered from top to bottom
1c. The remaining space is divided up between the percentage sized ConstraintColumns and pixel values are assigned for them.
**Step 2. Determine the size and position of children in each ConstraintColumn (pretty much the same as the existing layout algorithm).**
2a. If both left and right anchors are specified, the actual width is determined by them, relative to the constraint column width. However, the actual width is subject to the child's minWidth.
2b. Otherwise, if a percentWidth was specified, this percentage is applied to the constraint column's width (the widest specified point of content, or the width of the constraint column, whichever is greater). The actual width is subject to the child's minWidth and maxWidth.
2c. Otherwise, if an explicitWidth was specified, this is used as the actual width.
2d. Otherwise, the measuredWidth is used as the actual width.
**Step 3. Then the x coordinate of the child is determined.**
-Note: If we were working on the vertical plan and trying to determine the y coordinate of a child, the following step would be added:
3a. If a baseline constraint is specified, the center of the child is placed relative to the top constraint row or the top edge of the parent if the top constraint row is not set.-
3a. If a horizonalCenter constraint is specified, the center of the child is placed relative to the center of the content area.
3b. Otherwise, if a left constraint is specified, the left edge of the child is placed there.
3c. Otherwise, if a right constraint is specified, the right edge of the child is placed there.
3d. Otherwise, the child is left at its previously set x coordinate.
**Step 4. If the child's width is a percentage, try to make sure it doesn't overflow the constraint column width (while still honoring minWidth). We need to wait until after the x coordinate is set to test this.**
### Creating Sibling Relative Layouts
With the addition of ConstraintColumns and ConstraintRows along with constraints, it should now be easy for visual designers to create sibling-relative layouts. For example, a common requirement is something like: "let the OK button be its natural size, and put the cancel button next to it and let the cancel button take up no more then 100 pixels."
That can be achieved like so:
<mx:canvas>
<mx:constraintcolumns>
<mx:constraintcolumn id="col1">
<mx:constraintcolumn id="col2" width="100">
</mx:constraintcolumn></mx:constraintcolumn></mx:constraintcolumns>
<mx:button label="OK" left="col1:0" right="col1:0">
<mx:button label="Cancel" left="col2:0" right="col2:0">
</mx:button></mx:button></mx:canvas>
If the OK button grows and shrinks, the Cancel button is always anchored 0 pixels from the OK button's right edge.
If the Canvas has a fixed size, ie: 500 pixels, like so:
<mx:canvas width="500">
<mx:constraintcolumns>
<mx:constraintcolumn id="col1">
<mx:constraintcolumn id="col2" width="100">
</mx:constraintcolumn></mx:constraintcolumn></mx:constraintcolumns>
<mx:button label="OK" left="col1:0" right="col1:0">
<mx:button label="Cancel" left="col2:0" right="col2:0">
</mx:button></mx:button></mx:canvas>
The behavior would be the same. Now, if the OK button grows so that its larger then 400 pixels a scrollbar will sprout on the Canvas such that the user can scroll and see the rest of the Button.
## Terminology
Why did we chose constraint terminology (column/rows with a width/height) instead of guides with an x/y location? Well - it was hotly debated but in the end we decided this was a layout feature where perhaps controls were being anchored to guides, but fundamentally we were concerned with the space that was being managed by those guides. And that space contains columns and rows, leading to a constraint-like terminology. The way people primarily understand the feature will be how we author it, and the key is to make the API readable and easy to understandand. The FlexBuilder UI will make the idea of constraint rows and columns easy to understand and visualize.
API Description
The following classes will be added to the package: mx.containers.utilityClasses
package mx.containers.utilityClasses
{
/**
* IConstraintLayout is a marker interface that indicates a container
* is capable of a constraint layout. Those containers in the
* framework that support a constraint layout are Canvas, Application
* and Panel. To utilize the constraint layout in these containers,
* set the layout
property to "absolute" and create
* constraint regions.
*
* @see mx.containers.Canvas
* @see mx.containers.Panel
* @see mx.core.Application
* @see mx.containers.utilityClasses.ConstraintColumn;
* @see mx.containers.utilityClasses.ConstraintRow;
*/
public interface IConstraintLayout
{
//--------------------------------------------------------------------------
//
// Properties
//
//--------------------------------------------------------------------------
//----------------------------------
// constraintColumns
//----------------------------------
/**
* The ConstraintColumn objects partitioning this Canvas. The ConstraintColumn
* at index 0 is the left-most column and indices increase to the
* right.
*
* @default []
*/
function get constraintColumns():Array;
/**
* @private
*/
function set constraintColumns(value:Array):void;
//----------------------------------
// constraintRows
//----------------------------------
/**
* The ConstraintRow objects partitioning this Canvas. The ConstraintRow
* at index 0 is the top-most row and indices increase to the
* bottom.
*
* @default []
*/
function get constraintRows():Array;
/**
* @private
*/
function set constraintRows(value:Array):void;
}
}
package mx.containers.utilityClasses
{
/**
* ConstraintColumns are used to partition a Canvas in the vertical plane.
*
* ConstraintColumns have 3 sizing options available to them: fixed size,
* percentage size, and content size. These sizing options dictate
* the position of the constraint column, the amount of space the constraint column
* takes up in the Canvas and how the constraint column deals with a change in size
* of the Canvas.
*/
public class ConstraintColumn implements IMXMLObject
{
//--------------------------------------------------------------------------
//
// Constructor
//
//--------------------------------------------------------------------------
/**
* Constructor.
*/
public function ConstraintColumn()
{
super();
}
//--------------------------------------------------------------------------
//
// Properties
//
//--------------------------------------------------------------------------
/**
* The unique id for this ConstraintColumn.
*/
public function get id():String;
public function set id(value:String):void;
/**
* The width of this ConstraintColumn. The ConstraintColumn can be either
* a fixed size ConstraintColumn (width = pixel value), a
* percentage sized ConstraintColumn (width = percentage) or
* a content sized ConstraintColumn (width property is unset).
*/
public function get width():Number;
public function set width(value:Number):void;
public function get minWidth():Number;
public function set minWidth():Number;
public function get maxWidth():Number;
public function set maxWidth():Number;
}
}
package mx.containers.utilityClasses
{
/**
* ConstraintRows are used to partition a Canvas in the horizontal plane.
*
* ConstraintRows have 3 sizing options available to them: fixed size,
* percentage size, and content size. These sizing options dictate
* the position of the constraint row, the amount of space the constraint row
* takes up in the Canvas and how the constraint row deals with a change in size
* of the Canvas.
*/
public class ConstraintRow implements IMXMLObject
{
//--------------------------------------------------------------------------
//
// Constructor
//
//--------------------------------------------------------------------------
/**
* Constructor.
*/
public function ConstraintRow()
{
super();
}
//--------------------------------------------------------------------------
//
// Properties
//
//--------------------------------------------------------------------------
/**
* The unique id for this ConstraintRow.
*/
public function get id():String;
public function set id(value:String):void;
/**
* The height of this ConstraintRow. The ConstraintRow can be either
* a fixed size ConstraintRow (height = pixel value), a
* percentage sized ConstraintRow (height = percentage) or
* a content sized ConstraintRow (height property is unset).
*/
public function get height():Number;
public function set height(value:Number):void;
public function get minHeight():Number;
public function set minHeight():Number;
public function get maxHeight():Number;
public function set maxHeight():Number;
}
}
Canvas, Application and Panel will be made to implement IConstraintLayout and the following properties will be added:
/**
* Returns an array of the ConstraintColumn objects partitioning
* this Canvas.
*/
public function get constraintColumns():Array
/**
* @private
* Sets the ConstraintColumn objects partitioning this Canvas.
*/
public function set constraintColumns(value:Array):void
/**
* Returns an array of the ConstraintRow objects partitioning
* this Canvas.
*/
public function get constraintRows():Array
/**
* @private
* Sets the ConstraintRow objects partitioning this Canvas.
*/
public function set constraintRows(value:Array):void
Additions to AnchorStyles.as
/**
* The vertical distance in pixels from the top edge of a control's
* content area and the baseline of the component.
*
* If this style is set, the baseline of the component is
* anchored to the top edge of its top ConstraintRow, or the top of the
* parent container if no top ConstraintRow is set; when its container
* is resized, the two edges maintain their separation.
*
* This style only has an effect when used on a component in a Canvas container,
* or when used on a component in a Panel or Application container that has the
* layout
property set to absolute
.
The default value is undefined
, which means it is not set.
target
* relative to the relativeTo
component. Must be a number within
* the range of constraint columns existing in the Canvas, or 0
* if this is the only constraint column in the Canvas.
*/
public function AddConstraintColumn(relativeTo:UIComponent = null, target:ConstraintColumn = null, index:Number = 0);
RemoveConstraintColumn.as:
/**
* Constructor.
*
* @param target The ConstraintColumn object being removed
*
* @param relativeTo The Canvas which the ConstraintColumn is being removed from.
*/
public function RemoveConstraintColumn(relativeTo:UIComponent = null, target:ConstraintColumn= null)
The same API would be used for AddConstraintRow and RemoveConstraintRow.
2. Reuse the refactoring features built into FlexBuilder so that at authoring time if a constraint region's id is changed, constraint expressions referencing that constraint region are changed throughout the application.
## QA
A few things to think about while testing:
*Test effects where a control moves from one constraint region to another (ie: its content area changes). Transitions calculate toValue and fromValue and we should be able to do the same with constraint columns/rows such that the animation is smooth.
*Test the new constraint column/row layouts with states extensively. Ie: states where constraint regions are added or removed from the base state.
</mx:removeconstraintrow></mx:addconstraintrow></mx:removeconstraintcolumn></mx:addconstraintcolumn></mx:linkbutton></mx:label></mx:button></mx:button></mx:button></mx:button></mx:button></mx:button></mx:button></mx:button></mx:button></mx:button></mx:button></mx:list>