Revision: 9970
Author: jlaba...@google.com
Date: Mon Apr 11 07:56:01 2011
Log: Adds {moz,webkit}RequestAnimationFrame support to animations.
Refactor Animation with different implementations, adding
mozRequestAnimationFrame and webkitRequestAnimationFrame support in
addition to
the timer-based implementation.
ALso adds run() overloads taking an 'element' argument, that
visually "scopes"
the animation (so that browsers can, as an optimization, skip steps when the
element is not visible to the user).
Code Review: http://gwt-code-reviews.appspot.com/1355805/
Author: tbroyer
Review by: jlabanca
http://code.google.com/p/google-web-toolkit/source/detail?r=9970
Added:
/trunk/user/src/com/google/gwt/animation/client/AnimationImpl.java
/trunk/user/src/com/google/gwt/animation/client/AnimationImplMozAnimTiming.java
/trunk/user/src/com/google/gwt/animation/client/AnimationImplTimer.java
/trunk/user/src/com/google/gwt/animation/client/AnimationImplWebkitAnimTiming.java
Modified:
/trunk/user/src/com/google/gwt/animation/Animation.gwt.xml
/trunk/user/src/com/google/gwt/animation/client/Animation.java
/trunk/user/src/com/google/gwt/layout/client/Layout.java
/trunk/user/src/com/google/gwt/user/cellview/client/CellBrowser.java
/trunk/user/src/com/google/gwt/user/client/ui/DeckPanel.java
=======================================
--- /dev/null
+++ /trunk/user/src/com/google/gwt/animation/client/AnimationImpl.java Mon
Apr 11 07:56:01 2011
@@ -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);
+ }
+}
=======================================
--- /dev/null
+++
/trunk/user/src/com/google/gwt/animation/client/AnimationImplMozAnimTiming.java
Mon Apr 11 07:56:01 2011
@@ -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 =
th...@com.google.gwt.animation.client.AnimationImplMozAnimTiming::handle;
+ var callback = $entry(function(time) {
+ if (handle !=
se...@com.google.gwt.animation.client.AnimationImplMozAnimTiming::handle) {
+ return; // cancelled
+ }
+ var complete =
se...@com.google.gwt.animation.client.AnimationImpl::updateAnimation(Lcom/google/gwt/animation/client/Animation;D)(animation,
time);
+ if (!complete) {
+ $wnd.mozRequestAnimationFrame(callback);
+ }
+ });
+
+ $wnd.mozRequestAnimationFrame(callback);
+ }-*/;
+}
=======================================
--- /dev/null
+++ /trunk/user/src/com/google/gwt/animation/client/AnimationImplTimer.java
Mon Apr 11 07:56:01 2011
@@ -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);
+ }
+ }
+}
=======================================
--- /dev/null
+++
/trunk/user/src/com/google/gwt/animation/client/AnimationImplWebkitAnimTiming.java
Mon Apr 11 07:56:01 2011
@@ -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 =
se...@com.google.gwt.animation.client.AnimationImpl::updateAnimation(Lcom/google/gwt/animation/client/Animation;D)(animation,
time);
+ if (!complete) {
+
se...@com.google.gwt.animation.client.AnimationImplWebkitAnimTiming::handle
= $wnd.webkitRequestAnimationFrame(callback, element);
+ }
+ });
+
+ return $wnd.webkitRequestAnimationFrame(callback, element);
+ }-*/;
+}
=======================================
--- /trunk/user/src/com/google/gwt/animation/Animation.gwt.xml Tue Apr 29
12:33:26 2008
+++ /trunk/user/src/com/google/gwt/animation/Animation.gwt.xml Mon Apr 11
07:56:01 2011
@@ -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>
=======================================
--- /trunk/user/src/com/google/gwt/animation/client/Animation.java Tue Oct
12 07:55:56 2010
+++ /trunk/user/src/com/google/gwt/animation/client/Animation.java Mon Apr
11 07:56:01 2011
@@ -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);
}
/**
@@ -206,6 +190,14 @@
* @param progress a double, normally between 0.0 and 1.0 (inclusive)
*/
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}.
@@ -213,7 +205,7 @@
* @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.
=======================================
--- /trunk/user/src/com/google/gwt/layout/client/Layout.java Tue Feb 8
07:19:49 2011
+++ /trunk/user/src/com/google/gwt/layout/client/Layout.java Mon Apr 11
07:56:01 2011
@@ -578,7 +578,7 @@
}
};
- animation.run(duration);
+ animation.run(duration, parentElem);
}
/**
=======================================
--- /trunk/user/src/com/google/gwt/user/cellview/client/CellBrowser.java
Wed Dec 1 05:40:20 2010
+++ /trunk/user/src/com/google/gwt/user/cellview/client/CellBrowser.java
Mon Apr 11 07:56:01 2011
@@ -713,7 +713,7 @@
if (isAnimationEnabled()) {
// Animate the scrolling.
startScrollLeft = elem.getScrollLeft();
- run(250);
+ run(250, elem);
} else {
// Scroll instantly.
onComplete();
=======================================
--- /trunk/user/src/com/google/gwt/user/client/ui/DeckPanel.java Mon Sep 13
10:28:49 2010
+++ /trunk/user/src/com/google/gwt/user/client/ui/DeckPanel.java Mon Apr 11
07:56:01 2011
@@ -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");
--
http://groups.google.com/group/Google-Web-Toolkit-Contributors