Bug 701594 - Part 6: Separate out fling and bounce animations and make them use the "valid viewport metrics" infrastructure. r=kats
authorPatrick Walton <pwalton@mozilla.com>
Wed, 07 Dec 2011 13:33:55 -0800
changeset 83851 d1466b26c89182580134e4aa854bfdbc336e634e
parent 83850 331e25310bf790a5b5262c0aa59247c2ad1aa448
child 83852 1a233691d283b418a2ae602a02bca0fff3802667
push id519
push userakeybl@mozilla.com
push dateWed, 01 Feb 2012 00:38:35 +0000
treeherdermozilla-beta@788ea1ef610b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskats
bugs701594
milestone11.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 701594 - Part 6: Separate out fling and bounce animations and make them use the "valid viewport metrics" infrastructure. r=kats
mobile/android/base/ui/PanZoomController.java
--- a/mobile/android/base/ui/PanZoomController.java
+++ b/mobile/android/base/ui/PanZoomController.java
@@ -128,29 +128,39 @@ public class PanZoomController
         FLING,          /* all touches removed, but we're still scrolling page */
         TOUCHING,       /* one touch-start event received */
         PANNING_LOCKED, /* touch-start followed by move (i.e. panning with axis lock) */
         PANNING,        /* panning without axis lock */
         PANNING_HOLD,   /* in panning, but not moving.
                          * similar to TOUCHING but after starting a pan */
         PANNING_HOLD_LOCKED, /* like PANNING_HOLD, but axis lock still in effect */
         PINCHING,       /* nth touch-start, where n > 1. this mode allows pan and zoom */
-        ANIMATED_ZOOM   /* animated zoom to a new rect */
+        ANIMATED_ZOOM,  /* animated zoom to a new rect */
+        BOUNCING,       /* bouncing back */
     }
 
     private PanZoomState mState;
 
     private boolean mOverridePanning;
     private boolean mOverrideScrollAck;
     private boolean mOverrideScrollPending;
 
+    /* The current frame of the bounce-back animation, or -1 if the animation is not running. */
+    private int mBounceFrame;
+    /*
+     * The viewport metrics that represent the start and end of the bounce-back animation,
+     * respectively.
+     */
+    private ViewportMetrics mBounceStartMetrics, mBounceEndMetrics;
+
     public PanZoomController(LayerController controller) {
         mController = controller;
         mX = new AxisX(); mY = new AxisY();
         mState = PanZoomState.NOTHING;
+        mBounceFrame = -1;
 
         GeckoAppShell.registerGeckoEventListener("Browser:ZoomToRect", this);
         GeckoAppShell.registerGeckoEventListener("Browser:ZoomToPageWidth", this);
         GeckoAppShell.registerGeckoEventListener("Panning:Override", this);
         GeckoAppShell.registerGeckoEventListener("Panning:CancelOverride", this);
         GeckoAppShell.registerGeckoEventListener("Gesture:ScrollAck", this);
     }
 
@@ -234,17 +244,17 @@ public class PanZoomController
             // any fling that's in progress and re-fling so that the page snaps to edges. for
             // other cases (where the user's finger(s) are down) don't do anything special.
             switch (mState) {
             case FLING:
                 mX.velocity = mY.velocity = 0.0f;
                 mState = PanZoomState.NOTHING;
                 // fall through
             case NOTHING:
-                fling();
+                bounce();
                 break;
             }
         }
     }
 
     /*
      * Panning/scrolling
      */
@@ -320,17 +330,17 @@ public class PanZoomController
             // should never happen
             Log.e(LOGTAG, "Received impossible touch end while in " + mState);
             return false;
         case TOUCHING:
             mState = PanZoomState.NOTHING;
             // the switch into TOUCHING might have happened while the page was
             // snapping back after overscroll. we need to finish the snap if that
             // was the case
-            fling();
+            bounce();
             return false;
         case PANNING:
         case PANNING_LOCKED:
         case PANNING_HOLD:
         case PANNING_HOLD_LOCKED:
             mState = PanZoomState.FLING;
             fling();
             return true;
@@ -354,17 +364,17 @@ public class PanZoomController
         }
         Log.e(LOGTAG, "Unhandled case " + mState + " in onTouchEnd");
         return false;
     }
 
     private boolean onTouchCancel(MotionEvent event) {
         mState = PanZoomState.NOTHING;
         // ensure we snap back if we're overscrolled
-        fling();
+        bounce();
         return false;
     }
 
     private float panDistance(MotionEvent move) {
         float dx = mX.firstTouchPos - move.getX(0);
         float dy = mY.firstTouchPos - move.getY(0);
         return (float)Math.sqrt(dx * dx + dy * dy);
     }
@@ -478,16 +488,34 @@ public class PanZoomController
         stopAnimationTimer();
 
         boolean stopped = stopped();
         mX.startFling(stopped); mY.startFling(stopped);
 
         startAnimationTimer(new FlingRunnable());
     }
 
