Bug 798539 - Run the animation timer faster to improve panning smoothness. r=blassey,kats
authorBenoit Girard <b56girard@gmail.com>
Thu, 11 Oct 2012 12:58:53 -0400
changeset 110095 c58bfc3f6b488c1195d013b7fc9405f8453e9172
parent 110094 5b26e3ffe80d70e02dd3dccad3185dc177d10dac
child 110096 32d7a71b3e3a1c76413db7acd269e54f4a14f6b1
push id93
push usernmatsakis@mozilla.com
push dateWed, 31 Oct 2012 21:26:57 +0000
reviewersblassey, kats
bugs798539
milestone19.0a1
Bug 798539 - Run the animation timer faster to improve panning smoothness. r=blassey,kats
mobile/android/app/mobile.js
mobile/android/base/ui/Axis.java
mobile/android/base/ui/PanZoomController.java
--- a/mobile/android/app/mobile.js
+++ b/mobile/android/app/mobile.js
@@ -599,32 +599,26 @@ pref("direct-texture.force.disabled", fa
 
 // show checkerboard pattern on android; we use background colour instead
 pref("gfx.show_checkerboard_pattern", true);
 
 // This fraction in 1000ths of velocity remains after every animation frame when the velocity is low.
 pref("ui.scrolling.friction_slow", -1);
 // This fraction in 1000ths of velocity remains after every animation frame when the velocity is high.
 pref("ui.scrolling.friction_fast", -1);
-// Below this velocity (in pixels per frame), the friction starts increasing from friction_fast
-// to friction_slow.
-pref("ui.scrolling.velocity_threshold", -1);
 // The maximum velocity change factor between events, per ms, in 1000ths.
 // Direction changes are excluded.
 pref("ui.scrolling.max_event_acceleration", -1);
 // The rate of deceleration when the surface has overscrolled, in 1000ths.
 pref("ui.scrolling.overscroll_decel_rate", -1);
 // The fraction of the surface which can be overscrolled before it must snap back, in 1000ths.
 pref("ui.scrolling.overscroll_snap_limit", -1);
 // The minimum amount of space that must be present for an axis to be considered scrollable,
 // in 1/1000ths of pixels.
 pref("ui.scrolling.min_scrollable_distance", -1);
-// A comma-separated list of float values in the range [0.0, 1.0) that are used as
-// interpolation frames for zoom animations.
-pref("ui.zooming.animation_frames", "");
 
 // Enable accessibility mode if platform accessibility is enabled.
 pref("accessibility.accessfu.activate", 2);
 // Enable explore by touch if it is enabled in the platform
 pref("accessibility.accessfu.explorebytouch", 2);
 
 // Mobile manages state by autodetection
 pref("network.manage-offline-status", true);
--- a/mobile/android/base/ui/Axis.java
+++ b/mobile/android/base/ui/Axis.java
@@ -22,17 +22,16 @@ import java.util.Map;
  * like displacement, velocity, viewport dimensions, etc. pertaining to
  * a particular axis.
  */
 abstract class Axis {
     private static final String LOGTAG = "GeckoAxis";
 
     private static final String PREF_SCROLLING_FRICTION_SLOW = "ui.scrolling.friction_slow";
     private static final String PREF_SCROLLING_FRICTION_FAST = "ui.scrolling.friction_fast";
-    private static final String PREF_SCROLLING_VELOCITY_THRESHOLD = "ui.scrolling.velocity_threshold";
     private static final String PREF_SCROLLING_MAX_EVENT_ACCELERATION = "ui.scrolling.max_event_acceleration";
     private static final String PREF_SCROLLING_OVERSCROLL_DECEL_RATE = "ui.scrolling.overscroll_decel_rate";
     private static final String PREF_SCROLLING_OVERSCROLL_SNAP_LIMIT = "ui.scrolling.overscroll_snap_limit";
     private static final String PREF_SCROLLING_MIN_SCROLLABLE_DISTANCE = "ui.scrolling.min_scrollable_distance";
 
     // This fraction of velocity remains after every animation frame when the velocity is low.
     private static float FRICTION_SLOW;
     // This fraction of velocity remains after every animation frame when the velocity is high.
@@ -62,17 +61,16 @@ abstract class Axis {
         Integer value = (prefs == null ? null : prefs.get(prefName));
         return (value == null || value < 0 ? defaultValue : value);
     }
 
     static void initPrefs() {
         JSONArray prefs = new JSONArray();
         prefs.put(PREF_SCROLLING_FRICTION_FAST);
         prefs.put(PREF_SCROLLING_FRICTION_SLOW);
-        prefs.put(PREF_SCROLLING_VELOCITY_THRESHOLD);
         prefs.put(PREF_SCROLLING_MAX_EVENT_ACCELERATION);
         prefs.put(PREF_SCROLLING_OVERSCROLL_DECEL_RATE);
         prefs.put(PREF_SCROLLING_OVERSCROLL_SNAP_LIMIT);
         prefs.put(PREF_SCROLLING_MIN_SCROLLABLE_DISTANCE);
 
         PrefsHelper.getPrefs(prefs, new PrefsHelper.PrefHandlerBase() {
             Map<String, Integer> mPrefs = new HashMap<String, Integer>();
 
@@ -81,36 +79,43 @@ abstract class Axis {
             }
 
             @Override public void finish() {
                 setPrefs(mPrefs);
             }
         });
     }
 
+    static final float MS_PER_FRAME = 4.0f;
+    private static final float FRAMERATE_MULTIPLIER = (1000f/60f) / MS_PER_FRAME;
+
+    //  The values we use for friction are based on a 16.6ms frame, adjust them to MS_PER_FRAME:
+    //  FRICTION^1 = FRICTION_ADJUSTED^(16/MS_PER_FRAME)
+    //  FRICTION_ADJUSTED = e ^ ((ln(FRICTION))/FRAMERATE_MULTIPLIER)
+    static float getFrameAdjustedFriction(float baseFriction) {
+        return (float)Math.pow(Math.E, (Math.log(baseFriction) / FRAMERATE_MULTIPLIER));
+    }
+
     static void setPrefs(Map<String, Integer> prefs) {
-        FRICTION_SLOW = getFloatPref(prefs, PREF_SCROLLING_FRICTION_SLOW, 850);
-        FRICTION_FAST = getFloatPref(prefs, PREF_SCROLLING_FRICTION_FAST, 970);
-        VELOCITY_THRESHOLD = getIntPref(prefs, PREF_SCROLLING_VELOCITY_THRESHOLD, 10);
+        FRICTION_SLOW = getFrameAdjustedFriction(getFloatPref(prefs, PREF_SCROLLING_FRICTION_SLOW, 850));
+        FRICTION_FAST = getFrameAdjustedFriction(getFloatPref(prefs, PREF_SCROLLING_FRICTION_FAST, 970));
+        VELOCITY_THRESHOLD = 10 / FRAMERATE_MULTIPLIER;
         MAX_EVENT_ACCELERATION = getFloatPref(prefs, PREF_SCROLLING_MAX_EVENT_ACCELERATION, 12);
-        OVERSCROLL_DECEL_RATE = getFloatPref(prefs, PREF_SCROLLING_OVERSCROLL_DECEL_RATE, 40);
+        OVERSCROLL_DECEL_RATE = getFrameAdjustedFriction(getFloatPref(prefs, PREF_SCROLLING_OVERSCROLL_DECEL_RATE, 40));
         SNAP_LIMIT = getFloatPref(prefs, PREF_SCROLLING_OVERSCROLL_SNAP_LIMIT, 300);
         MIN_SCROLLABLE_DISTANCE = getFloatPref(prefs, PREF_SCROLLING_MIN_SCROLLABLE_DISTANCE, 500);
         Log.i(LOGTAG, "Prefs: " + FRICTION_SLOW + "," + FRICTION_FAST + "," + VELOCITY_THRESHOLD + ","
                 + MAX_EVENT_ACCELERATION + "," + OVERSCROLL_DECEL_RATE + "," + SNAP_LIMIT + "," + MIN_SCROLLABLE_DISTANCE);
     }
 
     static {
         // set the scrolling parameters to default values on startup
         setPrefs(null);
     }
 
-    // The number of milliseconds per frame assuming 60 fps
-    private static final float MS_PER_FRAME = 1000.0f / 60.0f;
-
     private enum FlingStates {
         STOPPED,
         PANNING,
         FLINGING,
     }
 
     private enum Overscroll {
         NONE,
@@ -177,17 +182,17 @@ abstract class Axis {
     }
 
     void updateWithTouchAt(float pos, float timeDelta) {
         float newVelocity = (mTouchPos - pos) / timeDelta * MS_PER_FRAME;
 
         // If there's a direction change, or current velocity is very low,
         // allow setting of the velocity outright. Otherwise, use the current
         // velocity and a maximum change factor to set the new velocity.
-        boolean curVelocityIsLow = Math.abs(mVelocity) < 1.0f;
+        boolean curVelocityIsLow = Math.abs(mVelocity) < 1.0f / FRAMERATE_MULTIPLIER;
         boolean directionChange = (mVelocity > 0) != (newVelocity > 0);
         if (curVelocityIsLow || (directionChange && !FloatUtils.fuzzyEquals(newVelocity, 0.0f))) {
             mVelocity = newVelocity;
         } else {
             float maxChange = Math.abs(mVelocity * timeDelta * MAX_EVENT_ACCELERATION);
             mVelocity = Math.min(mVelocity + maxChange, Math.max(mVelocity - maxChange, newVelocity));
         }
 
--- a/mobile/android/base/ui/PanZoomController.java
+++ b/mobile/android/base/ui/PanZoomController.java
@@ -41,54 +41,32 @@ public class PanZoomController
     extends GestureDetector.SimpleOnGestureListener
     implements SimpleScaleGestureDetector.SimpleScaleGestureListener, GeckoEventListener
 {
     private static final String LOGTAG = "GeckoPanZoomController";
 
     private static String MESSAGE_ZOOM_RECT = "Browser:ZoomToRect";
     private static String MESSAGE_ZOOM_PAGE = "Browser:ZoomToPageWidth";
 
-    private static final String PREF_ZOOM_ANIMATION_FRAMES = "ui.zooming.animation_frames";
-
     // Animation stops if the velocity is below this value when overscrolled or panning.
     private static final float STOPPED_THRESHOLD = 4.0f;
 
     // Animation stops is the velocity is below this threshold when flinging.
     private static final float FLING_STOPPED_THRESHOLD = 0.1f;
 
     // The distance the user has to pan before we recognize it as such (e.g. to avoid 1-pixel pans
     // between the touch-down and touch-up of a click). In units of density-independent pixels.
     public static final float PAN_THRESHOLD = 1/16f * GeckoAppShell.getDpi();
 
     // Angle from axis within which we stay axis-locked
     private static final double AXIS_LOCK_ANGLE = Math.PI / 6.0; // 30 degrees
 
     // The maximum amount we allow you to zoom into a page
     private static final float MAX_ZOOM = 4.0f;
 
-    /* 16 precomputed frames of the _ease-out_ animation from the CSS Transitions specification. */
-    private static float[] ZOOM_ANIMATION_FRAMES = new float[] {
-        0.00000f,   /* 0 */
-        0.10211f,   /* 1 */
-        0.19864f,   /* 2 */
-        0.29043f,   /* 3 */
-        0.37816f,   /* 4 */
-        0.46155f,   /* 5 */
-        0.54054f,   /* 6 */
-        0.61496f,   /* 7 */
-        0.68467f,   /* 8 */
-        0.74910f,   /* 9 */
-        0.80794f,   /* 10 */
-        0.86069f,   /* 11 */
-        0.90651f,   /* 12 */
-        0.94471f,   /* 13 */
-        0.97401f,   /* 14 */
-        0.99309f,   /* 15 */
-    };
-
     private enum PanZoomState {
         NOTHING,        /* no touch-start events received */
         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 */
@@ -130,30 +108,32 @@ public class PanZoomController
         checkMainThread();
 
         setState(PanZoomState.NOTHING);
 
         mEventDispatcher = eventDispatcher;
         registerEventListener(MESSAGE_ZOOM_RECT);
         registerEventListener(MESSAGE_ZOOM_PAGE);
 
-        PrefsHelper.getPref(PREF_ZOOM_ANIMATION_FRAMES, new PrefsHelper.PrefHandlerBase() {
-            @Override public void prefValue(String pref, String value) {
-                setZoomAnimationFrames(value);
-            }
-        });
         Axis.initPrefs();
     }
 
     public void destroy() {
         unregisterEventListener(MESSAGE_ZOOM_RECT);
         unregisterEventListener(MESSAGE_ZOOM_PAGE);
         mSubscroller.destroy();
     }
 
+    private final static float easeOut(float t) {
+        // ease-out approx.
+        // -(t-1)^2+1
+        t = t-1;
+        return -t*t+1;
+    }
+
     private void registerEventListener(String event) {
         mEventDispatcher.registerEventListener(event, this);
     }
 
     private void unregisterEventListener(String event) {
         mEventDispatcher.unregisterEventListener(event, this);
     }
 
@@ -209,33 +189,16 @@ public class PanZoomController
                     }
                 });
             }
         } catch (Exception e) {
             Log.e(LOGTAG, "Exception handling message \"" + event + "\":", e);
         }
     }
 
