Initial implementation of layout system, along with the first two layout widgets.
Review: http://gwt-code-reviews.appspot.com/51830
  (TBR, until jlabanca has time to finish reviewing)


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@5877 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/javadoc/com/google/gwt/examples/LayoutPanelExample.java b/user/javadoc/com/google/gwt/examples/LayoutPanelExample.java
new file mode 100644
index 0000000..13372d7
--- /dev/null
+++ b/user/javadoc/com/google/gwt/examples/LayoutPanelExample.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2009 Google Inc.
+ * 
+ * Licensed 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 com.google.gwt.examples;
+
+import static com.google.gwt.dom.client.Style.Unit.PCT;
+
+import com.google.gwt.core.client.EntryPoint;
+import com.google.gwt.layout.client.Layout.Layer;
+import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.LayoutPanel;
+import com.google.gwt.user.client.ui.RootLayoutPanel;
+import com.google.gwt.user.client.ui.Widget;
+
+public class LayoutPanelExample implements EntryPoint {
+
+  public void onModuleLoad() {
+    // Attach two child widgets to a LayoutPanel, laying them out horizontally,
+    // splitting at 50%.
+    Widget childOne = new HTML(), childTwo = new HTML();
+    LayoutPanel p = new LayoutPanel();
+    p.add(childOne);
+    p.add(childTwo);
+
+    Layer layerOne = p.getLayer(childOne), layerTwo = p.getLayer(childTwo);
+    layerOne.setLeftWidth(0, PCT, 50, PCT);
+    layerTwo.setRightWidth(0, PCT, 50, PCT);
+
+    // Note the explicit call to layout(). This is required for the layout to
+    // take effect.
+    p.layout();
+
+    // Attach the LayoutPanel to the RootLayoutPanel. The latter will listen for
+    // resize events on the window to ensure that its children are informed of
+    // possible size changes.
+    RootLayoutPanel rp = RootLayoutPanel.get();
+    rp.add(p);
+
+    // The RootLayoutPanel also requires that its layout() method be explicitly
+    // called for the initial layout to take effect.
+    rp.layout();
+  }
+}
\ No newline at end of file
diff --git a/user/src/com/google/gwt/layout/Layout.gwt.xml b/user/src/com/google/gwt/layout/Layout.gwt.xml
new file mode 100644
index 0000000..6619b69
--- /dev/null
+++ b/user/src/com/google/gwt/layout/Layout.gwt.xml
@@ -0,0 +1,25 @@
+<!--                                                                        -->
+<!-- Copyright 2009 Google Inc.                                             -->
+<!-- Licensed under the Apache License, Version 2.0 (the "License"); you    -->
+<!-- may not use this file except in compliance with the License. You may   -->
+<!-- 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. License for the specific language governing permissions and   -->
+<!-- limitations under the License.                                         -->
+
+<module>
+  <inherits name="com.google.gwt.core.Core"/>
+  <inherits name="com.google.gwt.user.UserAgent"/>
+  <inherits name="com.google.gwt.dom.DOM"/>
+  <inherits name="com.google.gwt.animation.Animation"/>
+
+  <replace-with class="com.google.gwt.layout.client.LayoutImplIE6">
+    <when-type-is class="com.google.gwt.layout.client.LayoutImpl"/>
+    <when-property-is name="user.agent" value="ie6"/>
+  </replace-with>
+</module>
diff --git a/user/src/com/google/gwt/layout/client/Layout.java b/user/src/com/google/gwt/layout/client/Layout.java
new file mode 100644
index 0000000..6df50e1
--- /dev/null
+++ b/user/src/com/google/gwt/layout/client/Layout.java
@@ -0,0 +1,640 @@
+/*
+ * Copyright 2009 Google Inc.
+ * 
+ * Licensed 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 com.google.gwt.layout.client;
+
+import static com.google.gwt.dom.client.Style.Unit.PX;
+
+import com.google.gwt.animation.client.Animation;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.Style.Unit;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Helper class for laying out a container element and its children.
+ * 
+ * <p>
+ * This class is typically used by higher-level widgets to implement layout on
+ * their behalf. It is intended to wrap an element (usually a &lt;div&gt;), 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.
+ * </p>
+ * 
+ * <p>
+ * To use this class, create a container element (again, usually a &lt;div&gt;)
+ * 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.
+ * </p>
+ * 
+ * <p>
+ * 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 #animate(int)}. This allows many changes to different layers to be
+ * applied efficiently, and to be animated.
+ * </p>
+ * 
+ * <p>
+ * On most browsers, this is implemented using absolute positioning. It also
+ * contains extra logic to make IE6 work properly.
+ * </p>
+ * 
+ * <p>
+ * <h3>Example</h3>
+ * {@example com.google.gwt.examples.LayoutExample}
+ * </p>
+ * 
+ * <p>
+ * NOTE: This class will <em>only</em> work in standards mode, which requires
+ * that the HTML page in which it is run have an explicit &lt;!DOCTYPE&gt;
+ * declaration.
+ * </p>
+ * 
+ * <p>
+ * NOTE: This class is still very new, and its interface may change without
+ * warning. Use at your own risk.
+ * </p>
+ */
+public class Layout {
+
+  /**
+   * Callback interface used by {@link Layout#animate(int, AnimationCallback)}
+   * to provide updates on animation progress.
+   */
+  public interface AnimationCallback {
+
+    /**
+     * Called immediately after the animation is complete, and the entire layout
+     * is in its final state.
+     */
+    void onAnimationComplete();
+
+    /**
+     * Called at each step of the animation, for each layer being laid out.
+     * 
+     * @param layer the layer being laid out
+     */
+    void onLayout(Layer layer, double progress);
+  }
+
+  /**
+   * This class is used to set the position and size of child elements.
+   * 
+   * <p>
+   * Each child element has three values associated with each axis: {left,
+   * right, width} on the horizontal axis, and {top, bottom, height} on the
+   * vertical axis. Precisely two of three values may be set at a time, or the
+   * system will be over- or under-contrained. For this reason, the following
+   * methods are provided for setting these values:
+   * <ul>
+   * <li>{@link #setLeftRight(double, Unit, double, Unit)}</li>
+   * <li>{@link #setLeftWidth(double, Unit, double, Unit)}</li>
+   * <li>{@link #setRightWidth(double, Unit, double, Unit)}</li>
+   * <li>{@link #setTopBottom(double, Unit, double, Unit)}</li>
+   * <li>{@link #setTopHeight(double, Unit, double, Unit)}</li>
+   * <li>{@link #setBottomHeight(double, Unit, double, Unit)}</li>
+   * </ul>
+   * </p>
+   * 
+   * <p>
+   * By default, each layer is set to fill the entire parent (i.e., {left, top,
+   * right, bottom} = {0, 0, 0, 0}).
+   * </p>
+   */
+  public class Layer {
+    final Element container, child;
+
+    Object userObject;
+
+    boolean setLeft, setRight, setTop, setBottom, setWidth, setHeight;
+    boolean setTargetLeft = true, setTargetRight = true, setTargetTop = true,
+        setTargetBottom = true, setTargetWidth, setTargetHeight;
+    Unit leftUnit, topUnit, rightUnit, bottomUnit, widthUnit, heightUnit;
+    Unit targetLeftUnit = PX, targetTopUnit = PX, targetRightUnit = PX,
+        targetBottomUnit = PX, targetWidthUnit, targetHeightUnit;
+    double left, top, right, bottom, width, height;
+    double sourceLeft, sourceTop, sourceRight, sourceBottom, sourceWidth,
+        sourceHeight;
+    double targetLeft, targetTop, targetRight, targetBottom, targetWidth,
+        targetHeight;
+
+    Layer(Element container, Element child, Object userObject) {
+      this.container = container;
+      this.child = child;
+      this.userObject = userObject;
+    }
+
+    /**
+     * Gets the container element associated with this layer.
+     * 
+     * <p>
+     * This is the element that sits between the parent and child elements. It
+     * is normally necessary to operate on this element only when you need to
+     * modify certain CSS properties, such as visibility.
+     * </p>
+     * 
+     * @return the container element
+     */
+    public Element getContainerElement() {
+      return container;
+    }
+
+    /**
+     * Gets the user-data associated with this layer.
+     * 
+     * @return the layer's user-data object
+     */
+    public Object getUserObject() {
+      return this.userObject;
+    }
+
+    /**
+     * Sets the layer's bottom and height values.
+     * 
+     * @param bottom
+     * @param bottomUnit
+     * @param height
+     * @param heightUnit
+     */
+    public void setBottomHeight(double bottom, Unit bottomUnit, double height,
+        Unit heightUnit) {
+      this.setTargetBottom = this.setTargetHeight = true;
+      this.setTargetTop = false;
+      this.targetBottom = bottom;
+      this.targetHeight = height;
+      this.targetBottomUnit = bottomUnit;
+      this.targetHeightUnit = heightUnit;
+    }
+
+    /**
+     * Sets the layer's left and right values.
+     * 
+     * @param left
+     * @param leftUnit
+     * @param right
+     * @param rightUnit
+     */
+    public void setLeftRight(double left, Unit leftUnit, double right,
+        Unit rightUnit) {
+      this.setTargetLeft = this.setTargetRight = true;
+      this.setTargetWidth = false;
+      this.targetLeft = left;
+      this.targetRight = right;
+      this.targetLeftUnit = leftUnit;
+      this.targetRightUnit = rightUnit;
+    }
+
+    /**
+     * Sets the layer's left and width values.
+     * 
+     * @param left
+     * @param leftUnit
+     * @param width
+     * @param widthUnit
+     */
+    public void setLeftWidth(double left, Unit leftUnit, double width,
+        Unit widthUnit) {
+      this.setTargetLeft = this.setTargetWidth = true;
+      this.setTargetRight = false;
+      this.targetLeft = left;
+      this.targetWidth = width;
+      this.targetLeftUnit = leftUnit;
+      this.targetWidthUnit = widthUnit;
+    }
+
+    /**
+     * Sets the layer's right and width values.
+     * 
+     * @param right
+     * @param rightUnit
+     * @param width
+     * @param widthUnit
+     */
+    public void setRightWidth(double right, Unit rightUnit, double width,
+        Unit widthUnit) {
+      this.setTargetRight = this.setTargetWidth = true;
+      this.setTargetLeft = false;
+      this.targetRight = right;
+      this.targetWidth = width;
+      this.targetRightUnit = rightUnit;
+      this.targetWidthUnit = widthUnit;
+    }
+
+    /**
+     * Sets the layer's top and bottom values.
+     * 
+     * @param top
+     * @param topUnit
+     * @param bottom
+     * @param bottomUnit
+     */
+    public void setTopBottom(double top, Unit topUnit, double bottom,
+        Unit bottomUnit) {
+      this.setTargetTop = this.setTargetBottom = true;
+      this.setTargetHeight = false;
+      this.targetTop = top;
+      this.targetBottom = bottom;
+      this.targetTopUnit = topUnit;
+      this.targetBottomUnit = bottomUnit;
+    }
+
+    /**
+     * Sets the layer's top and height values.
+     * 
+     * @param top
+     * @param topUnit
+     * @param height
+     * @param heightUnit
+     */
+    public void setTopHeight(double top, Unit topUnit, double height,
+        Unit heightUnit) {
+      this.setTargetTop = this.setTargetHeight = true;
+      this.setTargetBottom = false;
+      this.targetTop = top;
+      this.targetHeight = height;
+      this.targetTopUnit = topUnit;
+      this.targetHeightUnit = heightUnit;
+    }
+  }
+
+  private static LayoutImpl impl = GWT.create(LayoutImpl.class);
+
+  private List<Layer> layers = new ArrayList<Layer>();
+  private final Element parentElem;
+  private Animation animation;
+
+  /**
+   * Constructs a new layout associated with the given parent element.
+   * 
+   * @param parent the element to serve as the layout parent
+   */
+  public Layout(Element parent) {
+    this.parentElem = parent;
+    impl.initParent(parent);
+  }
+
+  /**
+   * Updates the layout by animating it over time.
+   * 
+   * @param duration the duration of the animation
+   * @see #animate(int, AnimationCallback)
+   */
+  public void animate(int duration) {
+    animate(duration, 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
+   */
+  public void animate(int duration, final AnimationCallback callback) {
+    // Deal with constraint changes (e.g. left-width => right-width, etc)
+    int parentWidth = parentElem.getClientWidth();
+    int parentHeight = parentElem.getClientHeight();
+    for (Layer l : layers) {
+      adjustHorizontalConstraints(parentWidth, l);
+      adjustVerticalConstraints(parentHeight, l);
+    }
+
+    // Cancel the old animation, if there is one.
+    if (animation != null) {
+      animation.cancel();
+    }
+
+    animation = new Animation() {
+      @Override
+      protected void onCancel() {
+        onComplete();
+      }
+
+      @Override
+      protected void onComplete() {
+        layout();
+        if (callback != null) {
+          callback.onAnimationComplete();
+        }
+        animation = null;
+      }
+
+      @Override
+      protected void onUpdate(double progress) {
+        for (Layer l : layers) {
+          if (l.setTargetLeft) {
+            l.left = l.sourceLeft + (l.targetLeft - l.sourceLeft) * progress;
+          }
+          if (l.setTargetRight) {
+            l.right = l.sourceRight + (l.targetRight - l.sourceRight)
+                * progress;
+          }
+          if (l.setTargetTop) {
+            l.top = l.sourceTop + (l.targetTop - l.sourceTop) * progress;
+          }
+          if (l.setTargetBottom) {
+            l.bottom = l.sourceBottom + (l.targetBottom - l.sourceBottom)
+                * progress;
+          }
+          if (l.setTargetWidth) {
+            l.width = l.sourceWidth + (l.targetWidth - l.sourceWidth)
+                * progress;
+          }
+          if (l.setTargetHeight) {
+            l.height = l.sourceHeight + (l.targetHeight - l.sourceHeight)
+                * progress;
+          }
+
+          impl.layout(l);
+          if (callback != null) {
+            callback.onLayout(l, progress);
+          }
+        }
+        impl.finalizeLayout(parentElem);
+      }
+    };
+
+    animation.run(duration);
+  }
+
+  /**
+   * Asserts that the given child element is managed by this layout
+   * 
+   * @param elem the element to be tested
+   */
+  public void assertIsChild(Element elem) {
+    assert elem.getParentElement().getParentElement() == this.parentElem : "Element is not a child of this layout";
+  }
+
+  /**
+   * This method must be called when the parent element becomes attached to the
+   * document.
+   * 
+   * @see #detach()
+   */
+  public void attach() {
+    impl.attach(parentElem);
+  }
+
+  /**
+   * 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
+   * @return the {@link Layer} associated with the element
+   */
+  public Layer attachChild(Element child) {
+    return attachChild(child, null);
+  }
+
+  /**
+   * 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 userObject an arbitrary object to be associated with this layer
+   * @return the {@link Layer} associated with the element
+   */
+  public Layer attachChild(Element child, Object userObject) {
+    Element container = impl.attachChild(parentElem, child);
+    Layer layer = new Layer(container, child, userObject);
+    layers.add(layer);
+    return layer;
+  }
+
+  /**
+   * This method must be called when the parent element becomes detached from
+   * the document.
+   * 
+   * @see #attach()
+   */
+  public void detach() {
+    impl.detach(parentElem);
+  }
+
+  /**
+   * 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 &lt;body&gt;.
+   * </p>
+   */
+  public 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
+   */
+  public double getUnitSize(Unit unit, boolean vertical) {
+    return impl.getUnitSizeInPixels(parentElem, unit, vertical);
+  }
+
+  /**
+   * Updates this layout's children immediately. This method <em>must</em> be
+   * called after updating any of its children's {@link Layer layers}.
+   */
+  public void layout() {
+    for (Layer l : 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);
+  }
+
+  /**
+   * Removes a child element from this layout.
+   * 
+   * @param layer the layer associated with the child to be removed
+   */
+  public void removeChild(Layer layer) {
+    impl.removeChild(layer.container, layer.child);
+    layers.remove(layer);
+  }
+
+  private 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;
+  }
+
+  private 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;
+  }
+}
diff --git a/user/src/com/google/gwt/layout/client/LayoutImpl.java b/user/src/com/google/gwt/layout/client/LayoutImpl.java
new file mode 100644
index 0000000..a5e9730
--- /dev/null
+++ b/user/src/com/google/gwt/layout/client/LayoutImpl.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2009 Google Inc.
+ * 
+ * Licensed 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 com.google.gwt.layout.client;
+
+import static com.google.gwt.dom.client.Style.Unit.EM;
+import static com.google.gwt.dom.client.Style.Unit.EX;
+import static com.google.gwt.dom.client.Style.Unit.IN;
+import static com.google.gwt.dom.client.Style.Unit.PX;
+
+import com.google.gwt.dom.client.DivElement;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.Style;
+import com.google.gwt.dom.client.Style.Position;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.layout.client.Layout.Layer;
+
+/**
+ * Default implementation, which works with all browsers except for IE6. It uses
+ * only the "top", "left", "bottom", "right", "width", and "height" CSS
+ * properties.
+ * 
+ * Note: This implementation class has state, so {@link Layout} must create a
+ * new instance for each layout-parent.
+ */
+class LayoutImpl {
+
+  private static DivElement fixedRuler;
+
+  static {
+    Document doc = Document.get();
+    fixedRuler = createRuler(IN, IN);
+    doc.getBody().appendChild(fixedRuler);
+  }
+
+  protected static DivElement createRuler(Unit widthUnit, Unit heightUnit) {
+    DivElement ruler = Document.get().createDivElement();
+    ruler.setInnerHTML("&nbsp;");
+    Style style = ruler.getStyle();
+    style.setPosition(Position.ABSOLUTE);
+    style.setZIndex(-32767);
+    style.setLeft(-10000, PX);
+    style.setWidth(1, widthUnit);
+    style.setHeight(1, heightUnit);
+    return ruler;
+  }
+
+  protected DivElement relativeRuler;
+
+  public Element attachChild(Element parent, Element child) {
+    DivElement container = Document.get().createDivElement();
+    container.appendChild(child);
+
+    container.getStyle().setProperty("position", "absolute");
+    container.getStyle().setProperty("overflow", "hidden");
+
+    fillParent(child);
+
+    Style style = child.getStyle();
+    style.setWidth(100, Unit.PCT);
+    style.setHeight(100, Unit.PCT);
+
+    parent.appendChild(container);
+    return container;
+  }
+
+  public void fillParent(Element elem) {
+    Style style = elem.getStyle();
+    style.setPosition(Position.ABSOLUTE);
+    style.setLeft(0, PX);
+    style.setTop(0, PX);
+    style.setRight(0, PX);
+    style.setBottom(0, PX);
+  }
+
+  public void finalizeLayout(Element parent) {
+  }
+
+  public double getUnitSizeInPixels(Element parent, Unit unit, boolean vertical) {
+    if (unit == null) {
+      return 1;
+    }
+
+    switch (unit) {
+      default:
+      case PX:
+        return 1;
+      case PCT:
+        return (vertical ? parent.getClientHeight() : parent.getClientWidth()) / 100.0;
+      case EM:
+        return relativeRuler.getOffsetWidth();
+      case EX:
+        return relativeRuler.getOffsetHeight();
+      case CM:
+        return fixedRuler.getOffsetWidth();
+      case MM:
+        return fixedRuler.getOffsetWidth() / 10.0;
+      case IN:
+        return fixedRuler.getOffsetWidth() / 2.54;
+      case PT:
+        return fixedRuler.getOffsetWidth() / 28.4;
+      case PC:
+        return fixedRuler.getOffsetWidth() / 2.36;
+    }
+  }
+
+  public void initParent(Element parent) {
+    parent.getStyle().setProperty("position", "relative");
+    parent.appendChild(relativeRuler = createRuler(EM, EX));
+  }
+
+  public void layout(Layer layer) {
+    Style style = layer.container.getStyle();
+
+    style.setProperty("left", layer.setLeft
+        ? (layer.left + layer.leftUnit.getType()) : "");
+    style.setProperty("top", layer.setTop
+        ? (layer.top + layer.topUnit.getType()) : "");
+    style.setProperty("right", layer.setRight
+        ? (layer.right + layer.rightUnit.getType()) : "");
+    style.setProperty("bottom", layer.setBottom
+        ? (layer.bottom + layer.bottomUnit.getType()) : "");
+    style.setProperty("width", layer.setWidth
+        ? (layer.width + layer.widthUnit.getType()) : "");
+    style.setProperty("height", layer.setHeight
+        ? (layer.height + layer.heightUnit.getType()) : "");
+  }
+
+  public void removeChild(Element container, Element child) {
+    container.removeFromParent();
+
+    // We want this code to be resilient to the child having already been
+    // removed from its container (perhaps by widget code).
+    if (child.getParentElement() == container) {
+      child.removeFromParent();
+    }
+
+    // Cleanup child styles set by fillParent().
+    Style style = child.getStyle();
+    style.clearPosition();
+    style.clearLeft();
+    style.clearTop();
+    style.clearWidth();
+    style.clearHeight();
+  }
+
+  public void detach(Element parent) {
+    // Do nothing. This exists only to help LayoutImplIE6 avoid memory leaks.
+  }
+
+  public void attach(Element parent) {
+    // Do nothing. This exists only to help LayoutImplIE6 avoid memory leaks.
+  }
+}
diff --git a/user/src/com/google/gwt/layout/client/LayoutImplIE6.java b/user/src/com/google/gwt/layout/client/LayoutImplIE6.java
new file mode 100644
index 0000000..38231a5
--- /dev/null
+++ b/user/src/com/google/gwt/layout/client/LayoutImplIE6.java
@@ -0,0 +1,352 @@
+/*
+ * Copyright 2009 Google Inc.
+ * 
+ * Licensed 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 com.google.gwt.layout.client;
+
+import static com.google.gwt.dom.client.Style.Unit.PX;
+
+import com.google.gwt.dom.client.DivElement;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.Style.Overflow;
+import com.google.gwt.dom.client.Style.Position;
+import com.google.gwt.event.logical.shared.ResizeEvent;
+import com.google.gwt.event.logical.shared.ResizeHandler;
+import com.google.gwt.layout.client.Layout.Layer;
+import com.google.gwt.user.client.Window;
+
+/**
+ * IE6-specific implementation, which uses the "onresize" event, along with a
+ * series of measurement tools, to deal with several IE6 bugs. Specifically,
+ * IE6 doesn't support simultaneous left-right and top-bottom values, and it
+ * misplaces by one pixel elements whose right/bottom properties are set.
+ * 
+ * Because this implementation gets compiled in for both IE6 and 7, it
+ * dynamically detects IE7 and punts to the super implementation.
+ */
+class LayoutImplIE6 extends LayoutImpl {
+
+  private static Element createStyleRuler(Element parent) {
+    DivElement styleRuler = Document.get().createDivElement();
+    DivElement styleInner = Document.get().createDivElement();
+
+    styleRuler.getStyle().setPosition(Position.ABSOLUTE);
+    styleRuler.getStyle().setLeft(-10000, PX);
+
+    parent.appendChild(styleRuler);
+    styleRuler.appendChild(styleInner);
+    return styleRuler;
+  }
+
+  private static void hookWindowResize(final Element elem) {
+    Window.addResizeHandler(new ResizeHandler() {
+      public void onResize(ResizeEvent event) {
+        resizeRelativeToParent(elem);
+      }
+    });
+  }
+
+  private static native void measureDecoration(Element elem) /*-{
+    var ruler = elem.__styleRuler;
+    var inner = ruler.children[0];
+    var s = inner.style, cs = elem.currentStyle;
+
+    s.borderLeftStyle = cs.borderLeftStyle;
+    s.borderRightStyle = cs.borderRightStyle;
+    s.borderTopStyle = cs.borderTopStyle;
+    s.borderBottomStyle = cs.borderBottomStyle;
+
+    s.borderLeftWidth = cs.borderLeftWidth;
+    s.borderRightWidth = cs.borderRightWidth;
+    s.borderTopWidth = cs.borderTopWidth;
+    s.borderBottomWidth = cs.borderBottomWidth;
+
+    // Oddly enough, allowing the word 'auto' to creep into the style
+    // ruler's margin causes it to take up all of its parent's space.
+    s.marginLeft = (cs.marginLeft == 'auto') ? '' : cs.marginLeft;
+    s.marginRight = (cs.marginRight == 'auto') ? '' : cs.marginRight;
+    s.marginTop = (cs.marginTop == 'auto') ? '' : cs.marginTop;
+    s.marginBottom = (cs.marginBottom == 'auto') ? '' : cs.marginBottom;
+
+    s.paddingLeft = cs.paddingLeft;
+    s.paddingRight = cs.paddingRight;
+    s.paddingTop = cs.paddingTop;
+    s.paddingBottom = cs.paddingBottom;
+
+    s.width = s.height = 32;
+    elem.__decoWidth = ruler.offsetWidth - 32;
+    elem.__decoHeight = ruler.offsetHeight - 32;
+  }-*/;
+
+  private static native void resizeRelativeToParent(Element elem) /*-{
+    var parent = elem.__resizeParent;
+    if (parent) {
+      @com.google.gwt.layout.client.LayoutImplIE6::measureDecoration(Lcom/google/gwt/dom/client/Element;)(elem);
+      elem.style.left = 0;
+      elem.style.top = 0;
+      elem.style.width = parent.clientWidth - elem.__decoWidth;
+      elem.style.height = parent.clientHeight - elem.__decoHeight;
+    }
+  }-*/;
+
+  private static native void setLayer(Element container, Layer layer) /*-{
+    // Potential leak: This is cleaned up in detach().
+    container.__layer = layer;
+  }-*/;
+
+  private static native void setPropertyElement(Element elem, String name,
+      Element value) /*-{
+    elem[name] = value;
+  }-*/;
+
+  @Override
+  public void attach(Element parent) {
+    if (UserAgent.isIE6()) {
+      // No need to re-connect layer refs. This will be taken care of
+      // automatically in layout().
+      initResizeHandler(parent);
+      initUnitChangeHandler(parent, relativeRuler);
+    }
+  }
+
+  public Element attachChild(Element parent, Element child) {
+    if (!UserAgent.isIE6()) {
+      return super.attachChild(parent, child);
+    }
+
+    DivElement container = Document.get().createDivElement();
+    container.appendChild(child);
+
+    container.getStyle().setPosition(Position.ABSOLUTE);
+    container.getStyle().setOverflow(Overflow.HIDDEN);
+    child.getStyle().setPosition(Position.ABSOLUTE);
+
+    // Hang the style ruler from the container element, but associate it with
+    // the child element, so that measureDecoration(child) will work.
+    setPropertyElement(child, "__styleRuler", createStyleRuler(container));
+
+    parent.appendChild(container);
+    return container;
+  }
+
+  @Override
+  public void detach(Element parent) {
+    if (UserAgent.isIE6()) {
+      removeLayerRefs(parent);
+      removeResizeHandler(parent);
+      removeUnitChangeHandler(relativeRuler);
+    }
+  }
+
+  @Override
+  public void fillParent(Element elem) {
+    if (!UserAgent.isIE6()) {
+      super.fillParent(elem);
+      return;
+    }
+
+    fillParentImpl(elem);
+  }
+
+  @Override
+  public void finalizeLayout(Element parent) {
+    if (!UserAgent.isIE6()) {
+      super.finalizeLayout(parent);
+      return;
+    }
+
+    resizeRelativeToParent(parent);
+    resizeHandler(parent, true);
+  }
+
+  @Override
+  public void initParent(Element parent) {
+    super.initParent(parent);
+
+    if (UserAgent.isIE6()) {
+      setPropertyElement(parent, "__styleRuler", createStyleRuler(parent));
+    }
+  }
+
+  public void layout(Layer layer) {
+    if (!UserAgent.isIE6()) {
+      super.layout(layer);
+      return;
+    }
+
+    Element elem = layer.getContainerElement();
+    setLayer(elem, layer);
+  }
+
+  private native void fillParentImpl(Element elem) /*-{
+    // Hook the parent element's onresize event. If the parent is the <body>,
+    // then we have to go through the Window class to get the resize event,
+    // because IE6 requires a bunch of foul hackery to safely hook it.
+    var parent = elem.parentElement;
+    if (parent.tagName.toLowerCase() == 'body') {
+      elem.style.position = 'absolute';
+      var docElem = parent.parentElement;
+      elem.__resizeParent = docElem;
+      @com.google.gwt.layout.client.LayoutImplIE6::hookWindowResize(Lcom/google/gwt/dom/client/Element;)(elem);
+      return;
+    }
+
+    function resize() {
+      @com.google.gwt.layout.client.LayoutImplIE6::resizeRelativeToParent(Lcom/google/gwt/dom/client/Element;)(elem);
+    }
+
+    elem.__resizeParent = parent;
+    parent.onresize = resize;
+    resize();
+  }-*/;
+
+  private native void initResizeHandler(Element parent) /*-{
+    // Potential leak: This is cleaned up in detach().
+    var self = this;
+    parent.onresize = function() {
+      self.@com.google.gwt.layout.client.LayoutImplIE6::resizeHandler(Lcom/google/gwt/dom/client/Element;)(parent);
+    };
+  }-*/;
+
+  private native void initUnitChangeHandler(Element parent, Element ruler) /*-{
+    // Potential leak: This is cleaned up in detach().
+    var self = this;
+    ruler.onresize = function() {
+      self.@com.google.gwt.layout.client.LayoutImplIE6::resizeHandler(Lcom/google/gwt/dom/client/Element;Z)(parent, true);
+    };
+  }-*/;
+
+  private native void removeLayerRefs(Element parent) /*-{
+    for (var i = 0; i < parent.childNodes.length; ++i) {
+      var container = parent.childNodes[i];
+      if (container.__layer) {
+        container.__layer = null;
+      }
+    }
+  }-*/;
+
+  private native void removeResizeHandler(Element parent) /*-{
+    parent.onresize = null;
+  }-*/;
+
+  private native void removeUnitChangeHandler(Element ruler) /*-{
+    ruler.onresize = null;
+  }-*/;
+
+  private void resizeHandler(Element parent) {
+    resizeHandler(parent, false);
+  }
+  
+  private native void resizeHandler(Element parent, boolean force) /*-{
+    if (!force &&
+        ((parent.offsetWidth == parent.__oldWidth) &&
+         (parent.offsetHeight == parent.__oldHeight))) {
+      return;
+    }
+    parent.__oldWidth = parent.offsetWidth;
+    parent.__oldHeight = parent.offsetHeight;
+
+    var parentWidth = parent.clientWidth;
+    var parentHeight = parent.clientHeight;
+
+    // Iterate over the children, assuming each of them is an unstyled
+    // container element.
+    for (var i = 0; i < parent.childNodes.length; ++i) {
+      // Don't attempt to layout the rulers.
+      var container = parent.childNodes[i];
+      var layer = container.__layer;
+      if (!layer) {
+        continue;
+      }
+
+      // This is ugly, but it's less ugly than writing all these JSNI refs inline.
+      var _setLeft = layer.@com.google.gwt.layout.client.Layout.Layer::setLeft;
+      var _setTop = layer.@com.google.gwt.layout.client.Layout.Layer::setTop;
+      var _setWidth = layer.@com.google.gwt.layout.client.Layout.Layer::setWidth;
+      var _setHeight = layer.@com.google.gwt.layout.client.Layout.Layer::setHeight;
+      var _setRight = layer.@com.google.gwt.layout.client.Layout.Layer::setRight;
+      var _setBottom = layer.@com.google.gwt.layout.client.Layout.Layer::setBottom;
+
+      var _left = layer.@com.google.gwt.layout.client.Layout.Layer::left;
+      var _top = layer.@com.google.gwt.layout.client.Layout.Layer::top;
+      var _width = layer.@com.google.gwt.layout.client.Layout.Layer::width;
+      var _height = layer.@com.google.gwt.layout.client.Layout.Layer::height;
+      var _right = layer.@com.google.gwt.layout.client.Layout.Layer::right;
+      var _bottom = layer.@com.google.gwt.layout.client.Layout.Layer::bottom;
+
+      var _leftUnit = layer.@com.google.gwt.layout.client.Layout.Layer::leftUnit;
+      var _topUnit = layer.@com.google.gwt.layout.client.Layout.Layer::topUnit;
+      var _widthUnit = layer.@com.google.gwt.layout.client.Layout.Layer::widthUnit;
+      var _heightUnit = layer.@com.google.gwt.layout.client.Layout.Layer::heightUnit;
+      var _rightUnit = layer.@com.google.gwt.layout.client.Layout.Layer::rightUnit;
+      var _bottomUnit = layer.@com.google.gwt.layout.client.Layout.Layer::bottomUnit;
+
+      // Apply the requested position & size values to the element's style.
+      var style = container.style;
+      style.left = _setLeft ? (_left + _leftUnit.@com.google.gwt.dom.client.Style.Unit::getType()()) : "";
+      style.top = _setTop ? (_top + _topUnit.@com.google.gwt.dom.client.Style.Unit::getType()()) : "";
+      style.width = _setWidth ? (_width + _widthUnit.@com.google.gwt.dom.client.Style.Unit::getType()()) : "";
+      style.height = _setHeight ? (_height + _heightUnit.@com.google.gwt.dom.client.Style.Unit::getType()()) : "";
+
+      // If right is defined, reposition/size the container horizontally.
+      if (_setRight) {
+        var ratio = this.@com.google.gwt.layout.client.LayoutImplIE6::getUnitSizeInPixels(Lcom/google/gwt/dom/client/Element;Lcom/google/gwt/dom/client/Style$Unit;Z)(parent, _rightUnit, false);
+        var right = parentWidth - (_right * ratio);
+
+        if (!_setLeft) {
+          // There's no left defined; adjust the left position move the element to the right edge.
+          container.style.left = (right - container.offsetWidth) + 'px';
+        } else {
+          // Both left and right are defined; calculate the width and set it.
+          var leftRatio = this.@com.google.gwt.layout.client.LayoutImplIE6::getUnitSizeInPixels(Lcom/google/gwt/dom/client/Element;Lcom/google/gwt/dom/client/Style$Unit;Z)(parent, _leftUnit, false);
+          var left = (_left * leftRatio);
+          if (right > left) {
+            container.style.width = (right - left) + 'px';
+          }
+        }
+      }
+
+      // If bottom is defined, reposition/size the container vertically.
+      if (_setBottom) {
+        var ratio = this.@com.google.gwt.layout.client.LayoutImplIE6::getUnitSizeInPixels(Lcom/google/gwt/dom/client/Element;Lcom/google/gwt/dom/client/Style$Unit;Z)(parent, _bottomUnit, true);
+        var bottom = parentHeight - (_bottom * ratio);
+
+        if (!_setTop) {
+          // There's no top defined; adjust the left position move the element to the bottom edge.
+          container.style.top = (bottom - container.offsetHeight) + 'px';
+        } else {
+          // Both top and bottom are defined; calculate the height and set it.
+          var topRatio = this.@com.google.gwt.layout.client.LayoutImplIE6::getUnitSizeInPixels(Lcom/google/gwt/dom/client/Element;Lcom/google/gwt/dom/client/Style$Unit;Z)(parent, _topUnit, true);
+          var top = (_top * topRatio);
+          if (bottom > top) {
+            container.style.height = (bottom - top) + 'px';
+          }
+        }
+      }
+
+      // Resize the child to take up all of its container's width and height.
+      var child = container.firstChild;
+      @com.google.gwt.layout.client.LayoutImplIE6::measureDecoration(Lcom/google/gwt/dom/client/Element;)(child);
+      var childDecoWidth = child.__decoWidth;
+      var childDecoHeight = child.__decoHeight;
+      if (container.offsetWidth > childDecoWidth) {
+        child.style.width = (container.offsetWidth - childDecoWidth) + 'px';
+      }
+      if (container.offsetHeight > childDecoHeight) {
+        child.style.height = (container.offsetHeight - childDecoHeight) + 'px';
+      }
+    }
+  }-*/;
+}
diff --git a/user/src/com/google/gwt/layout/client/UserAgent.java b/user/src/com/google/gwt/layout/client/UserAgent.java
new file mode 100644
index 0000000..2127a59
--- /dev/null
+++ b/user/src/com/google/gwt/layout/client/UserAgent.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2009 Google Inc.
+ * 
+ * Licensed 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 com.google.gwt.layout.client;
+
+/**
+ * User-Agent utility methods used by {@link LayoutImplIE6}.
+ * 
+ * TODO: Generalize this, move it into a common place, and make it available for
+ * use by other classes.
+ */
+class UserAgent {
+
+  // Stolen and modified from UserAgent.gwt.xml.
+  public static native boolean isIE6() /*-{
+     function makeVersion(result) {
+       return (parseInt(result[1]) * 1000) + parseInt(result[2]);
+     }
+
+     var ua = navigator.userAgent.toLowerCase();
+     if (ua.indexOf("msie") != -1) {
+       var result = /msie ([0-9]+)\.([0-9]+)/.exec(ua);
+       if (result && result.length == 3) {
+         var v = makeVersion(result);
+         if (v < 7000) {
+           return true;
+         }
+       }
+     }
+
+     return false;
+   }-*/;
+}
diff --git a/user/src/com/google/gwt/user/client/ui/LayoutComposite.java b/user/src/com/google/gwt/user/client/ui/LayoutComposite.java
new file mode 100644
index 0000000..f4f5067
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/ui/LayoutComposite.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2009 Google Inc.
+ * 
+ * Licensed 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 com.google.gwt.user.client.ui;
+
+/**
+ * A {@link Composite} implementation that implements {@link RequiresLayout} and
+ * automatically delegates that interface's methods to its wrapped widget, which
+ * must itself implement {@link RequiresLayout}.
+ */
+public abstract class LayoutComposite extends Composite implements RequiresLayout {
+
+  @Override
+  protected void initWidget(Widget widget) {
+    assert widget instanceof RequiresLayout :
+      "LayoutComposite requires that its wrapped widget implement HasLayout";
+    super.initWidget(widget);
+  }
+
+  public void onLayout() {
+    ((RequiresLayout) getWidget()).onLayout();
+  }
+}
diff --git a/user/src/com/google/gwt/user/client/ui/LayoutPanel.java b/user/src/com/google/gwt/user/client/ui/LayoutPanel.java
new file mode 100644
index 0000000..880b8ae
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/ui/LayoutPanel.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright 2009 Google Inc.
+ * 
+ * Licensed 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 com.google.gwt.user.client.ui;
+
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.layout.client.Layout;
+import com.google.gwt.layout.client.Layout.Layer;
+
+/**
+ * A panel that lays its children out in arbitrary {@link Layout.Layer layers}
+ * using the {@link Layout} class.
+ * 
+ * <p>
+ * NOTE: This widget will <em>only</em> work in standards mode, which requires
+ * that the HTML page in which it is run have an explicit &lt;!DOCTYPE&gt;
+ * declaration.
+ * </p>
+ * 
+ * <p>
+ * NOTE: This class is still very new, and its interface may change without
+ * warning. Use at your own risk.
+ * </p>
+ * 
+ * <p>
+ * <h3>Example</h3>
+ * {@example com.google.gwt.examples.LayoutPanelExample}
+ * </p>
+ */
+public class LayoutPanel extends ComplexPanel implements RequiresLayout,
+    ProvidesLayout {
+
+  private final Layout layout;
+
+  /**
+   * Creates an empty layout panel.
+   */
+  public LayoutPanel() {
+    setElement(Document.get().createDivElement());
+    layout = new Layout(getElement());
+  }
+
+  /**
+   * Adds a widget to this panel.
+   * 
+   * <p>
+   * By default, each child will fill the panel. To build more interesting
+   * layouts, use {@link #getLayer(Widget)} to get the {@link Layout.Layer}
+   * associated with each child, and set its layout constraints as desired.
+   * </p>
+   * 
+   * @param widget the widget to be added
+   */
+  public void add(Widget widget) {
+    // Detach new child.
+    widget.removeFromParent();
+
+    // Logical attach.
+    getChildren().add(widget);
+
+    // Physical attach.
+    Layer layer = layout.attachChild(widget.getElement(), widget);
+    widget.setLayoutData(layer);
+
+    // Adopt.
+    adopt(widget);
+  }
+
+  /**
+   * This method, or one of its overloads, must be called whenever any of the
+   * {@link Layout.Layer layers} associated with its children is modified.
+   * 
+   * @see #layout(int)
+   * @see #layout(int, com.google.gwt.layout.client.Layout.AnimationCallback)
+   */
+  public void layout() {
+    layout.layout();
+  }
+
+  /**
+   * This method, or one of its overloads, must be called whenever any of the
+   * {@link Layout.Layer layers} associated with its children is modified.
+   * 
+   * <p>
+   * This overload will cause the layout to be updated by animating over a
+   * specified period of time.
+   * </p>
+   * 
+   * @param duration the animation duration, in milliseconds
+   * 
+   * @see #layout()
+   * @see #layout(int, com.google.gwt.layout.client.Layout.AnimationCallback)
+   */
+  public void layout(int duration) {
+    layout.animate(duration);
+  }
+
+  /**
+   * This method, or one of its overloads, must be called whenever any of the
+   * {@link Layout.Layer layers} associated with its children is modified.
+   * 
+   * <p>
+   * This overload will cause the layout to be updated by animating over a
+   * specified period of time. In addition, it provides a callback that will be
+   * informed of updates to the layers. This can be used to create more complex
+   * animation effects.
+   * </p>
+   * 
+   * @param duration the animation duration, in milliseconds
+   * @param callback the animation callback
+   * 
+   * @see #layout()
+   * @see #layout(int, com.google.gwt.layout.client.Layout.AnimationCallback)
+   */
+  public void layout(int duration, final Layout.AnimationCallback callback) {
+    layout.animate(duration, new Layout.AnimationCallback() {
+      public void onAnimationComplete() {
+        // Chain to the passed callback.
+        if (callback != null) {
+          callback.onAnimationComplete();
+        }
+      }
+
+      public void onLayout(Layer layer, double progress) {
+        // Inform the child associated with this layer that its size may have
+        // changed.
+        Widget child = (Widget) layer.getUserObject();
+        if (child instanceof RequiresLayout) {
+          ((RequiresLayout) child).onLayout();
+        }
+
+        // Chain to the passed callback.
+        if (callback != null) {
+          callback.onLayout(layer, progress);
+        }
+      }
+    });
+  }
+
+  public void onLayout() {
+    for (Widget child : getChildren()) {
+      if (child instanceof RequiresLayout) {
+        ((RequiresLayout) child).onLayout();
+      }
+    }
+  }
+
+  /**
+   * Gets the {@link Layer} associated with the given widget. This layer may be
+   * used to manipulate the child widget's layout constraints.
+   * 
+   * <p>
+   * After you have made changes to any of the child widgets' constraints, you
+   * must call one of the {@link HasAnimatedLayout} methods for those changes to
+   * be reflected visually.
+   * </p>
+   * 
+   * @param child the child widget whose layer is to be retrieved
+   * @return the associated layer
+   */
+  public Layout.Layer getLayer(Widget child) {
+    assert child.getParent() == this : "The requested widget is not a child of this panel";
+    return (Layout.Layer) child.getLayoutData();
+  }
+
+  @Override
+  public boolean remove(Widget w) {
+    boolean removed = super.remove(w);
+    if (removed) {
+      layout.removeChild((Layer) w.getLayoutData());
+    }
+    return removed;
+  }
+
+  /**
+   * Gets the {@link Layout} instance associated with this widget.
+   * 
+   * @return this widget's layout instance
+   */
+  protected Layout getLayout() {
+    return layout;
+  }
+
+  @Override
+  protected void onLoad() {
+    layout.attach();
+  }
+
+  @Override
+  protected void onUnload() {
+    layout.detach();
+  }
+}
diff --git a/user/src/com/google/gwt/user/client/ui/ProvidesLayout.java b/user/src/com/google/gwt/user/client/ui/ProvidesLayout.java
new file mode 100644
index 0000000..f6e6d07
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/ui/ProvidesLayout.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2009 Google Inc.
+ * 
+ * Licensed 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 com.google.gwt.user.client.ui;
+
+/**
+ * This tag interface specifies that the implementing widget will call
+ * {@link RequiresLayout#onLayout()} on its children whenever their size may
+ * have changed.
+ * 
+ * <p>
+ * With limited exceptions (such as {@link RootLayoutPanel}), widgets that
+ * implement this interface will also implement {@link RequiresLayout}. A typical
+ * widget will implement {@link RequiresLayout#onLayout()} like this:
+ * 
+ * <code>
+ * public void onLayout() {
+ *   for (Widget child : getChildren()) {
+ *     if (child instanceof RequiresLayout) {
+ *       ((RequiresLayout) child).onLayout();
+ *     }
+ *   }
+ * }
+ * </code>
+ * </p>
+ */
+public interface ProvidesLayout {
+}
diff --git a/user/src/com/google/gwt/user/client/ui/RequiresLayout.java b/user/src/com/google/gwt/user/client/ui/RequiresLayout.java
new file mode 100644
index 0000000..be81be6
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/ui/RequiresLayout.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2009 Google Inc.
+ * 
+ * Licensed 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 com.google.gwt.user.client.ui;
+
+/**
+ * This interface designates that its implementor needs to be informed whenever
+ * its size is modified.
+ * 
+ * <p>
+ * Widgets that implement this interface should only be added to those that
+ * implement {@link ProvidesLayout}. Failure to do so will usually result in
+ * {@link #onLayout()} not being called.
+ * </p>
+ */
+public interface RequiresLayout {
+
+  /**
+   * Update the layout immediately.
+   */
+  void onLayout();
+}
diff --git a/user/src/com/google/gwt/user/client/ui/RootLayoutPanel.java b/user/src/com/google/gwt/user/client/ui/RootLayoutPanel.java
new file mode 100644
index 0000000..9c30378
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/ui/RootLayoutPanel.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2009 Google Inc.
+ * 
+ * Licensed 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 com.google.gwt.user.client.ui;
+
+import com.google.gwt.event.logical.shared.ResizeEvent;
+import com.google.gwt.event.logical.shared.ResizeHandler;
+import com.google.gwt.user.client.Window;
+
+/**
+ * A singleton implementation of {@link LayoutPanel} that always attaches itself
+ * to the document body (i.e. {@link RootPanel#get()}).
+ * 
+ * <p>
+ * This panel automatically calls {@link RequiresLayout#layout()} on itself when
+ * initially created, and whenever the window is resized.
+ * </p>
+ * 
+ * <p>
+ * NOTE: This widget will <em>only</em> work in standards mode, which requires
+ * that the HTML page in which it is run have an explicit &lt;!DOCTYPE&gt;
+ * declaration.
+ * </p>
+ * 
+ * <p>
+ * NOTE: This class is still very new, and its interface may change without
+ * warning. Use at your own risk.
+ * </p>
+ * 
+ * <p>
+ * <h3>Example</h3>
+ * {@example com.google.gwt.examples.LayoutPanelExample}
+ * </p>
+ */
+public class RootLayoutPanel extends LayoutPanel implements ProvidesLayout {
+
+  private static RootLayoutPanel singleton;
+
+  /**
+   * Gets the singleton instance of RootLayoutPanel. This instance will always
+   * be attached to the document body via {@link RootPanel#get()}.
+   * 
+   * <p>
+   * Note that, unlike {@link RootPanel#get(String)}, this class provides no way
+   * to get an instance for any element on the page other than the document
+   * body. This is because we know of no way to get resize events for anything
+   * but the window.
+   * </p>
+   */
+  public static RootLayoutPanel get() {
+    if (singleton == null) {
+      singleton = new RootLayoutPanel();
+      RootPanel.get().add(singleton);
+    }
+    return singleton;
+  }
+
+  private RootLayoutPanel() {
+    Window.addResizeHandler(new ResizeHandler() {
+      public void onResize(ResizeEvent event) {
+        onLayout();
+      }
+    });
+
+    // TODO: We need notification of font-size changes as well.
+    // I believe there's a hidden iframe trick that we can use to get
+    // a font-size-change event (really an em-definition-change event).
+  }
+
+  @Override
+  protected void onLoad() {
+    getLayout().attach();
+    getLayout().fillParent();
+  }
+}
diff --git a/user/src/com/google/gwt/user/client/ui/Widget.java b/user/src/com/google/gwt/user/client/ui/Widget.java
index 8c998b3..dcc0a73 100644
--- a/user/src/com/google/gwt/user/client/ui/Widget.java
+++ b/user/src/com/google/gwt/user/client/ui/Widget.java
@@ -54,6 +54,16 @@
   }
 
   /**
+   * Gets the panel-defined layout data associated with this widget.
+   * 
+   * @return the widget's layout data
+   * @see #setLayoutData
+   */
+  public Object getLayoutData() {
+    return layoutData;
+  }
+
+  /**
    * Gets this widget's parent panel.
    * 
    * @return the widget's parent panel
@@ -120,6 +130,18 @@
   }
 
   /**
+   * Sets the panel-defined layout data associated with this widget. Only the
+   * panel that currently contains a widget should ever set this value. It
+   * serves as a place to store layout bookkeeping data associated with a
+   * widget.
+   * 
+   * @param layoutData the widget's layout data
+   */
+  public void setLayoutData(Object layoutData) {
+    this.layoutData = layoutData;
+  }
+
+  /**
    * Overridden to defer the call to super.sinkEvents until the first time this
    * widget is attached to the dom, as a performance enhancement. Subclasses
    * wishing to customize sinkEvents can preserve this deferred sink behavior by
@@ -325,16 +347,6 @@
     return handlerManager;
   }
 
-  /**
-   * Gets the panel-defined layout data associated with this widget.
-   * 
-   * @return the widget's layout data
-   * @see #setLayoutData
-   */
-  Object getLayoutData() {
-    return layoutData;
-  }
-
   @Override
   void replaceElement(com.google.gwt.dom.client.Element elem) {
     if (isAttached()) {
@@ -355,18 +367,6 @@
   }
 
   /**
-   * Sets the panel-defined layout data associated with this widget. Only the
-   * panel that currently contains a widget should ever set this value. It
-   * serves as a place to store layout bookkeeping data associated with a
-   * widget.
-   * 
-   * @param layoutData the widget's layout data
-   */
-  void setLayoutData(Object layoutData) {
-    this.layoutData = layoutData;
-  }
-
-  /**
    * Sets this widget's parent. This method should only be called by
    * {@link Panel} and {@link Composite}.
    * 
diff --git a/user/test/com/google/gwt/layout/LayoutTest.gwt.xml b/user/test/com/google/gwt/layout/LayoutTest.gwt.xml
new file mode 100644
index 0000000..bae48b5
--- /dev/null
+++ b/user/test/com/google/gwt/layout/LayoutTest.gwt.xml
@@ -0,0 +1,17 @@
+<!--                                                                        -->
+<!-- Copyright 2009 Google Inc.                                             -->
+<!-- Licensed under the Apache License, Version 2.0 (the "License"); you    -->
+<!-- may not use this file except in compliance with the License. You may   -->
+<!-- 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. License for the specific language governing permissions and   -->
+<!-- limitations under the License.                                         -->
+
+<module rename-to="LayoutTest">
+	<inherits name="com.google.gwt.layout.Layout"/>
+</module>
diff --git a/user/test/com/google/gwt/layout/client/LayoutTest.java b/user/test/com/google/gwt/layout/client/LayoutTest.java
new file mode 100644
index 0000000..7611556
--- /dev/null
+++ b/user/test/com/google/gwt/layout/client/LayoutTest.java
@@ -0,0 +1,551 @@
+/*
+ * Copyright 2009 Google Inc.
+ * 
+ * Licensed 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 com.google.gwt.layout.client;
+
+import static com.google.gwt.dom.client.Style.Unit.CM;
+import static com.google.gwt.dom.client.Style.Unit.EM;
+import static com.google.gwt.dom.client.Style.Unit.EX;
+import static com.google.gwt.dom.client.Style.Unit.IN;
+import static com.google.gwt.dom.client.Style.Unit.MM;
+import static com.google.gwt.dom.client.Style.Unit.PC;
+import static com.google.gwt.dom.client.Style.Unit.PCT;
+import static com.google.gwt.dom.client.Style.Unit.PT;
+import static com.google.gwt.dom.client.Style.Unit.PX;
+
+import com.google.gwt.dom.client.DivElement;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.Style.Position;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.junit.client.GWTTestCase;
+import com.google.gwt.layout.client.Layout.Layer;
+import com.google.gwt.user.client.Window;
+
+/**
+ * Tests for the {@link Layout} class.
+ * 
+ * TODO: Note that this test is *not* currently part of any suite. We can't run
+ * it reliably until we have a way to switch on "standards" mode in
+ * GWTTestCases.
+ */
+public class LayoutTest extends GWTTestCase {
+
+  private static interface LayerInitializer {
+    void setupLayers(Layer l0, Layer l1);
+  }
+
+  private DivElement parent, child0, child1;
+  private Element wrapper0, wrapper1;
+  private Layout layout;
+  private Layer layer0, layer1;
+
+  @Override
+  public String getModuleName() {
+    return "com.google.gwt.layout.Layout";
+  }
+
+  /**
+   * Tests animation constraint- and unit-transitions.
+   */
+  public void testAnimationTransitions_LTWH_LTRB_PX_CM() {
+    testAnimationTransitions_LTWH_LTRB(PX, CM);
+  }
+
+  /**
+   * Tests animation constraint- and unit-transitions.
+   */
+  public void testAnimationTransitions_LTWH_LTRB_PX_EM() {
+    testAnimationTransitions_LTWH_LTRB(PX, EM);
+  }
+
+  /**
+   * Tests animation constraint- and unit-transitions.
+   */
+  public void testAnimationTransitions_LTWH_LTRB_PX_EX() {
+    testAnimationTransitions_LTWH_LTRB(PX, EX);
+  }
+
+  /**
+   * Tests animation constraint- and unit-transitions.
+   */
+  public void testAnimationTransitions_LTWH_LTRB_PX_PCT() {
+    testAnimationTransitions_LTWH_LTRB(PX, PCT);
+  }
+
+  /**
+   * Tests animation constraint- and unit-transitions.
+   */
+  public void testAnimationTransitions_LTWH_RBWH_PX_CM() {
+    testAnimationTransitions_LTWH_RBWH(PX, CM);
+  }
+
+  /**
+   * Tests animation constraint- and unit-transitions.
+   */
+  public void testAnimationTransitions_LTWH_RBWH_PX_EM() {
+    testAnimationTransitions_LTWH_RBWH(PX, EM);
+  }
+
+  /**
+   * Tests animation constraint- and unit-transitions.
+   */
+  public void testAnimationTransitions_LTWH_RBWH_PX_EX() {
+    testAnimationTransitions_LTWH_RBWH(PX, EX);
+  }
+
+  /**
+   * Tests animation constraint- and unit-transitions.
+   */
+  public void testAnimationTransitions_LTWH_RBWH_PX_PCT() {
+    testAnimationTransitions_LTWH_RBWH(PX, PCT);
+  }
+
+  /**
+   * Tests animation constraint- and unit-transitions.
+   */
+  public void testAnimationTransitions_RBWH_LTRB_PX_CM() {
+    testAnimationTransitions_RBWH_LTRB(PX, CM);
+  }
+
+  /**
+   * Tests animation constraint- and unit-transitions.
+   */
+  public void testAnimationTransitions_RBWH_LTRB_PX_EM() {
+    testAnimationTransitions_RBWH_LTRB(PX, EM);
+  }
+
+  /**
+   * Tests animation constraint- and unit-transitions.
+   */
+  public void testAnimationTransitions_RBWH_LTRB_PX_EX() {
+    testAnimationTransitions_RBWH_LTRB(PX, EX);
+  }
+
+  /**
+   * Tests animation constraint- and unit-transitions.
+   */
+  public void testAnimationTransitions_RBWH_LTRB_PX_PCT() {
+    testAnimationTransitions_RBWH_LTRB(PX, PCT);
+  }
+
+  /**
+   * Test that fillParent() works properly when the outer div is a child of
+   * another div, and that it correctly follows that div's size.
+   */
+  public void testFillParent() {
+    // We don't use the default elements created in gwtSetUp() because we need
+    // to test the behavior when the layout is contained by an element other
+    // than the <body>.
+    Document doc = Document.get();
+    DivElement container = doc.createDivElement();
+    DivElement parent = doc.createDivElement();
+    DivElement child = doc.createDivElement();
+    child.setInnerHTML("&nbsp;");
+    doc.getBody().appendChild(container);
+    container.appendChild(parent);
+
+    // The container has to be position:relative so that it serves as an offset
+    // parent.
+    container.getStyle().setPosition(Position.RELATIVE);
+    container.getStyle().setWidth(128, PX);
+    container.getStyle().setHeight(256, PX);
+
+    Layout layout = new Layout(parent);
+    layout.attach();
+    Layer layer = layout.attachChild(child);
+    layer.setTopBottom(0, PX, 0, PX);
+    layer.setLeftRight(0, PX, 0, PX);
+
+    layout.fillParent();
+    layout.layout();
+
+    // Test 128x256.
+    assertEquals(128, container.getOffsetWidth());
+    assertEquals(256, container.getOffsetHeight());
+    assertEquals(128, parent.getOffsetWidth());
+    assertEquals(256, parent.getOffsetHeight());
+    assertEquals(128, child.getOffsetWidth());
+    assertEquals(256, child.getOffsetHeight());
+
+    // Expand to 256x256. The layout should react automatically.
+    container.getStyle().setWidth(256, PX);
+    container.getStyle().setHeight(128, PX);
+    assertEquals(256, container.getOffsetWidth());
+    assertEquals(256, parent.getOffsetWidth());
+    assertEquals(256, child.getOffsetWidth());
+
+    layout.detach();
+  }
+
+  /**
+   * Test that fillParent() works properly when the outer div is a child of the
+   * document body.
+   */
+  public void testFillWindow() {
+    layer0.setTopBottom(0, PX, 0, PX);
+    layer0.setLeftRight(0, PX, 0, PX);
+    layout.layout();
+
+    int w = Window.getClientWidth();
+    int h = Window.getClientHeight();
+    assertEquals(w, parent.getOffsetWidth());
+    assertEquals(h, parent.getOffsetHeight());
+    assertEquals(w, child0.getOffsetWidth());
+    assertEquals(h, child0.getOffsetHeight());
+  }
+
+  /**
+   * Tests that the layout reacts to font-size changes.
+   */
+  public void testFontSizeChange() {
+    layer0.setLeftWidth(0, PX, 1, EM);
+    layer0.setTopHeight(0, PX, 1, EM);
+    layout.layout();
+
+    parent.getStyle().setFontSize(12, PT);
+    int cw = child0.getOffsetWidth();
+    int ch = child0.getOffsetHeight();
+
+    parent.getStyle().setFontSize(24, PT);
+    int nw = child0.getOffsetWidth();
+    int nh = child0.getOffsetHeight();
+    assertTrue(nw > cw);
+    assertTrue(nh > ch);
+
+    parent.getStyle().clearFontSize();
+  }
+
+  /**
+   * Ensures that two children laid out using various units in such a way that
+   * they should abut one another actually do so.
+   */
+  public void testLayoutStructure() {
+    testHorizontalSplit(CM);
+    testHorizontalSplit(EM);
+    testHorizontalSplit(EX);
+    testHorizontalSplit(IN);
+    testHorizontalSplit(MM);
+    testHorizontalSplit(PC);
+    testHorizontalSplit(PT);
+    testHorizontalSplit(PX);
+  }
+
+  /**
+   * Tests (left-right, left-width, right-width) x (top-bottom, top-height,
+   * bottom-height). Ok, so we don't test the *entire* cross-product, but enough
+   * to be comfortable.
+   */
+  public void testStaticConstraints() {
+    // left-right, top-bottom
+    layer0.setTopBottom(32, PX, 32, PX);
+    layer0.setLeftRight(32, PX, 32, PX);
+    layout.layout();
+
+    int w = parent.getClientWidth();
+    int h = parent.getClientHeight();
+    assertEquals(32, wrapper0.getOffsetLeft());
+    assertEquals(32, wrapper0.getOffsetTop());
+    assertEquals(w - 64, wrapper0.getOffsetWidth());
+    assertEquals(h - 64, wrapper0.getOffsetHeight());
+
+    // left-width, top-height
+    layer0.setTopHeight(16, PX, 128, PX);
+    layer0.setLeftWidth(16, PX, 128, PX);
+    layout.layout();
+
+    assertEquals(16, wrapper0.getOffsetLeft());
+    assertEquals(16, wrapper0.getOffsetTop());
+    assertEquals(128, wrapper0.getOffsetWidth());
+    assertEquals(128, wrapper0.getOffsetHeight());
+
+    // right-width, bottom-height
+    layer0.setBottomHeight(16, PX, 128, PX);
+    layer0.setRightWidth(16, PX, 128, PX);
+    layout.layout();
+
+    assertEquals(w - (16 + 128), wrapper0.getOffsetLeft());
+    assertEquals(h - (16 + 128), wrapper0.getOffsetTop());
+    assertEquals(128, wrapper0.getOffsetWidth());
+    assertEquals(128, wrapper0.getOffsetHeight());
+  }
+
+  /**
+   * Tests all unit types.
+   */
+  public void testUnits() {
+    // CM
+    layer0.setTopBottom(1, CM, 1, CM);
+    layer0.setLeftRight(1, CM, 1, CM);
+    layout.layout();
+    assertLeftRightTopBottomUnitsMakeSense(wrapper0);
+
+    // MM
+    layer0.setTopBottom(1, MM, 1, MM);
+    layer0.setLeftRight(1, MM, 1, MM);
+    layout.layout();
+    assertLeftRightTopBottomUnitsMakeSense(wrapper0);
+
+    // IN
+    layer0.setTopBottom(1, IN, 1, IN);
+    layer0.setLeftRight(1, IN, 1, IN);
+    layout.layout();
+    assertLeftRightTopBottomUnitsMakeSense(wrapper0);
+
+    // EM
+    layer0.setTopBottom(1, EM, 1, EM);
+    layer0.setLeftRight(1, EM, 1, EM);
+    layout.layout();
+    assertLeftRightTopBottomUnitsMakeSense(wrapper0);
+
+    // EX
+    layer0.setTopBottom(1, EX, 1, EX);
+    layer0.setLeftRight(1, EX, 1, EX);
+    layout.layout();
+    assertLeftRightTopBottomUnitsMakeSense(wrapper0);
+
+    // PC
+    layer0.setTopBottom(1, PC, 1, PC);
+    layer0.setLeftRight(1, PC, 1, PC);
+    layout.layout();
+    assertLeftRightTopBottomUnitsMakeSense(wrapper0);
+
+    // PT
+    layer0.setTopBottom(10, PT, 10, PT);
+    layer0.setLeftRight(10, PT, 10, PT);
+    layout.layout();
+    assertLeftRightTopBottomUnitsMakeSense(wrapper0);
+
+    // PCT
+    layer0.setTopBottom(10, PCT, 10, PCT);
+    layer0.setLeftRight(10, PCT, 10, PCT);
+    layout.layout();
+    assertLeftRightTopBottomUnitsMakeSense(wrapper0);
+  }
+
+  /**
+   * Tests layout in the presence of decorations on the parent and child
+   * elements.
+   */
+  public void testWithDecorations() {
+    layer0.setTopBottom(0, PX, 0, PX);
+    layer0.setLeftRight(0, PX, 0, PX);
+    layout.layout();
+
+    // Give each of the parent and child 1px margin and 1px border.
+    parent.getStyle().setMargin(1, PX);
+    parent.getStyle().setProperty("border", "1px solid black");
+
+    child0.getStyle().setMargin(1, PX);
+    child0.getStyle().setProperty("border", "1px solid black");
+    layout.layout();
+
+    int w = Window.getClientWidth();
+    int h = Window.getClientHeight();
+    int pw = parent.getOffsetWidth();
+    int ph = parent.getOffsetHeight();
+
+    // The parent's offsetSize should be 2px smaller than the window's client
+    // area, because of the margin (the border is *included* in the offsetSize).
+    assertEquals(w - 2, pw);
+    assertEquals(h - 2, ph);
+
+    // The child's offsetSize (actually that of its wrapper element), should be
+    // 2px smaller than the parent, for precisely the same reason.
+    assertEquals(pw - 2, wrapper0.getOffsetWidth());
+    assertEquals(ph - 2, wrapper0.getOffsetHeight());
+  }
+
+  @Override
+  protected void gwtSetUp() throws Exception {
+    Window.enableScrolling(false);
+
+    Document doc = Document.get();
+    parent = doc.createDivElement();
+    child0 = doc.createDivElement();
+    child1 = doc.createDivElement();
+    doc.getBody().appendChild(parent);
+
+    layout = new Layout(parent);
+    layout.attach();
+    layout.fillParent();
+
+    layer0 = layout.attachChild(child0);
+    layer1 = layout.attachChild(child1);
+
+    wrapper0 = child0.getParentElement();
+    wrapper1 = child1.getParentElement();
+  }
+
+  @Override
+  protected void gwtTearDown() throws Exception {
+    Window.enableScrolling(true);
+    Document.get().getBody().removeChild(parent);
+    layout.detach();
+  }
+
+  private void assertLeftRightTopBottomUnitsMakeSense(Element elem) {
+    // Assume that the element has been laid out to (l, t, r, b) = (1u, 1u, 1u,
+    // 1u). Assert that the element's clientLeft/Top are non-zero, and that the
+    // clientLeft/Top/Width/Height are consistent with the parent's size.
+    int w = parent.getClientWidth();
+    int h = parent.getClientHeight();
+    int cl = elem.getOffsetLeft();
+    int ct = elem.getOffsetTop();
+    int cw = elem.getOffsetWidth();
+    int ch = elem.getOffsetHeight();
+
+    // Assert that the left-top unit came out at least non-zero size.
+    assertTrue(cl > 0);
+    assertTrue(ct > 0);
+
+    // Assert that the right-bottom also came out non-zero. We should be able
+    // to assert that it came out the same size as the top-left, but it turns
+    // out that this isn't quite reliable because of rounding errors.
+    assertTrue(w - (cl + cw) > 0);
+    assertTrue(h - (ct + ch) > 0);
+  }
+
+  // This method may only be called once per test, as it uses delayTestFinish()
+  // internally.
+  private void testAnimationTransitions_LTWH_LTRB(final Unit unit0,
+      final Unit unit1) {
+    testAnimationTransitionsHelper(new LayerInitializer() {
+      public void setupLayers(Layer l0, Layer l1) {
+        l0.setLeftWidth(0, unit0, 10, unit0);
+        l0.setTopHeight(0, unit0, 10, unit0);
+        l1.setLeftRight(1, unit1, 1, unit1);
+        l1.setTopBottom(1, unit1, 1, unit1);
+      }
+    }, new LayerInitializer() {
+      public void setupLayers(Layer l0, Layer l1) {
+        l1.setLeftWidth(0, unit0, 10, unit0);
+        l1.setTopHeight(0, unit0, 10, unit0);
+        l0.setLeftRight(1, unit1, 1, unit1);
+        l0.setTopBottom(1, unit1, 1, unit1);
+      }
+    });
+  }
+
+  // This method may only be called once per test, as it uses delayTestFinish()
+  // internally.
+  private void testAnimationTransitions_LTWH_RBWH(final Unit unit0,
+      final Unit unit1) {
+    testAnimationTransitionsHelper(new LayerInitializer() {
+      public void setupLayers(Layer l0, Layer l1) {
+        l0.setLeftWidth(0, unit0, 10, unit0);
+        l0.setTopHeight(0, unit0, 10, unit0);
+        l1.setRightWidth(0, unit1, 10, unit1);
+        l1.setBottomHeight(0, unit1, 10, unit1);
+      }
+    }, new LayerInitializer() {
+      public void setupLayers(Layer l0, Layer l1) {
+        l1.setLeftWidth(0, unit0, 10, unit0);
+        l1.setTopHeight(0, unit0, 10, unit0);
+        l0.setRightWidth(0, unit1, 10, unit1);
+        l0.setBottomHeight(0, unit1, 10, unit1);
+      }
+    });
+  }
+
+  // This method may only be called once per test, as it uses delayTestFinish()
+  // internally.
+  private void testAnimationTransitions_RBWH_LTRB(final Unit unit0,
+      final Unit unit1) {
+    testAnimationTransitionsHelper(new LayerInitializer() {
+      public void setupLayers(Layer l0, Layer l1) {
+        l0.setRightWidth(0, unit0, 10, unit0);
+        l0.setBottomHeight(0, unit0, 10, unit0);
+        l1.setLeftRight(1, unit1, 1, unit1);
+        l1.setTopBottom(1, unit1, 1, unit1);
+      }
+    }, new LayerInitializer() {
+      public void setupLayers(Layer l0, Layer l1) {
+        l1.setRightWidth(0, unit0, 10, unit0);
+        l1.setBottomHeight(0, unit0, 10, unit0);
+        l0.setLeftRight(1, unit1, 1, unit1);
+        l0.setTopBottom(1, unit1, 1, unit1);
+      }
+    });
+  }
+
+  // This method may only be called once per test, as it uses delayTestFinish()
+  // internally.
+  private void testAnimationTransitionsHelper(LayerInitializer before,
+      LayerInitializer after) {
+    before.setupLayers(layer0, layer1);
+    layout.layout();
+
+    final int l0 = wrapper0.getOffsetLeft();
+    final int t0 = wrapper0.getOffsetTop();
+    final int w0 = wrapper0.getOffsetWidth();
+    final int h0 = wrapper0.getOffsetHeight();
+
+    final int l1 = wrapper1.getOffsetLeft();
+    final int t1 = wrapper1.getOffsetTop();
+    final int w1 = wrapper1.getOffsetWidth();
+    final int h1 = wrapper1.getOffsetHeight();
+
+    after.setupLayers(layer0, layer1);
+    delayTestFinish(200);
+    layout.animate(100, new Layout.AnimationCallback() {
+      public void onAnimationComplete() {
+        // Assert that the two layers have swapped positions.
+        assertEquals(l0, wrapper1.getOffsetLeft());
+        assertEquals(t0, wrapper1.getOffsetTop());
+        assertEquals(w0, wrapper1.getOffsetWidth());
+        assertEquals(h0, wrapper1.getOffsetHeight());
+
+        assertEquals(l1, wrapper0.getOffsetLeft());
+        assertEquals(t1, wrapper0.getOffsetTop());
+        assertEquals(w1, wrapper0.getOffsetWidth());
+        assertEquals(h1, wrapper0.getOffsetHeight());
+
+        finishTest();
+      }
+
+      public void onLayout(Layer layer, double progress) {
+      }
+    });
+  }
+
+  private void testHorizontalSplit(Unit unit) {
+    // Line them up horizontally, split at 5 units.
+    layer0.setTopBottom(0, PX, 0, PX);
+    layer0.setLeftWidth(0, PX, 5, unit);
+
+    layer1.setTopBottom(0, PX, 0, PX);
+    layer1.setLeftRight(5, unit, 0, PX);
+    layout.layout();
+
+    int child0Right = wrapper0.getOffsetWidth();
+    int child1Left = wrapper1.getOffsetLeft();
+    assertEquals(child0Right, child1Left);
+  }
+
+  private void testVerticalSplit(Unit unit) {
+    // Line them up vertically, split at 5em.
+    layer0.setTopHeight(0, PX, 5, unit);
+    layer0.setLeftRight(0, PX, 0, PX);
+
+    layer1.setTopBottom(5, unit, 0, PX);
+    layer1.setLeftRight(0, PX, 0, PX);
+    layout.layout();
+
+    int child0Bottom = wrapper0.getOffsetHeight();
+    int child1Top = wrapper1.getOffsetTop();
+    assertEquals(child0Bottom, child1Top);
+  }
+}