This patch includes style and animation enhancements to multiple widgets. The Animation base class provides basic animation support controlled by a single Timer. Many widgets now use animations to create UI effects on user actions. Animations can be disabled en mass using one of two properties: gwt.enableAnimations and gwt.enabledWidgetAnimations. In addition, animations can be enabled or disabled on a Widget by Widget basis.
The DOM structure of some widgets has changed in order to provide more robust styles, which will be including the Showcase sample.
To be reviewed. This patch was partially reviewed by Rajeev, but a full review will be needed. Bruce, Joel, and I agreed that we should put these changes into milestone 2 so we can get some feedback from external users. All of these changes have been tested extensively on all browsers.
Issue: 1607
Patch by: jlabanca
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@2060 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/user/Animation.gwt.xml b/user/src/com/google/gwt/user/Animation.gwt.xml
new file mode 100644
index 0000000..e4f521d
--- /dev/null
+++ b/user/src/com/google/gwt/user/Animation.gwt.xml
@@ -0,0 +1,41 @@
+<!-- -->
+<!-- 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 -->
+<!-- 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. -->
+
+<!-- Adds Animation support. -->
+<module>
+ <!-- Enable or disable Animations -->
+ <define-property name="gwt.enableAnimations" values="true, false"/>
+
+ <!-- Enable or disable Widget Animations -->
+ <define-property name="gwt.enableWidgetAnimations" values="true, false"/>
+
+ <!-- Default to enabled -->
+ <set-property name="gwt.enableAnimations" value="true"/>
+ <set-property name="gwt.enableWidgetAnimations" value="true"/>
+
+ <!-- Replace the AnimationImpl -->
+ <replace-with class="com.google.gwt.user.client.animation.Animation.AnimationImplEnabled">
+ <when-type-is class="com.google.gwt.user.client.animation.Animation.AnimationImpl"/>
+ <when-property-is name="gwt.enableAnimations" value="true"/>
+ </replace-with>
+
+ <!-- Replace the WidgetAnimationImpl -->
+ <replace-with class="com.google.gwt.user.client.animation.WidgetAnimation.WidgetAnimationImplEnabled">
+ <when-type-is class="com.google.gwt.user.client.animation.WidgetAnimation.WidgetAnimationImpl"/>
+ <all>
+ <when-property-is name="gwt.enableWidgetAnimations" value="true"/>
+ <when-property-is name="gwt.enableAnimations" value="true"/>
+ </all>
+ </replace-with>
+</module>
diff --git a/user/src/com/google/gwt/user/StackPanel.gwt.xml b/user/src/com/google/gwt/user/StackPanel.gwt.xml
new file mode 100644
index 0000000..3076726
--- /dev/null
+++ b/user/src/com/google/gwt/user/StackPanel.gwt.xml
@@ -0,0 +1,28 @@
+<!--
+ 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.
+-->
+<module>
+ <inherits name="com.google.gwt.user.User"/>
+
+ <replace-with class="com.google.gwt.user.client.ui.StackPanel.StackAnimation">
+ <when-type-is class="com.google.gwt.user.client.ui.StackPanel.StackAnimation"/>
+ </replace-with>
+
+ <replace-with class="com.google.gwt.user.client.ui.StackPanel.StackAnimationIE">
+ <when-type-is class="com.google.gwt.user.client.ui.StackPanel.StackAnimation"/>
+ <when-property-is name="user.agent" value="ie6"/>
+ </replace-with>
+
+</module>
diff --git a/user/src/com/google/gwt/user/User.gwt.xml b/user/src/com/google/gwt/user/User.gwt.xml
index 4110e74..d26cbe3 100644
--- a/user/src/com/google/gwt/user/User.gwt.xml
+++ b/user/src/com/google/gwt/user/User.gwt.xml
@@ -19,6 +19,7 @@
<!-- -->
<module>
<inherits name="com.google.gwt.core.Core"/>
+ <inherits name="com.google.gwt.user.Animation"/>
<inherits name="com.google.gwt.user.RemoteService"/>
<inherits name="com.google.gwt.user.DocumentRoot" />
<inherits name="com.google.gwt.user.DOM"/>
@@ -33,6 +34,8 @@
<inherits name="com.google.gwt.user.ClippedImage"/>
<inherits name="com.google.gwt.user.RichText"/>
<inherits name="com.google.gwt.user.SplitPanel"/>
+ <inherits name="com.google.gwt.user.StackPanel"/>
<inherits name="com.google.gwt.user.ListBox" />
<inherits name="com.google.gwt.user.TitledPanel" />
+ <inherits name="com.google.gwt.user.Window" />
</module>
diff --git a/user/src/com/google/gwt/user/client/animation/Animation.java b/user/src/com/google/gwt/user/client/animation/Animation.java
new file mode 100644
index 0000000..1a9a398
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/animation/Animation.java
@@ -0,0 +1,246 @@
+/*
+ * 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.animation;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.user.client.Timer;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * An {@link Animation} is a continuous event that updates progressively over
+ * time at a non-fixed frame rate.
+ */
+public abstract class Animation {
+ /**
+ * Implementation class for {@link Animation} that does not allow any
+ * animations. If an animation is called, it is immediately started and
+ * completed.
+ */
+ public static class AnimationImpl {
+ protected void cancel(Animation anim) {
+ }
+
+ protected void run(Animation anim, int duration, long startTime) {
+ anim.onRunWhenDisabled();
+ }
+ }
+
+ /**
+ * Implementation class for {@link Animation} that actually performs
+ * animations.
+ */
+ public static class AnimationImplEnabled extends AnimationImpl {
+ /**
+ * The default time in milliseconds between frames.
+ */
+ private static final int DEFAULT_FRAME_DELAY = 25;
+
+ /**
+ * The {@link Animation}s that are currently in progress.
+ */
+ private static List<Animation> animations;
+
+ /**
+ * The {@link Timer} that applies the animations.
+ */
+ private static Timer animationTimer;
+
+ @Override
+ protected void cancel(Animation anim) {
+ // No animations available
+ if (animations == null) {
+ return;
+ }
+
+ // Remove the animation
+ anim.started = false;
+ if (animations.remove(anim)) {
+ anim.onCancel();
+ }
+ }
+
+ @Override
+ protected void run(Animation anim, int duration, long startTime) {
+ // Cancel the animation if it is running
+ anim.cancel();
+
+ // Save the duration and startTime
+ anim.duration = duration;
+ anim.startTime = startTime;
+
+ // Add to the list of animations
+ if (animations == null) {
+ animations = new ArrayList<Animation>();
+ animationTimer = new Timer() {
+ @Override
+ public void run() {
+ updateAnimations();
+ }
+ };
+ }
+ animations.add(anim);
+
+ // Restart the timer if there is the only animation
+ if (animations.size() == 1) {
+ animationTimer.schedule(DEFAULT_FRAME_DELAY);
+ }
+ }
+
+ /**
+ * Update all {@link Animation}s.
+ */
+ private void updateAnimations() {
+ // Iterator through the animations
+ long curTime = (new Date()).getTime();
+ for (int i = 0; i < animations.size(); i++) {
+ Animation animation = animations.get(i);
+ if (animation.update(curTime)) {
+ animations.remove(i);
+ i--;
+ }
+ }
+
+ // Reschedule the timer
+ if (animations.size() > 0) {
+ animationTimer.schedule(DEFAULT_FRAME_DELAY);
+ }
+ }
+ }
+
+ /**
+ * The implementation class.
+ */
+ static AnimationImpl impl = GWT.create(AnimationImpl.class);
+
+ /**
+ * The duration of the {@link Animation} in milliseconds.
+ */
+ private int duration = -1;
+
+ /**
+ * Has the {@link Animation} been started.
+ */
+ private boolean started = false;
+
+ /**
+ * The start time of the {@link Animation}.
+ */
+ private long startTime = -1;
+
+ /**
+ * Immediately cancel this animation.
+ */
+ public void cancel() {
+ impl.cancel(this);
+ }
+
+ /**
+ * Called immediately after the animation is canceled.
+ */
+ public abstract void onCancel();
+
+ /**
+ * Called immediately after the animation completes.
+ */
+ public abstract void onComplete();
+
+ /**
+ * Called immediately before the animation starts.
+ */
+ public abstract void onStart();
+
+ /**
+ * Called when the animation should be updated.
+ *
+ * The value of progress is between 0.0 and 1.0 inclusively, but it is not
+ * safe to assume that either 0.0 or 1.0 will be passed in. Use
+ * {@link #onStart()} and {@link #onComplete()} to do setup and tear down
+ * procedures.
+ */
+ public abstract void onUpdate(double progress);
+
+ /**
+ * Immediately run this animation. If the animation is already running, it
+ * will be canceled first.
+ *
+ * @param duration the duration of the animation in milliseconds
+ */
+ public void run(int duration) {
+ run(duration, (new Date()).getTime());
+ }
+
+ /**
+ * Run this animation at the given startTime. If the startTime has already
+ * passed, the animation will be synchronize as if it started at the specified
+ * start time. If the animation is already running, it will be canceled first.
+ *
+ * @param duration the duration of the animation in milliseconds
+ * @param startTime the synchronized start time in milliseconds
+ */
+ public void run(int duration, long startTime) {
+ impl.run(this, duration, startTime);
+ }
+
+ /**
+ * Interpolate the linear progress into a more natural easing function.
+ *
+ * Depending on the {@link Animation}, the return value of this method can be
+ * less than 0.0 or greater than 1.0.
+ *
+ * @param progress the linear progress, between 0.0 and 1.0
+ * @return the interpolated progress
+ */
+ protected double interpolate(double progress) {
+ return (1 + Math.cos(Math.PI + progress * Math.PI)) / 2;
+ }
+
+ /**
+ * Called when we run an animation that is disabled.
+ */
+ void onRunWhenDisabled() {
+ onStart();
+ onComplete();
+ }
+
+ /**
+ * Update the {@link Animation}.
+ *
+ * @param curTime the current time
+ * @return true if the animation is complete, false if still running
+ */
+ private boolean update(long curTime) {
+ // Start the animation
+ if (!started && curTime >= startTime) {
+ started = true;
+ onStart();
+ }
+
+ if (curTime >= startTime + duration) {
+ // Animation is complete
+ started = false;
+ onComplete();
+ return true;
+ } else if (curTime >= startTime) {
+ // Animation is in progress
+ double progress = (double) (curTime - startTime) / (double) duration;
+ onUpdate(interpolate(progress));
+ }
+ return false;
+ }
+}
diff --git a/user/src/com/google/gwt/user/client/animation/WidgetAnimation.java b/user/src/com/google/gwt/user/client/animation/WidgetAnimation.java
new file mode 100644
index 0000000..e7dd29b
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/animation/WidgetAnimation.java
@@ -0,0 +1,97 @@
+/*
+ * 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.animation;
+
+import com.google.gwt.core.client.GWT;
+
+/**
+ * An {@link WidgetAnimation} is an {@link Animation} specifically designed for
+ * use with Widgets. If the animation is disabled, the #onInstantaneousRun
+ * method will be called instead of onStart and onComplete. This allows you to
+ * increase performance when animations are disabled.
+ */
+public abstract class WidgetAnimation extends Animation {
+ /**
+ * Implementation class for {@link WidgetAnimation} that does not allow any
+ * animations. If an animation is called, it is immediately started and
+ * completed.
+ */
+ public static class WidgetAnimationImpl {
+ protected void cancel(WidgetAnimation anim) {
+ }
+
+ protected void run(WidgetAnimation anim, int duration, long startTime) {
+ anim.onRunWhenDisabled();
+ }
+ }
+
+ /**
+ * Implementation class for {@link WidgetAnimation} that actually performs
+ * animations.
+ */
+ public static class WidgetAnimationImplEnabled extends WidgetAnimationImpl {
+ @Override
+ protected void cancel(WidgetAnimation anim) {
+ Animation.impl.cancel(anim);
+ }
+
+ @Override
+ protected void run(WidgetAnimation anim, int duration, long startTime) {
+ Animation.impl.run(anim, duration, startTime);
+ }
+ }
+
+ /**
+ * The implementation class.
+ */
+ private static WidgetAnimationImpl widgetImpl = GWT.create(WidgetAnimationImpl.class);
+
+ /**
+ * Immediately cancel this animation.
+ */
+ @Override
+ public void cancel() {
+ widgetImpl.cancel(this);
+ }
+
+ /**
+ * Called if this animation is run when it is disabled. If this method is
+ * called, {@link #onStart()} and {@link #onComplete()} will not be called.
+ */
+ public abstract void onInstantaneousRun();
+
+ /**
+ * Run this animation at the given startTime. If the startTime has already
+ * passed, the animation will be synchronize as if it started at the specified
+ * start time. If the animation is already running, it will be canceled first.
+ *
+ * @param duration the duration of the animation in milliseconds
+ * @param startTime the synchronized start time in milliseconds
+ */
+ @Override
+ public void run(int duration, long startTime) {
+ widgetImpl.run(this, duration, startTime);
+ }
+
+ /**
+ * Called when we run an animation that is disabled.
+ */
+ @Override
+ void onRunWhenDisabled() {
+ onInstantaneousRun();
+ }
+
+}
diff --git a/user/src/com/google/gwt/user/client/ui/DeckPanel.java b/user/src/com/google/gwt/user/client/ui/DeckPanel.java
index 4e2b5d2..2a434a4 100644
--- a/user/src/com/google/gwt/user/client/ui/DeckPanel.java
+++ b/user/src/com/google/gwt/user/client/ui/DeckPanel.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2007 Google Inc.
+ * 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
@@ -17,6 +17,7 @@
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.animation.WidgetAnimation;
/**
* A panel that displays all of its child widgets in a 'deck', where only one
@@ -30,7 +31,161 @@
* cleared.
* </p>
*/
-public class DeckPanel extends ComplexPanel {
+public class DeckPanel extends ComplexPanel implements HasAnimation {
+ /**
+ * An {@link WidgetAnimation} used to slide in the new content.
+ */
+ private static class SlideAnimation extends WidgetAnimation {
+ /**
+ * 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;
+
+ @Override
+ public void onCancel() {
+ onComplete();
+ }
+
+ @Override
+ public void onComplete() {
+ if (growing) {
+ onUpdate(1.0);
+ 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);
+ }
+ container1 = null;
+ container2 = null;
+ }
+
+ @Override
+ public void onInstantaneousRun() {
+ UIObject.setVisible(container1, growing);
+ UIObject.setVisible(container2, !growing);
+ container1 = null;
+ container2 = null;
+ }
+
+ @Override
+ public void onStart() {
+ onUpdate(0.0);
+ UIObject.setVisible(container1, true);
+ UIObject.setVisible(container2, true);
+ }
+
+ @Override
+ public 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;
+ }
+ DOM.setStyleAttribute(container1, "height", height1 + "px");
+ DOM.setStyleAttribute(container2, "height", height2 + "px");
+ }
+
+ /**
+ * 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);
+ return;
+ }
+
+ // 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;
+ }
+
+ // Figure out if we are in a fixed height situation
+ fixedHeight = DOM.getElementPropertyInt(oldContainer, "offsetHeight");
+ if (fixedHeight == oldWidget.getOffsetHeight()) {
+ fixedHeight = -1;
+ }
+
+ // Start the animation
+ if (animate) {
+ run(350);
+ } else {
+ onInstantaneousRun();
+ }
+ }
+ }
+
+ /**
+ * The {@link WidgetAnimation} 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;
@@ -48,8 +203,10 @@
*/
@Override
public void add(Widget w) {
- super.add(w, getElement());
- initChildWidget(w);
+ Element container = DOM.createDiv();
+ DOM.appendChild(getElement(), container);
+ super.add(w, container);
+ initWidgetContainer(w);
}
/**
@@ -70,16 +227,25 @@
* range
*/
public void insert(Widget w, int beforeIndex) {
- super.insert(w, getElement(), beforeIndex, true);
- initChildWidget(w);
+ Element container = DOM.createDiv();
+ DOM.insertChild(getElement(), container, beforeIndex);
+ super.insert(w, container, beforeIndex, true);
+ initWidgetContainer(w);
+ }
+
+ /**
+ * @see HasAnimation#isAnimationEnabled()
+ */
+ 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;
}
@@ -88,6 +254,13 @@
}
/**
+ * @see HasAnimation#setAnimationEnabled(boolean)
+ */
+ public void setAnimationEnabled(boolean enable) {
+ isAnimationEnabled = enable;
+ }
+
+ /**
* Shows the widget at the specified index. This causes the currently- visible
* widget to be hidden.
*
@@ -95,32 +268,28 @@
*/
public void showWidget(int index) {
checkIndexBoundsForAccess(index);
-
- if (visibleWidget != null) {
- visibleWidget.setVisible(false);
- }
+ Widget oldWidget = visibleWidget;
visibleWidget = getWidget(index);
- visibleWidget.setVisible(true);
+
+ if (visibleWidget != oldWidget) {
+ if (slideAnimation == null) {
+ slideAnimation = new SlideAnimation();
+ }
+ slideAnimation.showWidget(oldWidget, visibleWidget, isAnimationEnabled
+ && isAttached());
+ }
}
/**
* Make the widget invisible, and set its width and height to full.
*/
- private void initChildWidget(Widget w) {
- Element child = w.getElement();
- DOM.setStyleAttribute(child, "width", "100%");
- DOM.setStyleAttribute(child, "height", "100%");
- w.setVisible(false);
- }
-
- /**
- * Make the widget visible, and clear the widget's width and height
- * attributes. This is done so that any changes to the visibility, height, or
- * width of the widget that were done by the panel are undone.
- */
- private void resetChildWidget(Widget w) {
- DOM.setStyleAttribute(w.getElement(), "width", "");
- DOM.setStyleAttribute(w.getElement(), "height", "");
- w.setVisible(true);
+ private void initWidgetContainer(Widget w) {
+ Element container = getContainer(w);
+ DOM.setStyleAttribute(container, "width", "100%");
+ DOM.setStyleAttribute(container, "height", "100%");
+ DOM.setStyleAttribute(container, "overflow", "hidden");
+ DOM.setStyleAttribute(container, "padding", "0px");
+ DOM.setStyleAttribute(container, "margin", "0px");
+ UIObject.setVisible(container, false);
}
}
diff --git a/user/src/com/google/gwt/user/client/ui/DialogBox.java b/user/src/com/google/gwt/user/client/ui/DialogBox.java
index 25df828..6042fc4 100644
--- a/user/src/com/google/gwt/user/client/ui/DialogBox.java
+++ b/user/src/com/google/gwt/user/client/ui/DialogBox.java
@@ -16,6 +16,7 @@
package com.google.gwt.user.client.ui;
import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
/**
@@ -30,20 +31,26 @@
* <ul class='css'>
* <li>.gwt-DialogBox { the outside of the dialog }</li>
* <li>.gwt-DialogBox .Caption { the caption }</li>
+ * <li>.gwt-DialogBox .content { the content }</li>
* </ul>
* <p>
+ * The styles that apply to {@link DecoratorPanel} also apply to DialogBox.
+ * </p>
+ * <p>
* <h3>Example</h3>
* {@example com.google.gwt.examples.DialogBoxExample}
* </p>
*/
public class DialogBox extends PopupPanel implements HasHTML, HasText,
MouseListener {
-
+ /**
+ * The default style name.
+ */
+ private static final String DEFAULT_STYLENAME = "gwt-DialogBox";
+
private HTML caption = new HTML();
- private Widget child;
private boolean dragging;
private int dragStartX, dragStartY;
- private FlexTable panel = new FlexTable();
/**
* Creates an empty dialog box. It should not be shown until its child widget
@@ -77,20 +84,17 @@
*/
public DialogBox(boolean autoHide, boolean modal) {
super(autoHide, modal);
- panel.setWidget(0, 0, caption);
- panel.setHeight("100%");
- panel.setBorderWidth(0);
- panel.setCellPadding(0);
- panel.setCellSpacing(0);
- panel.getCellFormatter().setHeight(1, 0, "100%");
- panel.getCellFormatter().setWidth(1, 0, "100%");
- panel.getCellFormatter().setAlignment(1, 0,
- HasHorizontalAlignment.ALIGN_CENTER, HasVerticalAlignment.ALIGN_MIDDLE);
- super.setWidget(panel);
- setStyleName("gwt-DialogBox");
+ // Add the caption to the top row of the decorator panel. We need to
+ // logically adopt the caption so we can catch mouse events.
+ Element td = getCellElement(0, 1);
+ DOM.appendChild(td, caption.getElement());
+ adopt(caption);
caption.setStyleName("Caption");
caption.addMouseListener(this);
+
+ // Set the style name
+ setStyleName(DEFAULT_STYLENAME);
}
public String getHTML() {
@@ -102,11 +106,6 @@
}
@Override
- public Widget getWidget() {
- return child;
- }
-
- @Override
public boolean onEventPreview(Event event) {
// We need to preventDefault() on mouseDown events (outside of the
// DialogBox content) to keep text from being selected when it
@@ -146,16 +145,6 @@
DOM.releaseCapture(caption.getElement());
}
- @Override
- public boolean remove(Widget w) {
- if (child != w) {
- return false;
- }
-
- panel.remove(w);
- return true;
- }
-
/**
* Sets the html inside the caption.
*
@@ -197,27 +186,31 @@
public void setText(String text) {
caption.setText(text);
}
+
+ @Override
+ protected void doAttachChildren() {
+ super.doAttachChildren();
+
+ // See comment in doDetachChildren for an explanation of this call
+ caption.onAttach();
+ }
@Override
- public void setWidget(Widget w) {
- // If there is already a widget, remove it.
- if (child != null) {
- panel.remove(child);
- }
-
- // Add the widget to the center of the cell.
- if (w != null) {
- panel.setWidget(1, 0, w);
- }
-
- child = w;
+ protected void doDetachChildren() {
+ super.doDetachChildren();
+
+ // We need to detach the caption specifically because it is not part of the
+ // iterator of Widgets that the {@link SimplePanel} super class returns.
+ // This is similar to a {@link ComplexPanel}, but we do not want to expose
+ // the caption widget, as its just an internal implementation.
+ caption.onDetach();
}
/**
* <b>Affected Elements:</b>
* <ul>
* <li>-caption = text at the top of the {@link DialogBox}.</li>
- * <li>-content = the table cell around the content.</li>
+ * <li>-content = the container around the content.</li>
* </ul>
*
* @see UIObject#onEnsureDebugId(String)
@@ -226,6 +219,6 @@
protected void onEnsureDebugId(String baseID) {
super.onEnsureDebugId(baseID);
caption.ensureDebugId(baseID + "-caption");
- ensureDebugId(panel.getCellFormatter().getElement(1, 0), baseID, "content");
+ ensureDebugId(getContainerElement(), baseID, "content");
}
}
diff --git a/user/src/com/google/gwt/user/client/ui/DisclosurePanel.java b/user/src/com/google/gwt/user/client/ui/DisclosurePanel.java
index d775288..5920238 100644
--- a/user/src/com/google/gwt/user/client/ui/DisclosurePanel.java
+++ b/user/src/com/google/gwt/user/client/ui/DisclosurePanel.java
@@ -19,6 +19,7 @@
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.animation.WidgetAnimation;
import java.util.ArrayList;
import java.util.Iterator;
@@ -46,7 +47,85 @@
* </p>
*/
public final class DisclosurePanel extends Composite implements
- FiresDisclosureEvents, HasWidgets {
+ FiresDisclosureEvents, HasWidgets, HasAnimation {
+ /**
+ * An {@link WidgetAnimation} used to open the content.
+ */
+ private static class ContentAnimation extends WidgetAnimation {
+ /**
+ * Whether the item is being opened or closed.
+ */
+ private boolean opening;
+
+ /**
+ * The {@link DisclosurePanel} being affected.
+ */
+ private DisclosurePanel curPanel;
+
+ @Override
+ public void onCancel() {
+ onComplete();
+ }
+
+ @Override
+ public void onComplete() {
+ if (!opening) {
+ curPanel.contentWrapper.setVisible(false);
+ }
+ DOM.setStyleAttribute(curPanel.contentWrapper.getElement(), "height",
+ "auto");
+ curPanel = null;
+ }
+
+ @Override
+ public void onInstantaneousRun() {
+ curPanel.contentWrapper.setVisible(opening);
+ curPanel = null;
+ }
+
+ @Override
+ public void onStart() {
+ onUpdate(0.0);
+ if (opening) {
+ curPanel.contentWrapper.setVisible(true);
+ }
+ }
+
+ @Override
+ public void onUpdate(double progress) {
+ int scrollHeight = DOM.getElementPropertyInt(
+ curPanel.contentWrapper.getElement(), "scrollHeight");
+ int height = (int) (progress * scrollHeight);
+ if (!opening) {
+ height = scrollHeight - height;
+ }
+ DOM.setStyleAttribute(curPanel.contentWrapper.getElement(), "height",
+ height + "px");
+ DOM.setStyleAttribute(curPanel.contentWrapper.getElement(), "width",
+ "auto");
+ }
+
+ /**
+ * Open or close the content.
+ *
+ * @param panel the panel to open or close
+ * @param animate true to animate, false to open instantly
+ */
+ public void setOpen(DisclosurePanel panel, boolean animate) {
+ // Immediately complete previous open
+ cancel();
+
+ // Open the new item
+ curPanel = panel;
+ opening = panel.isOpen;
+
+ if (animate) {
+ run(350);
+ } else {
+ onInstantaneousRun();
+ }
+ }
+ }
/**
* Used to wrap widgets in the header to provide click support. Effectively
@@ -67,7 +146,7 @@
}
@Override
- public final void onBrowserEvent(Event event) {
+ public void onBrowserEvent(Event event) {
// no need to call super.
switch (DOM.eventGetType(event)) {
case Event.ONCLICK:
@@ -89,16 +168,16 @@
* for the label.
*/
private final Element labelTD;
-
+
private final Image iconImage;
private final DisclosurePanelImages images;
-
+
private DefaultHeader(DisclosurePanelImages images, String text) {
this.images = images;
iconImage = isOpen ? images.disclosurePanelOpen().createImage()
: images.disclosurePanelClosed().createImage();
-
+
// I do not need any Widgets here, just a DOM structure.
Element root = DOM.createTable();
Element tbody = DOM.createTBody();
@@ -162,6 +241,11 @@
private static final String STYLENAME_CONTENT = "content";
+ /**
+ * The {@link WidgetAnimation} used to open and close the content.
+ */
+ private static ContentAnimation contentAnimation;
+
private static DisclosurePanelImages createDefaultImages() {
return GWT.create(DisclosurePanelImages.class);
}
@@ -174,6 +258,11 @@
private final VerticalPanel mainPanel = new VerticalPanel();
/**
+ * The wrapper around the content widget.
+ */
+ private final SimplePanel contentWrapper = new SimplePanel();
+
+ /**
* holds the header widget.
*/
private final ClickableHeader header = new ClickableHeader();
@@ -183,6 +272,8 @@
*/
private Widget content;
+ private boolean isAnimationEnabled = true;
+
private boolean isOpen = false;
/**
@@ -190,7 +281,7 @@
* initialized).
*/
private ArrayList<DisclosureHandler> handlers;
-
+
/**
* Creates an empty DisclosurePanel that is initially closed.
*/
@@ -243,7 +334,7 @@
public DisclosurePanel(Widget header) {
this(header, false);
}
-
+
/**
* Creates a DisclosurePanel using a widget as the header and an initial
* open/close state.
@@ -271,7 +362,7 @@
*
* @param handler the handler to be added (should not be null)
*/
- public final void addEventHandler(DisclosureHandler handler) {
+ public void addEventHandler(DisclosureHandler handler) {
if (handlers == null) {
handlers = new ArrayList<DisclosureHandler>();
}
@@ -287,7 +378,7 @@
*
* @return the panel's current content widget
*/
- public final Widget getContent() {
+ public Widget getContent() {
return content;
}
@@ -296,7 +387,7 @@
*
* @return the widget currently being used as a header
*/
- public final Widget getHeader() {
+ public Widget getHeader() {
return header.getWidget();
}
@@ -307,17 +398,24 @@
* @return a reference to the header widget if it implements {@link HasText},
* <code>null</code> otherwise
*/
- public final HasText getHeaderTextAccessor() {
+ public HasText getHeaderTextAccessor() {
Widget widget = header.getWidget();
return (widget instanceof HasText) ? (HasText) widget : null;
}
/**
+ * @see HasAnimation#isAnimationEnabled()
+ */
+ public boolean isAnimationEnabled() {
+ return isAnimationEnabled;
+ }
+
+ /**
* Determines whether the panel is open.
*
* @return <code>true</code> if panel is in open state
*/
- public final boolean isOpen() {
+ public boolean isOpen() {
return isOpen;
}
@@ -339,7 +437,7 @@
*
* @param handler the handler to be removed
*/
- public final void removeEventHandler(DisclosureHandler handler) {
+ public void removeEventHandler(DisclosureHandler handler) {
if (handlers == null) {
return;
}
@@ -347,26 +445,33 @@
}
/**
+ * @see HasAnimation#setAnimationEnabled(boolean)
+ */
+ public void setAnimationEnabled(boolean enable) {
+ isAnimationEnabled = enable;
+ }
+
+ /**
* Sets the content widget which can be opened and closed by this panel. If
* there is a preexisting content widget, it will be detached.
*
* @param content the widget to be used as the content panel
*/
- public final void setContent(Widget content) {
+ public void setContent(Widget content) {
final Widget currentContent = this.content;
// Remove existing content widget.
if (currentContent != null) {
- mainPanel.remove(currentContent);
+ contentWrapper.setWidget(null);
currentContent.removeStyleName(STYLENAME_CONTENT);
}
// Add new content widget if != null.
this.content = content;
if (content != null) {
- mainPanel.add(content);
+ contentWrapper.setWidget(content);
content.addStyleName(STYLENAME_CONTENT);
- setContentDisplay();
+ setContentDisplay(false);
}
}
@@ -375,7 +480,7 @@
*
* @param headerWidget the widget to be used as the header
*/
- public final void setHeader(Widget headerWidget) {
+ public void setHeader(Widget headerWidget) {
header.setWidget(headerWidget);
}
@@ -385,10 +490,10 @@
* @param isOpen <code>true</code> to open the panel, <code>false</code>
* to close
*/
- public final void setOpen(boolean isOpen) {
+ public void setOpen(boolean isOpen) {
if (this.isOpen != isOpen) {
this.isOpen = isOpen;
- setContentDisplay();
+ setContentDisplay(true);
fireEvent();
}
}
@@ -425,12 +530,15 @@
private void init(boolean isOpen) {
initWidget(mainPanel);
mainPanel.add(header);
+ mainPanel.add(contentWrapper);
+ DOM.setStyleAttribute(contentWrapper.getElement(), "padding", "0px");
+ DOM.setStyleAttribute(contentWrapper.getElement(), "overflow", "hidden");
this.isOpen = isOpen;
setStyleName(STYLENAME_DEFAULT);
- setContentDisplay();
+ setContentDisplay(false);
}
- private void setContentDisplay() {
+ private void setContentDisplay(boolean animate) {
if (isOpen) {
removeStyleDependentName(STYLENAME_SUFFIX_CLOSED);
addStyleDependentName(STYLENAME_SUFFIX_OPEN);
@@ -440,7 +548,10 @@
}
if (content != null) {
- content.setVisible(isOpen);
+ if (contentAnimation == null) {
+ contentAnimation = new ContentAnimation();
+ }
+ contentAnimation.setOpen(this, animate && isAnimationEnabled);
}
}
}
diff --git a/user/src/com/google/gwt/user/client/ui/HasAnimation.java b/user/src/com/google/gwt/user/client/ui/HasAnimation.java
new file mode 100644
index 0000000..350bac1
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/ui/HasAnimation.java
@@ -0,0 +1,34 @@
+/*
+ * 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;
+
+/**
+ * A {@link Widget} that uses an animation should implement this class so users
+ * can enable or disable animations.
+ */
+public interface HasAnimation {
+ /**
+ * @return true if animations are enabled, false if not
+ */
+ boolean isAnimationEnabled();
+
+ /**
+ * Enable or disable animations.
+ *
+ * @param enable true to enable, false to disable
+ */
+ void setAnimationEnabled(boolean enable);
+}
\ No newline at end of file
diff --git a/user/src/com/google/gwt/user/client/ui/MenuBar.java b/user/src/com/google/gwt/user/client/ui/MenuBar.java
index cf1df6e..c744660 100644
--- a/user/src/com/google/gwt/user/client/ui/MenuBar.java
+++ b/user/src/com/google/gwt/user/client/ui/MenuBar.java
@@ -15,11 +15,13 @@
*/
package com.google.gwt.user.client.ui;
+import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.DeferredCommand;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.PopupPanel.AnimationType;
import java.util.ArrayList;
import java.util.List;
@@ -52,7 +54,18 @@
* {@example com.google.gwt.examples.MenuBarExample}
* </p>
*/
-public class MenuBar extends Widget implements PopupListener {
+public class MenuBar extends Widget implements PopupListener, HasAnimation {
+ /**
+ * An {@link ImageBundle} that provides images for {@link MenuBar}.
+ */
+ public interface MenuBarImages extends ImageBundle {
+ /**
+ * An image indicating a {@link MenuItem} has an associated submenu.
+ *
+ * @return a prototype of this image
+ */
+ AbstractImagePrototype menuBarSubMenuIcon();
+ }
/**
* List of all {@link MenuItem}s and {@link MenuItemSeparator}s.
@@ -65,6 +78,9 @@
private ArrayList<MenuItem> items = new ArrayList<MenuItem>();
private Element body;
+
+ private MenuBarImages images = null;
+ private boolean isAnimationEnabled = true;
private MenuBar parentMenu;
private PopupPanel popup;
private MenuItem selectedItem;
@@ -122,6 +138,7 @@
item.setSelectionStyle(false);
items.add(item);
allItems.add(item);
+ updateSubmenuIcon(item);
return item;
}
@@ -193,6 +210,9 @@
* @return the {@link MenuItemSeparator} object
*/
public MenuItemSeparator addSeparator(MenuItemSeparator separator) {
+ if (vertical) {
+ setItemColSpan(separator, 2);
+ }
addItemElement(separator.getElement());
separator.setParentMenu(this);
allItems.add(separator);
@@ -210,6 +230,7 @@
// Set the parent of all items to null
for (UIObject item : allItems) {
+ setItemColSpan(item, 1);
if (item instanceof MenuItemSeparator) {
((MenuItemSeparator) item).setParentMenu(null);
} else {
@@ -232,6 +253,13 @@
return autoOpen;
}
+ /**
+ * @see HasAnimation#isAnimationEnabled()
+ */
+ public boolean isAnimationEnabled() {
+ return isAnimationEnabled;
+ }
+
@Override
public void onBrowserEvent(Event event) {
super.onBrowserEvent(event);
@@ -282,6 +310,7 @@
*/
public void removeItem(MenuItem item) {
if (removeItemElement(item)) {
+ setItemColSpan(item, 1);
items.remove(item);
item.setParentMenu(null);
}
@@ -299,6 +328,13 @@
}
/**
+ * @see HasAnimation#setAnimationEnabled(boolean)
+ */
+ public void setAnimationEnabled(boolean enable) {
+ isAnimationEnabled = enable;
+ }
+
+ /**
* Sets whether this menu bar's child menus will open when the mouse is moved
* over it.
*
@@ -440,6 +476,8 @@
return super.onEventPreview(event);
}
};
+ popup.setAnimationType(AnimationType.ONE_WAY_CORNER);
+ popup.setStyleName("gwt-MenuBarPopup");
popup.addPopupListener(this);
if (vertical) {
@@ -454,6 +492,15 @@
shownChildMenu = item.getSubMenu();
item.getSubMenu().parentMenu = this;
+ // If any parent MenuBar has animations disabled, then do not animate
+ MenuBar parent = parentMenu;
+ boolean animate = isAnimationEnabled;
+ while (animate && parent != null) {
+ animate = parent.isAnimationEnabled();
+ parent = parent.parentMenu;
+ }
+ popup.setAnimationEnabled(animate);
+
// Show the popup, ensuring that the menubar's event preview remains on top
// of the popup's.
popup.show();
@@ -488,10 +535,27 @@
if (selectedItem != null) {
selectedItem.setSelectionStyle(false);
+ // Set the style of the submenu indicator
+ if (vertical) {
+ Element tr = DOM.getParent(selectedItem.getElement());
+ if (DOM.getChildCount(tr) == 2) {
+ Element td = DOM.getChild(tr, 1);
+ setStyleName(td, "subMenuIcon-selected", false);
+ }
+ }
}
if (item != null) {
item.setSelectionStyle(true);
+
+ // Set the style of the submenu indicator
+ if (vertical) {
+ Element tr = DOM.getParent(item.getElement());
+ if (DOM.getChildCount(tr) == 2) {
+ Element td = DOM.getChild(tr, 1);
+ setStyleName(td, "subMenuIcon-selected", true);
+ }
+ }
}
selectedItem = item;
@@ -511,6 +575,47 @@
}
/**
+ * Show or hide the icon used for items with a submenu.
+ *
+ * @param item the item with or without a submenu
+ */
+ void updateSubmenuIcon(MenuItem item) {
+ // The submenu icon only applies to vertical menus
+ if (!vertical) {
+ return;
+ }
+
+ // Get the index of the MenuItem
+ int idx = allItems.indexOf(item);
+ if (idx == -1) {
+ return;
+ }
+
+ Element container = getItemContainerElement();
+ Element tr = DOM.getChild(container, idx);
+ int tdCount = DOM.getChildCount(tr);
+ MenuBar submenu = item.getSubMenu();
+ if (submenu == null) {
+ // Remove the submenu indicator
+ if (tdCount == 2) {
+ DOM.removeChild(tr, DOM.getChild(tr, 1));
+ }
+ setItemColSpan(item, 2);
+ } else if (tdCount == 1) {
+ // Show the submenu indicator
+ if (images == null) {
+ images = GWT.create(MenuBarImages.class);
+ }
+ setItemColSpan(item, 1);
+ Element td = DOM.createTD();
+ DOM.setElementProperty(td, "vAlign", "middle");
+ DOM.setInnerHTML(td, images.menuBarSubMenuIcon().getHTML());
+ setStyleName(td, "subMenuIcon");
+ DOM.appendChild(tr, td);
+ }
+ }
+
+ /**
* Physically add the td element of a {@link MenuItem} or
* {@link MenuItemSeparator} to this {@link MenuBar}.
*
@@ -594,4 +699,14 @@
allItems.remove(idx);
return true;
}
+
+ /**
+ * Set the colspan of a {@link MenuItem} or {@link MenuItemSeparator}.
+ *
+ * @param item the {@link MenuItem} or {@link MenuItemSeparator}
+ * @param colspan the colspan
+ */
+ private void setItemColSpan(UIObject item, int colspan) {
+ DOM.setElementPropertyInt(item.getElement(), "colSpan", colspan);
+ }
}
diff --git a/user/src/com/google/gwt/user/client/ui/MenuItem.java b/user/src/com/google/gwt/user/client/ui/MenuItem.java
index e03ae76..d431497 100644
--- a/user/src/com/google/gwt/user/client/ui/MenuItem.java
+++ b/user/src/com/google/gwt/user/client/ui/MenuItem.java
@@ -144,6 +144,9 @@
*/
public void setSubMenu(MenuBar subMenu) {
this.subMenu = subMenu;
+ if (this.parentMenu != null) {
+ this.parentMenu.updateSubmenuIcon(this);
+ }
}
public void setText(String text) {
diff --git a/user/src/com/google/gwt/user/client/ui/PopupPanel.java b/user/src/com/google/gwt/user/client/ui/PopupPanel.java
index 7a69741..c3f1eb9 100644
--- a/user/src/com/google/gwt/user/client/ui/PopupPanel.java
+++ b/user/src/com/google/gwt/user/client/ui/PopupPanel.java
@@ -21,6 +21,7 @@
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.EventPreview;
import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.animation.WidgetAnimation;
import com.google.gwt.user.client.ui.impl.PopupImpl;
/**
@@ -38,9 +39,157 @@
* <h3>Example</h3>
* {@example com.google.gwt.examples.PopupPanelExample}
* </p>
+ * <h3>CSS Style Rules</h3>
+ * <ul class='css'>
+ * <li>.gwt-PopupPanel { the outside of the popup }</li>
+ * <li>.gwt-PopupPanel .content { the wrapper around the content }</li>
+ * </ul>
+ * <p>
+ * The styles that apply to {@link DecoratorPanel} also apply to PopupPanel.
+ * </p>
*/
-public class PopupPanel extends SimplePanel implements SourcesPopupEvents,
- EventPreview {
+public class PopupPanel extends DecoratorPanel implements SourcesPopupEvents,
+ EventPreview, HasAnimation {
+ /**
+ * The type of animation to use when opening the popup.
+ *
+ * <ul>
+ * <li>CENTER - Expand from the center of the popup</li>
+ * <li>ONE_WAY_CORNER - Expand from the top left corner, do not animate
+ * hiding</li>
+ * </ul>
+ */
+ public static enum AnimationType {
+ CENTER, ONE_WAY_CORNER
+ }
+
+ /**
+ * An {@link WidgetAnimation} used to enlarge the popup into view.
+ */
+ private static class ResizeAnimation extends WidgetAnimation {
+ /**
+ * The offset height and width of the current {@link PopupPanel}.
+ */
+ private int offsetHeight, offsetWidth = -1;
+
+ /**
+ * The {@link PopupPanel} being affected.
+ */
+ private PopupPanel curPanel = null;
+
+ @Override
+ public void onCancel() {
+ onComplete();
+ }
+
+ @Override
+ public void onComplete() {
+ if (!curPanel.showing) {
+ RootPanel.get().remove(curPanel);
+ impl.onHide(curPanel.getElement());
+ }
+ impl.setClip(curPanel.getElement(), "rect(auto, auto, auto, auto)");
+ curPanel = null;
+ }
+
+ @Override
+ public void onInstantaneousRun() {
+ if (curPanel.showing) {
+ // Set the position attribute, and then attach to the DOM. Otherwise,
+ // the PopupPanel will appear to 'jump' from its static/relative
+ // position to its absolute position (issue #1231).
+ DOM.setStyleAttribute(curPanel.getElement(), "position", "absolute");
+ if (curPanel.topPosition != -1) {
+ curPanel.setPopupPosition(curPanel.leftPosition, curPanel.topPosition);
+ }
+ RootPanel.get().add(curPanel);
+ impl.onShow(curPanel.getElement());
+ } else {
+ RootPanel.get().remove(curPanel);
+ impl.onHide(curPanel.getElement());
+ }
+ curPanel = null;
+ }
+
+ @Override
+ public void onStart() {
+ // Attach to the page
+ if (curPanel.showing) {
+ // Set the position attribute, and then attach to the DOM. Otherwise,
+ // the PopupPanel will appear to 'jump' from its static/relative
+ // position to its absolute position (issue #1231).
+ DOM.setStyleAttribute(curPanel.getElement(), "position", "absolute");
+ if (curPanel.topPosition != -1) {
+ curPanel.setPopupPosition(curPanel.leftPosition, curPanel.topPosition);
+ }
+ impl.setClip(curPanel.getElement(), getRectString(0, 0, 0, 0));
+ RootPanel.get().add(curPanel);
+ impl.onShow(curPanel.getElement());
+ }
+ offsetHeight = curPanel.getOffsetHeight();
+ offsetWidth = curPanel.getOffsetWidth();
+ onUpdate(0.0);
+ }
+
+ @Override
+ public void onUpdate(double progress) {
+ if (!curPanel.showing) {
+ progress = 1.0 - progress;
+ }
+
+ // Determine the clipping size
+ int top = 0;
+ int left = 0;
+ int right = 0;
+ int bottom = 0;
+ int height = (int) (progress * offsetHeight);
+ int width = (int) (progress * offsetWidth);
+ if (curPanel.animType == AnimationType.CENTER) {
+ top = (offsetHeight - height) / 2;
+ left = (offsetWidth - width) / 2;
+ }
+ right = left + width;
+ bottom = top + height;
+
+ // Set the rect clipping
+ impl.setClip(curPanel.getElement(), getRectString(top, right, bottom,
+ left));
+ }
+
+ /**
+ * Open or close the content. This method always called immediately after
+ * the PopupPanel showing state has changed, so we base the animation on the
+ * current state.
+ *
+ * @param panel the panel to open or close
+ */
+ public void setOpen(final PopupPanel panel) {
+ // Immediately complete previous open/close animation
+ cancel();
+
+ // Determine if we need to animate
+ boolean animate = panel.isAnimationEnabled;
+ if (panel.animType == AnimationType.ONE_WAY_CORNER && !panel.showing) {
+ animate = false;
+ }
+
+ // Open the new item
+ curPanel = panel;
+ if (animate) {
+ run(200);
+ } else {
+ onInstantaneousRun();
+ }
+ }
+
+ /**
+ * @return a rect string
+ */
+ private String getRectString(int top, int right, int bottom, int left) {
+ return "rect(" + top + "px, " + right + "px, " + bottom + "px, " + left
+ + "px)";
+ }
+ }
/**
* A callback that is used to set the position of a {@link PopupPanel} right
@@ -61,8 +210,25 @@
void setPosition(int offsetWidth, int offsetHeight);
}
+ /**
+ * The default style name.
+ */
+ private static final String DEFAULT_STYLENAME = "gwt-PopupPanel";
+
private static final PopupImpl impl = GWT.create(PopupImpl.class);
+ /**
+ * The {@link ResizeAnimation} used to open and close the {@link PopupPanel}s.
+ */
+ private static ResizeAnimation resizeAnimation;
+
+ /**
+ * If true, animate the opening of this popup from the center. If false,
+ * animate it open from top to bottom, and do not animate closing. Use false
+ * to animate menus.
+ */
+ private AnimationType animType = AnimationType.CENTER;
+
private boolean autoHide, modal, showing;
// Used to track requested size across changing child widgets
@@ -70,6 +236,8 @@
private String desiredWidth;
+ private boolean isAnimationEnabled = true;
+
// the left style attribute in pixels
private int leftPosition = -1;
@@ -83,12 +251,16 @@
* is shown.
*/
public PopupPanel() {
- super(impl.createElement());
+ super();
+ DOM.appendChild(super.getContainerElement(), impl.createElement());
// Default position of popup should be in the upper-left corner of the
// window. By setting a default position, the popup will not appear in
// an undefined location if it is shown before its position is set.
setPopupPosition(0, 0);
+ setStyleName(DEFAULT_STYLENAME);
+ setStyleName(getContainerElement(), "content");
+ DOM.setStyleAttribute(getElement(), "overflow", "hidden");
}
/**
@@ -128,9 +300,11 @@
*/
public void center() {
boolean initiallyShowing = showing;
-
+ boolean initiallyAnimated = isAnimationEnabled;
+
if (!initiallyShowing) {
setVisible(false);
+ setAnimationEnabled(false);
show();
}
@@ -139,7 +313,10 @@
setPopupPosition(Window.getScrollLeft() + left, Window.getScrollTop() + top);
if (!initiallyShowing) {
+ hide();
setVisible(true);
+ setAnimationEnabled(initiallyAnimated);
+ show();
}
}
@@ -196,6 +373,13 @@
hide(false);
}
+ /**
+ * @see HasAnimation#isAnimationEnabled()
+ */
+ public boolean isAnimationEnabled() {
+ return isAnimationEnabled;
+ }
+
public boolean onEventPreview(Event event) {
Element target = DOM.eventGetTarget(event);
@@ -299,10 +483,17 @@
}
/**
+ * @see HasAnimation#setAnimationEnabled(boolean)
+ */
+ public void setAnimationEnabled(boolean enable) {
+ isAnimationEnabled = enable;
+ }
+
+ /**
* Sets the height of the panel's child widget. If the panel's child widget
* has not been set, the height passed in will be cached and used to set the
* height immediately after the child widget is set.
- *
+ *
* <p>
* Note that subclasses may have a different behavior. A subclass may decide
* not to change the height of the child widget. It may instead decide to
@@ -410,7 +601,7 @@
* Sets the width of the panel's child widget. If the panel's child widget has
* not been set, the width passed in will be cached and used to set the width
* immediately after the child widget is set.
- *
+ *
* <p>
* Note that subclasses may have a different behavior. A subclass may decide
* not to change the width of the child widget. It may instead decide to
@@ -440,26 +631,15 @@
showing = true;
DOM.addEventPreview(this);
- // Set the position attribute, and then attach to the DOM. Otherwise,
- // the PopupPanel will appear to 'jump' from its static/relative position
- // to its absolute position (issue #1231).
- DOM.setStyleAttribute(getElement(), "position", "absolute");
- if (topPosition != -1) {
- setPopupPosition(leftPosition, topPosition);
+ if (resizeAnimation == null) {
+ resizeAnimation = new ResizeAnimation();
}
- RootPanel.get().add(this);
-
- impl.onShow(getElement());
+ resizeAnimation.setOpen(this);
}
@Override
protected Element getContainerElement() {
- return impl.getContainerElement(getElement());
- }
-
- @Override
- protected Element getStyleElement() {
- return impl.getContainerElement(getElement());
+ return impl.getContainerElement(DOM.getFirstChild(super.getContainerElement()));
}
/**
@@ -474,6 +654,15 @@
}
/**
+ * Enable or disable animation of the {@link PopupPanel}.
+ *
+ * @param type the type of animation to use
+ */
+ protected void setAnimationType(AnimationType type) {
+ animType = type;
+ }
+
+ /**
* Remove focus from an Element.
*
* @param elt The Element on which <code>blur()</code> will be invoked
@@ -490,8 +679,13 @@
}
showing = false;
- RootPanel.get().remove(this);
- impl.onHide(getElement());
+ // Hide the popup
+ if (resizeAnimation == null) {
+ resizeAnimation = new ResizeAnimation();
+ }
+ resizeAnimation.setOpen(this);
+
+ // Fire the event listeners
if (popupListeners != null) {
popupListeners.firePopupClosed(this, autoClosed);
}
diff --git a/user/src/com/google/gwt/user/client/ui/StackPanel.java b/user/src/com/google/gwt/user/client/ui/StackPanel.java
index 9922032..d0bb0b2 100644
--- a/user/src/com/google/gwt/user/client/ui/StackPanel.java
+++ b/user/src/com/google/gwt/user/client/ui/StackPanel.java
@@ -15,9 +15,11 @@
*/
package com.google.gwt.user.client.ui;
+import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.animation.WidgetAnimation;
/**
* A panel that stacks its children vertically, displaying only one at a time,
@@ -38,9 +40,143 @@
* {@example com.google.gwt.examples.StackPanelExample}
* </p>
*/
-public class StackPanel extends ComplexPanel {
+public class StackPanel extends ComplexPanel implements HasAnimation {
+ /**
+ * An {@link WidgetAnimation} used to switch between the visible stack.
+ */
+ public static class StackAnimation extends WidgetAnimation {
+ /**
+ * The {@link StackPanel} being affected.
+ */
+ private StackPanel curPanel = null;
+
+ /**
+ * The index of the stack that is being hidden.
+ */
+ private int oldIndex = -1;
+
+ /**
+ * The index of the stack that is being displayed.
+ */
+ private int newIndex = -1;
+
+ /**
+ * The container that is being displayed.
+ */
+ private Element newTD = null;
+
+ @Override
+ public void onCancel() {
+ onComplete();
+ }
+
+ @Override
+ public void onComplete() {
+ if (curPanel != null) {
+ curPanel.setStackContentVisible(oldIndex, false);
+ curPanel.setStackContentVisible(newIndex, true);
+ clearOpacity(newTD);
+ curPanel = null;
+ }
+ }
+
+ @Override
+ public void onInstantaneousRun() {
+ curPanel.setStackContentVisible(oldIndex, false);
+ curPanel.setStackContentVisible(newIndex, true);
+ curPanel = null;
+ }
+
+ @Override
+ public void onStart() {
+ onUpdate(0.0);
+ curPanel.setStackContentVisible(oldIndex, false);
+ curPanel.setStackContentVisible(newIndex, true);
+ }
+
+ @Override
+ public void onUpdate(double progress) {
+ setOpacity(newTD, progress);
+ }
+
+ /**
+ * Open or close the content.
+ *
+ * @param panel the panel being affected
+ * @param oldIndex the index being closed
+ * @param newIndex the index being opened
+ */
+ public void showStack(StackPanel panel, int oldIndex, int newIndex) {
+ // Immediately complete previous open
+ cancel();
+
+ // Do not animate if there is no stack currently visible
+ if (oldIndex < 0) {
+ panel.setStackContentVisible(newIndex, true);
+ return;
+ }
+
+ // Get all of the affected Elements
+ curPanel = panel;
+ this.oldIndex = oldIndex;
+ this.newIndex = newIndex;
+ newTD = DOM.getFirstChild(DOM.getChild(curPanel.body, (2 * newIndex) + 1));
+
+ // Run the animation
+ if (panel.isAnimationEnabled) {
+ run(250);
+ } else {
+ onInstantaneousRun();
+ }
+ }
+
+ /**
+ * Clear the opacity of an {@link Element}.
+ *
+ * @param elem the {@link Element}
+ */
+ protected void clearOpacity(Element elem) {
+ DOM.setStyleAttribute(elem, "opacity", "");
+ }
+
+ /**
+ * Set the opacity of an {@link Element}.
+ *
+ * @param elem the {@link Element}
+ * @param opacity the opacity between 0.0 and 1.0, inclusively
+ */
+ protected void setOpacity(Element elem, double opacity) {
+ DOM.setStyleAttribute(elem, "opacity", opacity + "");
+ }
+ }
+
+ /**
+ * IE version of {@link StackAnimation} that sets opacity using filters.
+ */
+ public static class StackAnimationIE extends StackAnimation {
+ @Override
+ protected void clearOpacity(Element elem) {
+ DOM.setStyleAttribute(elem, "filter", "");
+ }
+
+ @Override
+ protected void setOpacity(Element elem, double opacity) {
+ int opac = (int) (100 * opacity);
+ DOM.setStyleAttribute(elem, "filter", "alpha(opacity=" + opac + ")");
+ }
+ }
+
+ private static final String DEFAULT_STYLENAME = "gwt-StackPanel";
+ private static final String DEFAULT_ITEM_STYLENAME = DEFAULT_STYLENAME
+ + "Item";
+
+ /**
+ * The {@link WidgetAnimation} used to switch stacks.
+ */
+ private static StackAnimation stackAnimation;
private Element body;
+ private boolean isAnimationEnabled = true;
private int visibleStack = -1;
/**
@@ -56,7 +192,7 @@
DOM.setElementPropertyInt(table, "cellPadding", 0);
DOM.sinkEvents(table, Event.ONCLICK);
- setStyleName("gwt-StackPanel");
+ setStyleName(DEFAULT_STYLENAME);
}
/**
@@ -114,6 +250,7 @@
Element trh = DOM.createTR();
Element tdh = DOM.createTD();
DOM.appendChild(trh, tdh);
+ DOM.appendChild(tdh, createHeaderElem());
// body
Element trb = DOM.createTR();
@@ -129,12 +266,12 @@
DOM.insertChild(body, trh, effectiveIndex);
// header styling
- setStyleName(tdh, "gwt-StackPanelItem", true);
+ setStyleName(tdh, DEFAULT_ITEM_STYLENAME, true);
DOM.setElementPropertyInt(tdh, "__owner", hashCode());
DOM.setElementProperty(tdh, "height", "1px");
// body styling
- setStyleName(tdb, "gwt-StackPanelContent", true);
+ setStyleName(tdb, DEFAULT_STYLENAME + "Content", true);
DOM.setElementProperty(tdb, "height", "100%");
DOM.setElementProperty(tdb, "vAlign", "top");
@@ -149,13 +286,22 @@
if (visibleStack == -1) {
showStack(0);
} else {
- setStackVisible(beforeIndex, false);
+ setStackVisible(beforeIndex, false, true);
if (visibleStack >= beforeIndex) {
++visibleStack;
}
+ // Reshow the stack to apply style names
+ setStackVisible(visibleStack, true, true);
}
}
+ /**
+ * @see HasAnimation#isAnimationEnabled()
+ */
+ public boolean isAnimationEnabled() {
+ return isAnimationEnabled;
+ }
+
@Override
public void onBrowserEvent(Event event) {
if (DOM.eventGetType(event) == Event.ONCLICK) {
@@ -178,6 +324,13 @@
}
/**
+ * @see HasAnimation#setAnimationEnabled(boolean)
+ */
+ public void setAnimationEnabled(boolean enable) {
+ isAnimationEnabled = enable;
+ }
+
+ /**
* Sets the text associated with a child by its index.
*
* @param index the index of the child whose text is to be set
@@ -199,7 +352,10 @@
return;
}
- Element td = DOM.getChild(DOM.getChild(body, index * 2), 0);
+ Element tdWrapper = DOM.getChild(DOM.getChild(body, index * 2), 0);
+ Element tbody = DOM.getFirstChild(DOM.getFirstChild(tdWrapper));
+ Element tr = DOM.getChild(tbody, 1);
+ Element td = DOM.getChild(tr, 1);
if (asHTML) {
DOM.setInnerHTML(td, text);
} else {
@@ -213,22 +369,28 @@
* @param index the index of the child to be shown
*/
public void showStack(int index) {
- if ((index >= getWidgetCount()) || (index == visibleStack)) {
+ if ((index >= getWidgetCount()) || (index < 0) || (index == visibleStack)) {
return;
}
+ int oldIndex = visibleStack;
if (visibleStack >= 0) {
- setStackVisible(visibleStack, false);
+ setStackVisible(visibleStack, false, false);
}
visibleStack = index;
- setStackVisible(visibleStack, true);
+ setStackVisible(visibleStack, true, false);
+ if (stackAnimation == null) {
+ stackAnimation = GWT.create(StackAnimation.class);
+ }
+ stackAnimation.showStack(this, oldIndex, index);
}
/**
* <b>Affected Elements:</b>
* <ul>
* <li>-text# = The element around the header at the specified index.</li>
+ * <li>-text-wrapper# = The element around the header at the specified index.</li>
* <li>-content# = The element around the body at the specified index.</li>
* </ul>
*
@@ -242,11 +404,40 @@
for (int i = 0; i < numHeaders; i++) {
Element headerElem = DOM.getFirstChild(DOM.getChild(body, 2 * i));
Element bodyElem = DOM.getFirstChild(DOM.getChild(body, 2 * i + 1));
- ensureDebugId(headerElem, baseID, "text" + i);
+ ensureDebugId(headerElem, baseID, "text-wrapper" + i);
ensureDebugId(bodyElem, baseID, "content" + i);
+
+ // Set the ID on the inner most container of the stack item
+ Element table = DOM.getFirstChild(headerElem);
+ Element tbody = DOM.getFirstChild(DOM.getFirstChild(headerElem));
+ Element tr = DOM.getChild(tbody, 1);
+ Element td = DOM.getChild(tr, 1);
+ ensureDebugId(td, baseID, "text" + i);
}
}
+ /**
+ * @return a header element
+ */
+ private Element createHeaderElem() {
+ // Create the table
+ Element table = DOM.createTable();
+ Element tbody = DOM.createTBody();
+ DOM.appendChild(table, tbody);
+ DOM.setStyleAttribute(table, "width", "100%");
+ DOM.setElementPropertyInt(table, "cellSpacing", 0);
+ DOM.setElementPropertyInt(table, "cellPadding", 0);
+
+ // Add the top row
+ DOM.appendChild(tbody, DecoratorPanel.createTR("top"));
+
+ // Add the middle row
+ DOM.appendChild(tbody, DecoratorPanel.createTR("middle"));
+
+ // Return the table
+ return table;
+ }
+
private int findDividerIndex(Element elem) {
while (elem != getElement()) {
String expando = DOM.getElementProperty(elem, "__index");
@@ -285,12 +476,18 @@
}
// Update indices of all elements to the right.
- updateIndicesFrom(rowIndex);
+ updateIndicesFrom(index);
}
return removed;
}
- private void setStackVisible(int index, boolean visible) {
+ private void setStackContentVisible(int index, boolean visible) {
+ Element tr = DOM.getChild(body, (index * 2) + 1);
+ UIObject.setVisible(tr, visible);
+ getWidget(index).setVisible(visible);
+ }
+
+ private void setStackVisible(int index, boolean visible, boolean render) {
// Get the first table row containing the widget's selector item.
Element tr = DOM.getChild(body, (index * 2));
if (tr == null) {
@@ -299,12 +496,20 @@
// Style the stack selector item.
Element td = DOM.getFirstChild(tr);
- setStyleName(td, "gwt-StackPanelItem-selected", visible);
+ setStyleName(td, DEFAULT_ITEM_STYLENAME + "-selected", visible);
// Show/hide the contained widget.
- tr = DOM.getChild(body, (index * 2) + 1);
- UIObject.setVisible(tr, visible);
- getWidget(index).setVisible(visible);
+ if (render) {
+ setStackContentVisible(index, visible);
+ }
+
+ // Set the style of the next header
+ Element trNext = DOM.getChild(body, ((index + 1) * 2));
+ if (trNext != null) {
+ Element tdNext = DOM.getFirstChild(trNext);
+ Element divNext = DOM.getFirstChild(tdNext);
+ setStyleName(tdNext, DEFAULT_ITEM_STYLENAME + "-below-selected", visible);
+ }
}
private void updateIndicesFrom(int beforeIndex) {
@@ -312,6 +517,13 @@
Element childTR = DOM.getChild(body, i * 2);
Element childTD = DOM.getFirstChild(childTR);
DOM.setElementPropertyInt(childTD, "__index", i);
+
+ // Update the special style on the first element
+ if (beforeIndex == 0) {
+ setStyleName(childTD, DEFAULT_ITEM_STYLENAME + "-first", true);
+ } else {
+ setStyleName(childTD, DEFAULT_ITEM_STYLENAME + "-first", false);
+ }
}
}
diff --git a/user/src/com/google/gwt/user/client/ui/SuggestBox.java b/user/src/com/google/gwt/user/client/ui/SuggestBox.java
index a7ea661..6cd541a 100644
--- a/user/src/com/google/gwt/user/client/ui/SuggestBox.java
+++ b/user/src/com/google/gwt/user/client/ui/SuggestBox.java
@@ -18,6 +18,7 @@
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.PopupPanel.AnimationType;
import com.google.gwt.user.client.ui.SuggestOracle.Callback;
import com.google.gwt.user.client.ui.SuggestOracle.Request;
import com.google.gwt.user.client.ui.SuggestOracle.Response;
@@ -86,7 +87,7 @@
* @see TextBoxBase
*/
public final class SuggestBox extends Composite implements HasText, HasFocus,
- SourcesClickEvents, SourcesFocusEvents, SourcesChangeEvents,
+ HasAnimation, SourcesClickEvents, SourcesFocusEvents, SourcesChangeEvents,
SourcesKeyboardEvents, FiresSuggestionEvents {
/**
@@ -340,6 +341,7 @@
// suggestionMenu is suggestionPopup's widget
suggestionMenu = new SuggestionMenu(true);
suggestionPopup = new SuggestionPopup();
+ suggestionPopup.setAnimationType(AnimationType.ONE_WAY_CORNER);
addKeyboardSupport();
setOracle(oracle);
@@ -352,7 +354,7 @@
*
* @param listener the listener interface to add
*/
- public final void addChangeListener(ChangeListener listener) {
+ public void addChangeListener(ChangeListener listener) {
if (changeListeners == null) {
changeListeners = new DelegatingChangeListenerCollection(this, box);
}
@@ -365,14 +367,14 @@
*
* @param listener the listener interface to add
*/
- public final void addClickListener(ClickListener listener) {
+ public void addClickListener(ClickListener listener) {
if (clickListeners == null) {
clickListeners = new DelegatingClickListenerCollection(this, box);
}
clickListeners.add(listener);
}
- public final void addEventHandler(SuggestionHandler handler) {
+ public void addEventHandler(SuggestionHandler handler) {
if (suggestionHandlers == null) {
suggestionHandlers = new ArrayList<SuggestionHandler>();
}
@@ -385,7 +387,7 @@
*
* @param listener the listener interface to add
*/
- public final void addFocusListener(FocusListener listener) {
+ public void addFocusListener(FocusListener listener) {
if (focusListeners == null) {
focusListeners = new DelegatingFocusListenerCollection(this, box);
}
@@ -398,7 +400,7 @@
*
* @param listener the listener interface to add
*/
- public final void addKeyboardListener(KeyboardListener listener) {
+ public void addKeyboardListener(KeyboardListener listener) {
if (keyboardListeners == null) {
keyboardListeners = new DelegatingKeyboardListenerCollection(this, box);
}
@@ -412,7 +414,7 @@
*
* @return the limit for the number of suggestions
*/
- public final int getLimit() {
+ public int getLimit() {
return limit;
}
@@ -421,54 +423,62 @@
*
* @return the {@link SuggestOracle}
*/
- public final SuggestOracle getSuggestOracle() {
+ public SuggestOracle getSuggestOracle() {
return oracle;
}
- public final int getTabIndex() {
+ public int getTabIndex() {
return box.getTabIndex();
}
- public final String getText() {
+ public String getText() {
return box.getText();
}
- public final void removeChangeListener(ChangeListener listener) {
+ public boolean isAnimationEnabled() {
+ return suggestionPopup.isAnimationEnabled();
+ }
+
+ public void removeChangeListener(ChangeListener listener) {
if (changeListeners != null) {
changeListeners.remove(listener);
}
}
- public final void removeClickListener(ClickListener listener) {
+ public void removeClickListener(ClickListener listener) {
if (clickListeners != null) {
clickListeners.remove(listener);
}
}
- public final void removeEventHandler(SuggestionHandler handler) {
+ public void removeEventHandler(SuggestionHandler handler) {
if (suggestionHandlers == null) {
return;
}
suggestionHandlers.remove(handler);
}
- public final void removeFocusListener(FocusListener listener) {
+ public void removeFocusListener(FocusListener listener) {
if (focusListeners != null) {
focusListeners.remove(listener);
}
}
- public final void removeKeyboardListener(KeyboardListener listener) {
+ public void removeKeyboardListener(KeyboardListener listener) {
if (keyboardListeners != null) {
keyboardListeners.remove(listener);
}
}
- public final void setAccessKey(char key) {
+ public void setAccessKey(char key) {
box.setAccessKey(key);
}
- public final void setFocus(boolean focused) {
+ public void setAnimationEnabled(boolean enable) {
+ suggestionPopup.setAnimationEnabled(enable);
+ }
+
+ public void setFocus(boolean focused) {
box.setFocus(focused);
}
@@ -478,7 +488,7 @@
*
* @param limit the limit to the number of suggestions provided
*/
- public final void setLimit(int limit) {
+ public void setLimit(int limit) {
this.limit = limit;
}
@@ -488,15 +498,15 @@
* @param style the new primary style name
* @see UIObject#setStyleName(String)
*/
- public final void setPopupStyleName(String style) {
+ public void setPopupStyleName(String style) {
suggestionPopup.setStyleName(style);
}
- public final void setTabIndex(int index) {
+ public void setTabIndex(int index) {
box.setTabIndex(index);
}
- public final void setText(String text) {
+ public void setText(String text) {
box.setText(text);
}
diff --git a/user/src/com/google/gwt/user/client/ui/TabBar.java b/user/src/com/google/gwt/user/client/ui/TabBar.java
index 4d1a90d..f6d975a 100644
--- a/user/src/com/google/gwt/user/client/ui/TabBar.java
+++ b/user/src/com/google/gwt/user/client/ui/TabBar.java
@@ -16,7 +16,6 @@
package com.google.gwt.user.client.ui;
import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
/**
@@ -44,17 +43,23 @@
*/
public class TabBar extends Composite implements SourcesTabEvents,
ClickListener {
-
/**
* <code>ClickDecoratorPanel</code> decorates any widget with the minimal
* amount of machinery to receive clicks for delegation to the parent.
* {@link SourcesClickEvents} is not implemented due to the fact that only a
* single observer is needed.
*/
- private static final class ClickDecoratorPanel extends SimplePanel {
+ private static final class ClickDecoratorPanel extends DecoratorPanel {
+ /**
+ * The styles applied to the {@link DecoratorPanel}. We only need to six
+ * box the tabs because we only expect to have rounded corners on the top.
+ */
+ private static String[] ROW_STYLES = {"top", "middle"};
+
ClickListener delegate;
ClickDecoratorPanel(Widget child, ClickListener delegate) {
+ super(ROW_STYLES, 1);
this.delegate = delegate;
setWidget(child);
sinkEvents(Event.ONCLICK);
@@ -164,14 +169,15 @@
if (index >= getTabCount()) {
return null;
}
- Widget widget = panel.getWidget(index + 1);
+ ClickDecoratorPanel decPanel = (ClickDecoratorPanel) panel.getWidget(index + 1);
+ Widget widget = decPanel.getWidget();
if (widget instanceof HTML) {
return ((HTML) widget).getHTML();
} else if (widget instanceof Label) {
return ((Label) widget).getText();
} else {
// This will be a ClickDecorator holding a user-supplied widget.
- return DOM.getInnerHTML(widget.getElement());
+ return DOM.getInnerHTML(decPanel.getContainerElement());
}
}
@@ -193,11 +199,7 @@
}
item.setWordWrap(false);
- item.addClickListener(this);
- item.setStyleName(STYLENAME_DEFAULT);
- panel.insert(item, beforeIndex + 1);
- setStyleName(DOM.getParent(item.getElement()), STYLENAME_DEFAULT
- + "-wrapper", true);
+ insertTabWidget(item, beforeIndex);
}
/**
@@ -217,13 +219,7 @@
* @param beforeIndex the index before which this tab will be inserted.
*/
public void insertTab(Widget widget, int beforeIndex) {
- checkInsertBeforeTabIndex(beforeIndex);
-
- ClickDecoratorPanel decWidget = new ClickDecoratorPanel(widget, this);
- decWidget.addStyleName(STYLENAME_DEFAULT);
- panel.insert(decWidget, beforeIndex + 1);
- setStyleName(DOM.getParent(decWidget.getElement()), STYLENAME_DEFAULT
- + "-wrapper", true);
+ insertTabWidget(widget, beforeIndex);
}
public void onClick(Widget sender) {
@@ -291,6 +287,23 @@
}
/**
+ * Inserts a new tab at the specified index.
+ *
+ * @param widget widget to be used in the new tab.
+ * @param beforeIndex the index before which this tab will be inserted.
+ */
+ protected void insertTabWidget(Widget widget, int beforeIndex) {
+ checkInsertBeforeTabIndex(beforeIndex);
+
+ ClickDecoratorPanel decWidget = new ClickDecoratorPanel(widget, this);
+ decWidget.setStyleName(STYLENAME_DEFAULT);
+ panel.insert(decWidget, beforeIndex + 1);
+
+ setStyleName(DOM.getParent(decWidget.getElement()),
+ STYLENAME_DEFAULT + "-wrapper", true);
+ }
+
+ /**
* <b>Affected Elements:</b>
* <ul>
* <li>-tab# = The element containing the contents of the tab.</li>
@@ -305,10 +318,11 @@
int numTabs = getTabCount();
for (int i = 0; i < numTabs; i++) {
- Element widgetElem = panel.getWidget(i + 1).getElement();
- ensureDebugId(widgetElem, baseID, "tab" + i);
- ensureDebugId(DOM.getParent(widgetElem), baseID, "tab-wrapper" + i);
- }
+ DecoratorPanel decPanel = (DecoratorPanel) panel.getWidget(i + 1);
+ ensureDebugId(decPanel.getContainerElement(), baseID, "tab" + i);
+ ensureDebugId(DOM.getParent(decPanel.getElement()), baseID, "tab-wrapper"
+ + i);
+ }
}
private void checkInsertBeforeTabIndex(int beforeIndex) {
diff --git a/user/src/com/google/gwt/user/client/ui/TabPanel.java b/user/src/com/google/gwt/user/client/ui/TabPanel.java
index 4fa2c43..f9c2f2e 100644
--- a/user/src/com/google/gwt/user/client/ui/TabPanel.java
+++ b/user/src/com/google/gwt/user/client/ui/TabPanel.java
@@ -375,7 +375,7 @@
* <ul>
* <li>-bar = The tab bar.</li>
* <li>-bar-tab# = The element containing the content of the tab itself.</li>
- * <li>-bar-tab#-wrapper = The cell containing the tab at the index.</li>
+ * <li>-bar-tab-wrapper# = The cell containing the tab at the index.</li>
* <li>-bottom = The panel beneath the tab bar.</li>
* </ul>
*
diff --git a/user/src/com/google/gwt/user/client/ui/Tree.java b/user/src/com/google/gwt/user/client/ui/Tree.java
index f034004..d885a16 100644
--- a/user/src/com/google/gwt/user/client/ui/Tree.java
+++ b/user/src/com/google/gwt/user/client/ui/Tree.java
@@ -45,7 +45,7 @@
* </p>
*/
public class Tree extends Widget implements HasWidgets, SourcesTreeEvents,
- HasFocus {
+ HasFocus, HasAnimation {
/**
* Provides images to support the the deprecated case where a url prefix is
@@ -118,6 +118,7 @@
private final Element focusable;
private FocusListenerCollection focusListeners;
private TreeImages images;
+ private boolean isAnimationEnabled = true;
private KeyboardListenerCollection keyboardListeners;
private TreeListenerCollection listeners;
private MouseListenerCollection mouseListeners = null;
@@ -335,6 +336,13 @@
return FocusPanel.impl.getTabIndex(focusable);
}
+ /**
+ * @see HasAnimation#isAnimationEnabled()
+ */
+ public boolean isAnimationEnabled() {
+ return isAnimationEnabled;
+ }
+
public Iterator<Widget> iterator() {
final Widget[] widgets = new Widget[childWidgets.size()];
childWidgets.keySet().toArray(widgets);
@@ -450,7 +458,11 @@
break;
}
case KeyboardListener.KEY_LEFT: {
- if (curSelection.getState()) {
+ TreeItem topClosedParent = getTopClosedParent(curSelection);
+ if (topClosedParent != null) {
+ // Select the first visible parent if curSelection is hidden
+ setSelectedItem(topClosedParent);
+ } else if (curSelection.getState()) {
curSelection.setState(false);
} else {
TreeItem parent = curSelection.getParentItem();
@@ -462,7 +474,11 @@
break;
}
case KeyboardListener.KEY_RIGHT: {
- if (!curSelection.getState()) {
+ TreeItem topClosedParent = getTopClosedParent(curSelection);
+ if (topClosedParent != null) {
+ // Select the first visible parent if curSelection is hidden
+ setSelectedItem(topClosedParent);
+ } else if (!curSelection.getState()) {
curSelection.setState(true);
} else if (curSelection.getChildCount() > 0) {
setSelectedItem(curSelection.getChild(0));
@@ -554,6 +570,13 @@
FocusPanel.impl.setAccessKey(focusable, key);
}
+ /**
+ * @see HasAnimation#setAnimationEnabled(boolean)
+ */
+ public void setAnimationEnabled(boolean enable) {
+ isAnimationEnabled = enable;
+ }
+
public void setFocus(boolean focus) {
if (focus) {
FocusPanel.impl.focus(focusable);
@@ -765,6 +788,25 @@
}
/**
+ * Get the top parent above this {@link TreeItem} that is in closed state. In
+ * other words, get the parent that is guaranteed to be visible.
+ *
+ * @param item
+ * @return the closed parent, or null if all parents are opened
+ */
+ private TreeItem getTopClosedParent(TreeItem item) {
+ TreeItem topClosedParent = null;
+ TreeItem parent = item.getParentItem();
+ while (parent != null && parent != root) {
+ if (!parent.getState()) {
+ topClosedParent = parent;
+ }
+ parent = parent.getParentItem();
+ }
+ return topClosedParent;
+ }
+
+ /**
* Move the tree focus to the specified selected item.
*
* @param selection
@@ -786,6 +828,13 @@
int width = DOM.getElementPropertyInt(selectedElem, "offsetWidth");
int height = DOM.getElementPropertyInt(selectedElem, "offsetHeight");
+ // If the item is not visible, quite here
+ if (width == 0 || height == 0) {
+ DOM.setIntStyleAttribute(focusable, "left", 0);
+ DOM.setIntStyleAttribute(focusable, "top", 0);
+ return;
+ }
+
// Set the focusable element's position and size to exactly underlap the
// item's content element.
DOM.setIntStyleAttribute(focusable, "left", left);
@@ -810,6 +859,13 @@
return;
}
+ // Find a parent that is visible
+ TreeItem topClosedParent = getTopClosedParent(sel);
+ if (topClosedParent != null) {
+ moveSelectionDown(topClosedParent, false);
+ return;
+ }
+
TreeItem parent = sel.getParentItem();
if (parent == null) {
parent = root;
@@ -831,6 +887,13 @@
* Moves the selected item up one.
*/
private void moveSelectionUp(TreeItem sel) {
+ // Find a parent that is visible
+ TreeItem topClosedParent = getTopClosedParent(sel);
+ if (topClosedParent != null) {
+ onSelection(topClosedParent, true, true);
+ return;
+ }
+
TreeItem parent = sel.getParentItem();
if (parent == null) {
parent = root;
diff --git a/user/src/com/google/gwt/user/client/ui/TreeItem.java b/user/src/com/google/gwt/user/client/ui/TreeItem.java
index cbe236d..27cad42 100644
--- a/user/src/com/google/gwt/user/client/ui/TreeItem.java
+++ b/user/src/com/google/gwt/user/client/ui/TreeItem.java
@@ -17,6 +17,7 @@
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.animation.WidgetAnimation;
import java.util.ArrayList;
import java.util.List;
@@ -30,6 +31,91 @@
* </p>
*/
public class TreeItem extends UIObject implements HasHTML {
+ /**
+ * An {@link WidgetAnimation} used to open the child elements. If a
+ * {@link TreeItem} is in the process of opening, it will immediately be
+ * opened and the new {@link TreeItem} will use this animation.
+ */
+ private static class TreeItemAnimation extends WidgetAnimation {
+ /**
+ * The {@link TreeItem} currently being affected.
+ */
+ private TreeItem curItem = null;
+
+ /**
+ * Whether the item is being opened or closed.
+ */
+ private boolean opening = true;
+
+ @Override
+ public void onCancel() {
+ onComplete();
+ }
+
+ @Override
+ public void onComplete() {
+ if (curItem != null) {
+ if (opening) {
+ UIObject.setVisible(curItem.childSpanElem, true);
+ onUpdate(1.0);
+ DOM.setStyleAttribute(curItem.childSpanElem, "height", "auto");
+ } else {
+ UIObject.setVisible(curItem.childSpanElem, false);
+ }
+ curItem = null;
+ }
+ }
+
+ @Override
+ public void onInstantaneousRun() {
+ UIObject.setVisible(curItem.childSpanElem, opening);
+ curItem = null;
+ }
+
+ @Override
+ public void onStart() {
+ onUpdate(0.0);
+ if (opening) {
+ UIObject.setVisible(curItem.childSpanElem, true);
+ }
+ }
+
+ @Override
+ public void onUpdate(double progress) {
+ int scrollHeight = DOM.getElementPropertyInt(curItem.childSpanElem,
+ "scrollHeight");
+ int height = (int) (progress * scrollHeight);
+ if (!opening) {
+ height = scrollHeight - height;
+ }
+ DOM.setStyleAttribute(curItem.childSpanElem, "height", height + "px");
+ }
+
+ /**
+ * Open the specified {@link TreeItem}.
+ *
+ * @param item the {@link TreeItem} to open
+ * @param animate true to animate, false to open instantly
+ */
+ public void setItemState(TreeItem item, boolean animate) {
+ // Immediately complete previous open
+ cancel();
+
+ // Open the new item
+ curItem = item;
+ opening = item.open;
+ if (animate) {
+ run(350);
+ } else {
+ onInstantaneousRun();
+ }
+ }
+ }
+
+ /**
+ * The static animation used to open {@link TreeItem}s.
+ */
+ private static TreeItemAnimation itemAnimation;
private ArrayList<TreeItem> children = new ArrayList<TreeItem>();
private Element itemTable, contentElem, childSpanElem;
@@ -48,17 +134,17 @@
setElement(DOM.createDiv());
itemTable = DOM.createTable();
contentElem = DOM.createSpan();
- childSpanElem = DOM.createSpan();
+ childSpanElem = DOM.createDiv();
// Uses the following Element hierarchy:
// <div (handle)>
- // <table (itemElem)>
- // <tr>
- // <td><img (imgElem)/></td>
- // <td><span (contents)/></td>
- // </tr>
- // </table>
- // <span (childSpanElem)> children </span>
+ // <table (itemElem)>
+ // <tr>
+ // <td><img (imgElem)/></td>
+ // <td><span (contents)/></td>
+ // </tr>
+ // </table>
+ // <div (childSpanElem)> children </div>
// </div>
Element tbody = DOM.createTBody(), tr = DOM.createTR();
@@ -78,6 +164,7 @@
DOM.setStyleAttribute(contentElem, "display", "inline");
DOM.setStyleAttribute(getElement(), "whiteSpace", "nowrap");
DOM.setStyleAttribute(childSpanElem, "whiteSpace", "nowrap");
+ DOM.setStyleAttribute(childSpanElem, "overflow", "hidden");
setStyleName(contentElem, "gwt-TreeItem", true);
}
@@ -137,7 +224,7 @@
item.setTree(tree);
if (children.size() == 1) {
- updateState();
+ updateState(false);
}
}
@@ -288,7 +375,7 @@
children.remove(item);
if (children.size() == 0) {
- updateState();
+ updateState(false);
}
}
@@ -341,8 +428,11 @@
return;
}
- this.open = open;
- updateState();
+ // Only do the physical update if it changes
+ if (this.open != open) {
+ this.open = open;
+ updateState(true);
+ }
if (fireEvents && tree != null) {
tree.fireStateChanged(this);
@@ -458,7 +548,7 @@
Element getImageElement() {
return statusImage.getElement();
}
-
+
void setParentItem(TreeItem parent) {
this.parent = parent;
}
@@ -484,7 +574,7 @@
for (int i = 0, n = children.size(); i < n; ++i) {
children.get(i).setTree(newTree);
}
- updateState();
+ updateState(false);
if (newTree != null) {
if (widget != null) {
@@ -494,7 +584,7 @@
}
}
- void updateState() {
+ void updateState(boolean animate) {
// If the tree hasn't been set, there is no visual state to update.
if (tree == null) {
return;
@@ -510,17 +600,25 @@
// We must use 'display' rather than 'visibility' here,
// or the children will always take up space.
+ if (itemAnimation == null) {
+ itemAnimation = new TreeItemAnimation();
+ }
+ if (animate && (tree != null) && (tree.isAttached())) {
+ itemAnimation.setItemState(this, tree.isAnimationEnabled());
+ } else {
+ itemAnimation.setItemState(this, false);
+ }
+
+ // Change the status image
if (open) {
- UIObject.setVisible(childSpanElem, true);
images.treeOpen().applyTo(statusImage);
} else {
- UIObject.setVisible(childSpanElem, false);
images.treeClosed().applyTo(statusImage);
}
}
void updateStateRecursive() {
- updateState();
+ updateState(false);
for (int i = 0, n = children.size(); i < n; ++i) {
children.get(i).updateStateRecursive();
}
diff --git a/user/src/com/google/gwt/user/client/ui/impl/PopupImpl.java b/user/src/com/google/gwt/user/client/ui/impl/PopupImpl.java
index 6565964..9af98c1 100644
--- a/user/src/com/google/gwt/user/client/ui/impl/PopupImpl.java
+++ b/user/src/com/google/gwt/user/client/ui/impl/PopupImpl.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2007 Google Inc.
+ * 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
@@ -36,7 +36,11 @@
public void onShow(Element popup) {
}
-
+
+ public void setClip(Element popup, String rect) {
+ DOM.setStyleAttribute(popup, "clip", rect);
+ }
+
public void setVisible(Element popup, boolean visible) {
}
}
diff --git a/user/src/com/google/gwt/user/client/ui/impl/PopupImplMozilla.java b/user/src/com/google/gwt/user/client/ui/impl/PopupImplMozilla.java
index ee9b98e..a11fe48 100644
--- a/user/src/com/google/gwt/user/client/ui/impl/PopupImplMozilla.java
+++ b/user/src/com/google/gwt/user/client/ui/impl/PopupImplMozilla.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2007 Google Inc.
+ * 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
@@ -16,8 +16,8 @@
package com.google.gwt.user.client.ui.impl;
import com.google.gwt.user.client.Command;
-import com.google.gwt.user.client.DeferredCommand;
import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.DeferredCommand;
import com.google.gwt.user.client.Element;
/**
@@ -97,4 +97,11 @@
public Element getContainerElement(Element outerElem) {
return isMac ? DOM.getFirstChild(outerElem) : outerElem;
}
+
+ @Override
+ public void setClip(Element popup, String rect) {
+ super.setClip(popup, rect);
+ DOM.setStyleAttribute(popup, "display", "none");
+ DOM.setStyleAttribute(popup, "display", "");
+ }
}
diff --git a/user/src/com/google/gwt/user/client/ui/menuBarSubMenuIcon.gif b/user/src/com/google/gwt/user/client/ui/menuBarSubMenuIcon.gif
new file mode 100644
index 0000000..5ec2565
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/ui/menuBarSubMenuIcon.gif
Binary files differ
diff --git a/user/test/com/google/gwt/user/WidgetAnimationTest.gwt.xml b/user/test/com/google/gwt/user/WidgetAnimationTest.gwt.xml
new file mode 100644
index 0000000..f3db27d
--- /dev/null
+++ b/user/test/com/google/gwt/user/WidgetAnimationTest.gwt.xml
@@ -0,0 +1,18 @@
+<!-- -->
+<!-- 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 -->
+<!-- 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.user.UserTest"/>
+ <set-property name="gwt.enableWidgetAnimations" value="false"/>
+</module>
diff --git a/user/test/com/google/gwt/user/client/animation/AnimationTest.java b/user/test/com/google/gwt/user/client/animation/AnimationTest.java
new file mode 100644
index 0000000..50214ef
--- /dev/null
+++ b/user/test/com/google/gwt/user/client/animation/AnimationTest.java
@@ -0,0 +1,234 @@
+/*
+ * 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.animation;
+
+import com.google.gwt.junit.client.GWTTestCase;
+import com.google.gwt.user.client.Timer;
+
+import java.util.Date;
+
+/**
+ * Tests the {@link Animation} class.
+ */
+public class AnimationTest extends GWTTestCase {
+ /**
+ * A customer {@link Animation} used for testing.
+ */
+ private static class TestAnimation extends Animation {
+ public boolean cancelled = false;
+ public boolean completed = false;
+ public boolean started = false;
+ public double curProgress = -1.0;
+
+ @Override
+ public void onCancel() {
+ cancelled = true;
+ }
+
+ @Override
+ public void onComplete() {
+ completed = true;
+ }
+
+ @Override
+ public void onStart() {
+ started = true;
+ }
+
+ @Override
+ public void onUpdate(double progress) {
+ curProgress = progress;
+ }
+
+ public void reset() {
+ cancelled = false;
+ completed = false;
+ started = false;
+ curProgress = -1.0;
+ }
+ }
+
+ @Override
+ public String getModuleName() {
+ return "com.google.gwt.user.User";
+ }
+
+ /**
+ * Test general functionality.
+ */
+ public void testRun() {
+ final TestAnimation animNow = new TestAnimation();
+ final TestAnimation animPast = new TestAnimation();
+ final TestAnimation animFuture = new TestAnimation();
+
+ // Run animations
+ long curTime = (new Date()).getTime();
+ animNow.run(300);
+ animPast.run(300, curTime - 150);
+ animFuture.run(300, curTime + 150);
+
+ // Check progress
+ new Timer() {
+ @Override
+ public void run() {
+ assertTrue(animNow.started);
+ assertFalse(animNow.completed);
+ assertTrue(animNow.curProgress > 0.0 && animNow.curProgress <= 2.0);
+
+ assertTrue(animPast.started);
+ assertFalse(animPast.completed);
+ assertTrue(animPast.curProgress > 0.0 && animPast.curProgress <= 1.0);
+
+ assertFalse(animFuture.started);
+ assertFalse(animFuture.completed);
+ assertEquals(-1.0, animFuture.curProgress);
+ }
+ }.schedule(50);
+
+ // Check progress
+ new Timer() {
+ @Override
+ public void run() {
+ assertTrue(animNow.started);
+ assertTrue(animNow.completed);
+ assertTrue(animNow.curProgress > 0.0 && animNow.curProgress <= 1.0);
+
+ assertTrue(animPast.started);
+ assertTrue(animPast.completed);
+ assertTrue(animPast.curProgress > 0.0 && animPast.curProgress <= 1.0);
+
+ assertTrue(animFuture.started);
+ assertFalse(animFuture.completed);
+ assertTrue(animFuture.curProgress > 0.0
+ && animFuture.curProgress <= 1.0);
+ finishTest();
+ }
+ }.schedule(350);
+
+ // Wait for the test to finish
+ delayTestFinish(500);
+ }
+
+ /**
+ * Test canceling an {@link Animation} before it starts.
+ */
+ public void testCancelBeforeStarted() {
+ final TestAnimation anim = new TestAnimation();
+ long curTime = (new Date()).getTime();
+ anim.run(100, curTime + 200);
+
+ // Check progress
+ new Timer() {
+ @Override
+ public void run() {
+ assertFalse(anim.started);
+ assertFalse(anim.completed);
+ assertEquals(-1.0, anim.curProgress);
+ anim.cancel();
+ assertTrue(anim.cancelled);
+ assertFalse(anim.started);
+ assertFalse(anim.completed);
+ anim.reset();
+ }
+ }.schedule(50);
+
+ // Check progress
+ new Timer() {
+ @Override
+ public void run() {
+ assertFalse(anim.started);
+ assertFalse(anim.completed);
+ assertEquals(-1.0, anim.curProgress);
+ finishTest();
+ }
+ }.schedule(100);
+
+ // Wait for test to finish
+ delayTestFinish(150);
+ }
+
+ /**
+ * Test canceling an {@link Animation} after it completes.
+ */
+ public void testCancelWhenComplete() {
+ final TestAnimation anim = new TestAnimation();
+ anim.run(100);
+
+ // Check progress
+ new Timer() {
+ @Override
+ public void run() {
+ assertTrue(anim.started);
+ assertTrue(anim.completed);
+ assertTrue(anim.curProgress > 0.0 && anim.curProgress <= 1.0);
+ anim.cancel();
+ assertFalse(anim.cancelled);
+ assertTrue(anim.completed);
+ anim.reset();
+ }
+ }.schedule(150);
+
+ // Check progress
+ new Timer() {
+ @Override
+ public void run() {
+ assertFalse(anim.started);
+ assertFalse(anim.completed);
+ assertEquals(-1.0, anim.curProgress);
+ finishTest();
+ }
+ }.schedule(200);
+
+ // Wait for test to finish
+ delayTestFinish(250);
+ }
+
+ /**
+ * Test canceling an {@link Animation} while it is running.
+ */
+ public void testCancelWhileRunning() {
+ final TestAnimation anim = new TestAnimation();
+ anim.run(500);
+
+ // Check progress
+ new Timer() {
+ @Override
+ public void run() {
+ assertTrue(anim.started);
+ assertFalse(anim.completed);
+ assertTrue(anim.curProgress > 0.0 && anim.curProgress <= 1.0);
+ anim.cancel();
+ assertTrue(anim.cancelled);
+ assertFalse(anim.completed);
+ anim.reset();
+ }
+ }.schedule(50);
+
+ // Check progress
+ new Timer() {
+ @Override
+ public void run() {
+ assertFalse(anim.started);
+ assertFalse(anim.completed);
+ assertEquals(-1.0, anim.curProgress);
+ finishTest();
+ }
+ }.schedule(150);
+
+ // Wait for test to finish
+ delayTestFinish(200);
+ }
+}
diff --git a/user/test/com/google/gwt/user/client/animation/WidgetAnimationTest.java b/user/test/com/google/gwt/user/client/animation/WidgetAnimationTest.java
new file mode 100644
index 0000000..57ea73b
--- /dev/null
+++ b/user/test/com/google/gwt/user/client/animation/WidgetAnimationTest.java
@@ -0,0 +1,93 @@
+/*
+ * 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.animation;
+
+import com.google.gwt.junit.client.GWTTestCase;
+
+import java.util.Date;
+
+/**
+ * Tests the {@link WidgetAnimation} class.
+ */
+public class WidgetAnimationTest extends GWTTestCase {
+ /**
+ * A customer {@link Animation} used for testing.
+ */
+ private static class TestWidgetAnimation extends WidgetAnimation {
+ public boolean cancelled = false;
+ public boolean completed = false;
+ public boolean started = false;
+ public boolean instaneousRun = false;
+ public double curProgress = -1.0;
+
+ @Override
+ public void onCancel() {
+ cancelled = true;
+ }
+
+ @Override
+ public void onComplete() {
+ completed = true;
+ }
+
+ @Override
+ public void onInstantaneousRun() {
+ instaneousRun = true;
+ }
+
+ @Override
+ public void onStart() {
+ started = true;
+ }
+
+ @Override
+ public void onUpdate(double progress) {
+ curProgress = progress;
+ }
+
+ public void reset() {
+ cancelled = false;
+ completed = false;
+ started = false;
+ curProgress = -1.0;
+ }
+ }
+
+ @Override
+ /**
+ * Return a module that disables widget animations.
+ */
+ public String getModuleName() {
+ return "com.google.gwt.user.WidgetAnimationTest";
+ }
+
+ /**
+ * Test general functionality.
+ */
+ public void testRun() {
+ TestWidgetAnimation anim = new TestWidgetAnimation();
+
+ // Run animations
+ long curTime = (new Date()).getTime();
+ anim.run(300);
+
+ // Check the results
+ assertFalse(anim.started);
+ assertFalse(anim.completed);
+ assertTrue(anim.instaneousRun);
+ assertEquals(-1.0, anim.curProgress);
+ }
+}
diff --git a/user/test/com/google/gwt/user/client/ui/DialogBoxTest.java b/user/test/com/google/gwt/user/client/ui/DialogBoxTest.java
index bd6a696..b740d26 100644
--- a/user/test/com/google/gwt/user/client/ui/DialogBoxTest.java
+++ b/user/test/com/google/gwt/user/client/ui/DialogBoxTest.java
@@ -74,6 +74,7 @@
public void testDebugId() {
DialogBox dBox = new DialogBox();
+ dBox.setAnimationEnabled(false);
dBox.ensureDebugId("myDialogBox");
dBox.setText("test caption");
Label content = new Label("content");
diff --git a/user/test/com/google/gwt/user/client/ui/DisclosurePanelTest.java b/user/test/com/google/gwt/user/client/ui/DisclosurePanelTest.java
index 939fed2..f7f96e9 100644
--- a/user/test/com/google/gwt/user/client/ui/DisclosurePanelTest.java
+++ b/user/test/com/google/gwt/user/client/ui/DisclosurePanelTest.java
@@ -17,6 +17,7 @@
import com.google.gwt.junit.client.GWTTestCase;
import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Timer;
/**
* Tests core functionality of {@link DisclosurePanel}.
@@ -36,13 +37,25 @@
* appropriately.
*/
public void testCoreFunctionality() {
- DisclosurePanel panel = createTestPanel();
- assertTrue(DOM.getStyleAttribute(panel.getContent().getElement(), "display").equalsIgnoreCase(
+ final DisclosurePanel panel = createTestPanel();
+ assertTrue(DOM.getStyleAttribute(
+ DOM.getParent(panel.getContent().getElement()), "display").equalsIgnoreCase(
"none"));
panel.setOpen(true);
- assertTrue(DOM.getStyleAttribute(panel.getContent().getElement(), "display").trim().equals(
- ""));
+
+ // Allow the animation time to finish
+ Timer t = new Timer() {
+ @Override
+ public void run() {
+ assertTrue(DOM.getStyleAttribute(
+ DOM.getParent(panel.getContent().getElement()), "display").trim().equals(
+ ""));
+ finishTest();
+ }
+ };
+ t.schedule(450);
+ delayTestFinish(500);
}
public void testAttachDetachOrder() {
diff --git a/user/test/com/google/gwt/user/client/ui/MenuBarTest.java b/user/test/com/google/gwt/user/client/ui/MenuBarTest.java
index acdf590..24925b8 100644
--- a/user/test/com/google/gwt/user/client/ui/MenuBarTest.java
+++ b/user/test/com/google/gwt/user/client/ui/MenuBarTest.java
@@ -106,6 +106,7 @@
// Create a menu bar
MenuBar bar = new MenuBar(false);
+ bar.setAnimationEnabled(false);
bar.setAutoOpen(true);
bar.addItem("top0", emptyCommand);
bar.addItem("top1", emptyCommand);
diff --git a/user/test/com/google/gwt/user/client/ui/PopupTest.java b/user/test/com/google/gwt/user/client/ui/PopupTest.java
index bb39827..f228733 100644
--- a/user/test/com/google/gwt/user/client/ui/PopupTest.java
+++ b/user/test/com/google/gwt/user/client/ui/PopupTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2007 Google Inc.
+ * 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
@@ -28,20 +28,35 @@
* Expose otherwise private or protected methods.
*/
private class TestablePopupPanel extends PopupPanel {
+ @Override
public Element getContainerElement() {
return super.getContainerElement();
}
}
+ @Override
public String getModuleName() {
return "com.google.gwt.user.User";
}
+ /**
+ * Test the basic accessors.
+ */
+ public void testAccessors() {
+ PopupPanel popup = new PopupPanel();
+
+ // Animation enabled
+ assertTrue(popup.isAnimationEnabled());
+ popup.setAnimationEnabled(false);
+ assertFalse(popup.isAnimationEnabled());
+ }
+
public void testPopup() {
// Get rid of window margins so we can test absolute position.
Window.setMargin("0px");
PopupPanel popup = new PopupPanel();
+ popup.setAnimationEnabled(false);
Label lbl = new Label("foo");
// Make sure that setting the popup's size & position works _before_
@@ -51,8 +66,10 @@
popup.setWidget(lbl);
popup.show();
- assertEquals(384, popup.getOffsetWidth());
- assertEquals(128, popup.getOffsetHeight());
+ // DecoratorPanel adds width and height because it wraps the content in a
+ // 3x3 table.
+ assertTrue(popup.getOffsetWidth() >= 384);
+ assertTrue(popup.getOffsetHeight() >= 128);
assertEquals(128, popup.getPopupLeft());
assertEquals(64, popup.getPopupTop());
@@ -69,8 +86,10 @@
popup.setSize("", "");
popup.setPopupPosition(16, 16);
- assertEquals(lbl.getOffsetWidth(), popup.getOffsetWidth());
- assertEquals(lbl.getOffsetHeight(), popup.getOffsetHeight());
+ // DecoratorPanel adds width and height because it wraps the content in a
+ // 3x3 table.
+ assertTrue(popup.getOffsetWidth() >= lbl.getOffsetWidth());
+ assertTrue(popup.getOffsetWidth() >= lbl.getOffsetHeight());
assertEquals(16, popup.getAbsoluteLeft());
assertEquals(16, popup.getAbsoluteTop());
diff --git a/user/test/com/google/gwt/user/client/ui/StackPanelTest.java b/user/test/com/google/gwt/user/client/ui/StackPanelTest.java
index d824d1b..d449523 100644
--- a/user/test/com/google/gwt/user/client/ui/StackPanelTest.java
+++ b/user/test/com/google/gwt/user/client/ui/StackPanelTest.java
@@ -19,6 +19,7 @@
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.DeferredCommand;
+import com.google.gwt.user.client.Element;
/**
* Tests <code>ListBox</code>. Needs many, many more tests.
@@ -54,7 +55,7 @@
}
public void testDebugId() {
- StackPanel p = new StackPanel();
+ final StackPanel p = new StackPanel();
Label a = new Label("a");
Label b = new Label("b");
Label c = new Label("c");
@@ -64,13 +65,16 @@
RootPanel.get().add(p);
p.ensureDebugId("myStack");
-
+
// Check the body ids
UIObjectTest.assertDebugId("myStack", p.getElement());
- UIObjectTest.assertDebugId("myStack-content0", DOM.getParent(a.getElement()));
- UIObjectTest.assertDebugId("myStack-content1", DOM.getParent(b.getElement()));
- UIObjectTest.assertDebugId("myStack-content2", DOM.getParent(c.getElement()));
-
+ UIObjectTest.assertDebugId("myStack-content0",
+ DOM.getParent(a.getElement()));
+ UIObjectTest.assertDebugId("myStack-content1",
+ DOM.getParent(b.getElement()));
+ UIObjectTest.assertDebugId("myStack-content2",
+ DOM.getParent(c.getElement()));
+
// Check the header IDs
DeferredCommand.addCommand(new Command() {
public void execute() {
@@ -78,6 +82,17 @@
UIObjectTest.assertDebugIdContents("myStack-text0", "header a");
UIObjectTest.assertDebugIdContents("myStack-text1", "header b");
UIObjectTest.assertDebugIdContents("myStack-text2", "header c");
+
+ Element td0 = DOM.getElementById("gwt-debug-myStack-text-wrapper0");
+ Element td1 = DOM.getElementById("gwt-debug-myStack-text-wrapper1");
+ Element td2 = DOM.getElementById("gwt-debug-myStack-text-wrapper2");
+ assertEquals(p.getElement(),
+ DOM.getParent(DOM.getParent(DOM.getParent(td0))));
+ assertEquals(p.getElement(),
+ DOM.getParent(DOM.getParent(DOM.getParent(td1))));
+ assertEquals(p.getElement(),
+ DOM.getParent(DOM.getParent(DOM.getParent(td2))));
+
finishTest();
}
});
@@ -102,7 +117,7 @@
p.showStack(2);
assertEquals(2, p.getSelectedIndex());
p.showStack(-1);
- assertEquals(-1, p.getSelectedIndex());
+ assertEquals(2, p.getSelectedIndex());
}
/**
diff --git a/user/test/com/google/gwt/user/client/ui/TabBarTest.java b/user/test/com/google/gwt/user/client/ui/TabBarTest.java
index 3f1efb6..8319e88 100644
--- a/user/test/com/google/gwt/user/client/ui/TabBarTest.java
+++ b/user/test/com/google/gwt/user/client/ui/TabBarTest.java
@@ -17,6 +17,7 @@
import com.google.gwt.junit.client.GWTTestCase;
import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
/**
* TODO: document me.
@@ -43,14 +44,14 @@
bar.ensureDebugId("myBar");
UIObjectTest.assertDebugId("myBar", bar.getElement());
UIObjectTest.assertDebugId("myBar-tab0", DOM.getParent(tab0.getElement()));
- UIObjectTest.assertDebugId("myBar-tab-wrapper0",
- DOM.getParent(DOM.getParent(tab0.getElement())));
+ UIObjectTest.assertDebugId("myBar-tab-wrapper0", getGrandParent(
+ tab0.getElement(), 6));
UIObjectTest.assertDebugId("myBar-tab1", DOM.getParent(tab1.getElement()));
- UIObjectTest.assertDebugId("myBar-tab-wrapper1",
- DOM.getParent(DOM.getParent(tab1.getElement())));
+ UIObjectTest.assertDebugId("myBar-tab-wrapper1", getGrandParent(
+ tab1.getElement(), 6));
UIObjectTest.assertDebugId("myBar-tab2", DOM.getParent(tab2.getElement()));
- UIObjectTest.assertDebugId("myBar-tab-wrapper2",
- DOM.getParent(DOM.getParent(tab2.getElement())));
+ UIObjectTest.assertDebugId("myBar-tab-wrapper2", getGrandParent(
+ tab2.getElement(), 6));
}
public void testSelect() {
@@ -106,4 +107,19 @@
bar.removeTab(1);
assertEquals("baz", bar.getTabHTML(1));
}
+
+ /**
+ * Get a specific grand parent of the Element.
+ *
+ * @param elem the element
+ * @param level the level above the element
+ * @return the grand parent
+ */
+ private Element getGrandParent(Element elem, int level) {
+ Element grandParent = elem;
+ for (int i = 0; i < level; i++) {
+ grandParent = DOM.getParent(grandParent);
+ }
+ return grandParent;
+ }
}
diff --git a/user/test/com/google/gwt/user/client/ui/TabPanelTest.java b/user/test/com/google/gwt/user/client/ui/TabPanelTest.java
index a8bdc6b..f2f0855 100644
--- a/user/test/com/google/gwt/user/client/ui/TabPanelTest.java
+++ b/user/test/com/google/gwt/user/client/ui/TabPanelTest.java
@@ -63,15 +63,15 @@
UIObjectTest.assertDebugId("myPanel-bar-tab0",
DOM.getParent(tab0.getElement()));
UIObjectTest.assertDebugId("myPanel-bar-tab-wrapper0",
- DOM.getParent(DOM.getParent(tab0.getElement())));
+ DOM.getParent(tab0.getParent().getElement()));
UIObjectTest.assertDebugId("myPanel-bar-tab1",
DOM.getParent(tab1.getElement()));
UIObjectTest.assertDebugId("myPanel-bar-tab-wrapper1",
- DOM.getParent(DOM.getParent(tab1.getElement())));
+ DOM.getParent(tab1.getParent().getElement()));
UIObjectTest.assertDebugId("myPanel-bar-tab2",
DOM.getParent(tab2.getElement()));
UIObjectTest.assertDebugId("myPanel-bar-tab-wrapper2",
- DOM.getParent(DOM.getParent(tab2.getElement())));
+ DOM.getParent(tab2.getParent().getElement()));
}
public void testInsertMultipleTimes() {