blob: 58e15d34afc79f137675c3fd53b7bea1a4d2e60e [file] [log] [blame]
/*
* 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.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>
* Whenever children are added to, or removed from, this panel, you must call
* one of {@link #layout()}, {@link #layout(int)}, or
* {@link #layout(int, com.google.gwt.layout.client.Layout.AnimationCallback)}
* to update the panel's layout.
* </p>
*
* <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 &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.DockLayoutPanelExample}
* </p>
*
* TODO(jgw): RTL support.
*/
public class DockLayoutPanel extends ComplexPanel implements RequiresLayout,
RequiresResize, ProvidesResize {
/**
* Used in {@link DockLayoutPanel#add(Widget, Direction, double)} 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 final Unit unit;
private Widget center;
private final Layout layout;
/**
* 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());
}
/**
* 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 size the child widget's size
*
* @throws IllegalArgumentException when adding to the {@link #CENTER} and
* there is already a different widget there
*/
public void add(Widget widget, Direction direction, double size) {
insert(widget, direction, size, null);
}
/**
* TODO(jgw): Is this really the best way to do this?
*/
public Element getContainerElementFor(Widget widget) {
assertIsChild(widget);
return ((LayoutData) widget.getLayoutData()).layer.getContainerElement();
}
/**
* Gets the layout direction of the given child widget.
*
* @param w 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 w) {
if (w.getParent() != this) {
return null;
}
return ((LayoutData) w.getLayoutData()).direction;
}
/**
* 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
*
* @throws IllegalArgumentException when adding to the {@link #CENTER} and
* there is already a different widget there
*/
public 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.
getChildren().add(widget);
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);
}
public void layout() {
layout(0);
}
public void layout(int duration) {
layout(0, null);
}
public void layout(int duration, final Layout.AnimationCallback callback) {
int left = 0, top = 0, right = 0, bottom = 0;
for (Widget child : getChildren()) {
LayoutData data = (LayoutData) child.getLayoutData();
Layer layer = data.layer;
switch (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;
}
}
layout.layout(duration, new Layout.AnimationCallback() {
public void onAnimationComplete() {
for (Widget child : getChildren()) {
LayoutData data = (LayoutData) child.getLayoutData();
if (data.size != data.oldSize) {
data.oldSize = data.size;
if (child instanceof RequiresResize) {
((RequiresResize) child).onResize();
}
}
if (callback != null) {
callback.onAnimationComplete();
}
}
}
public void onLayout(Layer layer, double progress) {
Widget child = (Widget) layer.getUserObject();
if (child instanceof RequiresResize) {
((RequiresResize) child).onResize();
}
if (callback != null) {
callback.onLayout(layer, progress);
}
}
});
}
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;
}
protected Widget getCenter() {
return center;
}
protected Unit getUnit() {
return unit;
}
@Override
protected void onLoad() {
layout.onAttach();
}
@Override
protected void onUnload() {
layout.onDetach();
}
private void assertIsChild(Widget widget) {
assert (widget == null) || (widget.getParent() == this) : "TODO";
}
}