Skip to content

Commit

Permalink
add getOverflowInsetFrame to LayoutMetrics (#49197)
Browse files Browse the repository at this point in the history
Summary:

changelog: [internal]

Adds new method to LayoutMetrics that calculates frame adjusted for overflow inset.


For example, for the following view hierarchy. it would produce a frame that would fully contain view A and view B.
```
┌─────────────┐
│<View A />   │
│     ┌───────┴─────┐
└─────┤<View B />   │
      │             │
      └─────────────┘
```


See tests for more details

Reviewed By: javache, lenaic

Differential Revision: D68775683
  • Loading branch information
sammy-SC authored and facebook-github-bot committed Feb 7, 2025
1 parent ec5bd77 commit 05357cc
Show file tree
Hide file tree
Showing 2 changed files with 298 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include <react/renderer/graphics/Rect.h>
#include <react/renderer/graphics/RectangleEdges.h>
#include <react/utils/hash_combine.h>
#include <algorithm>

namespace facebook::react {

Expand Down Expand Up @@ -55,6 +56,21 @@ struct LayoutMetrics {
frame.size.height - contentInsets.top - contentInsets.bottom}};
}

// Calculates the frame of the node including overflow insets.
// If the frame was drawn on screen, it would include all the children of the
// node (and their children, recursively).
Rect getOverflowInsetFrame() const {
return Rect{
Point{
frame.origin.x + std::min(Float{0}, overflowInset.left),
frame.origin.y + std::min(Float{0}, overflowInset.top)},
Size{
frame.size.width - std::min(Float{0}, overflowInset.left) +
-std::min(Float{0}, overflowInset.right),
frame.size.height - std::min(Float{0}, overflowInset.top) +
-std::min(Float{0}, overflowInset.bottom)}};
}

