Gumbo Layout - Functional and Design Specification
Glossary
- Post-transform bounds - bounds of an object in object's parent coordinate space, i.e. bounts of the transformed object.
- Pre-transform bounds - bounds of an object in object's own coordinate space, i.e. bounds of the untransformed object (object's dimensions).
- Flexible size - size of a layout item that depends on parent size (changes whenever parent size changes), i.e. object with width="100%" has flexible width.
Summary and Background
This spec describes an interface that components/objects need to implement in order to be supported by the Gumbo layouts. This is an internal system interface (i.e. doesn't show up in mxml). This interface is purely a contract between the layout and the layout items.
This interface is not intended to be used by app developers/designers to specify user defined layout properties (i.e. properties like width="100%", minWidth="20", etc.). There will be a separate spec describing user defined layout properties for Gumbo.
Requirements for Gumbo layouts addressed by this spec
- With Gumbo our new components visuals are separated into the skin and they consist of differently typed objects: GraphicElement, IDisplayObject, UIComponent. To arrange those we need layouts that operate on various types of objects.
- We would like to enhance the layouts to handle objects with complex transform like rotation, 2.5d, etc. and still make it easy to create new layouts and extend existing ones.
Proposal
- Define an interface ILayoutElement that will abstract the different objects that need to be sized and arranged from the layout. This interface needs to be such that allows for easy layout of objects with complex transform as well as allowing creation of complex layouts that calculate and set transforms.
- Implement the ILayoutElement interface for the different objects that the new layouts would support (UIComponent, IFlexDisplayObject, GraphicElement)
- Implement a factory class that would instantiate the ILayoutElement concrete classes based on the passed in objects (needed by the layouts)
Usage Scenario
- Implement Gumbo layouts. The layout code will call directly methods on the ILayoutElement interface to arrange elements.
- Implement Gumbo components/GraphicElements. Whenever implementing objects that need to be arranged by the Gumbo layouts, those objects need to implement the ILayoutElement interface. In most cases the interface will be implemented in the base classes and developers can choose to override only particular ILayoutElement methods.
- Developers can use the ILayoutElement interface to query for size/position as set by the layout system by calling getLayoutPosition and getLayoutSize after the layout pass has completed.
Detailed Description
ILayoutElement interface
The ILayoutElement interface represents an object that supports being laid out. Any object that needs to be included in the Gumbo layout, needs to implement ILayoutElement interface. The layout element itself as a concept is a simple x, y axis-aligned bounding box in parent coordinate space. Object complex transforms, rotations, 3D, etc. can be abstracted away by this interface and are taken care of in the ILayoutElement implementations for the given object type (i.e. the implementations are responsible for converting between pre-transfrom and post-transform coordinates) The interface should also expose low-level information such as pre-transform bounds, transfomration matrix.
ILayoutElement needs to expose information that describes layout preferences as supported by the layout system:
- preferred, min, max bounds sizes (both pre-transfrom and post-trasform). The preferred bounds size represents the object's ideal size in the absence of any constraints. This is similar to the Halo getExplicitOrMeasured() methods. Preferred, min and max sizes must not depend on the current layout size that is set by the layout. Otherwise, repeated layout passes could result in layout sizes continuously changing and lead to infinite loop.
- percent (optional, parent coordinates): width, height - sizes of child's post-transfrom bounds as a percentage of the parent's pre-transform bounds size that is shared between flexible children.
- constraints (as set in mxml: optional, parent coordinates): left, top, right, bottom, horizontalCenter, verticalCenter, baseline.
ILayoutElement has to expose methods to allow the layout to position the object
- setLayoutPosition(x:Number, y:Number, postTransform:Boolean):void, specifies the coordinates of the child's pre/post-transform bounds top-left corner, in parent coordinates.
ILayoutElement has to expose methods to allow the layout to size the object:
In Halo the layout always specifies both width & height of the object. However we need to allow for more general cases, where the width & height of the object bounds may be co-dependent. Examples are:
- text - changing the number of rows generally changes the number of columns for a fixed text content;
- transformed objects - for example a rectangle with rotation=60 that has its width+=10 will have both its transformed bounds width and height increased by cos(60)*10 and sin(60)*10 respectively;
- sphere - changing the width directly changes the height (if we want to preserve the sphere)
- object with an option to preserve its aspect ratio.
- etc.
When a layout determines the size of an object, that size may depend on object's parent dimensions and layout calculations (i.e. both left & right are specified for an object in a canvas layout, or we have percent size in a vertical layout). When an object's width/height, as determined by the layout, has such a dependency we say it's "flexible" since it'll change whenever the size of the parent changes.
In Halo we have a couple of cases for object sizing with several sub-cases
- If the object is not flexible, we set its size to its preferred size (explicit if present, else measured)
- If the object is flexible then
- if only the object's width is flexible - we set the percent width and the preferred height with min/max limits applied
- if only the object's height is flexible - we set the percent height and the preferred width with min/max limits applied
- if both the object's width and height are flexible - we set both the percent width and percent height with min/max limits applied
What we propose for the Gumbo ILayoutElement interface API covers the same cases, however it allows ILayoutElement implementations to return different sizes in certain cases. This is new behavior which the new Gumbo layouts need to account for.
The ILayoutElement implementation would:
- If the object is not flexible - set its size to its preferred size.
- If the object is flexible then
- if only the width is flexible - set the object's pre-transformed bounds such that the post-transform bounds have the specified width. In the common cases when the width and height are not co-related, the post-transform height is set to the preferred height (same as Flex3). However, if they are co-related, then the height may be affected by the width and the the ILayoutElement implementation may choose to select different post-transform height.
An example illustrating this is a text label that has width=100% and height unspecified. The Gumbo layout would calculate the width in pixels and pass it to the ILayoutElement::setLayoutSize, the implementation will calculate the new height based on the specified width and return the new size.
- If only the height is flexible - treat similarly to the case above.
- If both the width & height are flexible - the pre-transform bounds will be calculated so that the post-transform bounds match the width & height. If that's not possible (usually when width and height of the object are co-related we get over constraint), the pre-transform bounds are set to the maximum possible value such that the post-transform bounds are within the specified width & height.
As a consequence the layout will need to deal with the fact that setLayoutSize() could return bounds size different from the specified. There are a couple of options for a general strategy to handle this new behavior (details will be in a separate spec):
- On updateDisplayList if the new bounds are different from the adjusted, the layout may invalidate again and do a second pass through measure using the new bounds. This is very similar to the way text measure works in Halo and we have a working prototype for the vertical layout. In addition, there are cases where the measured width & height of the layout are co-related as well (i.e. the layout has objects with co-related width & height and some are with flexible width and others with flexible height). Because of those cases we limit the measure passes in the prototype to 2 and we respect the height for the vertical layout rather than the width.
- We can modify the layout manager to pass down available size recursively during the measure (or return up during updateDisplayList) phase so that the layout can actually size the flexible children and report the correct size... no prototypes of this yet...
Additionally, to allow for custom layouts that explicitly calculate transforms and sizes in child coordinates, the ILayoutElement APIs must be able to work in both post and pre-transform coordinates as well as provide ways to set the layout transformation matrix explicitly.
API Description
B Features
Additional Implementation Details
Prototype Work
- ILayoutElement interface
- ILayoutElementUIC (ILayoutElement implemented for UIComponent)
- works with arbitrary transform matrices except for case when specifying arbitrary sizes (in those cases the prototype supports only rotation)
- Modified the Gumbo VerticalLayout to work with the ILayoutElement interface.
- The prototype uses the two-pass measure strategy to solve the co-related post-transform bounds width/height problem for its flexible width items.
- Modified the Gumbo BasicLayout to work with the ILayoutElement interface.
- The prototype doesn't have code for two-pass measure strategy.
- 3D wheel layout
Compiler Work
No compiler changes required by this feature.
Web Tier Compiler Impact
No impact by this feature.
Flex Feature Dependencies
Backwards Compatibility
Syntax changes
No syntax changes for this feature.
Behavior
Since these layouts will operate only within the new Gumbo components, there should be no behavior/performance implications to existing application.
Performance
ILayoutElement as an wrapper object
We've decided not to use ILayoutElement as an wrapper object.
- For certain objects ILayoutElement interface could be implemented via wrapper helper classes - ILayoutElementUIC works like this. Do we create/destroy those objects every time the layout manager does a measure/update pass, or does the layout keep those around between measure/update passes?
- We have decided to implement the interface directly by the object. No wrappers will be used.
- We could provide optimized implementations of ILayoutElement for the most common cases (i.e. no complex transforms, fixed explicit sizes).
What's the impact of using Point in the interface vs. simple types? How does this affect the garbage collector?
We've decided not to use Point base interface, but have pair of methods returning width and height.
-
- I took profiles of a scenarios with ~10,000 measure and 16,000 updateDisplayList of a lot of buttons, most of those with non-complex transforms. The profiles didn't show any noticable difference above margin of error for interface using Point and one returning simple types. After timing the same scenario in a tight loop I found that the non-Point returning interface was ~2.5% faster. This explains why I couldn't see that difference with the profiler, since measure and updateDisplayList took 20% of the app time hence the app time delta was 0.5%. Memory usage was the same.
- We could provide an optional parameter to pass in an already allocated Point as an out parameter to minimize the allocation of temporary objects.
|
 |
Can we add a property on ILayoutItem which points to the item being laid out. So for a GraphicElement, it just points to itself, but for a random DisplayObject, it'll point to that DisplayObject.