+    /* Performs a bounce-back animation to the given viewport metrics. */
+    private void bounce(ViewportMetrics metrics) {
+        stopAnimationTimer();
+
+        mBounceFrame = 0;
+        mState = PanZoomState.FLING;
+        mX.setFlingState(Axis.FlingStates.SNAPPING); mY.setFlingState(Axis.FlingStates.SNAPPING);
+        mBounceStartMetrics = mController.getViewportMetrics();
+        mBounceEndMetrics = metrics;
+
+        startAnimationTimer(new BounceRunnable());
+    }
+
+    /* Performs a bounce-back animation to the nearest valid viewport metrics. */
+    private void bounce() {
+        bounce(getValidViewportMetrics());
+    }
+
     /* Starts the fling or bounce animation. */
     private void startAnimationTimer(final Runnable runnable) {
         if (mAnimationTimer != null) {
             Log.e(LOGTAG, "Attempted to start a new fling without canceling the old one!");
             stopAnimationTimer();
         }
 
         mAnimationTimer = new Timer();
@@ -533,57 +561,105 @@ public class PanZoomController
             mOverrideScrollAck = false;
         } else {
             mController.scrollBy(new PointF(mX.displacement, mY.displacement));
         }
 
         mX.displacement = mY.displacement = 0;
     }
 
+    /* The callback that performs the bounce animation. */
+    private class BounceRunnable implements Runnable {
+        public void run() {
+            /*
+             * The pan/zoom controller might have signaled to us that it wants to abort the
+             * animation by setting the state to PanZoomState.NOTHING. Handle this case and bail
+             * out.
+             */
+            if (mState != PanZoomState.FLING) {
+                finishAnimation();
+                return;
+            }
+
+            /* Perform the next frame of the bounce-back animation. */
+            if (mBounceFrame < EASE_OUT_ANIMATION_FRAMES.length) {
+                advanceBounce();
+                return;
+            }
+
+            /* Finally, if there's nothing else to do, complete the animation and go to sleep. */
+            finishBounce();
+            finishAnimation();
+        }
+
+        /* Performs one frame of a bounce animation. */
+        private void advanceBounce() {
+            float t = EASE_OUT_ANIMATION_FRAMES[mBounceFrame];
+            ViewportMetrics newMetrics = mBounceStartMetrics.interpolate(mBounceEndMetrics, t);
+            mController.setViewportMetrics(newMetrics);
+            mController.notifyLayerClientOfGeometryChange();
+            mBounceFrame++;
+        }
+
+        /* Concludes a bounce animation and snaps the viewport into place. */
+        private void finishBounce() {
+            mController.setViewportMetrics(mBounceEndMetrics);
+            mController.notifyLayerClientOfGeometryChange();
+            mBounceFrame = -1;
+        }
+    }
+
     // The callback that performs the fling animation.
     private class FlingRunnable implements Runnable {
         public void run() {
-            mX.advanceFling(); mY.advanceFling();
-
-            if (!mOverridePanning) {
-                // If both X and Y axes are overscrolled, we have to wait until both axes have stopped
-                // to snap back to avoid a jarring effect.
-                boolean waitingToSnapX = mX.getFlingState() == Axis.FlingStates.WAITING_TO_SNAP;
-                boolean waitingToSnapY = mY.getFlingState() == Axis.FlingStates.WAITING_TO_SNAP;
-                if ((mX.getOverscroll() == Axis.Overscroll.PLUS || mX.getOverscroll() == Axis.Overscroll.MINUS) &&
-                    (mY.getOverscroll() == Axis.Overscroll.PLUS || mY.getOverscroll() == Axis.Overscroll.MINUS))
-                {
-                    if (waitingToSnapX && waitingToSnapY) {
-                        mX.startSnap(); mY.startSnap();
-                    }
-                } else {
-                    if (waitingToSnapX)
-                        mX.startSnap();
-                    if (waitingToSnapY)
-                        mY.startSnap();
-                }
+            /*
+             * The pan/zoom controller might have signaled to us that it wants to abort the
+             * animation by setting the state to PanZoomState.NOTHING. Handle this case and bail
+             * out.
+             */
+            if (mState != PanZoomState.FLING) {
+                finishAnimation();
+                return;
             }
 
-            mX.displace(); mY.displace();
-            updatePosition();
+            /* Advance flings, if necessary. */
+            boolean flingingX = mX.getFlingState() == Axis.FlingStates.FLINGING;
+            boolean flingingY = mY.getFlingState() == Axis.FlingStates.FLINGING;
+            if (flingingX)
+                mX.advanceFling();
+            if (flingingY)
+                mY.advanceFling();
 
-            if (mX.getFlingState() == Axis.FlingStates.STOPPED &&
-                    mY.getFlingState() == Axis.FlingStates.STOPPED) {
-                stop();
+            /* If we're still flinging in any direction, update the origin and finish here. */
+            if (flingingX || flingingY) {
+                mX.displace(); mY.displace();
+                updatePosition();
+                return;
             }
-        }
 
-        private void stop() {
-            mState = PanZoomState.NOTHING;
-            stopAnimationTimer();
+            /*
+             * Perform a bounce-back animation if overscrolled, unless panning is being overridden
+             * (which happens e.g. when the user is panning an iframe).
+             */
+            boolean overscrolledX = mX.getOverscroll() != Axis.Overscroll.NONE;
+            boolean overscrolledY = mY.getOverscroll() != Axis.Overscroll.NONE;
+            if (!mOverridePanning && (overscrolledX || overscrolledY))
+                bounce();
+            else
+                finishAnimation();
+        }
+    }
 
-            // Force a viewport synchronisation
-            mController.setForceRedraw();
-            mController.notifyLayerClientOfGeometryChange();
-        }
+    private void finishAnimation() {
+        mState = PanZoomState.NOTHING;
+        stopAnimationTimer();
+
+        // Force a viewport synchronisation
+        mController.setForceRedraw();
+        mController.notifyLayerClientOfGeometryChange();
     }
 
     private float computeElasticity(float excess, float viewportLength) {
         return 1.0f - excess / (viewportLength * SNAP_LIMIT);
     }
 
     // Physics information for one axis (X or Y).
     private abstract static class Axis {