diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/FabricMountItem.cpp b/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/FabricMountItem.cpp index 2d3dda6596..c678cbdc5b 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/FabricMountItem.cpp +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/FabricMountItem.cpp @@ -45,6 +45,10 @@ CppMountItem CppMountItem::UpdatePaddingMountItem( ShadowView const &shadowView) { return {CppMountItem::Type::UpdatePadding, {}, {}, shadowView, -1}; } +CppMountItem CppMountItem::UpdateOverflowInsetMountItem( + ShadowView const &shadowView) { + return {CppMountItem::Type::UpdateOverflowInset, {}, {}, shadowView, -1}; +} } // namespace react } // namespace facebook diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/FabricMountItem.h b/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/FabricMountItem.h index 6a851270ff..5c85738d70 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/FabricMountItem.h +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/FabricMountItem.h @@ -45,6 +45,9 @@ struct CppMountItem final { static CppMountItem UpdatePaddingMountItem(ShadowView const &shadowView); + static CppMountItem UpdateOverflowInsetMountItem( + ShadowView const &shadowView); + #pragma mark - Type enum Type { @@ -58,7 +61,8 @@ struct CppMountItem final { UpdateState = 64, UpdateLayout = 128, UpdateEventEmitter = 256, - UpdatePadding = 512 + UpdatePadding = 512, + UpdateOverflowInset = 1024 }; #pragma mark - Fields diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/FabricMountingManager.cpp b/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/FabricMountingManager.cpp index e78540767a..68bf552f04 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/FabricMountingManager.cpp +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/FabricMountingManager.cpp @@ -43,6 +43,8 @@ static inline int getIntBufferSizeForType(CppMountItem::Type mountItemType) { return 5; // tag, top, left, bottom, right case CppMountItem::Type::UpdateLayout: return 6; // tag, x, y, w, h, DisplayType + case CppMountItem::Type::UpdateOverflowInset: + return 5; // tag, left, top, right, bottom case CppMountItem::Undefined: case CppMountItem::Multiple: return -1; @@ -84,6 +86,7 @@ static inline void computeBufferSizes( std::vector &cppUpdateStateMountItems, std::vector &cppUpdatePaddingMountItems, std::vector &cppUpdateLayoutMountItems, + std::vector &cppUpdateOverflowInsetMountItems, std::vector &cppUpdateEventEmitterMountItems) { CppMountItem::Type lastType = CppMountItem::Type::Undefined; int numSameType = 0; @@ -128,6 +131,11 @@ static inline void computeBufferSizes( cppUpdateLayoutMountItems.size(), batchMountItemIntsSize, batchMountItemObjectsSize); + updateBufferSizes( + CppMountItem::Type::UpdateOverflowInset, + cppUpdateOverflowInsetMountItems.size(), + batchMountItemIntsSize, + batchMountItemObjectsSize); updateBufferSizes( CppMountItem::Type::UpdateEventEmitter, cppUpdateEventEmitterMountItems.size(), @@ -232,6 +240,7 @@ void FabricMountingManager::executeMount( std::vector cppUpdateStateMountItems; std::vector cppUpdatePaddingMountItems; std::vector cppUpdateLayoutMountItems; + std::vector cppUpdateOverflowInsetMountItems; std::vector cppUpdateEventEmitterMountItems; for (const auto &mutation : mutations) { @@ -293,6 +302,16 @@ void FabricMountingManager::executeMount( CppMountItem::UpdateLayoutMountItem( mutation.newChildShadowView)); } + + // OverflowInset: This is the values indicating boundaries including + // children of the current view. The layout of current view may not + // change, and we separate this part from layout mount items to not + // pack too much data there. + if (oldChildShadowView.layoutMetrics.overflowInset != + newChildShadowView.layoutMetrics.overflowInset) { + cppUpdateOverflowInsetMountItems.push_back( + CppMountItem::UpdateOverflowInsetMountItem(newChildShadowView)); + } } if (oldChildShadowView.eventEmitter != @@ -331,6 +350,13 @@ void FabricMountingManager::executeMount( // Layout cppUpdateLayoutMountItems.push_back( CppMountItem::UpdateLayoutMountItem(mutation.newChildShadowView)); + + // OverflowInset: This is the values indicating boundaries including + // children of the current view. The layout of current view may not + // change, and we separate this part from layout mount items to not + // pack too much data there. + cppUpdateOverflowInsetMountItems.push_back( + CppMountItem::UpdateOverflowInsetMountItem(newChildShadowView)); } // EventEmitter @@ -359,6 +385,7 @@ void FabricMountingManager::executeMount( cppUpdateStateMountItems, cppUpdatePaddingMountItems, cppUpdateLayoutMountItems, + cppUpdateOverflowInsetMountItems, cppUpdateEventEmitterMountItems); static auto createMountItemsIntBufferBatchContainer = @@ -407,7 +434,7 @@ void FabricMountingManager::executeMount( int intBufferPosition = 0; int objBufferPosition = 0; int prevMountItemType = -1; - jint temp[7]; + jint temp[6]; for (int i = 0; i < cppCommonMountItems.size(); i++) { const auto &mountItem = cppCommonMountItems[i]; const auto &mountItemType = mountItem.type; @@ -594,6 +621,36 @@ void FabricMountingManager::executeMount( intBufferPosition += 6; } } + if (!cppUpdateOverflowInsetMountItems.empty()) { + writeIntBufferTypePreamble( + CppMountItem::Type::UpdateOverflowInset, + cppUpdateOverflowInsetMountItems.size(), + env, + intBufferArray, + intBufferPosition); + + for (const auto &mountItem : cppUpdateOverflowInsetMountItems) { + auto layoutMetrics = mountItem.newChildShadowView.layoutMetrics; + auto pointScaleFactor = layoutMetrics.pointScaleFactor; + auto overflowInset = layoutMetrics.overflowInset; + + int overflowInsetLeft = + round(scale(overflowInset.left, pointScaleFactor)); + int overflowInsetTop = round(scale(overflowInset.top, pointScaleFactor)); + int overflowInsetRight = + round(scale(overflowInset.right, pointScaleFactor)); + int overflowInsetBottom = + round(scale(overflowInset.bottom, pointScaleFactor)); + + temp[0] = mountItem.newChildShadowView.tag; + temp[1] = overflowInsetLeft; + temp[2] = overflowInsetTop; + temp[3] = overflowInsetRight; + temp[4] = overflowInsetBottom; + env->SetIntArrayRegion(intBufferArray, intBufferPosition, 5, temp); + intBufferPosition += 5; + } + } if (!cppUpdateEventEmitterMountItems.empty()) { writeIntBufferTypePreamble( CppMountItem::Type::UpdateEventEmitter, diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/SurfaceMountingManager.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/SurfaceMountingManager.java index ec082c37dd..7b2051c92f 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/SurfaceMountingManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/SurfaceMountingManager.java @@ -32,6 +32,7 @@ import com.facebook.react.fabric.mounting.MountingManager.MountItemExecutor; import com.facebook.react.fabric.mounting.mountitems.MountItem; import com.facebook.react.touch.JSResponderHandler; import com.facebook.react.uimanager.IllegalViewOperationException; +import com.facebook.react.uimanager.ReactOverflowViewWithInset; import com.facebook.react.uimanager.ReactRoot; import com.facebook.react.uimanager.ReactStylesDiffMap; import com.facebook.react.uimanager.RootView; @@ -758,6 +759,35 @@ public class SurfaceMountingManager { viewManager.setPadding(viewToUpdate, left, top, right, bottom); } + @UiThread + public void updateOverflowInset( + int reactTag, + int overflowInsetLeft, + int overflowInsetTop, + int overflowInsetRight, + int overflowInsetBottom) { + if (isStopped()) { + return; + } + + ViewState viewState = getViewState(reactTag); + // Do not layout Root Views + if (viewState.mIsRoot) { + return; + } + + View viewToUpdate = viewState.mView; + if (viewToUpdate == null) { + throw new IllegalStateException("Unable to find View for tag: " + reactTag); + } + + if (viewToUpdate instanceof ReactOverflowViewWithInset) { + ((ReactOverflowViewWithInset) viewToUpdate) + .setOverflowInset( + overflowInsetLeft, overflowInsetTop, overflowInsetRight, overflowInsetBottom); + } + } + @UiThread public void updateState(final int reactTag, @Nullable StateWrapper stateWrapper) { UiThreadUtil.assertOnUiThread(); diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/IntBufferBatchMountItem.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/IntBufferBatchMountItem.java index 038596c6ac..f768453cb8 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/IntBufferBatchMountItem.java +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/IntBufferBatchMountItem.java @@ -49,6 +49,7 @@ public class IntBufferBatchMountItem implements MountItem { static final int INSTRUCTION_UPDATE_LAYOUT = 128; static final int INSTRUCTION_UPDATE_EVENT_EMITTER = 256; static final int INSTRUCTION_UPDATE_PADDING = 512; + static final int INSTRUCTION_UPDATE_OVERFLOW_INSET = 1024; private final int mSurfaceId; private final int mCommitNumber; @@ -163,11 +164,25 @@ public class IntBufferBatchMountItem implements MountItem { int width = mIntBuffer[i++]; int height = mIntBuffer[i++]; int displayType = mIntBuffer[i++]; + surfaceMountingManager.updateLayout(reactTag, x, y, width, height, displayType); } else if (type == INSTRUCTION_UPDATE_PADDING) { surfaceMountingManager.updatePadding( mIntBuffer[i++], mIntBuffer[i++], mIntBuffer[i++], mIntBuffer[i++], mIntBuffer[i++]); + } else if (type == INSTRUCTION_UPDATE_OVERFLOW_INSET) { + int reactTag = mIntBuffer[i++]; + int overflowInsetLeft = mIntBuffer[i++]; + int overflowInsetTop = mIntBuffer[i++]; + int overflowInsetRight = mIntBuffer[i++]; + int overflowInsetBottom = mIntBuffer[i++]; + + surfaceMountingManager.updateOverflowInset( + reactTag, + overflowInsetLeft, + overflowInsetTop, + overflowInsetRight, + overflowInsetBottom); } else if (type == INSTRUCTION_UPDATE_EVENT_EMITTER) { surfaceMountingManager.updateEventEmitter( mIntBuffer[i++], castToEventEmitter(mObjBuffer[j++])); @@ -251,6 +266,15 @@ public class IntBufferBatchMountItem implements MountItem { mIntBuffer[i++], mIntBuffer[i++], mIntBuffer[i++])); + } else if (type == INSTRUCTION_UPDATE_OVERFLOW_INSET) { + s.append( + String.format( + "UPDATE OVERFLOWINSET [%d]: left:%d top:%d right:%d bottom:%d\n", + mIntBuffer[i++], + mIntBuffer[i++], + mIntBuffer[i++], + mIntBuffer[i++], + mIntBuffer[i++])); } else if (type == INSTRUCTION_UPDATE_EVENT_EMITTER) { j += 1; s.append(String.format("UPDATE EVENTEMITTER [%d]\n", mIntBuffer[i++])); diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactOverflowViewWithInset.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactOverflowViewWithInset.java new file mode 100644 index 0000000000..e8f0e522f2 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactOverflowViewWithInset.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.react.uimanager; + +import android.graphics.Rect; +import android.view.View; + +/** + * Interface that should be implemented by {@link View} subclasses that support {@code overflow} + * style and want to use the overflowInset values. This allows the overflow information to be used + * by {@link TouchTargetHelper} to determine if a View is touchable. + */ +public interface ReactOverflowViewWithInset extends ReactOverflowView { + /** + * Get the overflow inset rect values which indicate the extensions to the boundaries of current + * view that wraps all of its children views + * + * @return Rect of integers indicating the left, top, right, bottom pixel extensions. The values + * are non-positive (indicating enlarged boundaries). + */ + Rect getOverflowInset(); + + /** + * Set the overflow inset rect values which indicate the extensions to the boundaries of current + * view that wraps all of its children views + */ + void setOverflowInset(int left, int top, int right, int bottom); +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/TouchTargetHelper.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/TouchTargetHelper.java index e96077edde..9a1bc71f88 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/TouchTargetHelper.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/TouchTargetHelper.java @@ -182,8 +182,14 @@ public class TouchTargetHelper { if (!isTouchPointInView(eventCoords[0], eventCoords[1], view)) { // We don't allow touches on views that are outside the bounds of an `overflow: hidden` and // `overflow: scroll` View. - if (view instanceof ReactOverflowView) { - @Nullable String overflow = ((ReactOverflowView) view).getOverflow(); + if (view instanceof ReactOverflowViewWithInset) { + // If the touch point is outside of the overflowinset for the view, we can safely ignore + // it. + if (!isTouchPointInViewWithOverflowInset(eventCoords[0], eventCoords[1], view)) { + return null; + } + + @Nullable String overflow = ((ReactOverflowViewWithInset) view).getOverflow(); if (ViewProps.HIDDEN.equals(overflow) || ViewProps.SCROLL.equals(overflow)) { return null; } @@ -253,6 +259,16 @@ public class TouchTargetHelper { } } + private static boolean isTouchPointInViewWithOverflowInset(float x, float y, View view) { + if (!(view instanceof ReactOverflowViewWithInset)) { + return false; + } + + final Rect overflowInset = ((ReactOverflowViewWithInset) view).getOverflowInset(); + return (x >= -overflowInset.left && x < view.getWidth() - overflowInset.right) + && (y >= -overflowInset.top && y < view.getHeight() - overflowInset.bottom); + } + /** * Returns the coordinates of a touch in the child View. It is transform aware and will invert the * transform Matrix to find the true local points This code is taken from {@link diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.java b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.java index d033e06871..bbb228d8fa 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.java @@ -42,7 +42,7 @@ import com.facebook.react.uimanager.FabricViewStateManager; import com.facebook.react.uimanager.MeasureSpecAssertions; import com.facebook.react.uimanager.ReactClippingViewGroup; import com.facebook.react.uimanager.ReactClippingViewGroupHelper; -import com.facebook.react.uimanager.ReactOverflowView; +import com.facebook.react.uimanager.ReactOverflowViewWithInset; import com.facebook.react.uimanager.ViewProps; import com.facebook.react.uimanager.events.NativeGestureUtil; import com.facebook.react.views.scroll.ReactScrollViewHelper.HasFlingAnimator; @@ -57,7 +57,7 @@ import java.util.List; public class ReactHorizontalScrollView extends HorizontalScrollView implements ReactClippingViewGroup, FabricViewStateManager.HasFabricViewStateManager, - ReactOverflowView, + ReactOverflowViewWithInset, HasScrollState, HasFlingAnimator { @@ -77,6 +77,7 @@ public class ReactHorizontalScrollView extends HorizontalScrollView private final @Nullable OverScroller mScroller; private final VelocityHelper mVelocityHelper = new VelocityHelper(); private final Rect mRect = new Rect(); + private final Rect mOverflowInset = new Rect(); private boolean mActivelyScrolling; private @Nullable Rect mClippingRect; @@ -256,6 +257,16 @@ public class ReactHorizontalScrollView extends HorizontalScrollView return mOverflow; } + @Override + public void setOverflowInset(int left, int top, int right, int bottom) { + mOverflowInset.set(left, top, right, bottom); + } + + @Override + public Rect getOverflowInset() { + return mOverflowInset; + } + @Override protected void onDraw(Canvas canvas) { if (DEBUG_MODE) { diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java index 652ec95130..b029e951e1 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java @@ -38,7 +38,7 @@ import com.facebook.react.uimanager.FabricViewStateManager; import com.facebook.react.uimanager.MeasureSpecAssertions; import com.facebook.react.uimanager.ReactClippingViewGroup; import com.facebook.react.uimanager.ReactClippingViewGroupHelper; -import com.facebook.react.uimanager.ReactOverflowView; +import com.facebook.react.uimanager.ReactOverflowViewWithInset; import com.facebook.react.uimanager.ViewProps; import com.facebook.react.uimanager.events.NativeGestureUtil; import com.facebook.react.views.scroll.ReactScrollViewHelper.HasFlingAnimator; @@ -60,7 +60,7 @@ public class ReactScrollView extends ScrollView ViewGroup.OnHierarchyChangeListener, View.OnLayoutChangeListener, FabricViewStateManager.HasFabricViewStateManager, - ReactOverflowView, + ReactOverflowViewWithInset, HasScrollState, HasFlingAnimator { @@ -73,6 +73,7 @@ public class ReactScrollView extends ScrollView private final @Nullable OverScroller mScroller; private final VelocityHelper mVelocityHelper = new VelocityHelper(); private final Rect mRect = new Rect(); // for reuse to avoid allocation + private final Rect mOverflowInset = new Rect(); private boolean mActivelyScrolling; private @Nullable Rect mClippingRect; @@ -231,6 +232,16 @@ public class ReactScrollView extends ScrollView return mOverflow; } + @Override + public void setOverflowInset(int left, int top, int right, int bottom) { + mOverflowInset.set(left, top, right, bottom); + } + + @Override + public Rect getOverflowInset() { + return mOverflowInset; + } + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { MeasureSpecAssertions.assertExplicitMeasureSpec(widthMeasureSpec, heightMeasureSpec); diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java index 8aedfecf77..d0d019a3f4 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java @@ -44,7 +44,7 @@ import com.facebook.react.uimanager.PointerEvents; import com.facebook.react.uimanager.ReactClippingProhibitedView; import com.facebook.react.uimanager.ReactClippingViewGroup; import com.facebook.react.uimanager.ReactClippingViewGroupHelper; -import com.facebook.react.uimanager.ReactOverflowView; +import com.facebook.react.uimanager.ReactOverflowViewWithInset; import com.facebook.react.uimanager.ReactPointerEventsView; import com.facebook.react.uimanager.ReactZIndexedViewGroup; import com.facebook.react.uimanager.RootView; @@ -66,11 +66,12 @@ public class ReactViewGroup extends ViewGroup ReactPointerEventsView, ReactHitSlopView, ReactZIndexedViewGroup, - ReactOverflowView { + ReactOverflowViewWithInset { private static final int ARRAY_CAPACITY_INCREMENT = 12; private static final int DEFAULT_BACKGROUND_COLOR = Color.TRANSPARENT; private static final LayoutParams sDefaultLayoutParam = new ViewGroup.LayoutParams(0, 0); + private final Rect mOverflowInset = new Rect(); /* should only be used in {@link #updateClippingToRect} */ private static final Rect sHelperRect = new Rect(); @@ -726,6 +727,16 @@ public class ReactViewGroup extends ViewGroup return mOverflow; } + @Override + public void setOverflowInset(int left, int top, int right, int bottom) { + mOverflowInset.set(left, top, right, bottom); + } + + @Override + public Rect getOverflowInset() { + return mOverflowInset; + } + /** * Set the background for the view or remove the background. It calls {@link * #setBackground(Drawable)} or {@link #setBackgroundDrawable(Drawable)} based on the sdk version.