Bug 1180295 - Hook up touch-based scrolling to the new DynamicToolbarAnimator. r=rbarker
authorKartikaya Gupta <kgupta@mozilla.com>
Tue, 18 Aug 2015 14:27:18 -0400
changeset 258262 0700729ed00747937218685cd95a660a3bfc1910
parent 258261 b675e378dadfe18c73577d03b7651e4aa0e11f5b
child 258263 69a90ad6126e513bd9566efb72276a7cc5df9ecf
push id29249
push userryanvm@gmail.com
push dateWed, 19 Aug 2015 11:17:27 +0000
treeherdermozilla-central@706b23a03d1c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrbarker
bugs1180295
milestone43.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 1180295 - Hook up touch-based scrolling to the new DynamicToolbarAnimator. r=rbarker
mobile/android/app/mobile.js
mobile/android/base/gfx/DynamicToolbarAnimator.java
mobile/android/base/gfx/LayerView.java
--- a/mobile/android/app/mobile.js
+++ b/mobile/android/app/mobile.js
@@ -429,18 +429,19 @@ pref("ui.touch.radius.visitedWeight", 12
 pref("ui.mouse.radius.enabled", true);
 pref("ui.mouse.radius.leftmm", 3);
 pref("ui.mouse.radius.topmm", 5);
 pref("ui.mouse.radius.rightmm", 3);
 pref("ui.mouse.radius.bottommm", 2);
 pref("ui.mouse.radius.visitedWeight", 120);
 pref("ui.mouse.radius.reposition", true);
 
-// The percentage of the screen that needs to be scrolled before margins are exposed.
-pref("browser.ui.show-margins-threshold", 10);
+// The percentage of the screen that needs to be scrolled before toolbar
+// manipulation is allowed.
+pref("browser.ui.scroll-toolbar-threshold", 10);
 
 // Maximum distance from the point where the user pressed where we still
 // look for text to select
 pref("browser.ui.selection.distance", 250);
 
 // plugins
 pref("plugin.disable", false);
 pref("dom.ipc.plugins.enabled", false);
--- a/mobile/android/base/gfx/DynamicToolbarAnimator.java
+++ b/mobile/android/base/gfx/DynamicToolbarAnimator.java
@@ -4,18 +4,20 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.gfx;
 
 import org.mozilla.gecko.PrefsHelper;
 import org.mozilla.gecko.util.FloatUtils;
 import org.mozilla.gecko.util.ThreadUtils;
 
+import android.graphics.PointF;
 import android.util.Log;
 import android.view.animation.DecelerateInterpolator;
+import android.view.MotionEvent;
 
 import java.util.ArrayList;
 import java.util.List;
 
 public class DynamicToolbarAnimator {
     private static final String LOGTAG = "GeckoDynamicToolbarAnimator";
     private static final String PREF_SCROLL_TOOLBAR_THRESHOLD = "browser.ui.scroll-toolbar-threshold";
 
@@ -57,16 +59,21 @@ public class DynamicToolbarAnimator {
      */
     private float SCROLL_TOOLBAR_THRESHOLD = 0.20f;
     /* The ID of the prefs listener for the scroll-toolbar threshold */
     private Integer mPrefObserverId;
 
     /* The task that handles showing/hiding toolbar */
     private DynamicToolbarAnimationTask mAnimationTask;
 
+    /* The start point of a drag, used for scroll-based dynamic toolbar
+     * behaviour. */
+    private PointF mTouchStart;
+    private float mLastTouch;
+
     public DynamicToolbarAnimator(GeckoLayerClient aTarget) {
         mTarget = aTarget;
         mListeners = new ArrayList<LayerView.DynamicToolbarListener>();
 
         mInterpolator = new DecelerateInterpolator();
 
         // Listen to the dynamic toolbar pref
         mPrefObserverId = PrefsHelper.getPref(PREF_SCROLL_TOOLBAR_THRESHOLD, new PrefsHelper.PrefHandlerBase() {
@@ -168,16 +175,144 @@ public class DynamicToolbarAnimator {
             int viewHeight = mTarget.getView().getHeight();
             int viewHeightVisible = viewHeight - Math.round(mMaxTranslation - mToolbarTranslation);
 
             Log.v(LOGTAG, "Resize viewport to dimensions " + viewWidth + "x" + viewHeightVisible);
             mTarget.setViewportSize(viewWidth, viewHeightVisible);
         }
     }
 
+    /**
+     * "Shrinks" the absolute value of aValue by moving it closer to zero by
+     * aShrinkAmount, but prevents it from crossing over zero. If aShrinkAmount
+     * is negative it is ignored.
+     * @return The shrunken value.
+     */
+    private static float shrinkAbs(float aValue, float aShrinkAmount) {
+        if (aShrinkAmount <= 0) {
+            return aValue;
+        }
+        float shrinkBy = Math.min(Math.abs(aValue), aShrinkAmount);
+        return (aValue < 0 ? aValue + shrinkBy : aValue - shrinkBy);
+    }
+
+    /**
+     * This function takes in a scroll amount and decides how much of that
+     * should be used up to translate things on screen because of the dynamic
+     * toolbar behaviour. It returns the maximum amount that could be used
+     * for translation purposes; the rest must be used for scrolling.
+     */
+    private float decideTranslation(float aDelta,
+                                    ImmutableViewportMetrics aMetrics,
+                                    float aTouchTravelDistance) {
+
+        float exposeThreshold = aMetrics.getHeight() * SCROLL_TOOLBAR_THRESHOLD;
+        float translation = aDelta;
+
+        if (translation < 0) { // finger moving upwards
+            translation = shrinkAbs(translation, aMetrics.getOverscroll().top);
+
+            // If the toolbar is in a state between fully hidden and fully shown
+            // (i.e. the user is actively translating it), then we want the
+            // translation to take effect right away. Or if the user has moved
+            // their finger past the required threshold (and is not trying to
+            // scroll past the bottom of the page) then also we want the touch
+            // to cause translation.
+            boolean inBetween = (mToolbarTranslation != 0 && mToolbarTranslation != mMaxTranslation);
+            boolean reachedThreshold = -aTouchTravelDistance >= exposeThreshold;
+            boolean atBottomOfPage = aMetrics.viewportRectBottom() >= aMetrics.pageRectBottom;
+            if (inBetween || (reachedThreshold && !atBottomOfPage)) {
+                return translation;
+            }
+        } else {    // finger moving downwards
+            translation = shrinkAbs(translation, aMetrics.getOverscroll().bottom);
+
+            // Ditto above comment, but in this case if they reached the top and
+            // the toolbar is not shown, then we do want to allow translation
+            // right away.
+            boolean inBetween = (mToolbarTranslation != 0 && mToolbarTranslation != mMaxTranslation);
+            boolean reachedThreshold = aTouchTravelDistance >= exposeThreshold;
+            boolean atTopOfPage = aMetrics.viewportRectTop <= aMetrics.pageRectTop;
+            boolean isToolbarTranslated = (mToolbarTranslation != 0);
+            if (inBetween || reachedThreshold || (atTopOfPage && isToolbarTranslated)) {
+                return translation;
+            }
+        }
+
+        return 0;
+    }
+
+    boolean onInterceptTouchEvent(MotionEvent event) {
+        if (mPinned) {
+            return false;
+        }
+
+        // Animations should never co-exist with the user touching the screen.
+        if (mAnimationTask != null) {
+            mTarget.getView().removeRenderTask(mAnimationTask);
+            mAnimationTask = null;
+        }
+
+        // we only care about single-finger drags here; any other kind of event
+        // should reset and cause us to start over.
+        if (event.getActionMasked() != MotionEvent.ACTION_MOVE ||
+            event.getPointerCount() != 1)
+        {
+            if (mTouchStart != null) {
+                Log.v(LOGTAG, "Resetting touch sequence due to non-move");
+                mTouchStart = null;
+            }
+            return false;
+        }
+
+        if (mTouchStart != null) {
+            float prevDir = mLastTouch - mTouchStart.y;
+            float newDir = event.getRawY() - mLastTouch;
+            if (prevDir != 0 && newDir != 0 && ((prevDir < 0) != (newDir < 0))) {
+                Log.v(LOGTAG, "Direction changed: " + mTouchStart.y + " -> " + mLastTouch + " -> " + event.getRawY());
+                // If the direction of movement changed, reset the travel
+                // distance properties.
+                mTouchStart = null;
+            }
+        }
+
+        if (mTouchStart == null) {
+            mTouchStart = new PointF(event.getRawX(), event.getRawY());
+            mLastTouch = event.getRawY();
+            return false;
+        }
+
+        float deltaY = event.getRawY() - mLastTouch;
+        mLastTouch = event.getRawY();
+        float travelDistance = event.getRawY() - mTouchStart.y;
+
+        ImmutableViewportMetrics metrics = mTarget.getViewportMetrics();
+
+        if (metrics.getPageHeight() < metrics.getHeight()) {
+            return false;
+        }
+
+        float translation = decideTranslation(deltaY, metrics, travelDistance);
+        Log.v(LOGTAG, "Got vertical translation " + translation);
+
+        float oldToolbarTranslation = mToolbarTranslation;
+        float oldLayerViewTranslation = mLayerViewTranslation;
+        mToolbarTranslation = FloatUtils.clamp(mToolbarTranslation - translation, 0, mMaxTranslation);
+        mLayerViewTranslation = FloatUtils.clamp(mLayerViewTranslation - translation, 0, mMaxTranslation);
+
+        if (oldToolbarTranslation == mToolbarTranslation &&
+            oldLayerViewTranslation == mLayerViewTranslation) {
+            return false;
+        }
+
+        fireListeners();
+        mTarget.getView().requestRender();
+        return true;
+    }
+
     class DynamicToolbarAnimationTask extends RenderTask {
         private final float mStartTranslation;
         private final float mEndTranslation;
         private boolean mContinueAnimation;
 
         public DynamicToolbarAnimationTask(boolean aRunAfter, float aTranslation) {
             super(aRunAfter);
             mContinueAnimation = true;
--- a/mobile/android/base/gfx/LayerView.java
+++ b/mobile/android/base/gfx/LayerView.java
@@ -230,17 +230,17 @@ public class LayerView extends FrameLayo
     }
 
     @Override
     public boolean onTouchEvent(MotionEvent event) {
         if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
             requestFocus();
         }
 
-        if (mMarginsAnimator != null && mMarginsAnimator.onInterceptTouchEvent(event)) {
+        if (mToolbarAnimator != null && mToolbarAnimator.onInterceptTouchEvent(event)) {
             return true;
         }
         if (mPanZoomController != null && mPanZoomController.onTouchEvent(event)) {
             return true;
         }
         return sendEventToGecko(event);
     }