Layout class
Helper class for laying out a container element and its children.
This class is typically used by higher-level widgets to implement layout on their behalf. It is intended to wrap an element (usually a <div>), and lay its children out in a predictable fashion, automatically accounting for changes to the parent's size, and for all elements' margins, borders, and padding.
To use this class, create a container element (again, usually a <div>) and pass it to {@link #Layout(Element)}. Rather than attaching child elements directly to the element managed by this {@link Layout}, use the {@link Layout#attachChild(Element)} method. This will attach the child element and return a {@link Layout.Layer} object which is used to manage the child.
A separate {@link Layout.Layer} instance is associated with each child element. There is a set of methods available on this class to manipulate the child element's position and size. In order for changes to a layer to take effect, you must finally call one of {@link #layout()} or {@link #layout(int)}. This allows many changes to different layers to be applied efficiently, and to be animated.
On most browsers, this is implemented using absolute positioning. It also contains extra logic to make IE6 work properly.
Example
{@example com.google.gwt.examples.LayoutExample}NOTE: This class will only work in standards mode, which requires that the HTML page in which it is run have an explicit <!DOCTYPE> declaration.
NOTE: This class is still very new, and its interface may change without warning. Use at your own risk.
class Layout { LayoutImpl impl = new LayoutImpl.browserDependent(); List<Layer> layers = new List<Layer>(); dart_html.Element parentElem; Animation animation; /** * Constructs a new layout associated with the given parent element. * * @param parent the element to serve as the layout parent */ Layout(dart_html.Element parent) { this.parentElem = parent; impl.initParent(parent); } /** * Asserts that the given child element is managed by this layout. * * @param elem the element to be tested */ void assertIsChild(dart_html.Element elem) { assert (elem.parent.parent == this.parentElem); // : "Element is not a child of this layout"; } /** * Attaches a child element to this layout. * * <p> * This method will attach the child to the layout, removing it from its * current parent element. Use the {@link Layer} it returns to manipulate the * child. * </p> * * @param child the child to be attached * @param before the child element before which to insert * @param userObject an arbitrary object to be associated with this layer * @return the {@link Layer} associated with the element */ Layer attachChild(dart_html.Element child, {dart_html.Element before:null, Object userObject}) { dart_html.Element container = impl.attachChild(parentElem, child, before); Layer layer = new Layer(container, child, userObject); layers.add(layer); return layer; } /** * Causes the parent element to fill its own parent. * * <p> * This is most useful for top-level layouts that need to follow the size of * another element, such as the <body>. * </p> */ void fillParent() { impl.fillParent(parentElem); } /** * Returns the size of one unit, in pixels, in the context of this layout. * * <p> * This will work for any unit type, but be aware that certain unit types, * such as {@link Unit#EM}, and {@link Unit#EX}, will return different values * based upon the parent's associated font size. {@link Unit#PCT} is dependent * upon the parent's actual size, and the axis to be measured. * </p> * * @param unit the unit type to be measured * @param vertical whether the unit to be measured is on the vertical or * horizontal axis (this matters only for {@link Unit#PCT}) * @return the unit size, in pixels */ double getUnitSize(Unit unit, bool vertical) { return impl.getUnitSizeInPixels(parentElem, unit, vertical); } /** * Updates the layout by animating it over time, with a callback on each frame * of the animation, and upon completion. * * @param duration the duration of the animation * @param callback the animation callback */ void layout([int duration = 0, LayoutAnimationCallback callback = null]) { // Cancel the old animation, if there is one. if (animation != null) { animation.cancel(); } // If there's no actual animation going on, don't do any of the expensive // constraint calculations or anything like that. if (duration == 0) { for (Layer l in layers) { l.left = l.sourceLeft = l.targetLeft; l.top = l.sourceTop = l.targetTop; l.right = l.sourceRight = l.targetRight; l.bottom = l.sourceBottom = l.targetBottom; l.width = l.sourceWidth = l.targetWidth; l.height = l.sourceHeight = l.targetHeight; l.setLeft = l.setTargetLeft; l.setTop = l.setTargetTop; l.setRight = l.setTargetRight; l.setBottom = l.setTargetBottom; l.setWidth = l.setTargetWidth; l.setHeight = l.setTargetHeight; l.leftUnit = l.targetLeftUnit; l.topUnit = l.targetTopUnit; l.rightUnit = l.targetRightUnit; l.bottomUnit = l.targetBottomUnit; l.widthUnit = l.targetWidthUnit; l.heightUnit = l.targetHeightUnit; impl.layout(l); } impl.finalizeLayout(parentElem); if (callback != null) { callback.onAnimationComplete(); } return; } // Deal with constraint changes (e.g. left-width => right-width, etc) int parentWidth = parentElem.client.width; int parentHeight = parentElem.client.height; for (Layer l in layers) { adjustHorizontalConstraints(parentWidth, l); adjustVerticalConstraints(parentHeight, l); } animation = new LayoutAnimation(this, callback); animation.run(duration, element:parentElem); } /** * This method must be called when the parent element becomes attached to the * document. * * @see #onDetach() */ void onAttach() { impl.onAttach(parentElem); } /** * This method must be called when the parent element becomes detached from * the document. * * @see #onAttach() */ void onDetach() { impl.onDetach(parentElem); } /** * Removes a child element from this layout. * * @param layer the layer associated with the child to be removed */ void removeChild(Layer layer) { impl.removeChild(layer.container, layer.child); // int indx = layers.indexOf(layer); if (indx != -1) { layers.removeAt(indx); } } void adjustHorizontalConstraints(int parentWidth, Layer l) { double leftPx = l.left * getUnitSize(l.leftUnit, false); double rightPx = l.right * getUnitSize(l.rightUnit, false); double widthPx = l.width * getUnitSize(l.widthUnit, false); if (l.setLeft && !l.setTargetLeft) { // -left l.setLeft = false; if (!l.setWidth) { // +width l.setTargetWidth = true; l.sourceWidth = (parentWidth - (leftPx + rightPx)) / getUnitSize(l.targetWidthUnit, false); } else { // +right l.setTargetRight = true; l.sourceRight = (parentWidth - (leftPx + widthPx)) / getUnitSize(l.targetRightUnit, false); } } else if (l.setWidth && !l.setTargetWidth) { // -width l.setWidth = false; if (!l.setLeft) { // +left l.setTargetLeft = true; l.sourceLeft = (parentWidth - (rightPx + widthPx)) / getUnitSize(l.targetLeftUnit, false); } else { // +right l.setTargetRight = true; l.sourceRight = (parentWidth - (leftPx + widthPx)) / getUnitSize(l.targetRightUnit, false); } } else if (l.setRight && !l.setTargetRight) { // -right l.setRight = false; if (!l.setWidth) { // +width l.setTargetWidth = true; l.sourceWidth = (parentWidth - (leftPx + rightPx)) / getUnitSize(l.targetWidthUnit, false); } else { // +left l.setTargetLeft = true; l.sourceLeft = (parentWidth - (rightPx + widthPx)) / getUnitSize(l.targetLeftUnit, false); } } l.setLeft = l.setTargetLeft; l.setRight = l.setTargetRight; l.setWidth = l.setTargetWidth; l.leftUnit = l.targetLeftUnit; l.rightUnit = l.targetRightUnit; l.widthUnit = l.targetWidthUnit; } void adjustVerticalConstraints(int parentHeight, Layer l) { double topPx = l.top * getUnitSize(l.topUnit, true); double bottomPx = l.bottom * getUnitSize(l.bottomUnit, true); double heightPx = l.height * getUnitSize(l.heightUnit, true); if (l.setTop && !l.setTargetTop) { // -top l.setTop = false; if (!l.setHeight) { // +height l.setTargetHeight = true; l.sourceHeight = (parentHeight - (topPx + bottomPx)) / getUnitSize(l.targetHeightUnit, true); } else { // +bottom l.setTargetBottom = true; l.sourceBottom = (parentHeight - (topPx + heightPx)) / getUnitSize(l.targetBottomUnit, true); } } else if (l.setHeight && !l.setTargetHeight) { // -height l.setHeight = false; if (!l.setTop) { // +top l.setTargetTop = true; l.sourceTop = (parentHeight - (bottomPx + heightPx)) / getUnitSize(l.targetTopUnit, true); } else { // +bottom l.setTargetBottom = true; l.sourceBottom = (parentHeight - (topPx + heightPx)) / getUnitSize(l.targetBottomUnit, true); } } else if (l.setBottom && !l.setTargetBottom) { // -bottom l.setBottom = false; if (!l.setHeight) { // +height l.setTargetHeight = true; l.sourceHeight = (parentHeight - (topPx + bottomPx)) / getUnitSize(l.targetHeightUnit, true); } else { // +top l.setTargetTop = true; l.sourceTop = (parentHeight - (bottomPx + heightPx)) / getUnitSize(l.targetTopUnit, true); } } l.setTop = l.setTargetTop; l.setBottom = l.setTargetBottom; l.setHeight = l.setTargetHeight; l.topUnit = l.targetTopUnit; l.bottomUnit = l.targetBottomUnit; l.heightUnit = l.targetHeightUnit; } }
Constructors
new Layout(Element parent) #
Constructs a new layout associated with the given parent element.
@param parent the element to serve as the layout parent
Layout(dart_html.Element parent) { this.parentElem = parent; impl.initParent(parent); }
Properties
LayoutImpl impl #
LayoutImpl impl = new LayoutImpl.browserDependent()
Element parentElem #
dart_html.Element parentElem
Methods
void adjustHorizontalConstraints(int parentWidth, Layer l) #
void adjustHorizontalConstraints(int parentWidth, Layer l) { double leftPx = l.left * getUnitSize(l.leftUnit, false); double rightPx = l.right * getUnitSize(l.rightUnit, false); double widthPx = l.width * getUnitSize(l.widthUnit, false); if (l.setLeft && !l.setTargetLeft) { // -left l.setLeft = false; if (!l.setWidth) { // +width l.setTargetWidth = true; l.sourceWidth = (parentWidth - (leftPx + rightPx)) / getUnitSize(l.targetWidthUnit, false); } else { // +right l.setTargetRight = true; l.sourceRight = (parentWidth - (leftPx + widthPx)) / getUnitSize(l.targetRightUnit, false); } } else if (l.setWidth && !l.setTargetWidth) { // -width l.setWidth = false; if (!l.setLeft) { // +left l.setTargetLeft = true; l.sourceLeft = (parentWidth - (rightPx + widthPx)) / getUnitSize(l.targetLeftUnit, false); } else { // +right l.setTargetRight = true; l.sourceRight = (parentWidth - (leftPx + widthPx)) / getUnitSize(l.targetRightUnit, false); } } else if (l.setRight && !l.setTargetRight) { // -right l.setRight = false; if (!l.setWidth) { // +width l.setTargetWidth = true; l.sourceWidth = (parentWidth - (leftPx + rightPx)) / getUnitSize(l.targetWidthUnit, false); } else { // +left l.setTargetLeft = true; l.sourceLeft = (parentWidth - (rightPx + widthPx)) / getUnitSize(l.targetLeftUnit, false); } } l.setLeft = l.setTargetLeft; l.setRight = l.setTargetRight; l.setWidth = l.setTargetWidth; l.leftUnit = l.targetLeftUnit; l.rightUnit = l.targetRightUnit; l.widthUnit = l.targetWidthUnit; }
void adjustVerticalConstraints(int parentHeight, Layer l) #
void adjustVerticalConstraints(int parentHeight, Layer l) { double topPx = l.top * getUnitSize(l.topUnit, true); double bottomPx = l.bottom * getUnitSize(l.bottomUnit, true); double heightPx = l.height * getUnitSize(l.heightUnit, true); if (l.setTop && !l.setTargetTop) { // -top l.setTop = false; if (!l.setHeight) { // +height l.setTargetHeight = true; l.sourceHeight = (parentHeight - (topPx + bottomPx)) / getUnitSize(l.targetHeightUnit, true); } else { // +bottom l.setTargetBottom = true; l.sourceBottom = (parentHeight - (topPx + heightPx)) / getUnitSize(l.targetBottomUnit, true); } } else if (l.setHeight && !l.setTargetHeight) { // -height l.setHeight = false; if (!l.setTop) { // +top l.setTargetTop = true; l.sourceTop = (parentHeight - (bottomPx + heightPx)) / getUnitSize(l.targetTopUnit, true); } else { // +bottom l.setTargetBottom = true; l.sourceBottom = (parentHeight - (topPx + heightPx)) / getUnitSize(l.targetBottomUnit, true); } } else if (l.setBottom && !l.setTargetBottom) { // -bottom l.setBottom = false; if (!l.setHeight) { // +height l.setTargetHeight = true; l.sourceHeight = (parentHeight - (topPx + bottomPx)) / getUnitSize(l.targetHeightUnit, true); } else { // +top l.setTargetTop = true; l.sourceTop = (parentHeight - (bottomPx + heightPx)) / getUnitSize(l.targetTopUnit, true); } } l.setTop = l.setTargetTop; l.setBottom = l.setTargetBottom; l.setHeight = l.setTargetHeight; l.topUnit = l.targetTopUnit; l.bottomUnit = l.targetBottomUnit; l.heightUnit = l.targetHeightUnit; }
void assertIsChild(Element elem) #
Asserts that the given child element is managed by this layout.
@param elem the element to be tested
void assertIsChild(dart_html.Element elem) { assert (elem.parent.parent == this.parentElem); // : "Element is not a child of this layout"; }
Layer attachChild(Element child, {Element before: null, Object userObject}) #
Attaches a child element to this layout.
This method will attach the child to the layout, removing it from its current parent element. Use the {@link Layer} it returns to manipulate the child.
@param child the child to be attached @param before the child element before which to insert @param userObject an arbitrary object to be associated with this layer @return the {@link Layer} associated with the element
Layer attachChild(dart_html.Element child, {dart_html.Element before:null, Object userObject}) { dart_html.Element container = impl.attachChild(parentElem, child, before); Layer layer = new Layer(container, child, userObject); layers.add(layer); return layer; }
void fillParent() #
Causes the parent element to fill its own parent.
This is most useful for top-level layouts that need to follow the size of another element, such as the <body>.
void fillParent() { impl.fillParent(parentElem); }
double getUnitSize(Unit unit, bool vertical) #
Returns the size of one unit, in pixels, in the context of this layout.
This will work for any unit type, but be aware that certain unit types, such as {@link Unit#EM}, and {@link Unit#EX}, will return different values based upon the parent's associated font size. {@link Unit#PCT} is dependent upon the parent's actual size, and the axis to be measured.
@param unit the unit type to be measured @param vertical whether the unit to be measured is on the vertical or
horizontal axis (this matters only for {@link Unit#PCT})
@return the unit size, in pixels
double getUnitSize(Unit unit, bool vertical) { return impl.getUnitSizeInPixels(parentElem, unit, vertical); }
void layout([int duration = 0, LayoutAnimationCallback callback = null]) #
Updates the layout by animating it over time, with a callback on each frame of the animation, and upon completion.
@param duration the duration of the animation @param callback the animation callback
void layout([int duration = 0, LayoutAnimationCallback callback = null]) { // Cancel the old animation, if there is one. if (animation != null) { animation.cancel(); } // If there's no actual animation going on, don't do any of the expensive // constraint calculations or anything like that. if (duration == 0) { for (Layer l in layers) { l.left = l.sourceLeft = l.targetLeft; l.top = l.sourceTop = l.targetTop; l.right = l.sourceRight = l.targetRight; l.bottom = l.sourceBottom = l.targetBottom; l.width = l.sourceWidth = l.targetWidth; l.height = l.sourceHeight = l.targetHeight; l.setLeft = l.setTargetLeft; l.setTop = l.setTargetTop; l.setRight = l.setTargetRight; l.setBottom = l.setTargetBottom; l.setWidth = l.setTargetWidth; l.setHeight = l.setTargetHeight; l.leftUnit = l.targetLeftUnit; l.topUnit = l.targetTopUnit; l.rightUnit = l.targetRightUnit; l.bottomUnit = l.targetBottomUnit; l.widthUnit = l.targetWidthUnit; l.heightUnit = l.targetHeightUnit; impl.layout(l); } impl.finalizeLayout(parentElem); if (callback != null) { callback.onAnimationComplete(); } return; } // Deal with constraint changes (e.g. left-width => right-width, etc) int parentWidth = parentElem.client.width; int parentHeight = parentElem.client.height; for (Layer l in layers) { adjustHorizontalConstraints(parentWidth, l); adjustVerticalConstraints(parentHeight, l); } animation = new LayoutAnimation(this, callback); animation.run(duration, element:parentElem); }
void onAttach() #
This method must be called when the parent element becomes attached to the document.
@see #onDetach()
void onAttach() { impl.onAttach(parentElem); }
void onDetach() #
This method must be called when the parent element becomes detached from the document.
@see #onAttach()
void onDetach() { impl.onDetach(parentElem); }