/* * 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.dom.client.Element; import com.google.gwt.dom.client.Style.Unit; import com.google.gwt.i18n.client.LocaleInfo; import com.google.gwt.layout.client.Layout; import com.google.gwt.layout.client.Layout.Layer; /** * A panel that lays its child widgets out "docked" at its outer edges, and * allows its last widget to take up the remaining space in its center. * * <p> * This widget will <em>only</em> work in standards mode, which requires that * the HTML page in which it is run have an explicit <!DOCTYPE> * declaration. * </p> * * <p> * <h3>Example</h3> * {@example com.google.gwt.examples.DockLayoutPanelExample} * </p> * * <h3>Use in UiBinder Templates</h3> * <p> * DockLayoutPanel elements in * {@link com.google.gwt.uibinder.client.UiBinder UiBinder} templates * lay out their children in elements tagged with the cardinal directions, * and center: * * <p> * <dl> * <dt><g:center> * <dt><g:north> * <dt><g:south> * <dt><g:west> * <dt><g:east> * </dl> * * <p> * Each child can hold only widget, and there can be only one <g:center>. * However, there can be any number of the directional children. *<p> * (Note that the tags of the child elements are not * capitalized. This is meant to signal that they are not runtime objects, * and so cannot have a <code>ui:field</code> attribute.) * <p> * For example:<pre> * <g:DockLayoutPanel unit='EM'> * <g:north size='5'> * <g:Label>Top</g:Label> * </g:north> * <g:center> * <g:Label>Body</g:Label> * </g:center> * <g:west size='192'> * <g:HTML> * <ul> * <li>Sidebar</li> * <li>Sidebar</li> * <li>Sidebar</li> * </ul> * </g:HTML> * </g:west> * </g:DockLayoutPanel> * </pre> */ public class DockLayoutPanel extends ComplexPanel implements AnimatedLayout, RequiresResize, ProvidesResize { /** * Used in {@link DockLayoutPanel#addEast(Widget, double)} et al to specify * the direction in which a child widget will be added. */ public enum Direction { NORTH, EAST, SOUTH, WEST, CENTER, LINE_START, LINE_END } /** * Layout data associated with each widget. */ protected static class LayoutData { public Direction direction; public double oldSize, size; public double originalSize; public boolean hidden; public Layer layer; public LayoutData(Direction direction, double size, Layer layer) { this.direction = direction; this.size = size; this.layer = layer; } } private class DockAnimateCommand extends LayoutCommand { public DockAnimateCommand(Layout layout) { super(layout); } @Override protected void doBeforeLayout() { doLayout(); } } private final Unit unit; private Widget center; private final Layout layout; private final LayoutCommand layoutCmd; private double filledWidth, filledHeight; /** * Creates an empty dock panel. * * @param unit the unit to be used for layout */ public DockLayoutPanel(Unit unit) { this.unit = unit; setElement(Document.get().createDivElement()); layout = new Layout(getElement()); layoutCmd = new DockAnimateCommand(layout); } /** * Adds a widget at the center of the dock. No further widgets may be added * after this one. * * @param widget the widget to be added */ @Override public void add(Widget widget) { insert(widget, Direction.CENTER, 0, null); } /** * Adds a widget to the east edge of the dock. * * @param widget the widget to be added * @param size the child widget's size */ public void addEast(Widget widget, double size) { insert(widget, Direction.EAST, size, null); } /** * Overloaded version for IsWidget. * * @see #addEast(Widget,double) */ public void addEast(IsWidget widget, double size) { this.addEast(widget.asWidget(), size); } /** * Adds a widget to the end of the line. In LTR mode, the widget is added to * the east. In RTL mode, the widget is added to the west. * * @param widget the widget to be added * @param size the child widget's size */ public void addLineEnd(Widget widget, double size) { insert(widget, Direction.LINE_END, size, null); } /** * Adds a widget to the start of the line. In LTR mode, the widget is added to * the west. In RTL mode, the widget is added to the east. * * @param widget the widget to be added * @param size the child widget's size */ public void addLineStart(Widget widget, double size) { insert(widget, Direction.LINE_START, size, null); } /** * Adds a widget to the north edge of the dock. * * @param widget the widget to be added * @param size the child widget's size */ public void addNorth(Widget widget, double size) { insert(widget, Direction.NORTH, size, null); } /** * Overloaded version for IsWidget. * * @see #addNorth(Widget,double) */ public void addNorth(IsWidget widget, double size) { this.addNorth(widget.asWidget(), size); } /** * Adds a widget to the south edge of the dock. * * @param widget the widget to be added * @param size the child widget's size */ public void addSouth(Widget widget, double size) { insert(widget, Direction.SOUTH, size, null); } /** * Overloaded version for IsWidget. * * @see #addSouth(Widget,double) */ public void addSouth(IsWidget widget, double size) { this.addSouth(widget.asWidget(), size); } /** * Adds a widget to the west edge of the dock. * * @param widget the widget to be added * @param size the child widget's size */ public void addWest(Widget widget, double size) { insert(widget, Direction.WEST, size, null); } /** * Overloaded version for IsWidget. * * @see #addWest(Widget,double) */ public void addWest(IsWidget widget, double size) { this.addWest(widget.asWidget(), size); } public void animate(int duration) { animate(duration, null); } public void animate(int duration, final Layout.AnimationCallback callback) { layoutCmd.schedule(duration, callback); } public void forceLayout() { layoutCmd.cancel(); doLayout(); layout.layout(); onResize(); } /** * Gets the container element wrapping the given child widget. * * @param child * @return the widget's container element */ public Element getWidgetContainerElement(Widget child) { assertIsChild(child); return ((LayoutData) child.getLayoutData()).layer.getContainerElement(); } /** * Gets the layout direction of the given child widget. * * @param child the widget to be queried * @return the widget's layout direction, or <code>null</code> if it is not a * child of this panel */ public Direction getWidgetDirection(Widget child) { assertIsChild(child); if (child.getParent() != this) { return null; } return ((LayoutData) child.getLayoutData()).direction; } /** * Adds a widget to the east edge of the dock, inserting it before an existing * widget. * * @param widget the widget to be added * @param size the child widget's size * @param before the widget before which to insert the new child, or * <code>null</code> to append */ public void insertEast(Widget widget, double size, Widget before) { insert(widget, Direction.EAST, size, before); } /** * Adds a widget to the start of the line, inserting it before an existing * widget. In LTR mode, the widget is added to the east. In RTL mode, the * widget is added to the west. * * @param widget the widget to be added * @param size the child widget's size * @param before the widget before which to insert the new child, or * <code>null</code> to append */ public void insertLineEnd(Widget widget, double size, Widget before) { insert(widget, Direction.LINE_END, size, before); } /** * Adds a widget to the end of the line, inserting it before an existing * widget. In LTR mode, the widget is added to the west. In RTL mode, the * widget is added to the east. * * @param widget the widget to be added * @param size the child widget's size * @param before the widget before which to insert the new child, or * <code>null</code> to append */ public void insertLineStart(Widget widget, double size, Widget before) { insert(widget, Direction.LINE_START, size, before); } /** * Adds a widget to the north edge of the dock, inserting it before an * existing widget. * * @param widget the widget to be added * @param size the child widget's size * @param before the widget before which to insert the new child, or * <code>null</code> to append */ public void insertNorth(Widget widget, double size, Widget before) { insert(widget, Direction.NORTH, size, before); } /** * Adds a widget to the south edge of the dock, inserting it before an * existing widget. * * @param widget the widget to be added * @param size the child widget's size * @param before the widget before which to insert the new child, or * <code>null</code> to append */ public void insertSouth(Widget widget, double size, Widget before) { insert(widget, Direction.SOUTH, size, before); } /** * Adds a widget to the west edge of the dock, inserting it before an existing * widget. * * @param widget the widget to be added * @param size the child widget's size * @param before the widget before which to insert the new child, or * <code>null</code> to append */ public void insertWest(Widget widget, double size, Widget before) { insert(widget, Direction.WEST, size, before); } public void onResize() { for (Widget child : getChildren()) { if (child instanceof RequiresResize) { ((RequiresResize) child).onResize(); } } } @Override public boolean remove(Widget w) { boolean removed = super.remove(w); if (removed) { // Clear the center widget. if (w == center) { center = null; } LayoutData data = (LayoutData) w.getLayoutData(); layout.removeChild(data.layer); } return removed; } /** * Updates the size of the widget passed in as long as it is not the center * widget and updates the layout of the dock. * * @param widget the widget that needs to update its size * @param size the size to update the widget to */ public void setWidgetSize(Widget widget, double size) { assertIsChild(widget); LayoutData data = (LayoutData) widget.getLayoutData(); assert data.direction != Direction.CENTER : "The size of the center widget can not be updated."; data.size = size; // Update the layout. animate(0); } protected Widget getCenter() { return center; } protected double getCenterHeight() { return getElement().getClientHeight() / layout.getUnitSize(unit, true) - filledHeight; } protected double getCenterWidth() { return getElement().getClientWidth() / layout.getUnitSize(unit, false) - filledWidth; } /** * Resolve the specified direction based on the current locale. If the * direction is {@link Direction#LINE_START} or {@link Direction#LINE_END}, * the return value will be one of {@link Direction#EAST} or * {@link Direction#WEST} depending on the RTL mode of the locale. For all * other directions, the specified value is returned. * * @param direction the specified direction * @return the locale */ protected Direction getResolvedDirection(Direction direction) { if (direction == Direction.LINE_START) { return LocaleInfo.getCurrentLocale().isRTL() ? Direction.EAST : Direction.WEST; } else if (direction == Direction.LINE_END) { return LocaleInfo.getCurrentLocale().isRTL() ? Direction.WEST : Direction.EAST; } return direction; } protected Unit getUnit() { return unit; } /** * Adds a widget to the specified edge of the dock. If the widget is already a * child of this panel, this method behaves as though {@link #remove(Widget)} * had already been called. * * @param widget the widget to be added * @param direction the widget's direction in the dock * @param before the widget before which to insert the new child, or * <code>null</code> to append */ protected void insert(Widget widget, Direction direction, double size, Widget before) { assertIsChild(before); // Validation. if (before == null) { assert center == null : "No widget may be added after the CENTER widget"; } else { assert direction != Direction.CENTER : "A CENTER widget must always be added last"; } // Detach new child. widget.removeFromParent(); // Logical attach. WidgetCollection children = getChildren(); if (before == null) { children.add(widget); } else { int index = children.indexOf(before); children.insert(widget, index); } if (direction == Direction.CENTER) { center = widget; } // Physical attach. Layer layer = layout.attachChild(widget.getElement(), (before != null) ? before.getElement() : null, widget); LayoutData data = new LayoutData(direction, size, layer); widget.setLayoutData(data); // Adopt. adopt(widget); // Update the layout. animate(0); } @Override protected void onLoad() { layout.onAttach(); } @Override protected void onUnload() { layout.onDetach(); } void assertIsChild(Widget widget) { assert (widget == null) || (widget.getParent() == this) : "The specified widget is not a child of this panel"; } private void doLayout() { double left = 0; double top = 0; double right = 0; double bottom = 0; for (Widget child : getChildren()) { LayoutData data = (LayoutData) child.getLayoutData(); Layer layer = data.layer; switch (getResolvedDirection(data.direction)) { case NORTH: layer.setLeftRight(left, unit, right, unit); layer.setTopHeight(top, unit, data.size, unit); top += data.size; break; case SOUTH: layer.setLeftRight(left, unit, right, unit); layer.setBottomHeight(bottom, unit, data.size, unit); bottom += data.size; break; case WEST: layer.setTopBottom(top, unit, bottom, unit); layer.setLeftWidth(left, unit, data.size, unit); left += data.size; break; case EAST: layer.setTopBottom(top, unit, bottom, unit); layer.setRightWidth(right, unit, data.size, unit); right += data.size; break; case CENTER: layer.setLeftRight(left, unit, right, unit); layer.setTopBottom(top, unit, bottom, unit); break; } } filledWidth = left + right; filledHeight = top + bottom; } }