GUI layouts designed for locales where text runs from right to left are typically also arranged right to left.
This Spark/MX feature automates mapping a visual element's default left-to-right (LTR) layout, where X increases to the right and the origin is the upper left corner, to a right-to-left (RTL) layout, where X increases to the left and the origin is the upper right corner. This is accomplished by scaling the X axis by -1 and translating the UIComponent's by its width, after layout.
This feature is called "mirroring" rather than bi-directional layout or locale independent layout for good reason. It's intended to trivialize repurposing a GUI designed for a LTR language, like English, for a RTL language like Hebrew or Farsi. It's not intended to simplify designing RTL layouts or even to abstract our layout primitive, for example by giving developers new BasicLayout constraints like "leading" and "trailing" instead of left and right.
Typically this API will be used to repurpose a GUI designed for a LTR locale by specifying layoutDirection="rtl" for the Application element.
The mirroring API is defined by a new interface called ILayoutDirectionElement. IVisualElement will extend ILayoutDirection. Assets and other elements which are not visual elements but need to be layoutDirection aware, can implement the ILayoutDirectionElement interface.
The interface defines the layoutDirection property whose value can be "ltr" or "rtl" or null for visual elements that do not support inheriting styles. If property's value is null, the layoutDirection is inherited. The interface also defines invalidateLayoutDirection(), a method that's called when an element's layoutDirection changes relative to its parent. When the layoutDirections differ, the implemention applies the mirroring transform, which has the same effect as:
element.x += width; element.scaleX *= -1;
UIComponent implements the new API with a property that's backed by an inheriting style. The other IVisualElement classes: GraphicElement, SpriteVisualElement, UIMovieClip, implement the property directly. BitmapAsset and SpriteAsset implement the property directly. The UITextField, UIFTETextField, and FlexBitmap classes will provide limited support for mirroring (see "Mirroring Implementation Limitations" below).
All of the IVisualElement classes implement the mirroring transform using the same AdvancedLayoutFeatures mechanism as is used by the offsets transform. That means that they're combined with the layout transform the developer has specified after layout has occurred and they do not affect the actual values of the UIComponent x and scaleX properties. From the developer's perspective all coordinates and constraints are defined in terms of the default coordinate system.
The new API is defined in ILayoutDirectionElement.as because it's needed by the both UIComponents (Gumbo and Halo components), and the other IVisualElement implementations, like GraphicElement, as well as by assets which are not IVisualElement.
package mx.core { /** * The ILayoutDirectionElement interface defines the minimum properties and methods * required for an Object to support the layoutDirection property. * * @see mx.core.LayoutDirection * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 1.5 * @productversion Flex 4.1 */ public interface ILayoutDirectionElement { /** * Specifies the desired layout direction for an element: one of LayoutDirection.LTR * (left to right), LayoutDirection.RTL (right to left), or null (inherit). * * This property is typically backed by an inheriting style. If null, * the layoutDirection style will be set to undefined. * * Classes like GraphicElement, which implement ILayoutDirectionElement but do not * support styles, must additionally support a null value for this property * which means the layoutDirection must be inherited from its parent. * * @see mx.core.LayoutDirection * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 1.5 * @productversion Flex 4.1 */ function get layoutDirection():String; /** * @private */ function set layoutDirection(value:String):void; /** * Must be called when an element or its parent's layoutDirection changes. * * If they differ, the X axis is scaled by -1 and the x coordinate of the origin * is translated by the element's width. * * The net effect of this "mirror" transform is to reverse the direction * that the X axis increases without changing the element's * location relative to the parent's origin. * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 1.5 * @productversion Flex 4.1 */ function invalidateLayoutDirection():void; } }
The contants in LayoutDirection.as should be used to set the layoutDirection property.
package mx.core { /** * The LayoutDirection class defines the constant values * for the <code>layoutDirection</code> style of an IStyleClient and the * <code>layoutDirection</code> property of an ILayoutDirectionElement. * * Left-to-right layoutDirection is typically used with Latin-style * scripts. Right-to-left layoutDirection is used with scripts such as * Arabic or Hebrew. * * If an IStyleClient, set the layoutDirection style to undefined to * inherit the layoutDirection from its ancestor. * * If an ILayoutDirectionElement, set the layoutDirection property to null to * inherit the layoutDirection from its ancestor. * * @see mx.styles.IStyleClient * @see mx.core.ILayoutDirectionElement * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 2.0 * @productversion Flex 4.1 */ public final class LayoutDirection { include "Version.as"; //-------------------------------------------------------------------------- // // Class constants // //-------------------------------------------------------------------------- /** * Specifies left-to-right layout direction for a style client or an * element. * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 2.0 * @productversion Flex 4.1 */ public static const LTR:String = "ltr"; /** * Specifies right-to-left layout direction for a style client or an * element. * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 2.0 * @productversion Flex 4.1 */ public static const RTL:String = "rtl"; } }
In UIComponent, the layoutDirection property is backed by an inheriting style defined like this:
<a href="Style%28name%3D%26quot%3BlayoutDirection%26quot%3B%2C%20type%3D%26quot%3BString%26quot%3B%2C%20enumeration%3D%26quot%3Bltr%2Crtl%26quot%3B%2C%20inherit%3D%26quot%3Byes%26quot%3B%29">Style(name="layoutDirection", type="String", enumeration="ltr,rtl", inherit="yes")</a>
Changes to the style are detected by the styleChanged() machinery and deferred until commitProperties() time when they're handled by the invalidateLayoutDirection() method.
To support mirroring, the internal AdvancedLayoutFeatures class, which is used to implement the IVisualElement.postLayoutTransformOffsets property, was extended by adding boolean "mirror" property. If true, the mirroring transform is applied.
When a visual element is added to an IVisualElementContainer, or its layoutDirection property/style is set, the boolean mirror property for all elements of the component's subtree are updated by invalidateLayoutDirection(). For example if the Application's layoutDirection property is "ltr" then adding a component with an "rtl" element neccessitates setting the element's mirror flag. Similarly, if the Application's layoutDirection property is changed from "ltr" to "rtl", then its "ltr" descendants will be mirrored.
Elements that do no support styles, that are children of UIComponents, implement ILayoutDirectionElement if they must be layoutDirection aware. The element's invalidateLayoutDirection() will be called whenever its parent's layoutDirection changes. The element is responsible for detecting when it has been added to a UIComponent and then calling its own invalidateLayoutDirection(), if needed.
For these elements, that do not support styles, the default value of layoutDirection is "ltr". We will investigate adding a layoutDirection parameter to Embed so that it can be initialized. To change the value of layoutDirection at runtime you need to get a reference to the object and use its layoutDirection property.
The diagram below shows the state of the mirror flags when the layoutDirection of the root is changed. In the diagram, node rectangles with a horizontal arrow below them have been mirrored.
Applications that depend on the layout mirroring feature should be configured to the use the Flash Text Engine, which supports BIDI text. To do so in a Flash Builder project, select the "Use Flash Text Engine in Flex Components" checkbox under "Flash Compiler" in the project properties dialog. Selecting this option causes most internal dependencies on mx.core.UITextField to be replaced with mx.core.UIFTETextField. The MX TextInput, TextArea, and RichText text classes do not include this support, since one can just substitute the equivalent Spark text classes.
The UITextField, UIFTETextField and FlexBitmap classes do contain some minor changes so that they can acommodate being embedded in a mirrored IVisualElement. Applications that use these components directly may not layout correctly when the layout direction is "rtl".
In a mirrored IVisualElementContainer , the roles of the left and right BasicLayout constraints are effectively reversed: the left constraint specifies the distance from the component's right edge to the right edge of the container, and the right constraint specifies the distance between the left edges. This is not due to some special handling of these constraints, it's just a consequence of the mirroring transform.
Applications or libraries that convert coordinates to/from local to global coordinates will have to be modified to work correctly with mirrored layouts. One common problem is assuming that the origin of a mirrored component is still the upper left hand corner in the global coordinate system. If the component has been mirrored, i.e. if its layoutDirection is "rtl", then the component's left edge becomes the right edge in global coordinates and the application will have to compensate by offsetting the x coordinate by the component's width. A similar problem can occur when mapping from global to local coordinates.
Code that depends on the localToGlobal(), globalToLocal() methods or on the DisplayObject transform property, also needs to be aware of the difference between a component's "layoutMatrix" (see ILayoutElement) and "computedMatrix". The latter incorporates the layoutMatrix and an IVisualElement's postLayoutTransformOffsets, which include the mirroring transform. The localToGlobal() and globalToLocal() methods and the transform.concatenatedMatrix property reflect the computed matrix. To transform a point to global coordinates using only the layoutMatrix, use MatrixUtil.getConcatenatedMatrix():
var myGlobalPoint = MatrixUtil.getConcatenatedComputedMatrix(myIVisualElement).transformPoint(myPoint);
As with the "direction" (BIDI text direction) style, the global default for layoutDirection is "ltr". The text classes handle BiDi layout internally and do not expect to be mirrored. Other classes, like Image and VideoDisplay typically are not mirrored.
The following classes prevent mirroring by default, by setting layoutDirection to "ltr" in defaults.css:
Image VideoDisplay TextBase RichEditableText
One use case that the current spec does not address is "diagram children". A custom container subclass that displays a diagram or a map which should not be mirrored, will specify layoutDirection="ltr" to ensure that its contents are never flipped. If such a component has component children and is embedded in a RTL application, it's likely that the children should be mirrored to match the application. To enable this, we'd need a way to specify that a component's layoutDirection property was to be ignored by its children. Text components that support IVisualElement children will have the same limitation.
A prototype implementation was created about a year ago.
Mirroring depends on the (currently public) AdvancedLayoutFeatures class.
This feature adds a property and a method to the existing IVisualElement interface. There probably aren't many new implementations of this interface in the field however any examples that do exist would have to be recompiled to target the release supporting this feature.
Mirroring is not supported when -compatibility-version=3.0.
Mirroring is a globalization feature, it enables default LTR layouts to be mapped to RTL for RTL locales.
Graphic elements of static FXG aren't mirrorable. Although RichText components created for FXG text will handle their inherited layoutDirection correctly, the rest of an FXG document is treated as a leaf (UIMovieClip) node. There's no support for configuring the layoutDirection of individual static FXG elements.
Mirroring an application is likely to expose problems with event handling and popup placement. We're also likely to discover that there are more components that need to insist on LTR layout for themselves or for subparts, based on interactive testing.
For documentation and examples using this feature, see http://help.adobe.com/en_US/Flex/4.0/LayoutMirroring/index.html