mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
432b434b55
Summary:
This is a very crucial change, everything else in this stack depends on it.
Here is the set of constraints that we have for ShadowNode-based class hierarchy:
* `ShadowNode` is a base class that defines basic operations on the nodes. It does not have virtual methods by design (virtual dispatch hurts performance; we want to limit and centralize that in `ComponentDescriptor`s).
* `ConcreteShadowNode<>` template *statically* wires particular `ShadowNode` and particular `Props` establishing a type-safe relationship between them ensured on compile time.
* Not all `ShadowNode`s are "layoutable", not all "layoutable" `ShadowNode`s use Yoga to do layout.
* Layotability (and YogaLayotability) feature is implemented as two classes `LayoutableShadowNode` and `YogaLayoutableShadowNode`. These classes essentially need to know something about the ShadowNode nature of the object (get the list of children or replace some children).
Before the change, `LayoutableShadowNode` and `YogaLayoutableShadowNode`classes did not inherit `ShadowNode` because we don't want to use *virtual inheritance* for `ShadowNode`: `ConcreteShadowNode<>` already inherits `ShadowNode`, so if we make `LayoutableShadowNode` inherits `ShadowNode`, we will have to somehow flatten this inheritance hierarchy (and the virtual inheritance is an answer to that). (Yes, C++ supports multiple inheritance and virtual inheritance.)
Before this change, we solved this dilemma this way: We have a subclass-template `ConcreteViewShadowNode<>` that inherits `ConcreteShadowNode<>` and `YogaLayoutableShadowNode`. Then, we had a bunch of methods that implement the rerouting of some functionality from `YogaLayoutableShadowNode` to `ShadowNode` and vise-versa. (See the diagram "Before".)
That worked fine, except the caveats:
* That wiring is nasty, complex, hard to reason about and overall limiting.
* There is no way to statically cast `LayoutableShadowNode` to `ShadowNode` (because there is no common base class). That forces us to use dynamic_cast on some perf critical paths (including layout, diffing and so on).
* Adding features that rely on interop between `LayoutableShadowNode` and `ShadowNode` is a nightmare.
It should be a better way to deal with this dilemma, and this diff implements a different approach: We can have a base class of `ConcreteShadowNode<>` as a template parameter. With this approach, we can make `LayoutableShadowNode` inherit `ShadowNode` and when we need to instantiate a `ConcreteShadowNode<>` that needs to be layoutable, we can just specify `YogaLayoutableShadowNode` as a base class. (See the diagram "After".)
This simple change will allow us to simplify a lot of things. The rest of the stack is about getting rid of unnecessary moving parts. Which will finally allow us to build "Inline Views" feature.
```
╭──────────────────────╮
│ ◎ ○ ○ ░░░░░░░░░░░░░░░│
├──────────────────────┤
│ │
│ │
│ Before │
│ │ ┌────────────────────────────┐ ┌────────────────────────────┐
│ │ │ │ │ │
│ │ │ ShadowNode │ │ LayoutableShadowNode │
└──────────────────────┘ │ │ │ │
└────────────────────────────┘ └────────────────────────────┘
▲ ▲
│ │
╔════════════════════════════╗ ┌────────────────────────────┐
║ ║ │ │
┌────────�║ ConcreteShadowNode<> ║ │ YogaLayoutableShadowNode │
│ ║ ║ │ │
│ ╚════════════════════════════╝ └────────────────────────────┘
│ ▲ ▲
│ │ │
│ │ │
│ │ │
│ │ ╔════════════════════════════╗ │
│ │ ║ ║ │
│ └─────║ ConcreteViewShadowNode<> ║─┘
│ ║ ║
│ ╚════════════════════════════╝
│ ▲
│ │
│ ┌──────────────┴───────────────┐
│ │ │
│ │ │
│ │ │
┌────────────────────────────┐ ┌────────────────────────────┐ ┌────────────────────────────┐
│ │ │ │ │ │
│ TextShadowNode │ │ ViewShadowNode │ │ ParagraphShadowNode │
│ │ │ │ │ │
└────────────────────────────┘ └────────────────────────────┘ └────────────────────────────┘
╭──────────────────────╮
│ ◎ ○ ○ ░░░░░░░░░░░░░░░│
├──────────────────────┤
│ │ ┌────────────────────────────┐
│ │ │ │
│ After │ │ ShadowNode │
│ │ │ │
│ │ └────────────────────────────┘
│ │ ▲
└──────────────────────┘ ┌────────────────────────────────────┤
│ │
│ ╔════════════════════════════╗
┌────────────────────────────┐ ║ ConcreteShadowNode ║
│ │ ║ <Base: ShadowNode> ║
│ LayoutableShadowNode │ ║ ║
│ │ ╚════════════════════════════╝
└────────────────────────────┘ ▲
▲ │
│ ┌────────────────────────────┐
┌────────────────────────────┐ │ │
│ │ │ TextShadowNode │
│ YogaLayoutableShadowNode │ │ │
│ │ └────────────────────────────┘
└────────────────────────────┘
▲
│
╔════════════════════════════╗
║ ConcreteShadowNode ║
║ <Base: ║
║ YogaLayoutableShadowNode> ║
╚════════════════════════════╝
▲
│
╔════════════════════════════╗
║ ║
║ ConcreteViewShadowNode<> ║
║ ║
╚════════════════════════════╝
▲
├─────────────────────────────────────┐
│ │
┌────────────────────────────┐ ┌────────────────────────────┐
│ │ │ │
│ ParagraphShadowNode │ │ ViewShadowNode │
│ │ │ │
└────────────────────────────┘ └────────────────────────────┘
```
Changelog: [Internal] Fabric-specific internal change.
Reviewed By: sammy-SC
Differential Revision: D19963353
fbshipit-source-id: b65c8a5064bdb54ab64f08a8e546aa9e2b5a486b
247 lines
7.1 KiB
C++
247 lines
7.1 KiB
C++
/*
|
|
* 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.
|
|
*/
|
|
|
|
#include "LayoutableShadowNode.h"
|
|
|
|
#include <react/core/LayoutConstraints.h>
|
|
#include <react/core/LayoutContext.h>
|
|
#include <react/core/LayoutMetrics.h>
|
|
#include <react/core/ShadowNode.h>
|
|
#include <react/debug/DebugStringConvertibleItem.h>
|
|
#include <react/graphics/conversions.h>
|
|
|
|
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();
|
|
}
|
|
}
|
|
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 ¤tShadowNode = it->first.get();
|
|
|
|
if (currentShadowNode.getTraits().check(
|
|
ShadowNodeTraits::Trait::RootNodeKind)) {
|
|
break;
|
|
}
|
|
|
|
auto layoutableCurrentShadowNode =
|
|
dynamic_cast<LayoutableShadowNode const *>(¤tShadowNode);
|
|
|
|
if (!layoutableCurrentShadowNode) {
|
|
return EmptyLayoutMetrics;
|
|
}
|
|
|
|
auto origin = layoutableCurrentShadowNode->getLayoutMetrics().frame.origin;
|
|
|
|
if (policy.includeTransform || policy.includeScrollViewContentOffset) {
|
|
// The check for ScrollView will be implemented after we have
|
|
// a dedicated trait (part of `ShadowNodeTraits`) for that.
|
|
origin = origin * layoutableCurrentShadowNode->getTransform();
|
|
}
|
|
|
|
layoutMetrics.frame.origin += origin;
|
|
}
|
|
return layoutMetrics;
|
|
}
|
|
|
|
LayoutableShadowNode::LayoutableShadowNode(
|
|
ShadowNodeFragment const &fragment,
|
|
ShadowNodeFamily::Shared const &family,
|
|
ShadowNodeTraits traits)
|
|
: ShadowNode(fragment, family, traits), layoutMetrics_({}) {}
|
|
|
|
LayoutableShadowNode::LayoutableShadowNode(
|
|
ShadowNode const &sourceShadowNode,
|
|
ShadowNodeFragment const &fragment)
|
|
: ShadowNode(sourceShadowNode, fragment),
|
|
layoutMetrics_(static_cast<LayoutableShadowNode const &>(sourceShadowNode)
|
|
.layoutMetrics_) {}
|
|
|
|
LayoutMetrics LayoutableShadowNode::getLayoutMetrics() const {
|
|
return layoutMetrics_;
|
|
}
|
|
|
|
bool LayoutableShadowNode::setLayoutMetrics(LayoutMetrics layoutMetrics) {
|
|
ensureUnsealed();
|
|
|
|
if (layoutMetrics_ == layoutMetrics) {
|
|
return false;
|
|
}
|
|
|
|
layoutMetrics_ = layoutMetrics;
|
|
return true;
|
|
}
|
|
|
|
bool LayoutableShadowNode::LayoutableShadowNode::isLayoutOnly() const {
|
|
return false;
|
|
}
|
|
|
|
Transform LayoutableShadowNode::getTransform() const {
|
|
return Transform::Identity();
|
|
}
|
|
|
|
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.size() == 0) {
|
|
return EmptyLayoutMetrics;
|
|
}
|
|
|
|
auto newestChild =
|
|
findNewestChildInParent(ancestors.rbegin()->first.get(), shadowNode);
|
|
|
|
if (!newestChild) {
|
|
return EmptyLayoutMetrics;
|
|
}
|
|
|
|
auto layoutMetrics = dynamic_cast<LayoutableShadowNode const *>(newestChild)
|
|
->getLayoutMetrics();
|
|
|
|
return calculateOffsetForLayoutMetrics(layoutMetrics, ancestors, policy);
|
|
}
|
|
|
|
Size LayoutableShadowNode::measure(LayoutConstraints layoutConstraints) const {
|
|
return Size();
|
|
}
|
|
|
|
Float LayoutableShadowNode::firstBaseline(Size size) const {
|
|
return 0;
|
|
}
|
|
|
|
Float LayoutableShadowNode::lastBaseline(Size size) const {
|
|
return 0;
|
|
}
|
|
|
|
void LayoutableShadowNode::layout(LayoutContext layoutContext) {
|
|
layoutChildren(layoutContext);
|
|
|
|
for (auto child : getLayoutableChildNodes()) {
|
|
if (!child->getHasNewLayout()) {
|
|
continue;
|
|
}
|
|
|
|
child->ensureUnsealed();
|
|
child->setHasNewLayout(false);
|
|
|
|
auto childLayoutMetrics = child->getLayoutMetrics();
|
|
if (childLayoutMetrics.displayType == DisplayType::None) {
|
|
continue;
|
|
}
|
|
|
|
auto childLayoutContext = LayoutContext(layoutContext);
|
|
childLayoutContext.absolutePosition += childLayoutMetrics.frame.origin;
|
|
|
|
child->layout(layoutContext);
|
|
}
|
|
}
|
|
|
|
ShadowNode::Shared LayoutableShadowNode::findNodeAtPoint(
|
|
ShadowNode::Shared node,
|
|
Point point) {
|
|
auto layoutableShadowNode =
|
|
dynamic_cast<const LayoutableShadowNode *>(node.get());
|
|
|
|
if (!layoutableShadowNode) {
|
|
return nullptr;
|
|
}
|
|
auto frame = layoutableShadowNode->getLayoutMetrics().frame;
|
|
auto isPointInside = frame.containsPoint(point);
|
|
|
|
if (!isPointInside) {
|
|
return nullptr;
|
|
}
|
|
|
|
auto newPoint = point - frame.origin;
|
|
for (const auto &childShadowNode : node->getChildren()) {
|
|
auto hitView = findNodeAtPoint(childShadowNode, newPoint);
|
|
if (hitView) {
|
|
return hitView;
|
|
}
|
|
}
|
|
return isPointInside ? node : nullptr;
|
|
}
|
|
|
|
void LayoutableShadowNode::layoutChildren(LayoutContext layoutContext) {
|
|
// Default implementation does nothing.
|
|
}
|
|
|
|
#if RN_DEBUG_STRING_CONVERTIBLE
|
|
SharedDebugStringConvertibleList LayoutableShadowNode::getDebugProps() const {
|
|
auto list = SharedDebugStringConvertibleList{};
|
|
|
|
if (getHasNewLayout()) {
|
|
list.push_back(
|
|
std::make_shared<DebugStringConvertibleItem>("hasNewLayout"));
|
|
}
|
|
|
|
if (!getIsLayoutClean()) {
|
|
list.push_back(std::make_shared<DebugStringConvertibleItem>("dirty"));
|
|
}
|
|
|
|
auto layoutMetrics = getLayoutMetrics();
|
|
auto defaultLayoutMetrics = LayoutMetrics();
|
|
|
|
list.push_back(std::make_shared<DebugStringConvertibleItem>(
|
|
"frame", toString(layoutMetrics.frame)));
|
|
|
|
if (layoutMetrics.borderWidth != defaultLayoutMetrics.borderWidth) {
|
|
list.push_back(std::make_shared<DebugStringConvertibleItem>(
|
|
"borderWidth", toString(layoutMetrics.borderWidth)));
|
|
}
|
|
|
|
if (layoutMetrics.contentInsets != defaultLayoutMetrics.contentInsets) {
|
|
list.push_back(std::make_shared<DebugStringConvertibleItem>(
|
|
"contentInsets", toString(layoutMetrics.contentInsets)));
|
|
}
|
|
|
|
if (layoutMetrics.displayType == DisplayType::None) {
|
|
list.push_back(std::make_shared<DebugStringConvertibleItem>("hidden"));
|
|
}
|
|
|
|
if (layoutMetrics.layoutDirection == LayoutDirection::RightToLeft) {
|
|
list.push_back(std::make_shared<DebugStringConvertibleItem>("rtl"));
|
|
}
|
|
|
|
return list;
|
|
}
|
|
#endif
|
|
|
|
} // namespace react
|
|
} // namespace facebook
|