diff options
-rw-r--r-- | android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/Axis.java | 92 | ||||
-rw-r--r-- | android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/PanZoomController.java | 70 |
2 files changed, 88 insertions, 74 deletions
diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/Axis.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/Axis.java index fcdcd727ee42..7ae80843a892 100644 --- a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/Axis.java +++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/Axis.java @@ -6,8 +6,8 @@ package org.mozilla.gecko.ui; import android.util.Log; +import android.view.View; -import org.json.JSONArray; import org.mozilla.gecko.util.FloatUtils; import java.util.Map; @@ -23,7 +23,6 @@ abstract class Axis { 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"; @@ -59,22 +58,22 @@ abstract class Axis { return (value == null || value < 0 ? defaultValue : value); } - static void addPrefNames(JSONArray prefs) { - 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); + 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 + "," @@ -86,9 +85,6 @@ abstract class Axis { 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, @@ -104,6 +100,7 @@ abstract class Axis { private final SubdocumentScrollHelper mSubscroller; + private int mOverscrollMode; /* Default to only overscrolling if we're allowed to scroll in a direction */ private float mFirstTouchPos; /* Position of the first touch event on the current drag. */ private float mTouchPos; /* Position of the most recent touch event on the current drag. */ private float mLastTouchPos; /* Position of the touch event before touchPos. */ @@ -121,6 +118,15 @@ abstract class Axis { Axis(SubdocumentScrollHelper subscroller) { mSubscroller = subscroller; + mOverscrollMode = View.OVER_SCROLL_IF_CONTENT_SCROLLS; + } + + public void setOverScrollMode(int overscrollMode) { + mOverscrollMode = overscrollMode; + } + + public int getOverScrollMode() { + return mOverscrollMode; } private float getViewportEnd() { @@ -155,7 +161,7 @@ abstract class Axis { // 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; @@ -200,24 +206,31 @@ abstract class Axis { * Returns true if the page is zoomed in to some degree along this axis such that scrolling is * possible and this axis has not been scroll locked while panning. Otherwise, returns false. */ - private boolean scrollable() { + boolean scrollable() { // If we're scrolling a subdocument, ignore the viewport length restrictions (since those // apply to the top-level document) and only take into account axis locking. if (mSubscroller.scrolling()) { return !mScrollingDisabled; - } else { - return getViewportLength() <= getPageLength() - MIN_SCROLLABLE_DISTANCE && - !mScrollingDisabled; } + + // if we are axis locked, return false + if (mScrollingDisabled) { + return false; + } + + // there is scrollable space, and we're not disabled, or the document fits the viewport + // but we always allow overscroll anyway + return getViewportLength() <= getPageLength() - MIN_SCROLLABLE_DISTANCE || + getOverScrollMode() == View.OVER_SCROLL_ALWAYS; } /* * Returns the resistance, as a multiplier, that should be taken into account when * tracking or pinching. */ - float getEdgeResistance() { + float getEdgeResistance(boolean forPinching) { float excess = getExcess(); - if (excess > 0.0f) { + if (excess > 0.0f && (getOverscroll() == Overscroll.BOTH || !forPinching)) { // excess can be greater than viewport length, but the resistance // must never drop below 0.0 return Math.max(0.0f, SNAP_LIMIT - excess / getViewportLength()); @@ -257,7 +270,15 @@ abstract class Axis { } float excess = getExcess(); - if (mDisableSnap || FloatUtils.fuzzyEquals(excess, 0.0f)) { + Overscroll overscroll = getOverscroll(); + boolean decreasingOverscroll = false; + if ((overscroll == Overscroll.MINUS && mVelocity > 0) || + (overscroll == Overscroll.PLUS && mVelocity < 0)) + { + decreasingOverscroll = true; + } + + if (mDisableSnap || FloatUtils.fuzzyEquals(excess, 0.0f) || decreasingOverscroll) { // If we aren't overscrolled, just apply friction. if (Math.abs(mVelocity) >= VELOCITY_THRESHOLD) { mVelocity *= FRICTION_FAST; @@ -268,7 +289,7 @@ abstract class Axis { } else { // Otherwise, decrease the velocity linearly. float elasticity = 1.0f - excess / (getViewportLength() * SNAP_LIMIT); - if (getOverscroll() == Overscroll.MINUS) { + if (overscroll == Overscroll.MINUS) { mVelocity = Math.min((mVelocity + OVERSCROLL_DECEL_RATE) * elasticity, 0.0f); } else { // must be Overscroll.PLUS mVelocity = Math.max((mVelocity - OVERSCROLL_DECEL_RATE) * elasticity, 0.0f); @@ -285,14 +306,27 @@ abstract class Axis { // Performs displacement of the viewport position according to the current velocity. void displace() { - if (!scrollable()) { + // if this isn't scrollable just return + if (!scrollable()) return; - } if (mFlingState == FlingStates.PANNING) - mDisplacement += (mLastTouchPos - mTouchPos) * getEdgeResistance(); + mDisplacement += (mLastTouchPos - mTouchPos) * getEdgeResistance(false); else mDisplacement += mVelocity; + + // if overscroll is disabled and we're trying to overscroll, reset the displacement + // to remove any excess. Using getExcess alone isn't enough here since it relies on + // getOverscroll which doesn't take into account any new displacment being applied + if (getOverScrollMode() == View.OVER_SCROLL_NEVER) { + if (mDisplacement + getOrigin() < getPageStart()) { + mDisplacement = getPageStart() - getOrigin(); + stopFling(); + } else if (mDisplacement + getViewportEnd() > getPageEnd()) { + mDisplacement = getPageEnd() - getViewportEnd(); + stopFling(); + } + } } float resetDisplacement() { diff --git a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/PanZoomController.java b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/PanZoomController.java index a6b7d2785c75..02e45daf3a15 100644 --- a/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/PanZoomController.java +++ b/android/experimental/LOAndroid3/src/java/org/mozilla/gecko/ui/PanZoomController.java @@ -19,8 +19,6 @@ import org.mozilla.gecko.gfx.ImmutableViewportMetrics; import org.mozilla.gecko.gfx.ViewportMetrics; import org.mozilla.gecko.util.FloatUtils; -import java.util.Arrays; -import java.util.StringTokenizer; import java.util.Timer; import java.util.TimerTask; @@ -53,26 +51,6 @@ public class PanZoomController // 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 */ @@ -125,6 +103,13 @@ public class PanZoomController 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 setState(PanZoomState state) { mState = state; } @@ -145,23 +130,6 @@ public class PanZoomController } } - 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); @@ -357,6 +325,8 @@ public class PanZoomController } private boolean onTouchCancel(MotionEvent event) { + cancelTouch(); + if (mState == PanZoomState.WAITING_LISTENERS) { // we might get a cancel event from the TouchEventHandler while in the // WAITING_LISTENERS state if the touch listeners prevent-default the @@ -367,7 +337,6 @@ public class PanZoomController return false; } - cancelTouch(); // ensure we snap back if we're overscrolled bounce(); return false; @@ -392,7 +361,9 @@ public class PanZoomController mY.startTouch(y); mLastEventTime = time; - if (angle < AXIS_LOCK_ANGLE || angle > (Math.PI - AXIS_LOCK_ANGLE)) { + if (!mX.scrollable() || !mY.scrollable()) { + setState(PanZoomState.PANNING); + } else if (angle < AXIS_LOCK_ANGLE || angle > (Math.PI - AXIS_LOCK_ANGLE)) { mY.setScrollingDisabled(true); setState(PanZoomState.PANNING_LOCKED); } else if (Math.abs(angle - (Math.PI / 2)) < AXIS_LOCK_ANGLE) { @@ -507,7 +478,7 @@ public class PanZoomController 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. */ @@ -607,7 +578,7 @@ public class PanZoomController } /* 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; } @@ -621,7 +592,7 @@ public class PanZoomController /* 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++; @@ -818,7 +789,7 @@ public class PanZoomController * Apply edge resistance if we're zoomed out smaller than the page size by scaling the zoom * factor toward 1.0. */ - float resistance = Math.min(mX.getEdgeResistance(), mY.getEdgeResistance()); + float resistance = Math.min(mX.getEdgeResistance(true), mY.getEdgeResistance(true)); if (spanRatio > 1.0f) spanRatio = 1.0f + (spanRatio - 1.0f) * resistance; else @@ -978,4 +949,13 @@ public class PanZoomController checkMainThread(); bounce(); } + + public void setOverScrollMode(int overscrollMode) { + mX.setOverScrollMode(overscrollMode); + mY.setOverScrollMode(overscrollMode); + } + + public int getOverScrollMode() { + return mX.getOverScrollMode(); + } } |