Skip to content

Commit 96659f8

Browse files
genkikondofacebook-github-bot
authored andcommittedMar 7, 2023
Fix overflowInset to take into account hitSlop
Summary: Previously, the touch area could never extend past the parent view bounds. Thus, hitSlop would not have any effect if it extended beyond any ancestor view's view bounds. In this diff, we apply hitSlop when calculating overflowInset. Specifically, we make sure to union the hit slop areas of all children when calculating the contentFrame. overflowInset is then used for hit testing (as in [TouchTargetHelper.java](https://www.internalfb.com/code/fbsource/[b0f630bb24271d8ed543e98ff144590290e19805]/xplat/js/react-native-github/ReactAndroid/src/main/java/com/facebook/react/uimanager/TouchTargetHelper.java?lines=195)). A risk is that other optimizations that may potentially rely on overflowInset beyond hit testing (such as clipToBounds) may not be as efficient with a large hitSlop. Changelog: [General][Fixed] - Fix touch handling so that hitSlop can extend beyond parent view bounds. Reviewed By: javache Differential Revision: D43854774 fbshipit-source-id: 160bef135b8487c28c4ada662577c35a7a36f484
·
v0.80.1latest
1 parent f264fe1 commit 96659f8

File tree

3 files changed

+108
-2
lines changed

3 files changed

+108
-2
lines changed
 

