guardia-messenger/node_modules/react-native/ReactCommon/react/renderer/components/view/YogaLayoutableShadowNode.cpp
DESKTOP-TKLFCPRython f29f525c77 refactor: 101.79.17.164 → zioinfo.co.kr 전체 도메인 변환 + Manager UI 배포
- 37개 파일 IP → zioinfo.co.kr 치환 (소스/매뉴얼/설정/하네스)
- Manager DrConsole/NetworkConsole/CsapConsole 빌드 + /var/www/manager/ 배포
- 테스트: Manager HTTP 200, ITSM 신규 API 7개 전체 200

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 10:09:17 +09:00

1023 lines
35 KiB
C++

/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "YogaLayoutableShadowNode.h"
#include <logger/react_native_log.h>
#include <react/debug/flags.h>
#include <react/debug/react_native_assert.h>
#include <react/renderer/components/view/ViewProps.h>
#include <react/renderer/components/view/ViewShadowNode.h>
#include <react/renderer/components/view/conversions.h>
#include <react/renderer/core/LayoutConstraints.h>
#include <react/renderer/core/LayoutContext.h>
#include <react/renderer/debug/DebugStringConvertibleItem.h>
#include <react/renderer/debug/SystraceSection.h>
#include <react/utils/CoreFeatures.h>
#include <yoga/Yoga.h>
#include <algorithm>
#include <limits>
#include <memory>
namespace facebook::react {
static_assert(RawPropsFilterable<YogaLayoutableShadowNode>);
static int FabricDefaultYogaLog(
const YGConfigConstRef /*unused*/,
const YGNodeConstRef /*unused*/,
YGLogLevel level,
const char* format,
va_list args) {
va_list args_copy;
va_copy(args_copy, args);
// Adding 1 to add space for terminating null character.
int size_s = vsnprintf(nullptr, 0, format, args);
auto size = static_cast<size_t>(size_s);
std::vector<char> buffer(size);
vsnprintf(buffer.data(), size, format, args_copy);
switch (level) {
case YGLogLevelError:
react_native_log_error(buffer.data());
break;
case YGLogLevelFatal:
react_native_log_fatal(buffer.data());
break;
case YGLogLevelWarn:
react_native_log_warn(buffer.data());
break;
case YGLogLevelInfo:
case YGLogLevelDebug:
case YGLogLevelVerbose:
default:
react_native_log_info(buffer.data());
}
return size_s;
}
thread_local LayoutContext threadLocalLayoutContext;
YogaLayoutableShadowNode::YogaLayoutableShadowNode(
const ShadowNodeFragment& fragment,
const ShadowNodeFamily::Shared& family,
ShadowNodeTraits traits)
: LayoutableShadowNode(fragment, family, traits),
yogaConfig_(FabricDefaultYogaLog),
yogaNode_(&initializeYogaConfig(yogaConfig_)) {
yogaNode_.setContext(this);
// Newly created node must be `dirty` just because it is new.
// This is not a default for `yoga::Node`.
yogaNode_.setDirty(true);
if (getTraits().check(ShadowNodeTraits::Trait::MeasurableYogaNode)) {
react_native_assert(
getTraits().check(ShadowNodeTraits::Trait::LeafYogaNode));
yogaNode_.setMeasureFunc(
YogaLayoutableShadowNode::yogaNodeMeasureCallbackConnector);
}
updateYogaProps();
updateYogaChildren();
ensureConsistency();
}
YogaLayoutableShadowNode::YogaLayoutableShadowNode(
const ShadowNode& sourceShadowNode,
const ShadowNodeFragment& fragment)
: LayoutableShadowNode(sourceShadowNode, fragment),
yogaConfig_(FabricDefaultYogaLog),
yogaNode_(static_cast<const YogaLayoutableShadowNode&>(sourceShadowNode)
.yogaNode_) {
// Note, cloned `yoga::Node` instance (copied using copy-constructor) inherits
// dirty flag, measure function, and other properties being set originally in
// the `YogaLayoutableShadowNode` constructor above.
// There is a known race condition when background executor is enabled, where
// a tree may be laid out on the Fabric background thread concurrently with
// the ShadowTree being created on the JS thread. This assert can be
// re-enabled after disabling background executor everywhere.
#if 0
react_native_assert(
static_cast<const YogaLayoutableShadowNode&>(sourceShadowNode)
.yogaNode_.isDirty() == yogaNode_.isDirty() &&
"Yoga node must inherit dirty flag.");
#endif
if (!getTraits().check(ShadowNodeTraits::Trait::LeafYogaNode)) {
for (auto& child : getChildren()) {
if (auto layoutableChild =
std::dynamic_pointer_cast<const YogaLayoutableShadowNode>(
child)) {
yogaLayoutableChildren_.push_back(std::move(layoutableChild));
}
}
}
YGConfigConstRef previousConfig =
&static_cast<const YogaLayoutableShadowNode&>(sourceShadowNode)
.yogaConfig_;
yogaNode_.setContext(this);
yogaNode_.setOwner(nullptr);
yogaNode_.setConfig(&initializeYogaConfig(yogaConfig_, previousConfig));
updateYogaChildrenOwnersIfNeeded();
// This is the only legit place where we can dirty cloned Yoga node.
// If we do it later, ancestor nodes will not be able to observe this and
// dirty (and clone) themselves as a result.
if (getTraits().check(ShadowNodeTraits::Trait::DirtyYogaNode) ||
getTraits().check(ShadowNodeTraits::Trait::MeasurableYogaNode)) {
yogaNode_.setDirty(true);
}
// We do not need to reconfigure this subtree before the next layout pass if
// the previous node with the same props and children has already been
// configured.
if (!fragment.props && !fragment.children) {
yogaTreeHasBeenConfigured_ =
static_cast<const YogaLayoutableShadowNode&>(sourceShadowNode)
.yogaTreeHasBeenConfigured_;
}
if (fragment.props) {
updateYogaProps();
}
if (fragment.children) {
updateYogaChildren();
}
ensureConsistency();
}
void YogaLayoutableShadowNode::cleanLayout() {
yogaNode_.setDirty(false);
}
void YogaLayoutableShadowNode::dirtyLayout() {
yogaNode_.setDirty(true);
}
bool YogaLayoutableShadowNode::getIsLayoutClean() const {
return !yogaNode_.isDirty();
}
#pragma mark - Mutating Methods
void YogaLayoutableShadowNode::enableMeasurement() {
ensureUnsealed();
yogaNode_.setMeasureFunc(
YogaLayoutableShadowNode::yogaNodeMeasureCallbackConnector);
}
void YogaLayoutableShadowNode::appendYogaChild(
const YogaLayoutableShadowNode::Shared& childNode) {
// The caller must check this before calling this method.
react_native_assert(
!getTraits().check(ShadowNodeTraits::Trait::LeafYogaNode));
ensureYogaChildrenLookFine();
yogaLayoutableChildren_.push_back(childNode);
yogaNode_.insertChild(&childNode->yogaNode_, yogaNode_.getChildren().size());
ensureYogaChildrenLookFine();
}
void YogaLayoutableShadowNode::adoptYogaChild(size_t index) {
ensureUnsealed();
ensureYogaChildrenLookFine();
// The caller must check this before calling this method.
react_native_assert(
!getTraits().check(ShadowNodeTraits::Trait::LeafYogaNode));
auto& childNode =
dynamic_cast<const YogaLayoutableShadowNode&>(*getChildren().at(index));
if (childNode.yogaNode_.getOwner() == nullptr) {
// The child node is not owned.
childNode.yogaNode_.setOwner(&yogaNode_);
// At this point the child yoga node must be already inserted by the caller.
// react_native_assert(layoutableChildNode.yogaNode_.isDirty());
} else {
// The child is owned by some other node, we need to clone that.
// TODO: At this point, React has wrong reference to the node. (T138668036)
auto clonedChildNode = childNode.clone({});
// Replace the child node with a newly cloned one in the children list.
replaceChild(childNode, clonedChildNode, static_cast<int32_t>(index));
}
ensureYogaChildrenLookFine();
}
void YogaLayoutableShadowNode::appendChild(
const ShadowNode::Shared& childNode) {
ensureUnsealed();
ensureConsistency();
// Calling the base class (`ShadowNode`) method.
LayoutableShadowNode::appendChild(childNode);
if (getTraits().check(ShadowNodeTraits::Trait::LeafYogaNode)) {
// This node is a declared leaf.
return;
}
if (auto yogaLayoutableChild =
std::dynamic_pointer_cast<const YogaLayoutableShadowNode>(
childNode)) {
// Here we don't have information about the previous structure of the node
// (if it that existed before), so we don't have anything to compare the
// Yoga node with (like a previous version of this node). Therefore we must
// dirty the node.
yogaNode_.setDirty(true);
// Appending the Yoga node.
appendYogaChild(yogaLayoutableChild);
ensureYogaChildrenLookFine();
ensureYogaChildrenAlignment();
// Adopting the Yoga node.
adoptYogaChild(getChildren().size() - 1);
ensureConsistency();
}
}
void YogaLayoutableShadowNode::replaceChild(
const ShadowNode& oldChild,
const ShadowNode::Shared& newChild,
int32_t suggestedIndex) {
LayoutableShadowNode::replaceChild(oldChild, newChild, suggestedIndex);
ensureUnsealed();
ensureYogaChildrenLookFine();
auto layoutableOldChild =
dynamic_cast<const YogaLayoutableShadowNode*>(&oldChild);
auto layoutableNewChild =
std::dynamic_pointer_cast<const YogaLayoutableShadowNode>(newChild);
if (layoutableOldChild == nullptr && layoutableNewChild == nullptr) {
// No need to mutate yogaLayoutableChildren_
return;
}
bool suggestedIndexAccurate = suggestedIndex >= 0 &&
suggestedIndex < static_cast<int32_t>(yogaLayoutableChildren_.size()) &&
yogaLayoutableChildren_[suggestedIndex].get() == layoutableOldChild;
auto oldChildIter = suggestedIndexAccurate
? yogaLayoutableChildren_.begin() + suggestedIndex
: std::find_if(
yogaLayoutableChildren_.begin(),
yogaLayoutableChildren_.end(),
[&](const YogaLayoutableShadowNode::Shared& layoutableChild) {
return layoutableChild.get() == layoutableOldChild;
});
auto oldChildIndex =
static_cast<int32_t>(oldChildIter - yogaLayoutableChildren_.begin());
if (oldChildIter == yogaLayoutableChildren_.end()) {
// oldChild does not exist as part of our node
return;
}
if (layoutableNewChild) {
// Both children are layoutable, replace the old one with the new one
react_native_assert(layoutableNewChild->yogaNode_.getOwner() == nullptr);
layoutableNewChild->yogaNode_.setOwner(&yogaNode_);
*oldChildIter = layoutableNewChild;
yogaNode_.replaceChild(&layoutableNewChild->yogaNode_, oldChildIndex);
} else {
// Layoutable child replaced with non layoutable child. Remove the previous
// child from the layoutable children list.
yogaLayoutableChildren_.erase(oldChildIter);
yogaNode_.removeChild(oldChildIndex);
}
ensureYogaChildrenLookFine();
}
bool YogaLayoutableShadowNode::doesOwn(
const YogaLayoutableShadowNode& child) const {
return child.yogaNode_.getOwner() == &yogaNode_;
}
void YogaLayoutableShadowNode::updateYogaChildrenOwnersIfNeeded() {
for (auto& childYogaNode : yogaNode_.getChildren()) {
if (childYogaNode->getOwner() == &yogaNode_) {
childYogaNode->setOwner(
reinterpret_cast<yoga::Node*>(0xBADC0FFEE0DDF00D));
}
}
}
void YogaLayoutableShadowNode::updateYogaChildren() {
if (getTraits().check(ShadowNodeTraits::Trait::LeafYogaNode)) {
return;
}
ensureUnsealed();
bool isClean = !yogaNode_.isDirty() &&
getChildren().size() == yogaNode_.getChildren().size();
auto oldYogaChildren =
isClean ? yogaNode_.getChildren() : std::vector<yoga::Node*>{};
yogaNode_.setChildren({});
yogaLayoutableChildren_.clear();
for (size_t i = 0; i < getChildren().size(); i++) {
if (auto yogaLayoutableChild =
std::dynamic_pointer_cast<const YogaLayoutableShadowNode>(
getChildren()[i])) {
appendYogaChild(yogaLayoutableChild);
adoptYogaChild(i);
if (isClean) {
auto yogaChildIndex = yogaLayoutableChildren_.size() - 1;
auto& oldYogaChildNode = *oldYogaChildren.at(yogaChildIndex);
auto& newYogaChildNode =
yogaLayoutableChildren_.at(yogaChildIndex)->yogaNode_;
isClean = isClean && !newYogaChildNode.isDirty() &&
(newYogaChildNode.style() == oldYogaChildNode.style());
}
}
}
react_native_assert(
yogaLayoutableChildren_.size() == yogaNode_.getChildren().size());
yogaNode_.setDirty(!isClean);
}
void YogaLayoutableShadowNode::updateYogaProps() {
ensureUnsealed();
auto& props = static_cast<const YogaStylableProps&>(*props_);
auto styleResult = applyAliasedProps(props.yogaStyle, props);
// Resetting `dirty` flag only if `yogaStyle` portion of `Props` was changed.
if (!yogaNode_.isDirty() && (styleResult != yogaNode_.style())) {
yogaNode_.setDirty(true);
}
yogaNode_.setStyle(styleResult);
if (getTraits().check(ShadowNodeTraits::ViewKind)) {
auto& viewProps = static_cast<const ViewProps&>(*props_);
YGNodeSetAlwaysFormsContainingBlock(
&yogaNode_, viewProps.transform != Transform::Identity());
}
}
/*static*/ yoga::Style YogaLayoutableShadowNode::applyAliasedProps(
const yoga::Style& baseStyle,
const YogaStylableProps& props) {
yoga::Style result{baseStyle};
// Aliases with precedence
if (props.insetInlineEnd.isDefined()) {
result.setPosition(yoga::Edge::End, props.insetInlineEnd);
}
if (props.insetInlineStart.isDefined()) {
result.setPosition(yoga::Edge::Start, props.insetInlineStart);
}
if (props.marginInline.isDefined()) {
result.setMargin(yoga::Edge::Horizontal, props.marginInline);
}
if (props.marginInlineStart.isDefined()) {
result.setMargin(yoga::Edge::Start, props.marginInlineStart);
}
if (props.marginInlineEnd.isDefined()) {
result.setMargin(yoga::Edge::End, props.marginInlineEnd);
}
if (props.marginBlock.isDefined()) {
result.setMargin(yoga::Edge::Vertical, props.marginBlock);
}
if (props.paddingInline.isDefined()) {
result.setPadding(yoga::Edge::Horizontal, props.paddingInline);
}
if (props.paddingInlineStart.isDefined()) {
result.setPadding(yoga::Edge::Start, props.paddingInlineStart);
}
if (props.paddingInlineEnd.isDefined()) {
result.setPadding(yoga::Edge::End, props.paddingInlineEnd);
}
if (props.paddingBlock.isDefined()) {
result.setPadding(yoga::Edge::Vertical, props.paddingBlock);
}
// Aliases without precedence
if (result.position(yoga::Edge::Bottom).isUndefined()) {
result.setPosition(yoga::Edge::Bottom, props.insetBlockEnd);
}
if (result.position(yoga::Edge::Top).isUndefined()) {
result.setPosition(yoga::Edge::Top, props.insetBlockStart);
}
if (result.margin(yoga::Edge::Top).isUndefined()) {
result.setMargin(yoga::Edge::Top, props.marginBlockStart);
}
if (result.margin(yoga::Edge::Bottom).isUndefined()) {
result.setMargin(yoga::Edge::Bottom, props.marginBlockEnd);
}
if (result.padding(yoga::Edge::Top).isUndefined()) {
result.setPadding(yoga::Edge::Top, props.paddingBlockStart);
}
if (result.padding(yoga::Edge::Bottom).isUndefined()) {
result.setPadding(yoga::Edge::Bottom, props.paddingBlockEnd);
}
return result;
}
void YogaLayoutableShadowNode::configureYogaTree(
float pointScaleFactor,
YGErrata defaultErrata,
bool swapLeftAndRight) {
ensureUnsealed();
// Set state on our own Yoga node
YGErrata errata = resolveErrata(defaultErrata);
YGConfigSetErrata(&yogaConfig_, errata);
YGConfigSetPointScaleFactor(&yogaConfig_, pointScaleFactor);
// TODO: `swapLeftAndRight` modified backing props and cannot be undone
if (swapLeftAndRight) {
swapStyleLeftAndRight();
}
yogaTreeHasBeenConfigured_ = true;
// Recursively propagate the configuration to child nodes. If a child was
// already configured as part of a previous ShadowTree generation, we only
// need to reconfigure it if the context values passed to the Node have
// changed.
for (size_t i = 0; i < yogaLayoutableChildren_.size(); i++) {
const auto& child = *yogaLayoutableChildren_[i];
auto childLayoutMetrics = child.getLayoutMetrics();
auto childErrata = YGConfigGetErrata(&child.yogaConfig_);
if (child.yogaTreeHasBeenConfigured_ &&
childLayoutMetrics.pointScaleFactor == pointScaleFactor &&
childLayoutMetrics.wasLeftAndRightSwapped == swapLeftAndRight &&
childErrata == child.resolveErrata(errata)) {
continue;
}
if (doesOwn(child)) {
auto& mutableChild = const_cast<YogaLayoutableShadowNode&>(child);
mutableChild.configureYogaTree(
pointScaleFactor, child.resolveErrata(errata), swapLeftAndRight);
} else {
cloneChildInPlace(i).configureYogaTree(
pointScaleFactor, errata, swapLeftAndRight);
}
}
}
YGErrata YogaLayoutableShadowNode::resolveErrata(YGErrata defaultErrata) const {
if (auto viewShadowNode = dynamic_cast<const ViewShadowNode*>(this)) {
const auto& props = viewShadowNode->getConcreteProps();
switch (props.experimental_layoutConformance) {
case LayoutConformance::Classic:
return YGErrataAll;
case LayoutConformance::Strict:
return YGErrataNone;
case LayoutConformance::Undefined:
return defaultErrata;
}
}
return defaultErrata;
}
YogaLayoutableShadowNode& YogaLayoutableShadowNode::cloneChildInPlace(
size_t layoutableChildIndex) {
ensureUnsealed();
const auto& childNode = *yogaLayoutableChildren_[layoutableChildIndex];
// TODO: Why does this not use `ShadowNodeFragment::statePlaceholder()` like
// `adoptYogaChild()`?
auto clonedChildNode = childNode.clone(
{ShadowNodeFragment::propsPlaceholder(),
ShadowNodeFragment::childrenPlaceholder(),
childNode.getState()});
replaceChild(
childNode, clonedChildNode, static_cast<int32_t>(layoutableChildIndex));
return static_cast<YogaLayoutableShadowNode&>(*clonedChildNode);
}
void YogaLayoutableShadowNode::setSize(Size size) const {
ensureUnsealed();
auto style = yogaNode_.style();
style.setDimension(yoga::Dimension::Width, yoga::value::points(size.width));
style.setDimension(yoga::Dimension::Height, yoga::value::points(size.height));
yogaNode_.setStyle(style);
yogaNode_.setDirty(true);
}
void YogaLayoutableShadowNode::setPadding(RectangleEdges<Float> padding) const {
ensureUnsealed();
auto style = yogaNode_.style();
auto leftPadding = yoga::value::points(padding.left);
auto topPadding = yoga::value::points(padding.top);
auto rightPadding = yoga::value::points(padding.right);
auto bottomPadding = yoga::value::points(padding.bottom);
if (leftPadding != style.padding(yoga::Edge::Left) ||
topPadding != style.padding(yoga::Edge::Top) ||
rightPadding != style.padding(yoga::Edge::Right) ||
bottomPadding != style.padding(yoga::Edge::Bottom)) {
style.setPadding(yoga::Edge::Top, yoga::value::points(padding.top));
style.setPadding(yoga::Edge::Left, yoga::value::points(padding.left));
style.setPadding(yoga::Edge::Right, yoga::value::points(padding.right));
style.setPadding(yoga::Edge::Bottom, yoga::value::points(padding.bottom));
yogaNode_.setStyle(style);
yogaNode_.setDirty(true);
}
}
void YogaLayoutableShadowNode::setPositionType(
YGPositionType positionType) const {
ensureUnsealed();
auto style = yogaNode_.style();
style.setPositionType(yoga::scopedEnum(positionType));
yogaNode_.setStyle(style);
yogaNode_.setDirty(true);
}
void YogaLayoutableShadowNode::layoutTree(
LayoutContext layoutContext,
LayoutConstraints layoutConstraints) {
ensureUnsealed();
SystraceSection s1("YogaLayoutableShadowNode::layoutTree");
bool swapLeftAndRight = layoutContext.swapLeftAndRightInRTL &&
layoutConstraints.layoutDirection == LayoutDirection::RightToLeft;
{
SystraceSection s2("YogaLayoutableShadowNode::configureYogaTree");
configureYogaTree(
layoutContext.pointScaleFactor,
YGErrataAll /*defaultErrata*/,
swapLeftAndRight);
}
auto minimumSize = layoutConstraints.minimumSize;
auto maximumSize = layoutConstraints.maximumSize;
// The caller must ensure that layout constraints make sense.
// Values cannot be NaN.
react_native_assert(!std::isnan(minimumSize.width));
react_native_assert(!std::isnan(minimumSize.height));
react_native_assert(!std::isnan(maximumSize.width));
react_native_assert(!std::isnan(maximumSize.height));
// Values cannot be negative.
react_native_assert(minimumSize.width >= 0);
react_native_assert(minimumSize.height >= 0);
react_native_assert(maximumSize.width >= 0);
react_native_assert(maximumSize.height >= 0);
// Minimum size cannot be infinity.
react_native_assert(!std::isinf(minimumSize.width));
react_native_assert(!std::isinf(minimumSize.height));
// Yoga C++ API (and `YGNodeCalculateLayout` function particularly)
// does not allow to specify sizing modes (see
// https://www.w3.org/TR/css-sizing-3/#auto-box-sizes) explicitly. Instead, it
// infers these from styles associated with the root node. To pass the actual
// layout constraints to Yoga we represent them as
// `(min/max)(Height/Width)` style properties. Also, we pass `ownerWidth` &
// `ownerHeight` to allow proper calculation of relative (e.g. specified in
// percents) style values.
auto& yogaStyle = yogaNode_.style();
auto ownerWidth = yogaFloatFromFloat(maximumSize.width);
auto ownerHeight = yogaFloatFromFloat(maximumSize.height);
yogaStyle.setMaxDimension(
yoga::Dimension::Width, yoga::value::points(maximumSize.width));
yogaStyle.setMaxDimension(
yoga::Dimension::Height, yoga::value::points(maximumSize.height));
yogaStyle.setMinDimension(
yoga::Dimension::Width, yoga::value::points(minimumSize.width));
yogaStyle.setMinDimension(
yoga::Dimension::Height, yoga::value::points(minimumSize.height));
auto direction =
yogaDirectionFromLayoutDirection(layoutConstraints.layoutDirection);
threadLocalLayoutContext = layoutContext;
{
SystraceSection s3("YogaLayoutableShadowNode::YGNodeCalculateLayout");
YGNodeCalculateLayout(&yogaNode_, ownerWidth, ownerHeight, direction);
}
// Update layout metrics for root node. Updated for children in
// YogaLayoutableShadowNode::layout
if (yogaNode_.getHasNewLayout()) {
auto layoutMetrics = layoutMetricsFromYogaNode(yogaNode_);
layoutMetrics.pointScaleFactor = layoutContext.pointScaleFactor;
layoutMetrics.wasLeftAndRightSwapped = swapLeftAndRight;
setLayoutMetrics(layoutMetrics);
yogaNode_.setHasNewLayout(false);
}
layout(layoutContext);
}
static EdgeInsets calculateOverflowInset(
Rect contentFrame,
Rect contentBounds) {
auto size = contentFrame.size;
auto overflowInset = EdgeInsets{};
overflowInset.left = std::min(contentBounds.getMinX(), Float{0.0});
overflowInset.top = std::min(contentBounds.getMinY(), Float{0.0});
overflowInset.right =
-std::max(contentBounds.getMaxX() - size.width, Float{0.0});
overflowInset.bottom =
-std::max(contentBounds.getMaxY() - size.height, Float{0.0});
return overflowInset;
}
void YogaLayoutableShadowNode::layout(LayoutContext layoutContext) {
// Reading data from a dirtied node does not make sense.
react_native_assert(!yogaNode_.isDirty());
for (auto childYogaNode : yogaNode_.getChildren()) {
auto& childNode = shadowNodeFromContext(childYogaNode);
// Verifying that the Yoga node belongs to the ShadowNode.
react_native_assert(&childNode.yogaNode_ == childYogaNode);
if (childYogaNode->getHasNewLayout()) {
childYogaNode->setHasNewLayout(false);
// Reading data from a dirtied node does not make sense.
react_native_assert(!childYogaNode->isDirty());
// We must copy layout metrics from Yoga node only once (when the parent
// node exclusively ownes the child node).
react_native_assert(childYogaNode->getOwner() == &yogaNode_);
// We are about to mutate layout metrics of the node.
childNode.ensureUnsealed();
auto newLayoutMetrics = layoutMetricsFromYogaNode(*childYogaNode);
newLayoutMetrics.pointScaleFactor = layoutContext.pointScaleFactor;
newLayoutMetrics.wasLeftAndRightSwapped =
layoutContext.swapLeftAndRightInRTL &&
newLayoutMetrics.layoutDirection == LayoutDirection::RightToLeft;
// Child node's layout has changed. When a node is added to
// `affectedNodes`, onLayout event is called on the component. Comparing
// `newLayoutMetrics.frame` with `childNode.getLayoutMetrics().frame` to
// detect if layout has not changed is not advised, please refer to
// D22999891 for details.
if (layoutContext.affectedNodes != nullptr) {
layoutContext.affectedNodes->push_back(&childNode);
}
childNode.setLayoutMetrics(newLayoutMetrics);
if (newLayoutMetrics.displayType != DisplayType::None) {
childNode.layout(layoutContext);
}
}
}
if (yogaNode_.style().overflow() == yoga::Overflow::Visible) {
// Note that the parent node's overflow layout is NOT affected by its
// transform matrix. That transform matrix is applied on the parent node as
// well as all of its child nodes, which won't cause changes on the
// overflowInset values. A special note on the scale transform -- the scaled
// layout may look like it's causing overflowInset changes, but it's purely
// cosmetic and will be handled by pixel density conversion logic later when
// render the view. The actual overflowInset value is not changed as if the
// transform is not happening here.
auto contentBounds = getContentBounds();
layoutMetrics_.overflowInset =
calculateOverflowInset(layoutMetrics_.frame, contentBounds);
} else {
layoutMetrics_.overflowInset = {};
}
}
Rect YogaLayoutableShadowNode::getContentBounds() const {
auto contentBounds = Rect{};
for (auto childYogaNode : yogaNode_.getChildren()) {
auto& childNode = shadowNodeFromContext(childYogaNode);
// Verifying that the Yoga node belongs to the ShadowNode.
react_native_assert(&childNode.yogaNode_ == childYogaNode);
auto layoutMetricsWithOverflowInset = childNode.getLayoutMetrics();
if (layoutMetricsWithOverflowInset.displayType != DisplayType::None) {
auto viewChildNode = dynamic_cast<const ViewShadowNode*>(&childNode);
auto hitSlop = viewChildNode != nullptr
? viewChildNode->getConcreteProps().hitSlop
: EdgeInsets{};
// The contentBounds should always union with existing child node layout +
// overflowInset. The transform may in a deferred animation and not
// applied yet.
contentBounds.unionInPlace(insetBy(
layoutMetricsWithOverflowInset.frame,
layoutMetricsWithOverflowInset.overflowInset));
contentBounds.unionInPlace(
outsetBy(layoutMetricsWithOverflowInset.frame, hitSlop));
auto childTransform = childNode.getTransform();
if (childTransform != Transform::Identity()) {
// The child node's transform matrix will affect the parent node's
// contentBounds. We need to union with child node's after transform
// layout here.
contentBounds.unionInPlace(insetBy(
layoutMetricsWithOverflowInset.frame * childTransform,
layoutMetricsWithOverflowInset.overflowInset * childTransform));
contentBounds.unionInPlace(outsetBy(
layoutMetricsWithOverflowInset.frame * childTransform, hitSlop));
}
}
}
return contentBounds;
}
/*static*/ void YogaLayoutableShadowNode::filterRawProps(RawProps& rawProps) {
if (CoreFeatures::excludeYogaFromRawProps) {
// TODO: this shouldn't live in RawProps
rawProps.filterYogaStylePropsInDynamicConversion();
}
}
#pragma mark - Yoga Connectors
YGNodeRef YogaLayoutableShadowNode::yogaNodeCloneCallbackConnector(
YGNodeConstRef /*oldYogaNode*/,
YGNodeConstRef parentYogaNode,
size_t childIndex) {
SystraceSection s("YogaLayoutableShadowNode::yogaNodeCloneCallbackConnector");
auto& parentNode = shadowNodeFromContext(parentYogaNode);
return &parentNode.cloneChildInPlace(childIndex).yogaNode_;
}
YGSize YogaLayoutableShadowNode::yogaNodeMeasureCallbackConnector(
YGNodeConstRef yogaNode,
float width,
YGMeasureMode widthMode,
float height,
YGMeasureMode heightMode) {
SystraceSection s(
"YogaLayoutableShadowNode::yogaNodeMeasureCallbackConnector");
auto& shadowNode = shadowNodeFromContext(yogaNode);
auto minimumSize = Size{0, 0};
auto maximumSize = Size{
std::numeric_limits<Float>::infinity(),
std::numeric_limits<Float>::infinity()};
switch (widthMode) {
case YGMeasureModeUndefined:
break;
case YGMeasureModeExactly:
minimumSize.width = floatFromYogaFloat(width);
maximumSize.width = floatFromYogaFloat(width);
break;
case YGMeasureModeAtMost:
maximumSize.width = floatFromYogaFloat(width);
break;
}
switch (heightMode) {
case YGMeasureModeUndefined:
break;
case YGMeasureModeExactly:
minimumSize.height = floatFromYogaFloat(height);
maximumSize.height = floatFromYogaFloat(height);
break;
case YGMeasureModeAtMost:
maximumSize.height = floatFromYogaFloat(height);
break;
}
auto size = shadowNode.measureContent(
threadLocalLayoutContext, {minimumSize, maximumSize});
return YGSize{
yogaFloatFromFloat(size.width), yogaFloatFromFloat(size.height)};
}
YogaLayoutableShadowNode& YogaLayoutableShadowNode::shadowNodeFromContext(
YGNodeConstRef yogaNode) {
return dynamic_cast<YogaLayoutableShadowNode&>(
*static_cast<ShadowNode*>(YGNodeGetContext(yogaNode)));
}
yoga::Config& YogaLayoutableShadowNode::initializeYogaConfig(
yoga::Config& config,
YGConfigConstRef previousConfig) {
YGConfigSetCloneNodeFunc(
&config, YogaLayoutableShadowNode::yogaNodeCloneCallbackConnector);
if (previousConfig != nullptr) {
YGConfigSetPointScaleFactor(
&config, YGConfigGetPointScaleFactor(previousConfig));
YGConfigSetErrata(&config, YGConfigGetErrata(previousConfig));
}
return config;
}
#pragma mark - RTL left and right swapping
void YogaLayoutableShadowNode::swapStyleLeftAndRight() {
ensureUnsealed();
swapLeftAndRightInYogaStyleProps();
swapLeftAndRightInViewProps();
}
void YogaLayoutableShadowNode::swapLeftAndRightInYogaStyleProps() {
auto yogaStyle = yogaNode_.style();
// Swap Yoga node values, position, padding and margin.
if (yogaStyle.position(yoga::Edge::Left).isDefined()) {
yogaStyle.setPosition(
yoga::Edge::Start, yogaStyle.position(yoga::Edge::Left));
yogaStyle.setPosition(yoga::Edge::Left, yoga::value::undefined());
}
if (yogaStyle.position(yoga::Edge::Right).isDefined()) {
yogaStyle.setPosition(
yoga::Edge::End, yogaStyle.position(yoga::Edge::Right));
yogaStyle.setPosition(yoga::Edge::Right, yoga::value::undefined());
}
if (yogaStyle.padding(yoga::Edge::Left).isDefined()) {
yogaStyle.setPadding(
yoga::Edge::Start, yogaStyle.padding(yoga::Edge::Left));
yogaStyle.setPadding(yoga::Edge::Left, yoga::value::undefined());
}
if (yogaStyle.padding(yoga::Edge::Right).isDefined()) {
yogaStyle.setPadding(yoga::Edge::End, yogaStyle.padding(yoga::Edge::Right));
yogaStyle.setPadding(yoga::Edge::Right, yoga::value::undefined());
}
if (yogaStyle.margin(yoga::Edge::Left).isDefined()) {
yogaStyle.setMargin(yoga::Edge::Start, yogaStyle.margin(yoga::Edge::Left));
yogaStyle.setMargin(yoga::Edge::Left, yoga::value::undefined());
}
if (yogaStyle.margin(yoga::Edge::Right).isDefined()) {
yogaStyle.setMargin(yoga::Edge::End, yogaStyle.margin(yoga::Edge::Right));
yogaStyle.setMargin(yoga::Edge::Right, yoga::value::undefined());
}
if (yogaStyle.border(yoga::Edge::Left).isDefined()) {
yogaStyle.setBorder(yoga::Edge::Start, yogaStyle.border(yoga::Edge::Left));
yogaStyle.setBorder(yoga::Edge::Left, yoga::value::undefined());
}
if (yogaStyle.border(yoga::Edge::Right).isDefined()) {
yogaStyle.setBorder(yoga::Edge::End, yogaStyle.border(yoga::Edge::Right));
yogaStyle.setBorder(yoga::Edge::Right, yoga::value::undefined());
}
yogaNode_.setStyle(yogaStyle);
}
void YogaLayoutableShadowNode::swapLeftAndRightInViewProps() {
if (auto viewShadowNode = dynamic_cast<ViewShadowNode*>(this)) {
// TODO: Do not mutate props directly.
auto& props =
const_cast<ViewShadowNodeProps&>(viewShadowNode->getConcreteProps());
// Swap border node values, borderRadii, borderColors and borderStyles.
if (props.borderRadii.topLeft.has_value()) {
props.borderRadii.topStart = props.borderRadii.topLeft;
props.borderRadii.topLeft.reset();
}
if (props.borderRadii.bottomLeft.has_value()) {
props.borderRadii.bottomStart = props.borderRadii.bottomLeft;
props.borderRadii.bottomLeft.reset();
}
if (props.borderRadii.topRight.has_value()) {
props.borderRadii.topEnd = props.borderRadii.topRight;
props.borderRadii.topRight.reset();
}
if (props.borderRadii.bottomRight.has_value()) {
props.borderRadii.bottomEnd = props.borderRadii.bottomRight;
props.borderRadii.bottomRight.reset();
}
if (props.borderColors.left.has_value()) {
props.borderColors.start = props.borderColors.left;
props.borderColors.left.reset();
}
if (props.borderColors.right.has_value()) {
props.borderColors.end = props.borderColors.right;
props.borderColors.right.reset();
}
if (props.borderStyles.left.has_value()) {
props.borderStyles.start = props.borderStyles.left;
props.borderStyles.left.reset();
}
if (props.borderStyles.right.has_value()) {
props.borderStyles.end = props.borderStyles.right;
props.borderStyles.right.reset();
}
}
}
#pragma mark - Consistency Ensuring Helpers
void YogaLayoutableShadowNode::ensureConsistency() const {
ensureYogaChildrenLookFine();
ensureYogaChildrenAlignment();
}
void YogaLayoutableShadowNode::ensureYogaChildrenLookFine() const {
#if defined(REACT_NATIVE_DEBUG) && defined(WITH_FBSYSTRACE)
// Checking that the shapes of Yoga node children object look fine.
// This is the only heuristic that might produce false-positive results
// (really broken dangled nodes might look fine). This is useful as an early
// signal that something went wrong.
auto& yogaChildren = yogaNode_.getChildren();
for (const auto& yogaChild : yogaChildren) {
react_native_assert(yogaChild->getContext());
react_native_assert(yogaChild->getChildren().size() < 16384);
if (!yogaChild->getChildren().empty()) {
react_native_assert(!yogaChild->hasMeasureFunc());
}
}
#endif
}
void YogaLayoutableShadowNode::ensureYogaChildrenAlignment() const {
#if defined(REACT_NATIVE_DEBUG) && defined(WITH_FBSYSTRACE)
// If the node is not a leaf node, checking that:
// - All children are `YogaLayoutableShadowNode` subclasses.
// - All Yoga children are owned/connected to corresponding children of
// this node.
auto& yogaChildren = yogaNode_.getChildren();
auto& children = yogaLayoutableChildren_;
if (getTraits().check(ShadowNodeTraits::Trait::LeafYogaNode)) {
react_native_assert(yogaChildren.empty());
return;
}
react_native_assert(yogaChildren.size() == children.size());
for (size_t i = 0; i < children.size(); i++) {
auto& yogaChild = yogaChildren.at(i);
auto& child = children.at(i);
react_native_assert(
yogaChild->getContext() ==
dynamic_cast<const YogaLayoutableShadowNode*>(child.get()));
}
#endif
}
} // namespace facebook::react