Resubmtiting r9970. Review at http://gwt-code-reviews.appspot.com/1355805/ Review by: fabbott@google.com git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@9985 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/animation/Animation.gwt.xml b/user/src/com/google/gwt/animation/Animation.gwt.xml index a34f63d..bdc4b15 100644 --- a/user/src/com/google/gwt/animation/Animation.gwt.xml +++ b/user/src/com/google/gwt/animation/Animation.gwt.xml
@@ -18,7 +18,48 @@ <!-- --> <module> <inherits name="com.google.gwt.core.Core"/> - - <!-- Include User module to inherit Timer --> - <inherits name="com.google.gwt.user.User"/> + <inherits name="com.google.gwt.dom.DOM"/> + <inherits name="com.google.gwt.user.UserAgent"/> + + <define-property name="animationTimingSupport" values="no,moz,webkit"/> + <collapse-property name="animationTimingSupport" values="*"/> + <property-provider name="animationTimingSupport"><![CDATA[ + if ($wnd.webkitRequestAnimationFrame && $wnd.webkitCancelRequestAnimationFrame) { + return "webkit"; + } + if ($wnd.mozRequestAnimationFrame) { + return "moz"; + } + return "no"; + ]]></property-provider> + + <set-property name="animationTimingSupport" value="no"> + <any> + <when-property-is name="user.agent" value="ie6" /> + <when-property-is name="user.agent" value="ie8" /> + <when-property-is name="user.agent" value="ie9" /> + <when-property-is name="user.agent" value="opera" /> + </any> + </set-property> + + <!-- Fallback implementation, based on a timer --> + <replace-with class="com.google.gwt.animation.client.AnimationImplTimer"> + <when-type-is class="com.google.gwt.animation.client.AnimationImpl"/> + </replace-with> + + <!-- Implementation based on mozRequestAnimationFrame --> + <!-- Only used in Firefox when support has been detected --> + <replace-with class="com.google.gwt.animation.client.AnimationImplMozAnimTiming"> + <when-type-is class="com.google.gwt.animation.client.AnimationImpl"/> + <when-property-is name="animationTimingSupport" value="moz" /> + <when-property-is name="user.agent" value="gecko1_8"/> + </replace-with> + + <!-- Implementation based on webkitRequestAnimationFrame --> + <!-- Only used in WebKit when support has been detected --> + <replace-with class="com.google.gwt.animation.client.AnimationImplWebkitAnimTiming"> + <when-type-is class="com.google.gwt.animation.client.AnimationImpl"/> + <when-property-is name="animationTimingSupport" value="webkit" /> + <when-property-is name="user.agent" value="safari"/> + </replace-with> </module>
diff --git a/user/src/com/google/gwt/animation/client/Animation.java b/user/src/com/google/gwt/animation/client/Animation.java index f4225b2..194a587 100644 --- a/user/src/com/google/gwt/animation/client/Animation.java +++ b/user/src/com/google/gwt/animation/client/Animation.java
@@ -16,55 +16,16 @@ package com.google.gwt.animation.client; import com.google.gwt.core.client.Duration; -import com.google.gwt.user.client.Timer; - -import java.util.ArrayList; -import java.util.List; +import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.Element; /** * An {@link Animation} is a continuous event that updates progressively over * time at a non-fixed frame rate. */ public abstract class Animation { - /** - * The default time in milliseconds between frames. - */ - private static final int DEFAULT_FRAME_DELAY = 25; - /** - * The {@link Animation Animations} that are currently in progress. - */ - private static List<Animation> animations = null; - - /** - * The {@link Timer} that applies the animations. - */ - private static Timer animationTimer = null; - - /** - * Update all {@link Animation Animations}. - */ - private static void updateAnimations() { - // Duplicate the animations list in case it changes as we iterate over it - Animation[] curAnimations = new Animation[animations.size()]; - curAnimations = animations.toArray(curAnimations); - - // Iterator through the animations - double curTime = Duration.currentTimeMillis(); - for (Animation animation : curAnimations) { - if (animation.running && animation.update(curTime)) { - // We can't just remove the animation at the index, because calling - // animation.update may have the side effect of canceling this - // animation, running new animations, or canceling other animations. - animations.remove(animation); - } - } - - // Reschedule the timer - if (animations.size() > 0) { - animationTimer.schedule(DEFAULT_FRAME_DELAY); - } - } + private AnimationImpl impl = GWT.create(AnimationImpl.class); /** * The duration of the {@link Animation} in milliseconds. @@ -97,7 +58,7 @@ return; } - animations.remove(this); + impl.cancel(this); onCancel(); started = false; running = false; @@ -106,22 +67,64 @@ /** * Immediately run this animation. If the animation is already running, it * will be canceled first. + * <p> + * This is equivalent to <code>run(duration, null)</code>. * * @param duration the duration of the animation in milliseconds + * @see #run(int, Element) */ public void run(int duration) { - run(duration, Duration.currentTimeMillis()); + run(duration, null); + } + + /** + * Immediately run this animation. If the animation is already running, it + * will be canceled first. + * <p> + * If the element is not <code>null</code>, the {@link #onUpdate(double)} + * method might be called only if the element may be visible (generally left + * at the appreciation of the browser). Otherwise, it will be called + * unconditionally. + * + * @param duration the duration of the animation in milliseconds + * @param element the element that visually bounds the entire animation + */ + public void run(int duration, Element element) { + run(duration, Duration.currentTimeMillis(), element); } /** * 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. + * passed, the animation will run synchronously as if it started at the + * specified start time. If the animation is already running, it will be + * canceled first. + * <p> + * This is equivalent to <code>run(duration, startTime, null)</code>. * * @param duration the duration of the animation in milliseconds * @param startTime the synchronized start time in milliseconds + * @see #run(int, double, Element) */ public void run(int duration, double startTime) { + run(duration, startTime, null); + } + + /** + * Run this animation at the given startTime. If the startTime has already + * passed, the animation will run synchronously as if it started at the + * specified start time. If the animation is already running, it will be + * canceled first. + * <p> + * If the element is not <code>null</code>, the {@link #onUpdate(double)} + * method might be called only if the element may be visible (generally left + * at the appreciation of the browser). Otherwise, it will be called + * unconditionally. + * + * @param duration the duration of the animation in milliseconds + * @param startTime the synchronized start time in milliseconds + * @param element the element that visually bounds the entire animation + */ + public void run(int duration, double startTime, Element element) { // Cancel the animation if it is running cancel(); @@ -135,26 +138,7 @@ return; } - // Add to the list of animations - - // We use a static list of animations and a single timer, and create them - // only if we are the only active animation. This is safe since JS is - // single-threaded. - if (animations == null) { - animations = new ArrayList<Animation>(); - animationTimer = new Timer() { - @Override - public void run() { - updateAnimations(); - } - }; - } - animations.add(this); - - // Restart the timer if there is the only animation - if (animations.size() == 1) { - animationTimer.schedule(DEFAULT_FRAME_DELAY); - } + impl.run(this, element); } /** @@ -208,12 +192,20 @@ protected abstract void onUpdate(double progress); /** + * Is the {@link Animation} running, even if {@link #onStart()} has not yet + * been called. + */ + boolean isRunning() { + return running; + } + + /** * Update the {@link Animation}. * * @param curTime the current time * @return true if the animation is complete, false if still running */ - private boolean update(double curTime) { + boolean update(double curTime) { boolean finished = curTime >= startTime + duration; if (started && !finished) { // Animation is in progress.
diff --git a/user/src/com/google/gwt/animation/client/AnimationImpl.java b/user/src/com/google/gwt/animation/client/AnimationImpl.java new file mode 100644 index 0000000..6bd09dd --- /dev/null +++ b/user/src/com/google/gwt/animation/client/AnimationImpl.java
@@ -0,0 +1,45 @@ +/* + * Copyright 2011 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.animation.client; + +import com.google.gwt.dom.client.Element; + +/** + * Base class for animation implementations. + */ +abstract class AnimationImpl { + + /** + * Cancel the animation. + */ + public abstract void cancel(Animation animation); + + /** + * Run the animation with an optional bounding element. + */ + public abstract void run(Animation animation, Element element); + + /** + * Update the {@link Animation}. + * + * @param animation the {@link Animation} + * @param curTime the current time + * @return true if the animation is complete, false if still running + */ + protected final boolean updateAnimation(Animation animation, double curTime) { + return animation.isRunning() && animation.update(curTime); + } +}
diff --git a/user/src/com/google/gwt/animation/client/AnimationImplMozAnimTiming.java b/user/src/com/google/gwt/animation/client/AnimationImplMozAnimTiming.java new file mode 100644 index 0000000..aea7b73 --- /dev/null +++ b/user/src/com/google/gwt/animation/client/AnimationImplMozAnimTiming.java
@@ -0,0 +1,56 @@ +/* + * Copyright 2011 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.animation.client; + +import com.google.gwt.dom.client.Element; + +/** + * Implementation using <code>mozRequestAnimationFrame</code>. + * + * @see <a href="https://developer.mozilla.org/en/DOM/window.mozRequestAnimationFrame"> + * Documentation on the MDN</a> + */ +class AnimationImplMozAnimTiming extends AnimationImpl { + + private int handle; + + @Override + public void cancel(Animation animation) { + handle++; + } + + @Override + public void run(Animation animation, Element element) { + handle++; + nativeRun(animation); + } + + private native void nativeRun(Animation animation) /*-{ + var self = this; + var handle = this.@com.google.gwt.animation.client.AnimationImplMozAnimTiming::handle; + var callback = $entry(function(time) { + if (handle != self.@com.google.gwt.animation.client.AnimationImplMozAnimTiming::handle) { + return; // cancelled + } + var complete = self.@com.google.gwt.animation.client.AnimationImpl::updateAnimation(Lcom/google/gwt/animation/client/Animation;D)(animation, time); + if (!complete) { + $wnd.mozRequestAnimationFrame(callback); + } + }); + + $wnd.mozRequestAnimationFrame(callback); + }-*/; +}
diff --git a/user/src/com/google/gwt/animation/client/AnimationImplTimer.java b/user/src/com/google/gwt/animation/client/AnimationImplTimer.java new file mode 100644 index 0000000..129f951 --- /dev/null +++ b/user/src/com/google/gwt/animation/client/AnimationImplTimer.java
@@ -0,0 +1,87 @@ +/* + * Copyright 2011 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.animation.client; + +import com.google.gwt.core.client.Duration; +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.dom.client.Element; + +import java.util.ArrayList; +import java.util.List; + +/** + * Implementation using a timer. + */ +class AnimationImplTimer extends AnimationImpl { + + /** + * The default time in milliseconds between frames. + */ + private static final int DEFAULT_FRAME_DELAY = 25; + + /** + * The {@link Scheduler.RepeatingCommand} that applies the animations. + */ + private static Scheduler.RepeatingCommand animationCommand; + + /** + * The {@link Animation Animations} that are currently in progress. + */ + private static List<Animation> animations = null; + + @Override + public void cancel(Animation animation) { + animations.remove(animation); + } + + @Override + public void run(Animation animation, Element element) { + // Add to the list of animations + + // We use a static list of animations and a single timer, and create them + // only if we are the only active animation. This is safe since JS is + // single-threaded. + if (animations == null) { + animations = new ArrayList<Animation>(); + animationCommand = new Scheduler.RepeatingCommand() { + public boolean execute() { + // Duplicate the animations list in case it changes as we iterate over it + Animation[] curAnimations = new Animation[animations.size()]; + curAnimations = animations.toArray(curAnimations); + + // Iterate through the animations + double curTime = Duration.currentTimeMillis(); + for (Animation animation : curAnimations) { + if (updateAnimation(animation, curTime)) { + // We can't just remove the animation at the index, because calling + // animation.update may have the side effect of canceling this + // animation, running new animations, or canceling other animations. + animations.remove(animation); + } + } + + return (animations.size() > 0); + } + }; + } + animations.add(animation); + + // Restart the timer if this is the only animation + if (animations.size() == 1) { + Scheduler.get().scheduleFixedDelay(animationCommand, DEFAULT_FRAME_DELAY); + } + } +}
diff --git a/user/src/com/google/gwt/animation/client/AnimationImplWebkitAnimTiming.java b/user/src/com/google/gwt/animation/client/AnimationImplWebkitAnimTiming.java new file mode 100644 index 0000000..0f92b2b --- /dev/null +++ b/user/src/com/google/gwt/animation/client/AnimationImplWebkitAnimTiming.java
@@ -0,0 +1,61 @@ +/* + * Copyright 2011 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.animation.client; + +import com.google.gwt.dom.client.Element; + +/** + * Implementation using <code>webkitRequestAnimationFrame</code> and + * <code>webkitCancelRequestAnimationFrame</code>. + * + * @see <a + * href="http://www.chromium.org/developers/web-platform-status#TOC-requestAnimationFrame"> + * Chromium Web Platform Status</a> + * @see <a href="http://webstuff.nfshost.com/anim-timing/Overview.html"> + * webkit draft spec</a> + */ +class AnimationImplWebkitAnimTiming extends AnimationImpl { + + private static native void cancel(double handle) /*-{ + $wnd.webkitCancelRequestAnimationFrame(handle); + }-*/; + + private double handle; + + @Override + public void cancel(Animation animation) { + cancel(handle); + } + + @Override + public void run(Animation animation, Element element) { + handle = nativeRun(animation, element); + } + + private native double nativeRun(Animation animation, Element element) /*-{ + var self = this; + var callback = $entry(function(time) { + // Chrome 10 does not pass the 'time' argument, so we fake it. + time = time || @com.google.gwt.core.client.Duration::currentTimeMillis()(); + var complete = self.@com.google.gwt.animation.client.AnimationImpl::updateAnimation(Lcom/google/gwt/animation/client/Animation;D)(animation, time); + if (!complete) { + self.@com.google.gwt.animation.client.AnimationImplWebkitAnimTiming::handle = $wnd.webkitRequestAnimationFrame(callback, element); + } + }); + + return $wnd.webkitRequestAnimationFrame(callback, element); + }-*/; +}
diff --git a/user/src/com/google/gwt/layout/client/Layout.java b/user/src/com/google/gwt/layout/client/Layout.java index df84b04..218432a 100644 --- a/user/src/com/google/gwt/layout/client/Layout.java +++ b/user/src/com/google/gwt/layout/client/Layout.java
@@ -578,7 +578,7 @@ } }; - animation.run(duration); + animation.run(duration, parentElem); } /**
diff --git a/user/src/com/google/gwt/user/cellview/client/CellBrowser.java b/user/src/com/google/gwt/user/cellview/client/CellBrowser.java index 26e53ee..f0d25cd 100644 --- a/user/src/com/google/gwt/user/cellview/client/CellBrowser.java +++ b/user/src/com/google/gwt/user/cellview/client/CellBrowser.java
@@ -712,7 +712,7 @@ if (isAnimationEnabled()) { // Animate the scrolling. startScrollLeft = elem.getScrollLeft(); - run(250); + run(250, elem); } else { // Scroll instantly. onComplete();
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 b0fd606..393a1ed 100644 --- a/user/src/com/google/gwt/user/client/ui/DeckPanel.java +++ b/user/src/com/google/gwt/user/client/ui/DeckPanel.java
@@ -105,7 +105,25 @@ // Start the animation if (animate) { - run(ANIMATION_DURATION); + // Figure out if the deck panel has a fixed height + com.google.gwt.dom.client.Element deckElem = container1.getParentElement(); + int deckHeight = deckElem.getOffsetHeight(); + if (growing) { + fixedHeight = container2.getOffsetHeight(); + container2.getStyle().setPropertyPx("height", + Math.max(1, fixedHeight - 1)); + } else { + fixedHeight = container1.getOffsetHeight(); + container1.getStyle().setPropertyPx("height", + Math.max(1, fixedHeight - 1)); + } + if (deckElem.getOffsetHeight() != deckHeight) { + fixedHeight = -1; + } + + // Only scope to the deck if it's fixed height, otherwise it can affect + // the rest of the page, even if it's not visible to the user. + run(ANIMATION_DURATION, fixedHeight == -1 ? null : deckElem); } else { onInstantaneousRun(); } @@ -139,22 +157,6 @@ @Override protected void onStart() { - // Figure out if the deck panel has a fixed height - com.google.gwt.dom.client.Element deckElem = container1.getParentElement(); - int deckHeight = deckElem.getOffsetHeight(); - if (growing) { - fixedHeight = container2.getOffsetHeight(); - container2.getStyle().setPropertyPx("height", - Math.max(1, fixedHeight - 1)); - } else { - fixedHeight = container1.getOffsetHeight(); - container1.getStyle().setPropertyPx("height", - Math.max(1, fixedHeight - 1)); - } - if (deckElem.getOffsetHeight() != deckHeight) { - fixedHeight = -1; - } - // Start the animation DOM.setStyleAttribute(container1, "overflow", "hidden"); DOM.setStyleAttribute(container2, "overflow", "hidden");