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.

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

mercurial