mobile/android/base/gfx/Axis.java

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     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 java.util.HashMap;
     9 import java.util.Map;
    11 import org.mozilla.gecko.GeckoAppShell;
    12 import org.mozilla.gecko.PrefsHelper;
    13 import org.mozilla.gecko.util.FloatUtils;
    15 import android.util.Log;
    16 import android.view.View;
    18 /**
    19  * This class represents the physics for one axis of movement (i.e. either
    20  * horizontal or vertical). It tracks the different properties of movement
    21  * like displacement, velocity, viewport dimensions, etc. pertaining to
    22  * a particular axis.
    23  */
    24 abstract class Axis {
    25     private static final String LOGTAG = "GeckoAxis";
    27     private static final String PREF_SCROLLING_FRICTION_SLOW = "ui.scrolling.friction_slow";
    28     private static final String PREF_SCROLLING_FRICTION_FAST = "ui.scrolling.friction_fast";
    29     private static final String PREF_SCROLLING_MAX_EVENT_ACCELERATION = "ui.scrolling.max_event_acceleration";
    30     private static final String PREF_SCROLLING_OVERSCROLL_DECEL_RATE = "ui.scrolling.overscroll_decel_rate";
    31     private static final String PREF_SCROLLING_OVERSCROLL_SNAP_LIMIT = "ui.scrolling.overscroll_snap_limit";
    32     private static final String PREF_SCROLLING_MIN_SCROLLABLE_DISTANCE = "ui.scrolling.min_scrollable_distance";
    34     // This fraction of velocity remains after every animation frame when the velocity is low.
    35     private static float FRICTION_SLOW;
    36     // This fraction of velocity remains after every animation frame when the velocity is high.
    37     private static float FRICTION_FAST;
    38     // Below this velocity (in pixels per frame), the friction starts increasing from FRICTION_FAST
    39     // to FRICTION_SLOW.
    40     private static float VELOCITY_THRESHOLD;
    41     // The maximum velocity change factor between events, per ms, in %.
    42     // Direction changes are excluded.
    43     private static float MAX_EVENT_ACCELERATION;
    45     // The rate of deceleration when the surface has overscrolled.
    46     private static float OVERSCROLL_DECEL_RATE;
    47     // The percentage of the surface which can be overscrolled before it must snap back.
    48     private static float SNAP_LIMIT;
    50     // The minimum amount of space that must be present for an axis to be considered scrollable,
    51     // in pixels.
    52     private static float MIN_SCROLLABLE_DISTANCE;
    54     private static float getFloatPref(Map<String, Integer> prefs, String prefName, int defaultValue) {
    55         Integer value = (prefs == null ? null : prefs.get(prefName));
    56         return (float)(value == null || value < 0 ? defaultValue : value) / 1000f;
    57     }
    59     private static int getIntPref(Map<String, Integer> prefs, String prefName, int defaultValue) {
    60         Integer value = (prefs == null ? null : prefs.get(prefName));
    61         return (value == null || value < 0 ? defaultValue : value);
    62     }
    64     static void initPrefs() {
    65         final String[] prefs = { PREF_SCROLLING_FRICTION_FAST,
    66                                  PREF_SCROLLING_FRICTION_SLOW,
    67                                  PREF_SCROLLING_MAX_EVENT_ACCELERATION,
    68                                  PREF_SCROLLING_OVERSCROLL_DECEL_RATE,
    69                                  PREF_SCROLLING_OVERSCROLL_SNAP_LIMIT,
    70                                  PREF_SCROLLING_MIN_SCROLLABLE_DISTANCE };
    72         PrefsHelper.getPrefs(prefs, new PrefsHelper.PrefHandlerBase() {
    73             Map<String, Integer> mPrefs = new HashMap<String, Integer>();
    75             @Override public void prefValue(String name, int value) {
    76                 mPrefs.put(name, value);
    77             }
    79             @Override public void finish() {
    80                 setPrefs(mPrefs);
    81             }
    82         });
    83     }
    85     static final float MS_PER_FRAME = 1000.0f / 60.0f;
    86     static final long NS_PER_FRAME = Math.round(1000000000f / 60f);
    87     private static final float FRAMERATE_MULTIPLIER = (1000f/60f) / MS_PER_FRAME;
    88     private static final int FLING_VELOCITY_POINTS = 8;
    90     //  The values we use for friction are based on a 16.6ms frame, adjust them to currentNsPerFrame:
    91     static float getFrameAdjustedFriction(float baseFriction, long currentNsPerFrame) {
    92         float framerateMultiplier = (float)currentNsPerFrame / NS_PER_FRAME;
    93         return (float)Math.pow(Math.E, (Math.log(baseFriction) / framerateMultiplier));
    94     }
    96     static void setPrefs(Map<String, Integer> prefs) {
    97         FRICTION_SLOW = getFloatPref(prefs, PREF_SCROLLING_FRICTION_SLOW, 850);
    98         FRICTION_FAST = getFloatPref(prefs, PREF_SCROLLING_FRICTION_FAST, 970);
    99         VELOCITY_THRESHOLD = 10 / FRAMERATE_MULTIPLIER;
   100         MAX_EVENT_ACCELERATION = getFloatPref(prefs, PREF_SCROLLING_MAX_EVENT_ACCELERATION, GeckoAppShell.getDpi() > 300 ? 100 : 40);
   101         OVERSCROLL_DECEL_RATE = getFloatPref(prefs, PREF_SCROLLING_OVERSCROLL_DECEL_RATE, 40);
   102         SNAP_LIMIT = getFloatPref(prefs, PREF_SCROLLING_OVERSCROLL_SNAP_LIMIT, 300);
   103         MIN_SCROLLABLE_DISTANCE = getFloatPref(prefs, PREF_SCROLLING_MIN_SCROLLABLE_DISTANCE, 500);
   104         Log.i(LOGTAG, "Prefs: " + FRICTION_SLOW + "," + FRICTION_FAST + "," + VELOCITY_THRESHOLD + ","
   105                 + MAX_EVENT_ACCELERATION + "," + OVERSCROLL_DECEL_RATE + "," + SNAP_LIMIT + "," + MIN_SCROLLABLE_DISTANCE);
   106     }
   108     static {
   109         // set the scrolling parameters to default values on startup
   110         setPrefs(null);
   111     }
   113     private enum FlingStates {
   114         STOPPED,
   115         PANNING,
   116         FLINGING,
   117     }
   119     private enum Overscroll {
   120         NONE,
   121         MINUS,      // Overscrolled in the negative direction
   122         PLUS,       // Overscrolled in the positive direction
   123         BOTH,       // Overscrolled in both directions (page is zoomed to smaller than screen)
   124     }
   126     private final SubdocumentScrollHelper mSubscroller;
   128     private int mOverscrollMode; /* Default to only overscrolling if we're allowed to scroll in a direction */
   129     private float mFirstTouchPos;           /* Position of the first touch event on the current drag. */
   130     private float mTouchPos;                /* Position of the most recent touch event on the current drag. */
   131     private float mLastTouchPos;            /* Position of the touch event before touchPos. */
   132     private float mVelocity;                /* Velocity in this direction; pixels per animation frame. */
   133     private float[] mRecentVelocities;      /* Circular buffer of recent velocities since last touch start. */
   134     private int mRecentVelocityCount;       /* Number of values put into mRecentVelocities (unbounded). */
   135     private boolean mScrollingDisabled;     /* Whether movement on this axis is locked. */
   136     private boolean mDisableSnap;           /* Whether overscroll snapping is disabled. */
   137     private float mDisplacement;
   139     private FlingStates mFlingState = FlingStates.STOPPED; /* The fling state we're in on this axis. */
   141     protected abstract float getOrigin();
   142     protected abstract float getViewportLength();
   143     protected abstract float getPageStart();
   144     protected abstract float getPageLength();
   145     protected abstract float getMarginStart();
   146     protected abstract float getMarginEnd();
   147     protected abstract boolean marginsHidden();
   149     Axis(SubdocumentScrollHelper subscroller) {
   150         mSubscroller = subscroller;
   151         mOverscrollMode = View.OVER_SCROLL_IF_CONTENT_SCROLLS;
   152         mRecentVelocities = new float[FLING_VELOCITY_POINTS];
   153     }
   155     // Implementors can override these to show effects when the axis overscrolls
   156     protected void overscrollFling(float velocity) { }
   157     protected void overscrollPan(float displacement) { }
   159     public void setOverScrollMode(int overscrollMode) {
   160         mOverscrollMode = overscrollMode;
   161     }
   163     public int getOverScrollMode() {
   164         return mOverscrollMode;
   165     }
   167     private float getViewportEnd() {
   168         return getOrigin() + getViewportLength();
   169     }
   171     private float getPageEnd() {
   172         return getPageStart() + getPageLength();
   173     }
   175     void startTouch(float pos) {
   176         mVelocity = 0.0f;
   177         mScrollingDisabled = false;
   178         mFirstTouchPos = mTouchPos = mLastTouchPos = pos;
   179         mRecentVelocityCount = 0;
   180     }
   182     float panDistance(float currentPos) {
   183         return currentPos - mFirstTouchPos;
   184     }
   186     void setScrollingDisabled(boolean disabled) {
   187         mScrollingDisabled = disabled;
   188     }
   190     void saveTouchPos() {
   191         mLastTouchPos = mTouchPos;
   192     }
   194     void updateWithTouchAt(float pos, float timeDelta) {
   195         float newVelocity = (mTouchPos - pos) / timeDelta * MS_PER_FRAME;
   197         mRecentVelocities[mRecentVelocityCount % FLING_VELOCITY_POINTS] = newVelocity;
   198         mRecentVelocityCount++;
   200         // If there's a direction change, or current velocity is very low,
   201         // allow setting of the velocity outright. Otherwise, use the current
   202         // velocity and a maximum change factor to set the new velocity.
   203         boolean curVelocityIsLow = Math.abs(mVelocity) < 1.0f / FRAMERATE_MULTIPLIER;
   204         boolean directionChange = (mVelocity > 0) != (newVelocity > 0);
   205         if (curVelocityIsLow || (directionChange && !FloatUtils.fuzzyEquals(newVelocity, 0.0f))) {
   206             mVelocity = newVelocity;
   207         } else {
   208             float maxChange = Math.abs(mVelocity * timeDelta * MAX_EVENT_ACCELERATION);
   209             mVelocity = Math.min(mVelocity + maxChange, Math.max(mVelocity - maxChange, newVelocity));
   210         }
   212         mTouchPos = pos;
   213     }
   215     boolean overscrolled() {
   216         return getOverscroll() != Overscroll.NONE;
   217     }
   219     private Overscroll getOverscroll() {
   220         boolean minus = (getOrigin() < getPageStart());
   221         boolean plus = (getViewportEnd() > getPageEnd());
   222         if (minus && plus) {
   223             return Overscroll.BOTH;
   224         } else if (minus) {
   225             return Overscroll.MINUS;
   226         } else if (plus) {
   227             return Overscroll.PLUS;
   228         } else {
   229             return Overscroll.NONE;
   230         }
   231     }
   233     // Returns the amount that the page has been overscrolled. If the page hasn't been
   234     // overscrolled on this axis, returns 0.
   235     private float getExcess() {
   236         switch (getOverscroll()) {
   237         case MINUS:     return getPageStart() - getOrigin();
   238         case PLUS:      return getViewportEnd() - getPageEnd();
   239         case BOTH:      return (getViewportEnd() - getPageEnd()) + (getPageStart() - getOrigin());
   240         default:        return 0.0f;
   241         }
   242     }
   244     /*
   245      * Returns true if the page is zoomed in to some degree along this axis such that scrolling is
   246      * possible and this axis has not been scroll locked while panning. Otherwise, returns false.
   247      */
   248     boolean scrollable() {
   249         // If we're scrolling a subdocument, ignore the viewport length restrictions (since those
   250         // apply to the top-level document) and only take into account axis locking.
   251         if (mSubscroller.scrolling()) {
   252             return !mScrollingDisabled;
   253         }
   255         // if we are axis locked, return false
   256         if (mScrollingDisabled) {
   257             return false;
   258         }
   260         // if there are margins on this axis but they are currently hidden,
   261         // we must be able to scroll in order to make them visible, so allow
   262         // scrolling in that case
   263         if (marginsHidden()) {
   264             return true;
   265         }
   267         // there is scrollable space, and we're not disabled, or the document fits the viewport
   268         // but we always allow overscroll anyway
   269         return getViewportLength() <= getPageLength() - MIN_SCROLLABLE_DISTANCE ||
   270                getOverScrollMode() == View.OVER_SCROLL_ALWAYS;
   271     }
   273     /*
   274      * Returns the resistance, as a multiplier, that should be taken into account when
   275      * tracking or pinching.
   276      */
   277     float getEdgeResistance(boolean forPinching) {
   278         float excess = getExcess();
   279         if (excess > 0.0f && (getOverscroll() == Overscroll.BOTH || !forPinching)) {
   280             // excess can be greater than viewport length, but the resistance
   281             // must never drop below 0.0
   282             return Math.max(0.0f, SNAP_LIMIT - excess / getViewportLength());
   283         }
   284         return 1.0f;
   285     }
   287     /* Returns the velocity. If the axis is locked, returns 0. */
   288     float getRealVelocity() {
   289         return scrollable() ? mVelocity : 0f;
   290     }
   292     void startPan() {
   293         mFlingState = FlingStates.PANNING;
   294     }
   296     private float calculateFlingVelocity() {
   297         int usablePoints = Math.min(mRecentVelocityCount, FLING_VELOCITY_POINTS);
   298         if (usablePoints <= 1) {
   299             return mVelocity;
   300         }
   301         float average = 0;
   302         for (int i = 0; i < usablePoints; i++) {
   303             average += mRecentVelocities[i];
   304         }
   305         return average / usablePoints;
   306     }
   308     void startFling(boolean stopped) {
   309         mDisableSnap = mSubscroller.scrolling();
   311         if (stopped) {
   312             mFlingState = FlingStates.STOPPED;
   313         } else {
   314             mVelocity = calculateFlingVelocity();
   315             mFlingState = FlingStates.FLINGING;
   316         }
   317     }
   319     /* Advances a fling animation by one step. */
   320     boolean advanceFling(long realNsPerFrame) {
   321         if (mFlingState != FlingStates.FLINGING) {
   322             return false;
   323         }
   324         if (mSubscroller.scrolling() && !mSubscroller.lastScrollSucceeded()) {
   325             // if the subdocument stopped scrolling, it's because it reached the end
   326             // of the subdocument. we don't do overscroll on subdocuments, so there's
   327             // no point in continuing this fling.
   328             return false;
   329         }
   331         float excess = getExcess();
   332         Overscroll overscroll = getOverscroll();
   333         boolean decreasingOverscroll = false;
   334         if ((overscroll == Overscroll.MINUS && mVelocity > 0) ||
   335             (overscroll == Overscroll.PLUS && mVelocity < 0))
   336         {
   337             decreasingOverscroll = true;
   338         }
   340         if (mDisableSnap || FloatUtils.fuzzyEquals(excess, 0.0f) || decreasingOverscroll) {
   341             // If we aren't overscrolled, just apply friction.
   342             if (Math.abs(mVelocity) >= VELOCITY_THRESHOLD) {
   343                 mVelocity *= getFrameAdjustedFriction(FRICTION_FAST, realNsPerFrame);
   344             } else {
   345                 float t = mVelocity / VELOCITY_THRESHOLD;
   346                 mVelocity *= FloatUtils.interpolate(getFrameAdjustedFriction(FRICTION_SLOW, realNsPerFrame),
   347                                                     getFrameAdjustedFriction(FRICTION_FAST, realNsPerFrame), t);
   348             }
   349         } else {
   350             // Otherwise, decrease the velocity linearly.
   351             float elasticity = 1.0f - excess / (getViewportLength() * SNAP_LIMIT);
   352             float overscrollDecelRate = getFrameAdjustedFriction(OVERSCROLL_DECEL_RATE, realNsPerFrame);
   353             if (overscroll == Overscroll.MINUS) {
   354                 mVelocity = Math.min((mVelocity + overscrollDecelRate) * elasticity, 0.0f);
   355             } else { // must be Overscroll.PLUS
   356                 mVelocity = Math.max((mVelocity - overscrollDecelRate) * elasticity, 0.0f);
   357             }
   358         }
   360         return true;
   361     }
   363     void stopFling() {
   364         mVelocity = 0.0f;
   365         mFlingState = FlingStates.STOPPED;
   366     }
   368     // Performs displacement of the viewport position according to the current velocity.
   369     void displace() {
   370         // if this isn't scrollable just return
   371         if (!scrollable())
   372             return;
   374         if (mFlingState == FlingStates.PANNING)
   375             mDisplacement += (mLastTouchPos - mTouchPos) * getEdgeResistance(false);
   376         else
   377             mDisplacement += mVelocity * getEdgeResistance(false);
   379         // if overscroll is disabled and we're trying to overscroll, reset the displacement
   380         // to remove any excess. Using getExcess alone isn't enough here since it relies on
   381         // getOverscroll which doesn't take into account any new displacment being applied.
   382         // If we using a subscroller, we don't want to alter the scrolling being done
   383         if (getOverScrollMode() == View.OVER_SCROLL_NEVER && !mSubscroller.scrolling()) {
   384             float originalDisplacement = mDisplacement;
   386             if (mDisplacement + getOrigin() < getPageStart() - getMarginStart()) {
   387                 mDisplacement = getPageStart() - getMarginStart() - getOrigin();
   388             } else if (mDisplacement + getViewportEnd() > getPageEnd() + getMarginEnd()) {
   389                 mDisplacement = getPageEnd() - getMarginEnd() - getViewportEnd();
   390             }
   392             // Return the amount of overscroll so that the overscroll controller can draw it for us
   393             if (originalDisplacement != mDisplacement) {
   394                 if (mFlingState == FlingStates.FLINGING) {
   395                     overscrollFling(mVelocity / MS_PER_FRAME * 1000);
   396                     stopFling();
   397                 } else if (mFlingState == FlingStates.PANNING) {
   398                     overscrollPan(originalDisplacement - mDisplacement);
   399                 }
   400             }
   401         }
   402     }
   404     float resetDisplacement() {
   405         float d = mDisplacement;
   406         mDisplacement = 0.0f;
   407         return d;
   408     }
   410     void setAutoscrollVelocity(float velocity) {
   411         if (mFlingState != FlingStates.STOPPED) {
   412             Log.e(LOGTAG, "Setting autoscroll velocity while in a fling is not allowed!");
   413             return;
   414         }
   415         mVelocity = velocity;
   416     }
   417 }

mercurial