‎ReactCommon/react/renderer/components/view/YogaLayoutableShadowNode.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include <react/debug/flags.h>
1111
#include <react/debug/react_native_assert.h>
1212
#include <react/renderer/components/view/ViewProps.h>
13+
#include <react/renderer/components/view/ViewShadowNode.h>
1314
#include <react/renderer/components/view/conversions.h>
1415
#include <react/renderer/core/LayoutConstraints.h>
1516
#include <react/renderer/core/LayoutContext.h>
@@ -624,12 +625,19 @@ void YogaLayoutableShadowNode::layout(LayoutContext layoutContext) {
624625

625626
auto layoutMetricsWithOverflowInset = childNode.getLayoutMetrics();
626627
if (layoutMetricsWithOverflowInset.displayType != DisplayType::None) {
628+
auto viewChildNode = traitCast<ViewShadowNode const *>(&childNode);
629+
auto hitSlop = viewChildNode != nullptr
630+
? viewChildNode->getConcreteProps().hitSlop
631+
: EdgeInsets{};
632+
627633
// The contentFrame should always union with existing child node layout +
628634
// overflowInset. The transform may in a deferred animation and not
629635
// applied yet.
630636
contentFrame.unionInPlace(insetBy(
631637
layoutMetricsWithOverflowInset.frame,
632638
layoutMetricsWithOverflowInset.overflowInset));
639+
contentFrame.unionInPlace(
640+
outsetBy(layoutMetricsWithOverflowInset.frame, hitSlop));
633641

634642
auto childTransform = childNode.getTransform();
635643
if (childTransform != Transform::Identity()) {
@@ -639,6 +647,8 @@ void YogaLayoutableShadowNode::layout(LayoutContext layoutContext) {
639647
contentFrame.unionInPlace(insetBy(
640648
layoutMetricsWithOverflowInset.frame * childTransform,
641649
layoutMetricsWithOverflowInset.overflowInset * childTransform));
650+
contentFrame.unionInPlace(outsetBy(
651+
layoutMetricsWithOverflowInset.frame * childTransform, hitSlop));
642652
}
643653
}
644654
}

‎ReactCommon/react/renderer/components/view/tests/LayoutTest.cpp

Lines changed: 88 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,14 @@ namespace facebook::react {
4545
// ***********************│ │************************************
4646
// ***********************└──────────┘************************************
4747

48-
enum TestCase { AS_IS, CLIPPING, TRANSFORM_SCALE, TRANSFORM_TRANSLATE };
48+
enum TestCase {
49+
AS_IS,
50+
CLIPPING,
51+
HIT_SLOP,
52+
HIT_SLOP_TRANSFORM_TRANSLATE,
53+
TRANSFORM_SCALE,
54+
TRANSFORM_TRANSLATE,
55+
};
4956

5057
class LayoutTest : public ::testing::Test {
5158
protected:
@@ -105,9 +112,14 @@ class LayoutTest : public ::testing::Test {
105112
props.transform = props.transform * Transform::Scale(2, 2, 1);
106113
}
107114

108-
if (testCase == TRANSFORM_TRANSLATE) {
115+
if (testCase == TRANSFORM_TRANSLATE || testCase == HIT_SLOP_TRANSFORM_TRANSLATE) {
109116
props.transform = props.transform * Transform::Translate(10, 10, 0);
110117
}
118+
119+
if (testCase == HIT_SLOP || testCase == HIT_SLOP_TRANSFORM_TRANSLATE) {
120+
props.hitSlop = EdgeInsets{50, 50, 50, 50};
121+
}
122+
111123
return sharedProps;
112124
})
113125
.children({
@@ -358,4 +370,78 @@ TEST_F(LayoutTest, overflowInsetTransformScaleTest) {
358370
EXPECT_EQ(layoutMetricsABC.overflowInset.bottom, 0);
359371
}
360372

373+
TEST_F(LayoutTest, overflowInsetHitSlopTest) {
374+
initialize(HIT_SLOP);
375+
376+
auto layoutMetricsA = viewShadowNodeA_->getLayoutMetrics();
377+
378+
EXPECT_EQ(layoutMetricsA.frame.size.width, 50);
379+
EXPECT_EQ(layoutMetricsA.frame.size.height, 50);
380+
381+
// Change on parent node
382+
EXPECT_EQ(layoutMetricsA.overflowInset.left, -50);
383+
EXPECT_EQ(layoutMetricsA.overflowInset.top, -40);
384+
EXPECT_EQ(layoutMetricsA.overflowInset.right, -80);
385+
EXPECT_EQ(layoutMetricsA.overflowInset.bottom, -100);
386+
387+
auto layoutMetricsAB = viewShadowNodeAB_->getLayoutMetrics();
388+
389+
EXPECT_EQ(layoutMetricsAB.frame.size.width, 30);
390+
EXPECT_EQ(layoutMetricsAB.frame.size.height, 90);
391+
392+
// No change on self node
393+
EXPECT_EQ(layoutMetricsAB.overflowInset.left, -60);
394+
EXPECT_EQ(layoutMetricsAB.overflowInset.top, -40);
395+
EXPECT_EQ(layoutMetricsAB.overflowInset.right, -90);
396+
EXPECT_EQ(layoutMetricsAB.overflowInset.bottom, 0);
397+
398+
auto layoutMetricsABC = viewShadowNodeABC_->getLayoutMetrics();
399+
400+
EXPECT_EQ(layoutMetricsABC.frame.size.width, 110);
401+
EXPECT_EQ(layoutMetricsABC.frame.size.height, 20);
402+
403+
// No change on child node
404+
EXPECT_EQ(layoutMetricsABC.overflowInset.left, 0);
405+
EXPECT_EQ(layoutMetricsABC.overflowInset.top, -50);
406+
EXPECT_EQ(layoutMetricsABC.overflowInset.right, 0);
407+
EXPECT_EQ(layoutMetricsABC.overflowInset.bottom, 0);
408+
}
409+
410+
TEST_F(LayoutTest, overflowInsetHitSlopTransformTranslateTest) {
411+
initialize(HIT_SLOP_TRANSFORM_TRANSLATE);
412+
413+
auto layoutMetricsA = viewShadowNodeA_->getLayoutMetrics();
414+
415+
EXPECT_EQ(layoutMetricsA.frame.size.width, 50);
416+
EXPECT_EQ(layoutMetricsA.frame.size.height, 50);
417+
418+
// Change on parent node
419+
EXPECT_EQ(layoutMetricsA.overflowInset.left, -50);
420+
EXPECT_EQ(layoutMetricsA.overflowInset.top, -40);
421+
EXPECT_EQ(layoutMetricsA.overflowInset.right, -90);
422+
EXPECT_EQ(layoutMetricsA.overflowInset.bottom, -110);
423+
424+
auto layoutMetricsAB = viewShadowNodeAB_->getLayoutMetrics();
425+
426+
EXPECT_EQ(layoutMetricsAB.frame.size.width, 30);
427+
EXPECT_EQ(layoutMetricsAB.frame.size.height, 90);
428+
429+
// No change on self node
430+
EXPECT_EQ(layoutMetricsAB.overflowInset.left, -60);
431+
EXPECT_EQ(layoutMetricsAB.overflowInset.top, -40);
432+
EXPECT_EQ(layoutMetricsAB.overflowInset.right, -90);
433+
EXPECT_EQ(layoutMetricsAB.overflowInset.bottom, 0);
434+
435+
auto layoutMetricsABC = viewShadowNodeABC_->getLayoutMetrics();
436+
437+
EXPECT_EQ(layoutMetricsABC.frame.size.width, 110);
438+
EXPECT_EQ(layoutMetricsABC.frame.size.height, 20);
439+
440+
// No change on child node
441+
EXPECT_EQ(layoutMetricsABC.overflowInset.left, 0);
442+
EXPECT_EQ(layoutMetricsABC.overflowInset.top, -50);
443+
EXPECT_EQ(layoutMetricsABC.overflowInset.right, 0);
444+
EXPECT_EQ(layoutMetricsABC.overflowInset.bottom, 0);
445+
}
446+
361447
} // namespace facebook::react

‎ReactCommon/react/renderer/graphics/RectangleEdges.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,16 @@ inline Rect insetBy(Rect const &rect, EdgeInsets const &insets) noexcept {
8383
rect.size.height - insets.top - insets.bottom}};
8484
}
8585

86+
/*
87+
* Adjusts a rectangle by the given edge outsets.
88+
*/
89+
inline Rect outsetBy(Rect const &rect, EdgeInsets const &outsets) noexcept {
90+
return Rect{
91+
{rect.origin.x - outsets.left, rect.origin.y - outsets.top},
92+
{rect.size.width + outsets.left + outsets.right,
93+
rect.size.height + outsets.top + outsets.bottom}};
94+
}
95+
8696
} // namespace react
8797
} // namespace facebook
8898

0 commit comments

Comments
 (0)
Please sign in to comment.