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

Reply via email to