Skip to content

Commit

Permalink
Fabric: Refinement of LayoutableShadowNode::getRelativeLayoutMetrics
Browse files Browse the repository at this point in the history
Summary:
This diff simplifies the implementation of `LayoutableShadowNode::getRelativeLayoutMetrics`.

It fixes a small bug but the most important change is the new interface.

Now the function that does measurements accepts a node and a family instead of two nodes. It prevents misuse and misinterpretation of what the function does. The function needs two things to perform measurement:
 * an ancestor node that defines the tree is being measured and the base node of measurement;
* a family of some descendant node being measured relative to the ancestor node.

An API that accepts two nodes is misleading because it implies that the given descendant node will be measured (which is not true).

Changelog: [Internal] Fabric-specific internal change.

Reviewed By: sammy-SC

Differential Revision: D21480200

fbshipit-source-id: 9fddc361417fee47bbf66cc7ac2954eb088a3179
  • Loading branch information
shergin authored and facebook-github-bot committed May 18, 2020
1 parent 656db78 commit caab26e
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 64 deletions.
140 changes: 76 additions & 64 deletions ReactCommon/fabric/core/layout/LayoutableShadowNode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,54 +17,95 @@
namespace facebook {
namespace react {

/*
* `shadowNode` might not be the newest revision of `ShadowNodeFamily`.
* This function looks at `parentNode`'s children and finds one that belongs
* to the same family as `shadowNode`.
*/
static ShadowNode const *findNewestChildInParent(
ShadowNode const &parentNode,
ShadowNode const &shadowNode) {
for (auto const &child : parentNode.getChildren()) {
if (ShadowNode::sameFamily(*child, shadowNode)) {
return child.get();
LayoutMetrics LayoutableShadowNode::computeRelativeLayoutMetrics(
ShadowNodeFamily const &descendantNodeFamily,
LayoutableShadowNode const &ancestorNode,
LayoutInspectingPolicy policy) {
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`.
auto layoutMetrics = ancestorNode.getLayoutMetrics();
if (policy.includeTransform) {
layoutMetrics.frame = layoutMetrics.frame * ancestorNode.getTransform();
}
layoutMetrics.frame.origin = {0, 0};
return layoutMetrics;
}

auto ancestors = descendantNodeFamily.getAncestors(ancestorNode);

if (ancestors.size() == 0) {
// Specified nodes do not form an ancestor-descender relationship
// in the same tree. Aborting.
return EmptyLayoutMetrics;
}
return nullptr;
}

static LayoutMetrics calculateOffsetForLayoutMetrics(
LayoutMetrics layoutMetrics,
ShadowNode::AncestorList const &ancestors,
LayoutableShadowNode::LayoutInspectingPolicy const &policy) {
// `AncestorList` starts from the given ancestor node and ends with the parent
// node. We iterate from parent node (reverse iteration) and stop before the
// given ancestor (rend() - 1).
for (auto it = ancestors.rbegin(); it != ancestors.rend() - 1; ++it) {
auto &currentShadowNode = it->first.get();

if (currentShadowNode.getTraits().check(
ShadowNodeTraits::Trait::RootNodeKind)) {
// Step 1.
// Creating a list of nodes that form a chain from the descender node to
// ancestor node inclusively.
auto shadowNodeList = better::small_vector<ShadowNode const *, 16>{};

// Finding the measured node.
// The last element in the `AncestorList` is a pair of a parent of the node
// and an index of this node in the parent's children list.
auto &pair = ancestors.at(ancestors.size() - 1);
auto descendantNode = pair.first.get().getChildren().at(pair.second).get();

// Putting the node inside the list.
// Even if this is a node with a `RootNodeKind` trait, we don't treat it as
// root because we measure it from an outside tree perspective.
shadowNodeList.push_back(descendantNode);

for (auto it = ancestors.rbegin(); it != ancestors.rend(); it++) {
auto &shadowNode = it->first.get();

shadowNodeList.push_back(&shadowNode);

if (shadowNode.getTraits().check(ShadowNodeTraits::Trait::RootNodeKind)) {
// If this is a node with a `RootNodeKind` trait, we need to stop right
// there.
break;
}
}

// Step 2.
// Computing the initial size of the measured node.
auto descendantLayoutableNode =
traitCast<LayoutableShadowNode const *>(descendantNode);

auto layoutableCurrentShadowNode =
dynamic_cast<LayoutableShadowNode const *>(&currentShadowNode);
if (!descendantLayoutableNode) {
return EmptyLayoutMetrics;
}

if (!layoutableCurrentShadowNode) {
auto layoutMetrics = descendantLayoutableNode->getLayoutMetrics();
auto &resultFrame = layoutMetrics.frame;
resultFrame.origin = {0, 0};

// Step 3.
// Iterating on a list of nodes computing compound offset.
auto size = shadowNodeList.size();
for (int i = 0; i < size; i++) {
auto currentShadowNode =
traitCast<LayoutableShadowNode const *>(shadowNodeList.at(i));

if (!currentShadowNode) {
return EmptyLayoutMetrics;
}

auto frame = layoutableCurrentShadowNode->getLayoutMetrics().frame;
auto currentFrame = currentShadowNode->getLayoutMetrics().frame;
if (i == size - 1) {
// If it's the last element, its origin is irrelevant.
currentFrame.origin = {0, 0};
}

if (policy.includeTransform) {
layoutMetrics.frame.size = layoutMetrics.frame.size *
layoutableCurrentShadowNode->getTransform();
frame = frame * layoutableCurrentShadowNode->getTransform();
resultFrame.size = resultFrame.size * currentShadowNode->getTransform();
currentFrame = currentFrame * currentShadowNode->getTransform();
}

layoutMetrics.frame.origin += frame.origin;
resultFrame.origin += currentFrame.origin;
}

return layoutMetrics;
}

Expand Down Expand Up @@ -109,37 +150,8 @@ Transform LayoutableShadowNode::getTransform() const {
LayoutMetrics LayoutableShadowNode::getRelativeLayoutMetrics(
LayoutableShadowNode const &ancestorLayoutableShadowNode,
LayoutInspectingPolicy policy) const {
auto &ancestorShadowNode =
dynamic_cast<ShadowNode const &>(ancestorLayoutableShadowNode);
auto &shadowNode = dynamic_cast<ShadowNode const &>(*this);

if (ShadowNode::sameFamily(shadowNode, ancestorShadowNode)) {
auto layoutMetrics = getLayoutMetrics();
layoutMetrics.frame.origin = {0, 0};
return layoutMetrics;
}

auto ancestors = shadowNode.getFamily().getAncestors(ancestorShadowNode);

if (ancestors.empty()) {
return EmptyLayoutMetrics;
}

auto newestChild =
findNewestChildInParent(ancestors.rbegin()->first.get(), shadowNode);

if (!newestChild) {
return EmptyLayoutMetrics;
}

auto layoutableNewestChild =
dynamic_cast<LayoutableShadowNode const *>(newestChild);
auto layoutMetrics = layoutableNewestChild->getLayoutMetrics();
if (policy.includeTransform) {
layoutMetrics.frame =
layoutMetrics.frame * layoutableNewestChild->getTransform();
}
return calculateOffsetForLayoutMetrics(layoutMetrics, ancestors, policy);
return computeRelativeLayoutMetrics(
getFamily(), ancestorLayoutableShadowNode, policy);
}

LayoutableShadowNode::UnsharedList
Expand Down
19 changes: 19 additions & 0 deletions ReactCommon/fabric/core/layout/LayoutableShadowNode.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,17 @@ class LayoutableShadowNode : public ShadowNode {
using UnsharedList = better::
small_vector<LayoutableShadowNode *, kShadowNodeChildrenSmallVectorSize>;

/*
* Returns layout metrics of a node represented as `descendantNodeFamily`
* computed relatively to given `ancestorNode`. Returns `EmptyLayoutMetrics`
* if the nodes don't form an ancestor-descender relationship in the same
* tree.
*/
static LayoutMetrics computeRelativeLayoutMetrics(
ShadowNodeFamily const &descendantNodeFamily,
LayoutableShadowNode const &ancestorNode,
LayoutInspectingPolicy policy);

/*
* Performs layout of the tree starting from this node. Usually is being
* called on the root node.
Expand Down Expand Up @@ -99,6 +110,14 @@ class LayoutableShadowNode : public ShadowNode {
*/
virtual Transform getTransform() const;

/*
* Returns layout metrics relatively to the given ancestor node.
* Uses `computeRelativeLayoutMetrics()` under the hood.
*/
LayoutMetrics getRelativeLayoutMetrics(
ShadowNodeFamily const &descendantNodeFamily,
LayoutInspectingPolicy policy) const;

/*
* Returns layout metrics relatively to the given ancestor node.
*/
Expand Down
6 changes: 6 additions & 0 deletions ReactCommon/fabric/core/tests/LayoutableShadowNodeTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ TEST_F(LayoutableShadowNodeTest, relativeLayoutMetricsOnTransformedNode) {
EXPECT_EQ(relativeLayoutMetrics.frame.origin.y, 70);
EXPECT_EQ(relativeLayoutMetrics.frame.size.width, 50);
EXPECT_EQ(relativeLayoutMetrics.frame.size.height, 100);

nodeAA_->_transform = Transform::Identity();
}

TEST_F(LayoutableShadowNodeTest, relativeLayoutMetricsOnTransformedParent) {
Expand All @@ -160,6 +162,8 @@ TEST_F(LayoutableShadowNodeTest, relativeLayoutMetricsOnTransformedParent) {

EXPECT_EQ(relativeLayoutMetrics.frame.size.width, 25);
EXPECT_EQ(relativeLayoutMetrics.frame.size.height, 25);

nodeAAA_->_transform = Transform::Identity();
}

TEST_F(LayoutableShadowNodeTest, relativeLayoutMetricsOnSameNode) {
Expand Down Expand Up @@ -189,6 +193,8 @@ TEST_F(LayoutableShadowNodeTest, relativeLayoutMetricsOnSameTransformedNode) {
EXPECT_EQ(relativeLayoutMetrics.frame.origin.y, 0);
EXPECT_EQ(relativeLayoutMetrics.frame.size.width, 200);
EXPECT_EQ(relativeLayoutMetrics.frame.size.height, 400);

nodeA_->_transform = Transform::Identity();
}

TEST_F(LayoutableShadowNodeTest, relativeLayourMetricsOnClonedNode) {
Expand Down

0 comments on commit caab26e

Please sign in to comment.