From 4a12efad0257476e308fbe56b99a96b107125e47 Mon Sep 17 00:00:00 2001 From: Seth Kirby Date: Tue, 16 Aug 2016 11:26:27 -0700 Subject: [PATCH] Separate Node bounds and hit bounds within node region where needed. Summary: Node region bounds are assumed to equal the underlying node bounds. In the case of hit slop, these need to be abstracted. Reviewed By: ahmedre Differential Revision: D3713430 --- .../facebook/react/flat/FlatShadowNode.java | 31 +++---- .../react/flat/FlatUIViewOperationQueue.java | 8 +- .../react/flat/HitSlopNodeRegion.java | 58 ++++++++++++ .../flat/HorizontalDrawCommandManager.java | 4 +- .../com/facebook/react/flat/NodeRegion.java | 90 +++++++++++++++++-- .../java/com/facebook/react/flat/RCTText.java | 7 +- .../java/com/facebook/react/flat/RCTView.java | 11 +-- .../facebook/react/flat/TextNodeRegion.java | 4 +- .../flat/VerticalDrawCommandManager.java | 4 +- 9 files changed, 169 insertions(+), 48 deletions(-) create mode 100644 ReactAndroid/src/main/java/com/facebook/react/flat/HitSlopNodeRegion.java diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/FlatShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/flat/FlatShadowNode.java index d65c371f3..62c5899fa 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/flat/FlatShadowNode.java +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/FlatShadowNode.java @@ -187,9 +187,7 @@ import com.facebook.react.views.view.ReactClippingViewGroupHelper; if (mountsToView()) { return mViewRight - mViewLeft; } else { - // this is not technically correct since hitSlop affects the NodeRegion, but it's a temporary - // work around for now, since mView{Right,Left} are only set for views - return Math.round(mNodeRegion.mRight - mNodeRegion.mLeft); + return Math.round(mNodeRegion.getRight() - mNodeRegion.getLeft()); } } @@ -198,9 +196,7 @@ import com.facebook.react.views.view.ReactClippingViewGroupHelper; if (mountsToView()) { return mViewBottom - mViewTop; } else { - // this is not technically correct since hitSlop affects the NodeRegion, but it's a temporary - // work around for now, since mView{Bottom,Top} are only set for views - return Math.round(mNodeRegion.mBottom - mNodeRegion.mTop); + return Math.round(mNodeRegion.getBottom() - mNodeRegion.getTop()); } } @@ -332,8 +328,8 @@ import com.facebook.react.views.view.ReactClippingViewGroupHelper; /* package */ final void updateOverflowsContainer() { boolean overflowsContainer = false; - int width = (int) (mNodeRegion.mRight - mNodeRegion.mLeft); - int height = (int) (mNodeRegion.mBottom - mNodeRegion.mTop); + int width = (int) (mNodeRegion.getRight() - mNodeRegion.getLeft()); + int height = (int) (mNodeRegion.getBottom() - mNodeRegion.getTop()); float leftBound = 0; float rightBound = width; @@ -349,23 +345,23 @@ import com.facebook.react.views.view.ReactClippingViewGroupHelper; // to clip certain subviews. if (!mClipToBounds && height > 0 && width > 0) { for (NodeRegion region : mNodeRegions) { - if (region.mLeft < leftBound) { - leftBound = region.mLeft; + if (region.getLeft() < leftBound) { + leftBound = region.getLeft(); overflowsContainer = true; } - if (region.mRight > rightBound) { - rightBound = region.mRight; + if (region.getRight() > rightBound) { + rightBound = region.getRight(); overflowsContainer = true; } - if (region.mTop < topBound) { - topBound = region.mTop; + if (region.getTop() < topBound) { + topBound = region.getTop(); overflowsContainer = true; } - if (region.mBottom > bottomBound) { - bottomBound = region.mBottom; + if (region.getBottom() > bottomBound) { + bottomBound = region.getBottom(); overflowsContainer = true; } } @@ -424,8 +420,7 @@ import com.facebook.react.views.view.ReactClippingViewGroupHelper; float right, float bottom, boolean isVirtual) { - if (mNodeRegion.mLeft != left || mNodeRegion.mTop != top || mNodeRegion.mRight != right || - mNodeRegion.mBottom != bottom || mNodeRegion.mIsVirtual != isVirtual) { + if (!mNodeRegion.matches(left, top, right, bottom, isVirtual)) { setNodeRegion(new NodeRegion(left, top, right, bottom, getReactTag(), isVirtual)); } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/FlatUIViewOperationQueue.java b/ReactAndroid/src/main/java/com/facebook/react/flat/FlatUIViewOperationQueue.java index ef13a0f9f..712e2a688 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/flat/FlatUIViewOperationQueue.java +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/FlatUIViewOperationQueue.java @@ -356,12 +356,12 @@ import com.facebook.react.uimanager.UIViewOperationQueue; } int resultTag = region == NodeRegion.EMPTY ? touchTargetReactTag : region.mTag; - float x = PixelUtil.toDIPFromPixel(region.mLeft + MEASURE_BUFFER[0] - containerX); - float y = PixelUtil.toDIPFromPixel(region.mTop + MEASURE_BUFFER[1] - containerY); + float x = PixelUtil.toDIPFromPixel(region.getLeft() + MEASURE_BUFFER[0] - containerX); + float y = PixelUtil.toDIPFromPixel(region.getTop() + MEASURE_BUFFER[1] - containerY); float width = PixelUtil.toDIPFromPixel(isNativeView ? - MEASURE_BUFFER[2] : region.mRight - region.mLeft); + MEASURE_BUFFER[2] : region.getRight() - region.getLeft()); float height = PixelUtil.toDIPFromPixel(isNativeView ? - MEASURE_BUFFER[3] : region.mBottom - region.mTop); + MEASURE_BUFFER[3] : region.getBottom() - region.getTop()); mCallback.invoke(resultTag, x, y, width, height); } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/HitSlopNodeRegion.java b/ReactAndroid/src/main/java/com/facebook/react/flat/HitSlopNodeRegion.java new file mode 100644 index 000000000..09ef24254 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/HitSlopNodeRegion.java @@ -0,0 +1,58 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react.flat; + +import android.graphics.Rect; + +/** + * NodeRegion that has a hit slop. + */ +/* package */ final class HitSlopNodeRegion extends NodeRegion { + + private final Rect mHitSlop; + + HitSlopNodeRegion( + Rect hitSlop, + float left, + float top, + float right, + float bottom, + int tag, + boolean isVirtual) { + super(left, top, right, bottom, tag, isVirtual); + mHitSlop = hitSlop; + } + + @Override + /* package */ float getTouchableLeft() { + return getLeft() - mHitSlop.left; + } + + @Override + /* package */ float getTouchableTop() { + return getTop() - mHitSlop.top; + } + + @Override + /* package */ float getTouchableRight() { + return getRight() + mHitSlop.right; + } + + @Override + /* package */ float getTouchableBottom() { + return getBottom() + mHitSlop.bottom; + } + + @Override + /* package */ boolean withinBounds(float touchX, float touchY) { + return getTouchableLeft() <= touchX && touchX < getTouchableRight() && + getTouchableTop() <= touchY && touchY < getTouchableBottom(); + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/HorizontalDrawCommandManager.java b/ReactAndroid/src/main/java/com/facebook/react/flat/HorizontalDrawCommandManager.java index 2b0be0362..e80c4a0a7 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/flat/HorizontalDrawCommandManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/HorizontalDrawCommandManager.java @@ -70,11 +70,11 @@ import android.util.SparseIntArray; public static void fillMaxMinArrays(NodeRegion[] regions, float[] maxRight, float[] minLeft) { float last = 0; for (int i = 0; i < regions.length; i++) { - last = Math.max(last, regions[i].mRight); + last = Math.max(last, regions[i].getTouchableRight()); maxRight[i] = last; } for (int i = regions.length - 1; i >= 0; i--) { - last = Math.min(last, regions[i].mLeft); + last = Math.min(last, regions[i].getTouchableLeft()); minLeft[i] = last; } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/NodeRegion.java b/ReactAndroid/src/main/java/com/facebook/react/flat/NodeRegion.java index 6279c7faa..36e7b765c 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/flat/NodeRegion.java +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/NodeRegion.java @@ -13,10 +13,10 @@ package com.facebook.react.flat; /* package */ static final NodeRegion[] EMPTY_ARRAY = new NodeRegion[0]; /* package */ static final NodeRegion EMPTY = new NodeRegion(0, 0, 0, 0, -1, false); - /* package */ final float mLeft; - /* package */ final float mTop; - /* package */ final float mRight; - /* package */ final float mBottom; + private final float mLeft; + private final float mTop; + private final float mRight; + private final float mBottom; /* package */ final int mTag; /* package */ final boolean mIsVirtual; @@ -41,12 +41,88 @@ package com.facebook.react.flat; float right, float bottom, boolean isVirtual) { - return left == mLeft && top == mTop && bottom == mBottom && right == mRight && + return left == mLeft && top == mTop && right == mRight && bottom == mBottom && isVirtual == mIsVirtual; } - /* package */ final boolean withinBounds(float touchX, float touchY) { - return mLeft <= touchX && touchX < mRight && mTop <= touchY && touchY < mBottom; + /** + * The left bound of the underlying node. + * + * @return The node bound. + */ + /* package */ final float getLeft() { + return mLeft; + } + + /** + * The top bound of the underlying node. + * + * @return The node bound. + */ + /* package */ final float getTop() { + return mTop; + } + + /** + * The right bound of the underlying node. + * + * @return The node bound. + */ + /* package */ final float getRight() { + return mRight; + } + + /** + * The bottom bound of the underlying node. + * + * @return The node bound. + */ + /* package */ final float getBottom() { + return mBottom; + } + + /** + * The left bound of the region for the purpose of touch. This is usually the bound of the + * underlying node, except in the case of hit slop. + * + * @return The touch bound. + */ + /* package */ float getTouchableLeft() { + return getLeft(); + } + + /** + * The top bound of the region for the purpose of touch. This is usually the bound of the + * underlying node, except in the case of hit slop. + * + * @return The touch bound. + */ + /* package */ float getTouchableTop() { + return getTop(); + } + + /** + * The right bound of the region for the purpose of touch. This is usually the bound of the + * underlying node, except in the case of hit slop. + * + * @return The touch bound. + */ + /* package */ float getTouchableRight() { + return getRight(); + } + + /** + * The bottom bound of the region for the purpose of touch. This is usually the bound of the + * underlying node, except in the case of hit slop. + * + * @return The touch bound. + */ + /* package */ float getTouchableBottom() { + return getBottom(); + } + + /* package */ boolean withinBounds(float touchX, float touchY) { + return mLeft <= touchX && touchX < mRight && mTop <= touchY && touchY < mBottom; } /* package */ int getReactTag(float touchX, float touchY) { diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/RCTText.java b/ReactAndroid/src/main/java/com/facebook/react/flat/RCTText.java index b96acbac9..3d7794f4e 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/flat/RCTText.java +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/RCTText.java @@ -225,8 +225,7 @@ import com.facebook.textcachewarmer.DefaultTextLayoutCacheWarmer; NodeRegion nodeRegion = getNodeRegion(); if (mDrawCommand == null) { - if (nodeRegion.mLeft != left || nodeRegion.mTop != top || nodeRegion.mRight != right || - nodeRegion.mBottom != bottom || nodeRegion.mIsVirtual != isVirtual) { + if (!nodeRegion.matches(left, top, right, bottom, isVirtual)) { setNodeRegion(new TextNodeRegion(left, top, right, bottom, getReactTag(), isVirtual, null)); } return; @@ -239,9 +238,7 @@ import com.facebook.textcachewarmer.DefaultTextLayoutCacheWarmer; } Layout newLayout = mDrawCommand.getLayout(); - if (nodeRegion.mLeft != left || nodeRegion.mTop != top || - nodeRegion.mRight != right || nodeRegion.mBottom != bottom || - nodeRegion.mIsVirtual != isVirtual || layout != newLayout) { + if (!nodeRegion.matches(left, top, right, bottom, isVirtual) || layout != newLayout) { setNodeRegion( new TextNodeRegion(left, top, right, bottom, getReactTag(), isVirtual, newLayout)); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/RCTView.java b/ReactAndroid/src/main/java/com/facebook/react/flat/RCTView.java index 9af44551c..56eb01b5e 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/flat/RCTView.java +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/RCTView.java @@ -155,15 +155,10 @@ import com.facebook.react.uimanager.annotations.ReactPropGroup; float right, float bottom, boolean isVirtual) { - if (mHitSlop != null) { - left -= mHitSlop.left; - top -= mHitSlop.top; - bottom += mHitSlop.bottom; - right += mHitSlop.right; - } - if (!getNodeRegion().matches(left, top, right, bottom, isVirtual)) { - setNodeRegion(new NodeRegion(left, top, right, bottom, getReactTag(), isVirtual)); + setNodeRegion(mHitSlop == null ? + new NodeRegion(left, top, right, bottom, getReactTag(), isVirtual) : + new HitSlopNodeRegion(mHitSlop, left, top, right, bottom, getReactTag(), isVirtual)); } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/TextNodeRegion.java b/ReactAndroid/src/main/java/com/facebook/react/flat/TextNodeRegion.java index b556b362f..7fd6c6256 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/flat/TextNodeRegion.java +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/TextNodeRegion.java @@ -41,9 +41,9 @@ import android.text.Spanned; if (mLayout != null) { CharSequence text = mLayout.getText(); if (text instanceof Spanned) { - int y = Math.round(touchY - mTop); + int y = Math.round(touchY - getTop()); if (y >= mLayout.getLineTop(0) && y < mLayout.getLineBottom(mLayout.getLineCount() - 1)) { - float x = Math.round(touchX - mLeft); + float x = Math.round(touchX - getLeft()); int line = mLayout.getLineForVertical(y); if (mLayout.getLineLeft(line) <= x && x <= mLayout.getLineRight(line)) { diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/VerticalDrawCommandManager.java b/ReactAndroid/src/main/java/com/facebook/react/flat/VerticalDrawCommandManager.java index c2e08ed9e..0584823b8 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/flat/VerticalDrawCommandManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/VerticalDrawCommandManager.java @@ -70,11 +70,11 @@ import android.util.SparseIntArray; public static void fillMaxMinArrays(NodeRegion[] regions, float[] maxBottom, float[] minTop) { float last = 0; for (int i = 0; i < regions.length; i++) { - last = Math.max(last, regions[i].mBottom); + last = Math.max(last, regions[i].getTouchableBottom()); maxBottom[i] = last; } for (int i = regions.length - 1; i >= 0; i--) { - last = Math.min(last, regions[i].mTop); + last = Math.min(last, regions[i].getTouchableTop()); minTop[i] = last; } }