Bug 1180295 - Make the text selection handles position correctly. r=rbarker
authorKartikaya Gupta <kgupta@mozilla.com>
Tue, 18 Aug 2015 14:27:20 -0400
changeset 258277 e88f6a19cf800be7e94a36807c39a4d39f221d79
parent 258276 f2a739dbce5f4b1377382715b814b14307e17087
child 258278 3afafc10b12a3f05b4626d6e04efdf01be4aa15a
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 - Make the text selection handles position correctly. r=rbarker
mobile/android/base/TextSelection.java
mobile/android/base/TextSelectionHandle.java
--- a/mobile/android/base/TextSelection.java
+++ b/mobile/android/base/TextSelection.java
@@ -2,16 +2,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko;
 
 import android.content.res.Resources;
 import org.mozilla.gecko.gfx.BitmapUtils;
 import org.mozilla.gecko.gfx.BitmapUtils.BitmapLoader;
+import org.mozilla.gecko.gfx.ImmutableViewportMetrics;
 import org.mozilla.gecko.gfx.Layer;
 import org.mozilla.gecko.gfx.LayerView;
 import org.mozilla.gecko.gfx.LayerView.DrawListener;
 import org.mozilla.gecko.menu.GeckoMenu;
 import org.mozilla.gecko.menu.GeckoMenuItem;
 import org.mozilla.gecko.EventDispatcher;
 import org.mozilla.gecko.util.FloatUtils;
 import org.mozilla.gecko.util.GeckoEventListener;
@@ -30,31 +31,33 @@ import org.json.JSONException;
 import org.json.JSONObject;
 
 import java.util.Timer;
 import java.util.TimerTask;
 
 import android.util.Log;
 import android.view.View;
 
