diff --git a/ReactCommon/react/renderer/core/LayoutableShadowNode.cpp b/ReactCommon/react/renderer/core/LayoutableShadowNode.cpp index c5684692257915..7f1802906df70f 100644 --- a/ReactCommon/react/renderer/core/LayoutableShadowNode.cpp +++ b/ReactCommon/react/renderer/core/LayoutableShadowNode.cpp @@ -17,6 +17,60 @@ namespace facebook { namespace react { +template +using LayoutableSmallVector = butter::small_vector; + +static LayoutableSmallVector calculateTransformedFrames( + LayoutableSmallVector const &shadowNodeList, + LayoutableShadowNode::LayoutInspectingPolicy policy) { + auto size = shadowNodeList.size(); + auto transformedFrames = LayoutableSmallVector{size}; + auto transformation = Transform::Identity(); + + for (int i = size - 1; i >= 0; --i) { + auto currentShadowNode = + traitCast(shadowNodeList.at(i)); + auto currentFrame = currentShadowNode->getLayoutMetrics().frame; + + if (policy.includeTransform) { + if (Transform::isVerticalInversion(transformation)) { + auto parentShadowNode = + traitCast(shadowNodeList.at(i + 1)); + currentFrame.origin.y = + parentShadowNode->getLayoutMetrics().frame.size.height - + currentFrame.size.height - currentFrame.origin.y; + } + + if (Transform::isHorizontalInversion(transformation)) { + auto parentShadowNode = + traitCast(shadowNodeList.at(i + 1)); + currentFrame.origin.x = + parentShadowNode->getLayoutMetrics().frame.size.width - + currentFrame.size.width - currentFrame.origin.x; + } + + if (i != size - 1) { + auto parentShadowNode = + traitCast(shadowNodeList.at(i + 1)); + auto contentOritinOffset = parentShadowNode->getContentOriginOffset(); + if (Transform::isVerticalInversion(transformation)) { + contentOritinOffset.y = -contentOritinOffset.y; + } + if (Transform::isHorizontalInversion(transformation)) { + contentOritinOffset.x = -contentOritinOffset.x; + } + currentFrame.origin += contentOritinOffset; + } + + transformation = transformation * currentShadowNode->getTransform(); + } + + transformedFrames[i] = currentFrame; + } + + return transformedFrames; +} + LayoutableShadowNode::LayoutableShadowNode( ShadowNodeFragment const &fragment, ShadowNodeFamily::Shared const &family, @@ -34,6 +88,8 @@ LayoutMetrics LayoutableShadowNode::computeRelativeLayoutMetrics( ShadowNodeFamily const &descendantNodeFamily, LayoutableShadowNode const &ancestorNode, LayoutInspectingPolicy policy) { + // Prelude. + if (&descendantNodeFamily == &ancestorNode.getFamily()) { // Layout metrics of a node computed relatively to the same node are equal // to `transform`-ed layout metrics of the node with zero `origin`. @@ -53,10 +109,12 @@ LayoutMetrics LayoutableShadowNode::computeRelativeLayoutMetrics( return EmptyLayoutMetrics; } + // ------------------------------ + // Step 1. // Creating a list of nodes that form a chain from the descender node to // ancestor node inclusively. - auto shadowNodeList = butter::small_vector{}; + auto shadowNodeList = LayoutableSmallVector{}; // Finding the measured node. // The last element in the `AncestorList` is a pair of a parent of the node @@ -81,6 +139,8 @@ LayoutMetrics LayoutableShadowNode::computeRelativeLayoutMetrics( } } + // ------------------------------ + // Step 2. // Computing the initial size of the measured node. auto descendantLayoutableNode = @@ -90,6 +150,9 @@ LayoutMetrics LayoutableShadowNode::computeRelativeLayoutMetrics( return EmptyLayoutMetrics; } + // ------------------------------ + + auto transformedFrames = calculateTransformedFrames(shadowNodeList, policy); auto layoutMetrics = descendantLayoutableNode->getLayoutMetrics(); auto &resultFrame = layoutMetrics.frame; resultFrame.origin = {0, 0}; @@ -105,7 +168,7 @@ LayoutMetrics LayoutableShadowNode::computeRelativeLayoutMetrics( return EmptyLayoutMetrics; } - auto currentFrame = currentShadowNode->getLayoutMetrics().frame; + auto currentFrame = transformedFrames[i]; if (i == size - 1) { // If it's the last element, its origin is irrelevant. currentFrame.origin = {0, 0}; @@ -122,12 +185,10 @@ LayoutMetrics LayoutableShadowNode::computeRelativeLayoutMetrics( } resultFrame.origin += currentFrame.origin; - - if (i != 0 && policy.includeTransform) { - resultFrame.origin += currentShadowNode->getContentOriginOffset(); - } } + // ------------------------------ + return layoutMetrics; } diff --git a/ReactCommon/react/renderer/core/tests/LayoutableShadowNodeTest.cpp b/ReactCommon/react/renderer/core/tests/LayoutableShadowNodeTest.cpp index 55c761c9542457..8fde7fd7122701 100644 --- a/ReactCommon/react/renderer/core/tests/LayoutableShadowNodeTest.cpp +++ b/ReactCommon/react/renderer/core/tests/LayoutableShadowNodeTest.cpp @@ -431,3 +431,347 @@ TEST(LayoutableShadowNodeTest, includeViewportOffset) { EXPECT_EQ(layoutMetrics.frame.origin.x, 10); EXPECT_EQ(layoutMetrics.frame.origin.y, 20); } + +/* + * ┌───────────────────────────────┐ + * │ │ + * │ │ + * │┌─────────────────────────────┐│ + * ││ ││ + * ││ ││ + * │└─────────────────────────────┘│ + * │┌─────────────────────────────┐│ + * ││ ││ + * ││ ││ + * │└─────────────────────────────┘│ + * └───────────────────────────────┘ + */ +TEST(LayoutableShadowNodeTest, invertedVerticalView) { + auto builder = simpleComponentBuilder(); + auto childShadowNode1 = std::shared_ptr{}; + auto childShadowNode2 = std::shared_ptr{}; + // clang-format off + auto element = + Element() + .props([] { + auto sharedProps = std::make_shared(); + sharedProps->transform = Transform::VerticalInversion(); // Inverted + return sharedProps; + }) + .finalize([](ViewShadowNode &shadowNode){ + auto layoutMetrics = EmptyLayoutMetrics; + layoutMetrics.frame.size = {200, 200}; + shadowNode.setLayoutMetrics(layoutMetrics); + }).children({ + Element() + .reference(childShadowNode1) + .finalize([](ViewShadowNode &shadowNode){ + auto layoutMetrics = EmptyLayoutMetrics; + layoutMetrics.frame.origin = {0, 0}; + layoutMetrics.frame.size = {100, 100}; + shadowNode.setLayoutMetrics(layoutMetrics); + }), + Element() + .reference(childShadowNode2) + .finalize([](ViewShadowNode &shadowNode){ + auto layoutMetrics = EmptyLayoutMetrics; + layoutMetrics.frame.origin = {0, 100}; + layoutMetrics.frame.size = {100, 100}; + shadowNode.setLayoutMetrics(layoutMetrics); + }) + }); + // clang-format on + + auto scrollShadowNode = builder.build(element); + + auto firstItemRelativeLayoutMetrics = + LayoutableShadowNode::computeRelativeLayoutMetrics( + childShadowNode1->getFamily(), *scrollShadowNode, {}); + + EXPECT_EQ(firstItemRelativeLayoutMetrics.frame.origin.x, 0); + EXPECT_EQ(firstItemRelativeLayoutMetrics.frame.origin.y, 100); + EXPECT_EQ(firstItemRelativeLayoutMetrics.frame.size.width, 100); + EXPECT_EQ(firstItemRelativeLayoutMetrics.frame.size.height, 100); + + auto secondItemRelativeLayoutMetrics = + LayoutableShadowNode::computeRelativeLayoutMetrics( + childShadowNode2->getFamily(), *scrollShadowNode, {}); + + EXPECT_EQ(secondItemRelativeLayoutMetrics.frame.origin.x, 0); + EXPECT_EQ(secondItemRelativeLayoutMetrics.frame.origin.y, 0); + EXPECT_EQ(secondItemRelativeLayoutMetrics.frame.size.width, 100); + EXPECT_EQ(secondItemRelativeLayoutMetrics.frame.size.height, 100); +} + +/* + * ┌────────────────────────────────────┐ + * │ │ + * │ │ + * │ ┌───────────────────────────────┐ │ + * │ │ │ │ + * │ │ │ │ + * │ │┌─────────────────────────────┐│ │ + * │ ││ ││ │ + * │ ││ ││ │ + * │ │└─────────────────────────────┘│ │ + * │ │┌─────────────────────────────┐│ │ + * │ ││ ││ │ + * │ ││ ││ │ + * │ │└─────────────────────────────┘│ │ + * │ └───────────────────────────────┘ │ + * └────────────────────────────────────┘ + */ +TEST(LayoutableShadowNodeTest, nestedInvertedVerticalView) { + auto builder = simpleComponentBuilder(); + auto childShadowNode1 = std::shared_ptr{}; + auto childShadowNode2 = std::shared_ptr{}; + // clang-format off + auto element = + Element() + .props([] { + auto sharedProps = std::make_shared(); + sharedProps->transform = Transform::VerticalInversion(); // Inverted + return sharedProps; + }) + .finalize([](ViewShadowNode &shadowNode){ + auto layoutMetrics = EmptyLayoutMetrics; + layoutMetrics.frame.size = {400, 400}; + shadowNode.setLayoutMetrics(layoutMetrics); + }) + .children({ + Element() + .finalize([](ViewShadowNode &shadowNode){ + auto layoutMetrics = EmptyLayoutMetrics; + layoutMetrics.frame.origin = {100, 50}; + layoutMetrics.frame.size = {200, 200}; + shadowNode.setLayoutMetrics(layoutMetrics); + }).children({ + Element() + .reference(childShadowNode1) + .finalize([](ViewShadowNode &shadowNode){ + auto layoutMetrics = EmptyLayoutMetrics; + layoutMetrics.frame.origin = {0, 0}; + layoutMetrics.frame.size = {100, 100}; + shadowNode.setLayoutMetrics(layoutMetrics); + }), + Element() + .reference(childShadowNode2) + .finalize([](ViewShadowNode &shadowNode){ + auto layoutMetrics = EmptyLayoutMetrics; + layoutMetrics.frame.origin = {0, 100}; + layoutMetrics.frame.size = {100, 100}; + shadowNode.setLayoutMetrics(layoutMetrics); + }) + }) + }); + // clang-format on + + auto scrollShadowNode = builder.build(element); + + auto firstItemRelativeLayoutMetrics = + LayoutableShadowNode::computeRelativeLayoutMetrics( + childShadowNode1->getFamily(), *scrollShadowNode, {}); + + EXPECT_EQ(firstItemRelativeLayoutMetrics.frame.origin.x, 100); + EXPECT_EQ(firstItemRelativeLayoutMetrics.frame.origin.y, 250); + EXPECT_EQ(firstItemRelativeLayoutMetrics.frame.size.width, 100); + EXPECT_EQ(firstItemRelativeLayoutMetrics.frame.size.height, 100); + + auto secondItemRelativeLayoutMetrics = + LayoutableShadowNode::computeRelativeLayoutMetrics( + childShadowNode2->getFamily(), *scrollShadowNode, {}); + + EXPECT_EQ(secondItemRelativeLayoutMetrics.frame.origin.x, 100); + EXPECT_EQ(secondItemRelativeLayoutMetrics.frame.origin.y, 150); + EXPECT_EQ(secondItemRelativeLayoutMetrics.frame.size.width, 100); + EXPECT_EQ(secondItemRelativeLayoutMetrics.frame.size.height, 100); +} + +/* + * ┌──────────────────────────────────────┐ + * │ │ + * │ │ + * │┌─────────────────┐┌─────────────────┐│ + * ││ ││ ││ + * ││ ││ ││ + * │└─────────────────┘└─────────────────┘│ + * └──────────────────────────────────────┘ + */ +TEST(LayoutableShadowNodeTest, invertedHorizontalView) { + auto builder = simpleComponentBuilder(); + auto childShadowNode1 = std::shared_ptr{}; + auto childShadowNode2 = std::shared_ptr{}; + // clang-format off + auto element = + Element() + .props([] { + auto sharedProps = std::make_shared(); + sharedProps->transform = Transform::HorizontalInversion(); // Inverted + return sharedProps; + }) + .finalize([](ViewShadowNode &shadowNode){ + auto layoutMetrics = EmptyLayoutMetrics; + layoutMetrics.frame.size = {200, 200}; + shadowNode.setLayoutMetrics(layoutMetrics); + }).children({ + Element() + .reference(childShadowNode1) + .finalize([](ViewShadowNode &shadowNode){ + auto layoutMetrics = EmptyLayoutMetrics; + layoutMetrics.frame.origin = {0, 0}; + layoutMetrics.frame.size = {100, 100}; + shadowNode.setLayoutMetrics(layoutMetrics); + }), + Element() + .reference(childShadowNode2) + .finalize([](ViewShadowNode &shadowNode){ + auto layoutMetrics = EmptyLayoutMetrics; + layoutMetrics.frame.origin = {100, 0}; + layoutMetrics.frame.size = {100, 100}; + shadowNode.setLayoutMetrics(layoutMetrics); + }) + }); + // clang-format on + + auto scrollShadowNode = builder.build(element); + + auto firstItemRelativeLayoutMetrics = + LayoutableShadowNode::computeRelativeLayoutMetrics( + childShadowNode1->getFamily(), *scrollShadowNode, {}); + + EXPECT_EQ(firstItemRelativeLayoutMetrics.frame.origin.x, 100); + EXPECT_EQ(firstItemRelativeLayoutMetrics.frame.origin.y, 0); + EXPECT_EQ(firstItemRelativeLayoutMetrics.frame.size.width, 100); + EXPECT_EQ(firstItemRelativeLayoutMetrics.frame.size.height, 100); + + auto secondItemRelativeLayoutMetrics = + LayoutableShadowNode::computeRelativeLayoutMetrics( + childShadowNode2->getFamily(), *scrollShadowNode, {}); + + EXPECT_EQ(secondItemRelativeLayoutMetrics.frame.origin.x, 0); + EXPECT_EQ(secondItemRelativeLayoutMetrics.frame.origin.y, 0); + EXPECT_EQ(secondItemRelativeLayoutMetrics.frame.size.width, 100); + EXPECT_EQ(secondItemRelativeLayoutMetrics.frame.size.height, 100); +} + +/* + * ┌──────────────────────────────────────────┐ + * │ │ + * │ │ + * │ ┌──────────────────────────────────────┐ │ + * │ │ │ │ + * │ │ │ │ + * │ │┌─────────────────┐┌─────────────────┐│ │ + * │ ││ ││ ││ │ + * │ ││ ││ ││ │ + * │ │└─────────────────┘└─────────────────┘│ │ + * │ └──────────────────────────────────────┘ │ + * └──────────────────────────────────────────┘ + */ +TEST(LayoutableShadowNodeTest, nestedInvertedHorizontalView) { + auto builder = simpleComponentBuilder(); + auto childShadowNode1 = std::shared_ptr{}; + auto childShadowNode2 = std::shared_ptr{}; + // clang-format off + auto element = + Element() + .props([] { + auto sharedProps = std::make_shared(); + sharedProps->transform = Transform::HorizontalInversion(); // Inverted + return sharedProps; + }) + .finalize([](ViewShadowNode &shadowNode){ + auto layoutMetrics = EmptyLayoutMetrics; + layoutMetrics.frame.size = {400, 400}; + shadowNode.setLayoutMetrics(layoutMetrics); + }) + .children({ + Element() + .finalize([](ViewShadowNode &shadowNode){ + auto layoutMetrics = EmptyLayoutMetrics; + layoutMetrics.frame.origin = {50, 100}; + layoutMetrics.frame.size = {200, 200}; + shadowNode.setLayoutMetrics(layoutMetrics); + }).children({ + Element() + .reference(childShadowNode1) + .finalize([](ViewShadowNode &shadowNode){ + auto layoutMetrics = EmptyLayoutMetrics; + layoutMetrics.frame.origin = {0, 0}; + layoutMetrics.frame.size = {100, 100}; + shadowNode.setLayoutMetrics(layoutMetrics); + }), + Element() + .reference(childShadowNode2) + .finalize([](ViewShadowNode &shadowNode){ + auto layoutMetrics = EmptyLayoutMetrics; + layoutMetrics.frame.origin = {100, 0}; + layoutMetrics.frame.size = {100, 100}; + shadowNode.setLayoutMetrics(layoutMetrics); + }) + }) + }); + // clang-format on + + auto scrollShadowNode = builder.build(element); + + auto firstItemRelativeLayoutMetrics = + LayoutableShadowNode::computeRelativeLayoutMetrics( + childShadowNode1->getFamily(), *scrollShadowNode, {}); + + EXPECT_EQ(firstItemRelativeLayoutMetrics.frame.origin.x, 250); + EXPECT_EQ(firstItemRelativeLayoutMetrics.frame.origin.y, 100); + EXPECT_EQ(firstItemRelativeLayoutMetrics.frame.size.width, 100); + EXPECT_EQ(firstItemRelativeLayoutMetrics.frame.size.height, 100); + + auto secondItemRelativeLayoutMetrics = + LayoutableShadowNode::computeRelativeLayoutMetrics( + childShadowNode2->getFamily(), *scrollShadowNode, {}); + + EXPECT_EQ(secondItemRelativeLayoutMetrics.frame.origin.x, 150); + EXPECT_EQ(secondItemRelativeLayoutMetrics.frame.origin.y, 100); + EXPECT_EQ(secondItemRelativeLayoutMetrics.frame.size.width, 100); + EXPECT_EQ(secondItemRelativeLayoutMetrics.frame.size.height, 100); +} + +TEST(LayoutableShadowNodeTest, inversedContentOriginOffset) { + auto builder = simpleComponentBuilder(); + + auto childShadowNode = std::shared_ptr{}; + // clang-format off + auto element = + Element() + .props([] { + auto sharedProps = std::make_shared(); + sharedProps->transform = Transform::HorizontalInversion() * Transform::VerticalInversion(); + return sharedProps; + }) + .finalize([](ScrollViewShadowNode &shadowNode){ + auto layoutMetrics = EmptyLayoutMetrics; + layoutMetrics.frame.size = {300, 350}; + shadowNode.setLayoutMetrics(layoutMetrics); + }) + .stateData([](ScrollViewState &data) { + data.contentOffset = {10, 20}; + }) + .children({ + Element() + .finalize([](ViewShadowNode &shadowNode){ + auto layoutMetrics = EmptyLayoutMetrics; + layoutMetrics.frame.origin = {30, 40}; + layoutMetrics.frame.size = {100, 200}; + shadowNode.setLayoutMetrics(layoutMetrics); + }) + .reference(childShadowNode) + }); + // clang-format on + + auto parentShadowNode = builder.build(element); + + auto relativeLayoutMetrics = + LayoutableShadowNode::computeRelativeLayoutMetrics( + childShadowNode->getFamily(), *parentShadowNode, {}); + + EXPECT_EQ(relativeLayoutMetrics.frame.origin.x, 180); + EXPECT_EQ(relativeLayoutMetrics.frame.origin.y, 130); +} diff --git a/ReactCommon/react/renderer/graphics/Transform.cpp b/ReactCommon/react/renderer/graphics/Transform.cpp index d34246fa17ebc3..a8a7f1033b80e0 100644 --- a/ReactCommon/react/renderer/graphics/Transform.cpp +++ b/ReactCommon/react/renderer/graphics/Transform.cpp @@ -32,6 +32,14 @@ Transform Transform::Identity() { return {}; } +Transform Transform::VerticalInversion() { + return Transform::Scale(1, -1, 1); +} + +Transform Transform::HorizontalInversion() { + return Transform::Scale(-1, 1, 1); +} + Transform Transform::Perspective(Float perspective) { auto transform = Transform{}; transform.operations.push_back(TransformOperation{ @@ -241,6 +249,14 @@ Transform Transform::Interpolate( return result; } +bool Transform::isVerticalInversion(Transform const &transform) { + return transform.at(1, 1) == -1; +} + +bool Transform::isHorizontalInversion(Transform const &transform) { + return transform.at(0, 0) == -1; +} + bool Transform::operator==(Transform const &rhs) const { for (auto i = 0; i < 16; i++) { if (matrix[i] != rhs.matrix[i]) { @@ -387,8 +403,8 @@ Size operator*(Size const &size, Transform const &transform) { } auto result = Size{}; - result.width = transform.at(0, 0) * size.width; - result.height = transform.at(1, 1) * size.height; + result.width = std::abs(transform.at(0, 0) * size.width); + result.height = std::abs(transform.at(1, 1) * size.height); return result; } diff --git a/ReactCommon/react/renderer/graphics/Transform.h b/ReactCommon/react/renderer/graphics/Transform.h index a275afb1944183..e24f4af028b2aa 100644 --- a/ReactCommon/react/renderer/graphics/Transform.h +++ b/ReactCommon/react/renderer/graphics/Transform.h @@ -77,6 +77,18 @@ struct Transform { */ static Transform Identity(); + /* + * Returns the vertival inversion transform (`[1 0 0 0; 0 -1 0 0; 0 0 1 0; 0 0 + * 0 1]`). + */ + static Transform VerticalInversion(); + + /* + * Returns the horizontal inversion transform (`[-1 0 0 0; 0 1 0 0; 0 0 1 0; 0 + * 0 0 1]`). + */ + static Transform HorizontalInversion(); + /* * Returns a Perspective transform. */ @@ -121,6 +133,9 @@ struct Transform { Transform const &lhs, Transform const &rhs); + static bool isVerticalInversion(Transform const &transform); + static bool isHorizontalInversion(Transform const &transform); + /* * Equality operators. */ diff --git a/ReactCommon/react/renderer/graphics/tests/TransformTest.cpp b/ReactCommon/react/renderer/graphics/tests/TransformTest.cpp index bcd0641d62f903..70c232ecd019b8 100644 --- a/ReactCommon/react/renderer/graphics/tests/TransformTest.cpp +++ b/ReactCommon/react/renderer/graphics/tests/TransformTest.cpp @@ -41,6 +41,13 @@ TEST(TransformTest, scalingRect) { EXPECT_EQ(transformedRect.size.height, 200); } +TEST(TransformTest, invertingSize) { + auto size = facebook::react::Size{300, 400}; + auto transformedSize = size * Transform::VerticalInversion(); + EXPECT_EQ(transformedSize.width, 300); + EXPECT_EQ(transformedSize.height, 400); +} + TEST(TransformTest, rotatingRect) { auto point = facebook::react::Point{10, 10}; auto size = facebook::react::Size{10, 10};