mobile/android/base/gfx/LayerMarginsAnimator.java

Wed, 31 Dec 2014 07:22:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:22:50 +0100
branch
TOR_BUG_3246
changeset 4
fc2d59ddac77
permissions
-rw-r--r--

Correct previous dual key logic pending first delivery installment.

     1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
     2  * This Source Code Form is subject to the terms of the Mozilla Public
     3  * License, v. 2.0. If a copy of the MPL was not distributed with this
     4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 package org.mozilla.gecko.gfx;
     8 import org.mozilla.gecko.GeckoAppShell;
     9 import org.mozilla.gecko.GeckoEvent;
    10 import org.mozilla.gecko.PrefsHelper;
    11 import org.mozilla.gecko.TouchEventInterceptor;
    12 import org.mozilla.gecko.util.FloatUtils;
    13 import org.mozilla.gecko.util.ThreadUtils;
    15 import android.graphics.PointF;
    16 import android.graphics.RectF;
    17 import android.os.SystemClock;
    18 import android.util.Log;
    19 import android.view.animation.DecelerateInterpolator;
    20 import android.view.MotionEvent;
    21 import android.view.View;
    23 public class LayerMarginsAnimator implements TouchEventInterceptor {
    24     private static final String LOGTAG = "GeckoLayerMarginsAnimator";
    25     // The duration of the animation in ns
    26     private static final long MARGIN_ANIMATION_DURATION = 250000000;
    27     private static final String PREF_SHOW_MARGINS_THRESHOLD = "browser.ui.show-margins-threshold";
    29     /* This is the proportion of the viewport rect, minus maximum margins,
    30      * that needs to be travelled before margins will be exposed.
    31      */
    32     private float SHOW_MARGINS_THRESHOLD = 0.20f;
    34     /* This rect stores the maximum value margins can grow to when scrolling. When writing
    35      * to this member variable, or when reading from this member variable on a non-UI thread,
    36      * you must synchronize on the LayerMarginsAnimator instance. */
    37     private final RectF mMaxMargins;
    38     /* If this boolean is true, scroll changes will not affect margins */
    39     private boolean mMarginsPinned;
    40     /* The task that handles showing/hiding margins */
    41     private LayerMarginsAnimationTask mAnimationTask;
    42     /* This interpolator is used for the above mentioned animation */
    43     private final DecelerateInterpolator mInterpolator;
    44     /* The GeckoLayerClient whose margins will be animated */
    45     private final GeckoLayerClient mTarget;
    46     /* The distance that has been scrolled since either the first touch event,
    47      * or since the margins were last fully hidden */
    48     private final PointF mTouchTravelDistance;
    49     /* The ID of the prefs listener for the show-marginss threshold */
    50     private Integer mPrefObserverId;
    52     public LayerMarginsAnimator(GeckoLayerClient aTarget, LayerView aView) {
    53         // Assign member variables from parameters
    54         mTarget = aTarget;
    56         // Create other member variables
    57         mMaxMargins = new RectF();
    58         mInterpolator = new DecelerateInterpolator();
    59         mTouchTravelDistance = new PointF();
    61         // Listen to the dynamic toolbar pref
    62         mPrefObserverId = PrefsHelper.getPref(PREF_SHOW_MARGINS_THRESHOLD, new PrefsHelper.PrefHandlerBase() {
    63             @Override
    64             public void prefValue(String pref, int value) {
    65                 SHOW_MARGINS_THRESHOLD = (float)value / 100.0f;
    66             }
    68             @Override
    69             public boolean isObserver() {
    70                 return true;
    71             }
    72         });
    74         // Listen to touch events, for auto-pinning
    75         aView.addTouchInterceptor(this);
    76     }
    78     public void destroy() {
    79         if (mPrefObserverId != null) {
    80             PrefsHelper.removeObserver(mPrefObserverId);
    81             mPrefObserverId = null;
    82         }
    83     }
    85     /**
    86      * Sets the maximum values for margins to grow to, in pixels.
    87      */
    88     public synchronized void setMaxMargins(float left, float top, float right, float bottom) {
    89         ThreadUtils.assertOnUiThread();
    91         mMaxMargins.set(left, top, right, bottom);
    93         // Update the Gecko-side global for fixed viewport margins.
    94         GeckoAppShell.sendEventToGecko(
    95             GeckoEvent.createBroadcastEvent("Viewport:FixedMarginsChanged",
    96                 "{ \"top\" : " + top + ", \"right\" : " + right
    97                 + ", \"bottom\" : " + bottom + ", \"left\" : " + left + " }"));
    98     }
   100     RectF getMaxMargins() {
   101         return mMaxMargins;
   102     }
   104     private void animateMargins(final float left, final float top, final float right, final float bottom, boolean immediately) {
   105         if (mAnimationTask != null) {
   106             mTarget.getView().removeRenderTask(mAnimationTask);
   107             mAnimationTask = null;
   108         }
   110         if (immediately) {
   111             ImmutableViewportMetrics newMetrics = mTarget.getViewportMetrics().setMargins(left, top, right, bottom);
   112             mTarget.forceViewportMetrics(newMetrics, true, true);
   113             return;
   114         }
   116         ImmutableViewportMetrics metrics = mTarget.getViewportMetrics();
   118         mAnimationTask = new LayerMarginsAnimationTask(false, metrics, left, top, right, bottom);
   119         mTarget.getView().postRenderTask(mAnimationTask);
   120     }
   122     /**
   123      * Exposes the margin area by growing the margin components of the current
   124      * metrics to the values set in setMaxMargins.
   125      */
   126     public synchronized void showMargins(boolean immediately) {
   127         animateMargins(mMaxMargins.left, mMaxMargins.top, mMaxMargins.right, mMaxMargins.bottom, immediately);
   128     }
   130     public synchronized void hideMargins(boolean immediately) {
   131         animateMargins(0, 0, 0, 0, immediately);
   132     }
   134     public void setMarginsPinned(boolean pin) {
   135         if (pin == mMarginsPinned) {
   136             return;
   137         }
   139         mMarginsPinned = pin;
   140     }
   142     public boolean areMarginsShown() {
   143         final ImmutableViewportMetrics metrics = mTarget.getViewportMetrics();
   144         return metrics.marginLeft != 0  ||
   145                metrics.marginRight != 0 ||
   146                metrics.marginTop != 0   ||
   147                metrics.marginBottom != 0;
   148     }
   150     /**
   151      * This function will scroll a margin down to zero, or up to the maximum
   152      * specified margin size and return the left-over delta.
   153      * aMargins are in/out parameters. In specifies the current margin size,
   154      * and out specifies the modified margin size. They are specified in the
   155      * order of start-margin, then end-margin.
   156      * This function will also take into account how far the touch point has
   157      * moved and react accordingly. If a touch point hasn't moved beyond a
   158      * certain threshold, margins can only be hidden and not shown.
   159      * aNegativeOffset can be used if the remaining delta should be determined
   160      * by the end-margin instead of the start-margin (for example, in rtl
   161      * pages).
   162      */
   163     private float scrollMargin(float[] aMargins, float aDelta,
   164                                float aOverscrollStart, float aOverscrollEnd,
   165                                float aTouchTravelDistance,
   166                                float aViewportStart, float aViewportEnd,
   167                                float aPageStart, float aPageEnd,
   168                                float aMaxMarginStart, float aMaxMarginEnd,
   169                                boolean aNegativeOffset) {
   170         float marginStart = aMargins[0];
   171         float marginEnd = aMargins[1];
   172         float viewportSize = aViewportEnd - aViewportStart;
   173         float exposeThreshold = viewportSize * SHOW_MARGINS_THRESHOLD;
   175         if (aDelta >= 0) {
   176             float marginDelta = Math.max(0, aDelta - aOverscrollStart);
   177             aMargins[0] = marginStart - Math.min(marginDelta, marginStart);
   178             if (aTouchTravelDistance < exposeThreshold && marginEnd == 0) {
   179                 // We only want the margin to be newly exposed after the touch
   180                 // has moved a certain distance.
   181                 marginDelta = Math.max(0, marginDelta - (aPageEnd - aViewportEnd));
   182             }
   183             aMargins[1] = marginEnd + Math.min(marginDelta, aMaxMarginEnd - marginEnd);
   184         } else {
   185             float marginDelta = Math.max(0, -aDelta - aOverscrollEnd);
   186             aMargins[1] = marginEnd - Math.min(marginDelta, marginEnd);
   187             if (-aTouchTravelDistance < exposeThreshold && marginStart == 0) {
   188                 marginDelta = Math.max(0, marginDelta - (aViewportStart - aPageStart));
   189             }
   190             aMargins[0] = marginStart + Math.min(marginDelta, aMaxMarginStart - marginStart);
   191         }
   193         if (aNegativeOffset) {
   194             return aDelta - (marginEnd - aMargins[1]);
   195         }
   196         return aDelta - (marginStart - aMargins[0]);
   197     }
   199     /*
   200      * Taking maximum margins into account, offsets the margins and then the
   201      * viewport origin and returns the modified metrics.
   202      */
   203     ImmutableViewportMetrics scrollBy(ImmutableViewportMetrics aMetrics, float aDx, float aDy) {
   204         float[] newMarginsX = { aMetrics.marginLeft, aMetrics.marginRight };
   205         float[] newMarginsY = { aMetrics.marginTop, aMetrics.marginBottom };
   207         // Only alter margins if the toolbar isn't pinned
   208         if (!mMarginsPinned) {
   209             // Make sure to cancel any margin animations when margin-scrolling begins
   210             if (mAnimationTask != null) {
   211                 mTarget.getView().removeRenderTask(mAnimationTask);
   212                 mAnimationTask = null;
   213             }
   215             // Reset the touch travel when changing direction
   216             if ((aDx >= 0) != (mTouchTravelDistance.x >= 0)) {
   217                 mTouchTravelDistance.x = 0;
   218             }
   219             if ((aDy >= 0) != (mTouchTravelDistance.y >= 0)) {
   220                 mTouchTravelDistance.y = 0;
   221             }
   223             mTouchTravelDistance.offset(aDx, aDy);
   224             RectF overscroll = aMetrics.getOverscroll();
   226             // Only allow margins to scroll if the page can fill the viewport.
   227             if (aMetrics.getPageWidth() >= aMetrics.getWidth()) {
   228                 aDx = scrollMargin(newMarginsX, aDx,
   229                                    overscroll.left, overscroll.right,
   230                                    mTouchTravelDistance.x,
   231                                    aMetrics.viewportRectLeft, aMetrics.viewportRectRight,
   232                                    aMetrics.pageRectLeft, aMetrics.pageRectRight,
   233                                    mMaxMargins.left, mMaxMargins.right,
   234                                    aMetrics.isRTL);
   235             }
   236             if (aMetrics.getPageHeight() >= aMetrics.getHeight()) {
   237                 aDy = scrollMargin(newMarginsY, aDy,
   238                                    overscroll.top, overscroll.bottom,
   239                                    mTouchTravelDistance.y,
   240                                    aMetrics.viewportRectTop, aMetrics.viewportRectBottom,
   241                                    aMetrics.pageRectTop, aMetrics.pageRectBottom,
   242                                    mMaxMargins.top, mMaxMargins.bottom,
   243                                    false);
   244             }
   245         }
   247         return aMetrics.setMargins(newMarginsX[0], newMarginsY[0], newMarginsX[1], newMarginsY[1]).offsetViewportBy(aDx, aDy);
   248     }
   250     /** Implementation of TouchEventInterceptor */
   251     @Override
   252     public boolean onTouch(View view, MotionEvent event) {
   253         return false;
   254     }
   256     /** Implementation of TouchEventInterceptor */
   257     @Override
   258     public boolean onInterceptTouchEvent(View view, MotionEvent event) {
   259         int action = event.getActionMasked();
   260         if (action == MotionEvent.ACTION_DOWN && event.getPointerCount() == 1) {
   261             mTouchTravelDistance.set(0.0f, 0.0f);
   262         }
   264         return false;
   265     }
   267     class LayerMarginsAnimationTask extends RenderTask {
   268         private float mStartLeft, mStartTop, mStartRight, mStartBottom;
   269         private float mTop, mBottom, mLeft, mRight;
   270         private boolean mContinueAnimation;
   272         public LayerMarginsAnimationTask(boolean runAfter, ImmutableViewportMetrics metrics,
   273                 float left, float top, float right, float bottom) {
   274             super(runAfter);
   275             mContinueAnimation = true;
   276             this.mStartLeft = metrics.marginLeft;
   277             this.mStartTop = metrics.marginTop;
   278             this.mStartRight = metrics.marginRight;
   279             this.mStartBottom = metrics.marginBottom;
   280             this.mLeft = left;
   281             this.mRight = right;
   282             this.mTop = top;
   283             this.mBottom = bottom;
   284         }
   286         @Override
   287         public boolean internalRun(long timeDelta, long currentFrameStartTime) {
   288             if (!mContinueAnimation) {
   289                 return false;
   290             }
   292             // Calculate the progress (between 0 and 1)
   293             float progress = mInterpolator.getInterpolation(
   294                     Math.min(1.0f, (System.nanoTime() - getStartTime())
   295                                     / (float)MARGIN_ANIMATION_DURATION));
   297             // Calculate the new metrics accordingly
   298             synchronized (mTarget.getLock()) {
   299                 ImmutableViewportMetrics oldMetrics = mTarget.getViewportMetrics();
   300                 ImmutableViewportMetrics newMetrics = oldMetrics.setMargins(
   301                         FloatUtils.interpolate(mStartLeft, mLeft, progress),
   302                         FloatUtils.interpolate(mStartTop, mTop, progress),
   303                         FloatUtils.interpolate(mStartRight, mRight, progress),
   304                         FloatUtils.interpolate(mStartBottom, mBottom, progress));
   305                 PointF oldOffset = oldMetrics.getMarginOffset();
   306                 PointF newOffset = newMetrics.getMarginOffset();
   307                 newMetrics =
   308                         newMetrics.offsetViewportByAndClamp(newOffset.x - oldOffset.x,
   309                                                             newOffset.y - oldOffset.y);
   311                 if (progress >= 1.0f) {
   312                     mContinueAnimation = false;
   314                     // Force a redraw and update Gecko
   315                     mTarget.forceViewportMetrics(newMetrics, true, true);
   316                 } else {
   317                     mTarget.forceViewportMetrics(newMetrics, false, false);
   318                 }
   319             }
   320             return mContinueAnimation;
   321         }
   322     }
   324 }

mercurial