// Origin: the outer border of the node.
// Size: includes content and padding (but no borders).
Rect getPaddingFrame() const {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
#include <react/renderer/element/Element.h>
#include <react/renderer/element/testUtils.h>

using namespace facebook::react;
namespace facebook::react {

/*
* ┌────────┐
Expand Down Expand Up @@ -311,6 +311,285 @@ TEST(LayoutableShadowNodeTest, relativeLayoutMetricsOnTransformedNode) {
EXPECT_EQ(relativeLayoutMetrics.frame.size.height, 100);
}

/*
* View B is child of View A.
* In this example, there is no overflow and view B is completely within view A.
*
* ┌──────────────┐
* │<View A /> │
* │ ┌───────────┐│
* │ │<View B /> ││
* │ │ ││
* │ └───────────┘│
* └──────────────┘
*/
TEST(LayoutableShadowNodeTest, noOverflow) {
auto builder = simpleComponentBuilder();
auto shadowNode = std::shared_ptr<ViewShadowNode>{};
// clang-format off
auto element =
Element<RootShadowNode>()
.props([] {
auto sharedProps = std::make_shared<RootProps>();
auto &props = *sharedProps;
props.layoutConstraints = LayoutConstraints{{0,0}, {500, 500}};
auto &yogaStyle = props.yogaStyle;
yogaStyle.setDimension(yoga::Dimension::Width, yoga::StyleSizeLength::points(500));
yogaStyle.setDimension(yoga::Dimension::Height, yoga::StyleSizeLength::points(500));
return sharedProps;
}).children({
Element<ViewShadowNode>()
.props([=] {
auto sharedProps = std::make_shared<ViewShadowNodeProps>();
auto &props = *sharedProps;
auto &yogaStyle = props.yogaStyle;
yogaStyle.setDimension(yoga::Dimension::Width, yoga::StyleSizeLength::points(100));
yogaStyle.setDimension(yoga::Dimension::Height, yoga::StyleSizeLength::points(200));
return sharedProps;
})
.reference(shadowNode)
.children({
Element<ViewShadowNode>()
.props([] {
auto sharedProps = std::make_shared<ViewShadowNodeProps>();
auto &props = *sharedProps;
auto &yogaStyle = props.yogaStyle;
yogaStyle.setPositionType(yoga::PositionType::Absolute);
yogaStyle.setPosition(yoga::Edge::Left, yoga::StyleLength::points(50));
yogaStyle.setPosition(yoga::Edge::Top, yoga::StyleLength::points(100));
yogaStyle.setDimension(yoga::Dimension::Width, yoga::StyleSizeLength::points(25));
yogaStyle.setDimension(yoga::Dimension::Height, yoga::StyleSizeLength::points(50));
return sharedProps;
})
})
});
// clang-format on

auto parentShadowNode = builder.build(element);

parentShadowNode->layoutIfNeeded();

auto overflowInsetFrame =
shadowNode->getLayoutMetrics().getOverflowInsetFrame();
EXPECT_EQ(overflowInsetFrame.origin.x, 0);
EXPECT_EQ(overflowInsetFrame.origin.y, 0);
EXPECT_EQ(overflowInsetFrame.size.width, 100);
EXPECT_EQ(overflowInsetFrame.size.height, 200);
}

/*
* View B is child of view A.
* In this example, view B overflows from view A to the right and down.
*
* ┌─────────────┐
* │<View A /> │
* │ ┌───────┴─────┐
* └─────┤<View B /> │
* │ │
* └─────────────┘
*/
TEST(LayoutableShadowNodeTest, overflowInsetFrameToRightAndDown) {
auto builder = simpleComponentBuilder();
auto shadowNode = std::shared_ptr<ViewShadowNode>{};
// clang-format off
auto element =
Element<RootShadowNode>()
.props([] {
auto sharedProps = std::make_shared<RootProps>();
auto &props = *sharedProps;
props.layoutConstraints = LayoutConstraints{{0,0}, {500, 500}};
auto &yogaStyle = props.yogaStyle;
yogaStyle.setDimension(yoga::Dimension::Width, yoga::StyleSizeLength::points(500));
yogaStyle.setDimension(yoga::Dimension::Height, yoga::StyleSizeLength::points(500));
return sharedProps;
}).children({
Element<ViewShadowNode>()
.props([=] {
auto sharedProps = std::make_shared<ViewShadowNodeProps>();
auto &props = *sharedProps;
auto &yogaStyle = props.yogaStyle;
yogaStyle.setDimension(yoga::Dimension::Width, yoga::StyleSizeLength::points(100));
yogaStyle.setDimension(yoga::Dimension::Height, yoga::StyleSizeLength::points(200));
return sharedProps;
})
.reference(shadowNode)
.children({
Element<ViewShadowNode>()
.props([] {
auto sharedProps = std::make_shared<ViewShadowNodeProps>();
auto &props = *sharedProps;
auto &yogaStyle = props.yogaStyle;
yogaStyle.setPositionType(yoga::PositionType::Absolute);
yogaStyle.setPosition(yoga::Edge::Left, yoga::StyleLength::points(50));
yogaStyle.setPosition(yoga::Edge::Top, yoga::StyleLength::points(100));
yogaStyle.setDimension(yoga::Dimension::Width, yoga::StyleSizeLength::points(75));
yogaStyle.setDimension(yoga::Dimension::Height, yoga::StyleSizeLength::points(125));
return sharedProps;
})
})
});
// clang-format on

auto parentShadowNode = builder.build(element);

parentShadowNode->layoutIfNeeded();

auto overflowInsetFrame =
shadowNode->getLayoutMetrics().getOverflowInsetFrame();
EXPECT_EQ(overflowInsetFrame.origin.x, 0);
EXPECT_EQ(overflowInsetFrame.origin.y, 0);
EXPECT_EQ(overflowInsetFrame.size.width, 125);
EXPECT_EQ(overflowInsetFrame.size.height, 225);
}

/*
* View B is child of View A.
* In this example, view B overflows from view A to the left and top.
*
* ┌─────────────┐
* │<View B /> │
* │ ├───────────┐
* └───────┬─────┘ │
* │ <View A /> │
* │ │
* └─────────────────┘
*/
TEST(LayoutableShadowNodeTest, overflowInsetFrameToLeftAndTop) {
auto builder = simpleComponentBuilder();
auto shadowNode = std::shared_ptr<ViewShadowNode>{};
// clang-format off
auto element =
Element<RootShadowNode>()
.props([] {
auto sharedProps = std::make_shared<RootProps>();
auto &props = *sharedProps;
props.layoutConstraints = LayoutConstraints{{0,0}, {500, 500}};
auto &yogaStyle = props.yogaStyle;
yogaStyle.setDimension(yoga::Dimension::Width, yoga::StyleSizeLength::points(500));
yogaStyle.setDimension(yoga::Dimension::Height, yoga::StyleSizeLength::points(500));
return sharedProps;
}).children({
Element<ViewShadowNode>()
.props([=] {
auto sharedProps = std::make_shared<ViewShadowNodeProps>();
auto &props = *sharedProps;
auto &yogaStyle = props.yogaStyle;
yogaStyle.setDimension(yoga::Dimension::Width, yoga::StyleSizeLength::points(100));
yogaStyle.setDimension(yoga::Dimension::Height, yoga::StyleSizeLength::points(200));
return sharedProps;
})
.reference(shadowNode)
.children({
Element<ViewShadowNode>()
.props([] {
auto sharedProps = std::make_shared<ViewShadowNodeProps>();
auto &props = *sharedProps;
auto &yogaStyle = props.yogaStyle;
yogaStyle.setPositionType(yoga::PositionType::Absolute);
yogaStyle.setPosition(yoga::Edge::Left, yoga::StyleLength::points(-50));
yogaStyle.setPosition(yoga::Edge::Top, yoga::StyleLength::points(-100));
yogaStyle.setDimension(yoga::Dimension::Width, yoga::StyleSizeLength::points(75));
yogaStyle.setDimension(yoga::Dimension::Height, yoga::StyleSizeLength::points(125));
return sharedProps;
})
})
});
// clang-format on

auto parentShadowNode = builder.build(element);

parentShadowNode->layoutIfNeeded();

auto overflowInsetFrame =
shadowNode->getLayoutMetrics().getOverflowInsetFrame();
EXPECT_EQ(overflowInsetFrame.origin.x, -50);
EXPECT_EQ(overflowInsetFrame.origin.y, -100);
EXPECT_EQ(overflowInsetFrame.size.width, 150);
EXPECT_EQ(overflowInsetFrame.size.height, 300);
}

/*
* View B and view C are children of View A.
* In this example, view B overflows from view A to the left and top and view C
* overflows from view A to the right and down.
*
* ┌─────────────┐
* │<View B /> │
* │ ├───────────┐
* └───────┬─────┘ │
* │ <View A /> │
* │ │
* │ ┌───────────┴─┐
* └─────┤<View C /> │
* │ │
* └─────────────┘
*/
TEST(LayoutableShadowNodeTest, overflowInsetFrameToAllSides) {
auto builder = simpleComponentBuilder();
auto shadowNode = std::shared_ptr<ViewShadowNode>{};
// clang-format off
auto element =
Element<RootShadowNode>()
.props([] {
auto sharedProps = std::make_shared<RootProps>();
auto &props = *sharedProps;
props.layoutConstraints = LayoutConstraints{{0,0}, {500, 500}};
auto &yogaStyle = props.yogaStyle;
yogaStyle.setDimension(yoga::Dimension::Width, yoga::StyleSizeLength::points(500));
yogaStyle.setDimension(yoga::Dimension::Height, yoga::StyleSizeLength::points(500));
return sharedProps;
}).children({
Element<ViewShadowNode>()
.props([=] {
auto sharedProps = std::make_shared<ViewShadowNodeProps>();
auto &props = *sharedProps;
auto &yogaStyle = props.yogaStyle;
yogaStyle.setDimension(yoga::Dimension::Width, yoga::StyleSizeLength::points(100));
yogaStyle.setDimension(yoga::Dimension::Height, yoga::StyleSizeLength::points(200));
return sharedProps;
})
.reference(shadowNode)
.children({
Element<ViewShadowNode>()
.props([] {
auto sharedProps = std::make_shared<ViewShadowNodeProps>();
auto &props = *sharedProps;
auto &yogaStyle = props.yogaStyle;
yogaStyle.setPositionType(yoga::PositionType::Absolute);
yogaStyle.setPosition(yoga::Edge::Left, yoga::StyleLength::points(-50));
yogaStyle.setPosition(yoga::Edge::Top, yoga::StyleLength::points(-100));
yogaStyle.setDimension(yoga::Dimension::Width, yoga::StyleSizeLength::points(75));
yogaStyle.setDimension(yoga::Dimension::Height, yoga::StyleSizeLength::points(125));
return sharedProps;
}),
Element<ViewShadowNode>()
.props([] {
auto sharedProps = std::make_shared<ViewShadowNodeProps>();
auto &props = *sharedProps;
auto &yogaStyle = props.yogaStyle;
yogaStyle.setPositionType(yoga::PositionType::Absolute);
yogaStyle.setPosition(yoga::Edge::Left, yoga::StyleLength::points(50));
yogaStyle.setPosition(yoga::Edge::Top, yoga::StyleLength::points(100));
yogaStyle.setDimension(yoga::Dimension::Width, yoga::StyleSizeLength::points(75));
yogaStyle.setDimension(yoga::Dimension::Height, yoga::StyleSizeLength::points(125));
return sharedProps;
})
})
});
// clang-format on

auto parentShadowNode = builder.build(element);

parentShadowNode->layoutIfNeeded();

auto overflowInsetFrame =
shadowNode->getLayoutMetrics().getOverflowInsetFrame();
EXPECT_EQ(overflowInsetFrame.origin.x, -50);
EXPECT_EQ(overflowInsetFrame.origin.y, -100);
EXPECT_EQ(overflowInsetFrame.size.width, 175);
EXPECT_EQ(overflowInsetFrame.size.height, 325);
}

/*
* ┌────────────────────────┐
* │<Root> │
Expand Down Expand Up @@ -1146,3 +1425,5 @@ TEST(LayoutableShadowNodeTest, inversedContentOriginOffset) {
EXPECT_EQ(relativeLayoutMetrics.frame.origin.x, 180);
EXPECT_EQ(relativeLayoutMetrics.frame.origin.y, 130);
}

} // namespace facebook::react

0 comments on commit 05357cc

Please sign in to comment.