-class TextSelection extends Layer implements GeckoEventListener {
+class TextSelection extends Layer implements GeckoEventListener,
+                                             LayerView.DynamicToolbarListener {
     private static final String LOGTAG = "GeckoTextSelection";
     private static final int SHUTDOWN_DELAY_MS = 250;
 
     private final TextSelectionHandle anchorHandle;
     private final TextSelectionHandle caretHandle;
     private final TextSelectionHandle focusHandle;
 
     private final DrawListener mDrawListener;
     private boolean mDraggingHandles;
 
     private String selectionID; // Unique ID provided for each selection action.
     private float mViewLeft;
     private float mViewTop;
     private float mViewZoom;
+    private boolean mForceReposition;
 
     private String mCurrentItems;
 
     private TextSelectionActionModeCallback mCallback;
 
     // These timers are used to avoid flicker caused by selection handles showing/hiding quickly.
     // For instance when moving between single handle caret mode and two handle selection mode.
     private final Timer mActionModeTimer = new Timer("actionMode");
@@ -152,30 +155,32 @@ class TextSelection extends Layer implem
                         mViewTop = 0.0f;
                         mViewZoom = 0.0f;
 
                         // Create text selection layer and add draw-listener for positioning on reflows
                         LayerView layerView = GeckoAppShell.getLayerView();
                         if (layerView != null) {
                             layerView.addDrawListener(mDrawListener);
                             layerView.addLayer(TextSelection.this);
+                            layerView.getDynamicToolbarAnimator().addTranslationListener(TextSelection.this);
                         }
 
                         if (handles.length() > 1)
                             GeckoAppShell.performHapticFeedback(true);
                     } else if (event.equals("TextSelection:Update")) {
                         if (mActionModeTimerTask != null)
                             mActionModeTimerTask.cancel();
                         showActionMode(message.getJSONArray("actions"));
                     } else if (event.equals("TextSelection:HideHandles")) {
                         // Remove draw-listener and text selection layer
                         LayerView layerView = GeckoAppShell.getLayerView();
                         if (layerView != null) {
                             layerView.removeDrawListener(mDrawListener);
                             layerView.removeLayer(TextSelection.this);
+                            layerView.getDynamicToolbarAnimator().removeTranslationListener(TextSelection.this);
                         }
 
                         mActionModeTimerTask = new ActionModeTimerTask();
                         mActionModeTimer.schedule(mActionModeTimerTask, SHUTDOWN_DELAY_MS);
 
                         anchorHandle.setVisibility(View.GONE);
                         caretHandle.setVisibility(View.GONE);
                         focusHandle.setVisibility(View.GONE);
@@ -250,39 +255,56 @@ class TextSelection extends Layer implem
         mCurrentItems = null;
     }
 
     @Override
     public void draw(final RenderContext context) {
         // cache the relevant values from the context and bail out if they are the same. we do this
         // because this draw function gets called a lot (once per compositor frame) and we want to
         // avoid doing a lot of extra work in cases where it's not needed.
-        final float viewLeft = context.viewport.left - context.offset.x;
-        final float viewTop = context.viewport.top - context.offset.y;
+        final float viewLeft = context.viewport.left;
+        final float viewTop = context.viewport.top;
         final float viewZoom = context.zoomFactor;
 
-        if (FloatUtils.fuzzyEquals(mViewLeft, viewLeft)
-                && FloatUtils.fuzzyEquals(mViewTop, viewTop)
-                && FloatUtils.fuzzyEquals(mViewZoom, viewZoom)) {
+        if (!mForceReposition
+            && FloatUtils.fuzzyEquals(mViewLeft, viewLeft)
+            && FloatUtils.fuzzyEquals(mViewTop, viewTop)
+            && FloatUtils.fuzzyEquals(mViewZoom, viewZoom)) {
             return;
         }
+        mForceReposition = false;
         mViewLeft = viewLeft;
         mViewTop = viewTop;
         mViewZoom = viewZoom;
 
         ThreadUtils.postToUiThread(new Runnable() {
             @Override
             public void run() {
                 anchorHandle.repositionWithViewport(viewLeft, viewTop, viewZoom);
                 caretHandle.repositionWithViewport(viewLeft, viewTop, viewZoom);
                 focusHandle.repositionWithViewport(viewLeft, viewTop, viewZoom);
             }
         });
     }
 
+    @Override
+    public void onTranslationChanged(float aToolbarTranslation, float aLayerViewTranslation) {
+        mForceReposition = true;
+    }
+
+    @Override
+    public void onPanZoomStopped() {
+        // no-op
+    }
+
+    @Override
+    public void onMetricsChanged(ImmutableViewportMetrics viewport) {
+        mForceReposition = true;
+    }
+
     private class TextSelectionActionModeCallback implements Callback {
         private JSONArray mItems;
         private ActionModeCompat mActionMode;
     
         public TextSelectionActionModeCallback(JSONArray items) {
             mItems = items;
         }
 
--- a/mobile/android/base/TextSelectionHandle.java
+++ b/mobile/android/base/TextSelectionHandle.java
@@ -1,21 +1,23 @@
  /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko;
 
+import org.mozilla.gecko.animation.ViewHelper;
 import org.mozilla.gecko.gfx.ImmutableViewportMetrics;
 import org.mozilla.gecko.gfx.LayerView;
 
 import org.json.JSONObject;
 
 import android.content.Context;
 import android.content.res.TypedArray;
+import android.graphics.Point;
 import android.graphics.PointF;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.MotionEvent;
 import android.view.View;
 import android.widget.ImageView;
 import android.widget.RelativeLayout;
 
@@ -48,20 +50,17 @@ class TextSelectionHandle extends ImageV
     private final int mWidth;
     private final int mHeight;
     private final int mShadow;
 
     private float mLeft;
     private float mTop;
     private boolean mIsRTL; 
     private PointF mGeckoPoint;
-    private float mTouchStartX;
-    private float mTouchStartY;
-    private int mLayerViewX;
-    private int mLayerViewY;
+    private PointF mTouchStart;
 
     private RelativeLayout.LayoutParams mLayoutParams;
 
     private static final int IMAGE_LEVEL_LTR = 0;
     private static final int IMAGE_LEVEL_RTL = 1;
 
     public TextSelectionHandle(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -74,38 +73,43 @@ class TextSelectionHandle extends ImageV
         if (handleType == 0x01)
             mHandleType = HandleType.ANCHOR;
         else if (handleType == 0x02)
             mHandleType = HandleType.CARET;
         else
             mHandleType = HandleType.FOCUS;
 
         mGeckoPoint = new PointF(0.0f, 0.0f);
+        mTouchStart = new PointF(0.0f, 0.0f);
 
         mWidth = getResources().getDimensionPixelSize(R.dimen.text_selection_handle_width);
         mHeight = getResources().getDimensionPixelSize(R.dimen.text_selection_handle_height);
         mShadow = getResources().getDimensionPixelSize(R.dimen.text_selection_handle_shadow);
     }
 
+    private int getStatusBarHeight() {
+        int result = 0;
+        int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
+        if (resourceId > 0) {
+            result = getResources().getDimensionPixelSize(resourceId);
+        }
+        return result;
+    }
+
     @Override
     public boolean onTouch(View v, MotionEvent event) {
         switch (event.getActionMasked()) {
             case MotionEvent.ACTION_DOWN: {
-                mTouchStartX = event.getX();
-                mTouchStartY = event.getY();
-
-                int[] rect = new int[2];
-                GeckoAppShell.getLayerView().getLocationOnScreen(rect);
-                mLayerViewX = rect[0];
-                mLayerViewY = rect[1];
+                mTouchStart.x = event.getX();
+                mTouchStart.y = event.getY();
                 break;
             }
             case MotionEvent.ACTION_UP: {
-                mTouchStartX = 0;
-                mTouchStartY = 0;
+                mTouchStart.x = 0;
+                mTouchStart.y = 0;
 
                 // Reposition handles to line up with ends of selection
                 JSONObject args = new JSONObject();
                 try {
                     args.put("handleType", mHandleType.toString());
                 } catch (Exception e) {
                     Log.e(LOGTAG, "Error building JSON arguments for TextSelection:Position");
                 }
@@ -116,32 +120,40 @@ class TextSelectionHandle extends ImageV
                 move(event.getRawX(), event.getRawY());
                 break;
             }
         }
         return true;
     }
 
     private void move(float newX, float newY) {
-        // newX and newY are absolute coordinates, so we need to adjust them to
-        // account for other views on the screen (such as the URL bar). We also
-        // need to include the offset amount of the touch location relative to
-        // the top left of the handle (mTouchStartX and mTouchStartY).
-        mLeft = newX - mLayerViewX - mTouchStartX;
-        mTop = newY - mLayerViewY - mTouchStartY;
+        // newX and newY are in screen coordinates, but mLeft/mTop are relative
+        // to the ancestor (which is what LayerView is relative to also). So,
+        // we need to adjust them newX/newY. The |ancestorOrigin| variable computed
+        // below is the origin of the ancestor relative to the screen coordinates,
+        // so subtracting that from newY puts newY into the desired coordinate
+        // space. We also need to include the offset amount of the touch location
+        // relative to the top left of the handle (mTouchStart).
+        float layerViewTranslation = ViewHelper.getTranslationY(GeckoAppShell.getLayerView());
+        int[] layerViewPosition = new int[2];
+        GeckoAppShell.getLayerView().getLocationOnScreen(layerViewPosition);
+        float ancestorOrigin = layerViewPosition[1] - layerViewTranslation;
+
+        mLeft = newX - mTouchStart.x;
+        mTop = newY - mTouchStart.y - ancestorOrigin;
 
         LayerView layerView = GeckoAppShell.getLayerView();
         if (layerView == null) {
             Log.e(LOGTAG, "Can't move selection because layerView is null");
             return;
         }
+
         // Send x coordinate on the right side of the start handle, left side of the end handle.
-        float left = mLeft + adjustLeftForHandle();
-
-        PointF geckoPoint = new PointF(left, mTop);
+        PointF geckoPoint = new PointF(mLeft + adjustLeftForHandle(),
+                                       mTop - layerViewTranslation);
         geckoPoint = layerView.convertViewPointToLayerPoint(geckoPoint);
 
         JSONObject args = new JSONObject();
         try {
             args.put("handleType", mHandleType.toString());
             args.put("x", (int) geckoPoint.x);
             args.put("y", (int) geckoPoint.y);
         } catch (Exception e) {
@@ -167,26 +179,24 @@ class TextSelectionHandle extends ImageV
 
         mGeckoPoint = new PointF(left, top);
         if (mIsRTL != rtl) {
             mIsRTL = rtl;
             setImageLevel(mIsRTL ? IMAGE_LEVEL_RTL : IMAGE_LEVEL_LTR);
         }
 
         ImmutableViewportMetrics metrics = layerView.getViewportMetrics();
-        PointF offset = metrics.getMarginOffset();
-        repositionWithViewport(metrics.viewportRectLeft - offset.x, metrics.viewportRectTop - offset.y, metrics.zoomFactor);
+        repositionWithViewport(metrics.viewportRectLeft, metrics.viewportRectTop, metrics.zoomFactor);
     }
 
     void repositionWithViewport(float x, float y, float zoom) {
         PointF viewPoint = new PointF((mGeckoPoint.x * zoom) - x,
                                       (mGeckoPoint.y * zoom) - y);
-
         mLeft = viewPoint.x - adjustLeftForHandle();
-        mTop = viewPoint.y;
+        mTop = viewPoint.y + ViewHelper.getTranslationY(GeckoAppShell.getLayerView());
 
         setLayoutPosition();
     }
 
     private float adjustLeftForHandle() {
         if (mHandleType == HandleType.ANCHOR) {
             return mIsRTL ? mShadow : mWidth - mShadow;
         } else if (mHandleType == HandleType.CARET) {