-    private void setZoomAnimationFrames(String frames) {
-        try {
-            if (frames.length() > 0) {
-                StringTokenizer st = new StringTokenizer(frames, ",");
-                float[] values = new float[st.countTokens()];
-                for (int i = 0; i < values.length; i++) {
-                    values[i] = Float.parseFloat(st.nextToken());
-                }
-                ZOOM_ANIMATION_FRAMES = values;
-            }
-        } catch (NumberFormatException e) {
-            Log.e(LOGTAG, "Error setting zoom animation frames", e);
-        } finally {
-            Log.i(LOGTAG, "Zoom animation frames: " + Arrays.toString(ZOOM_ANIMATION_FRAMES));
-        }
-    }
-
     public boolean onTouchEvent(MotionEvent event) {
         switch (event.getAction() & MotionEvent.ACTION_MASK) {
         case MotionEvent.ACTION_DOWN:   return onTouchStart(event);
         case MotionEvent.ACTION_MOVE:   return onTouchMove(event);
         case MotionEvent.ACTION_UP:     return onTouchEnd(event);
         case MotionEvent.ACTION_CANCEL: return onTouchCancel(event);
         default:                        return false;
         }
@@ -574,17 +537,17 @@ public class PanZoomController
             stopAnimationTimer();
         }
 
         mAnimationTimer = new Timer("Animation Timer");
         mAnimationRunnable = runnable;
         mAnimationTimer.scheduleAtFixedRate(new TimerTask() {
             @Override
             public void run() { mTarget.post(runnable); }
-        }, 0, 1000L/60L);
+        }, 0, (int)Axis.MS_PER_FRAME);
     }
 
     /* Stops the fling or bounce animation. */
     private void stopAnimationTimer() {
         if (mAnimationTimer != null) {
             mAnimationTimer.cancel();
             mAnimationTimer = null;
         }
@@ -674,31 +637,31 @@ public class PanZoomController
              * out.
              */
             if (!(mState == PanZoomState.BOUNCE || mState == PanZoomState.ANIMATED_ZOOM)) {
                 finishAnimation();
                 return;
             }
 
             /* Perform the next frame of the bounce-back animation. */
-            if (mBounceFrame < ZOOM_ANIMATION_FRAMES.length) {
+            if (mBounceFrame < (int)(256f/Axis.MS_PER_FRAME)) {
                 advanceBounce();
                 return;
             }
 
             /* Finally, if there's nothing else to do, complete the animation and go to sleep. */
             finishBounce();
             finishAnimation();
             setState(PanZoomState.NOTHING);
         }
 
         /* Performs one frame of a bounce animation. */
         private void advanceBounce() {
             synchronized (mTarget.getLock()) {
-                float t = ZOOM_ANIMATION_FRAMES[mBounceFrame];
+                float t = easeOut(mBounceFrame * Axis.MS_PER_FRAME / 256f);
                 ViewportMetrics newMetrics = mBounceStartMetrics.interpolate(mBounceEndMetrics, t);
                 mTarget.setViewportMetrics(newMetrics);
                 mBounceFrame++;
             }
         }
 
         /* Concludes a bounce animation and snaps the viewport into place. */
         private void finishBounce() {