blob: 393a1ed1f9ff18a3e9110181d587f5284c9da06b [file] [log] [blame]
/*
* Copyright 2008 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.animation.client.Animation;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
/**
* A panel that displays all of its child widgets in a 'deck', where only one
* can be visible at a time. It is used by
* {@link com.google.gwt.user.client.ui.TabPanel}.
*
* <p>
* Once a widget has been added to a DeckPanel, its visibility, width, and
* height attributes will be manipulated. When the widget is removed from the
* DeckPanel, it will be visible, and its width and height attributes will be
* cleared.
* </p>
*/
public class DeckPanel extends ComplexPanel implements HasAnimation,
InsertPanel.ForIsWidget {
/**
* An {@link Animation} used to slide in the new content.
*/
private static class SlideAnimation extends Animation {
/**
* The {@link Element} holding the {@link Widget} with a lower index.
*/
private Element container1 = null;
/**
* The {@link Element} holding the {@link Widget} with a higher index.
*/
private Element container2 = null;
/**
* A boolean indicating whether container1 is growing or shrinking.
*/
private boolean growing = false;
/**
* The fixed height of a {@link TabPanel} in pixels. If the {@link TabPanel}
* does not have a fixed height, this will be set to -1.
*/
private int fixedHeight = -1;
/**
* The old {@link Widget} that is being hidden.
*/
private Widget oldWidget = null;
/**
* Switch to a new {@link Widget}.
*
* @param oldWidget the {@link Widget} to hide
* @param newWidget the {@link Widget} to show
* @param animate true to animate, false to switch instantly
*/
public void showWidget(Widget oldWidget, Widget newWidget, boolean animate) {
// Immediately complete previous animation
cancel();
// Get the container and index of the new widget
Element newContainer = getContainer(newWidget);
int newIndex = DOM.getChildIndex(DOM.getParent(newContainer),
newContainer);
// If we aren't showing anything, don't bother with the animation
if (oldWidget == null) {
UIObject.setVisible(newContainer, true);
newWidget.setVisible(true);
return;
}
this.oldWidget = oldWidget;
// Get the container and index of the old widget
Element oldContainer = getContainer(oldWidget);
int oldIndex = DOM.getChildIndex(DOM.getParent(oldContainer),
oldContainer);
// Figure out whether to grow or shrink the container
if (newIndex > oldIndex) {
container1 = oldContainer;
container2 = newContainer;
growing = false;
} else {
container1 = newContainer;
container2 = oldContainer;
growing = true;
}
// Start the animation
if (animate) {
// Figure out if the deck panel has a fixed height
com.google.gwt.dom.client.Element deckElem = container1.getParentElement();
int deckHeight = deckElem.getOffsetHeight();
if (growing) {
fixedHeight = container2.getOffsetHeight();
container2.getStyle().setPropertyPx("height",
Math.max(1, fixedHeight - 1));
} else {
fixedHeight = container1.getOffsetHeight();
container1.getStyle().setPropertyPx("height",
Math.max(1, fixedHeight - 1));
}
if (deckElem.getOffsetHeight() != deckHeight) {
fixedHeight = -1;
}
// Only scope to the deck if it's fixed height, otherwise it can affect
// the rest of the page, even if it's not visible to the user.
run(ANIMATION_DURATION, fixedHeight == -1 ? null : deckElem);
} else {
onInstantaneousRun();
}
// We call newWidget.setVisible(true) immediately after showing the
// widget's container so users can delay render their widget. Ultimately,
// we should have a better way of handling this, but we need to call
// setVisible for legacy support.
newWidget.setVisible(true);
}
@Override
protected void onComplete() {
if (growing) {
DOM.setStyleAttribute(container1, "height", "100%");
UIObject.setVisible(container1, true);
UIObject.setVisible(container2, false);
DOM.setStyleAttribute(container2, "height", "100%");
} else {
UIObject.setVisible(container1, false);
DOM.setStyleAttribute(container1, "height", "100%");
DOM.setStyleAttribute(container2, "height", "100%");
UIObject.setVisible(container2, true);
}
DOM.setStyleAttribute(container1, "overflow", "visible");
DOM.setStyleAttribute(container2, "overflow", "visible");
container1 = null;
container2 = null;
hideOldWidget();
}
@Override
protected void onStart() {
// Start the animation
DOM.setStyleAttribute(container1, "overflow", "hidden");
DOM.setStyleAttribute(container2, "overflow", "hidden");
onUpdate(0.0);
UIObject.setVisible(container1, true);
UIObject.setVisible(container2, true);
}
@Override
protected void onUpdate(double progress) {
if (!growing) {
progress = 1.0 - progress;
}
// Container1 expands (shrinks) to its target height
int height1;
int height2;
if (fixedHeight == -1) {
height1 = (int) (progress * DOM.getElementPropertyInt(container1,
"scrollHeight"));
height2 = (int) ((1.0 - progress) * DOM.getElementPropertyInt(
container2, "scrollHeight"));
} else {
height1 = (int) (progress * fixedHeight);
height2 = fixedHeight - height1;
}
// Issue 2339: If the height is 0px, IE7 will display the entire content
// widget instead of hiding it completely.
if (height1 == 0) {
height1 = 1;
height2 = Math.max(1, height2 - 1);
} else if (height2 == 0) {
height2 = 1;
height1 = Math.max(1, height1 - 1);
}
DOM.setStyleAttribute(container1, "height", height1 + "px");
DOM.setStyleAttribute(container2, "height", height2 + "px");
}
/**
* Hide the old widget when the animation completes.
*/
private void hideOldWidget() {
// Issue 2510: Hiding the widget isn't necessary because we hide its
// wrapper, but its in here for legacy support.
oldWidget.setVisible(false);
oldWidget = null;
}
private void onInstantaneousRun() {
UIObject.setVisible(container1, growing);
UIObject.setVisible(container2, !growing);
container1 = null;
container2 = null;
hideOldWidget();
}
}
/**
* The duration of the animation.
*/
private static final int ANIMATION_DURATION = 350;
/**
* The {@link Animation} used to slide in the new {@link Widget}.
*/
private static SlideAnimation slideAnimation;
/**
* The the container {@link Element} around a {@link Widget}.
*
* @param w the {@link Widget}
* @return the container {@link Element}
*/
private static Element getContainer(Widget w) {
return DOM.getParent(w.getElement());
}
private boolean isAnimationEnabled = false;
private Widget visibleWidget;
/**
* Creates an empty deck panel.
*/
public DeckPanel() {
setElement(DOM.createDiv());
}
@Override
public void add(Widget w) {
Element container = createWidgetContainer();
DOM.appendChild(getElement(), container);
// The order of these methods is very important. In order to preserve
// backward compatibility, the offsetWidth and offsetHeight of the child
// widget should be defined (greater than zero) when w.onLoad() is called.
// As a result, we first initialize the container with a height of 0px, then
// we attach the child widget to the container. See Issue 2321 for more
// details.
super.add(w, container);
// After w.onLoad is called, it is safe to make the container invisible and
// set the height of the container and widget to 100%.
finishWidgetInitialization(container, w);
}
/**
* Gets the index of the currently-visible widget.
*
* @return the visible widget's index
*/
public int getVisibleWidget() {
return getWidgetIndex(visibleWidget);
}
public void insert(IsWidget w, int beforeIndex) {
insert(asWidgetOrNull(w), beforeIndex);
}
public void insert(Widget w, int beforeIndex) {
Element container = createWidgetContainer();
DOM.insertChild(getElement(), container, beforeIndex);
// See add(Widget) for important comments
insert(w, container, beforeIndex, true);
finishWidgetInitialization(container, w);
}
public boolean isAnimationEnabled() {
return isAnimationEnabled;
}
@Override
public boolean remove(Widget w) {
Element container = getContainer(w);
boolean removed = super.remove(w);
if (removed) {
resetChildWidget(w);
DOM.removeChild(getElement(), container);
if (visibleWidget == w) {
visibleWidget = null;
}
}
return removed;
}
public void setAnimationEnabled(boolean enable) {
isAnimationEnabled = enable;
}
/**
* Shows the widget at the specified index. This causes the currently- visible
* widget to be hidden.
*
* @param index the index of the widget to be shown
*/
public void showWidget(int index) {
checkIndexBoundsForAccess(index);
Widget oldWidget = visibleWidget;
visibleWidget = getWidget(index);
if (visibleWidget != oldWidget) {
if (slideAnimation == null) {
slideAnimation = new SlideAnimation();
}
slideAnimation.showWidget(oldWidget, visibleWidget, isAnimationEnabled
&& isAttached());
}
}
/**
* Setup the container around the widget.
*/
private Element createWidgetContainer() {
Element container = DOM.createDiv();
DOM.setStyleAttribute(container, "width", "100%");
DOM.setStyleAttribute(container, "height", "0px");
DOM.setStyleAttribute(container, "padding", "0px");
DOM.setStyleAttribute(container, "margin", "0px");
return container;
}
/**
* Setup the container around the widget.
*/
private void finishWidgetInitialization(Element container, Widget w) {
UIObject.setVisible(container, false);
DOM.setStyleAttribute(container, "height", "100%");
// Set 100% by default.
Element element = w.getElement();
if (DOM.getStyleAttribute(element, "width").equals("")) {
w.setWidth("100%");
}
if (DOM.getStyleAttribute(element, "height").equals("")) {
w.setHeight("100%");
}
// Issue 2510: Hiding the widget isn't necessary because we hide its
// wrapper, but it's in here for legacy support.
w.setVisible(false);
}
/**
* Reset the dimensions of the widget when it is removed.
*/
private void resetChildWidget(Widget w) {
w.setSize("", "");
w.setVisible(true);
}
}