Files
yoga/yoga/YGNode.cpp
Joe Vilches 5425107246 Support transforms forming containing blocks (#1539)
Summary:
X-link: https://github.com/facebook/react-native/pull/42191

Pull Request resolved: https://github.com/facebook/yoga/pull/1539

React native supports transforms and if a node has a transform it will [form a containing block for absolute descendants regardless of position type](https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#identifying_the_containing_block). So we need to pass that information into Yoga to ensure this happens.

The verbiage for the field "alwaysFormsContainingBlock" is very specific. In a vacuum a node cannot simply "form a containing block". It only forms a containing block in reference to a different node. This can be illustrated in a scenario where we have a static node that is a flex container which has 1 absolute child and 1 relative child. This static node will form a containing block for the relative child but not the absolute one. We could just pass the information on rather something has a transform or not but Yoga is not supposed to know about transforms in general. As a result we have a notion of "always" forming a containing block. Since Yoga is a flexbox spec, non-absolute nodes' containing blocks will ways be their parent. If we add something like a transform to a node then that will also apply to absolute nodes - hence we can say the node will **always** form a CB, no matter who is the descendant.

Changelog: [Internal]

Reviewed By: NickGerleman

Differential Revision: D52521160

fbshipit-source-id: bab9319ffddec617f5281823930f2a00cc2967f2
2024-01-08 20:28:49 -08:00

374 lines
10 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 <yoga/Yoga.h>
#include <yoga/algorithm/Cache.h>
#include <yoga/algorithm/CalculateLayout.h>
#include <yoga/debug/AssertFatal.h>
#include <yoga/debug/Log.h>
#include <yoga/debug/NodeToString.h>
#include <yoga/event/event.h>
#include <yoga/node/Node.h>
using namespace facebook;
using namespace facebook::yoga;
YGNodeRef YGNodeNew(void) {
return YGNodeNewWithConfig(YGConfigGetDefault());
}
YGNodeRef YGNodeNewWithConfig(const YGConfigConstRef config) {
auto* node = new yoga::Node{resolveRef(config)};
yoga::assertFatal(
config != nullptr, "Tried to construct YGNode with null config");
Event::publish<Event::NodeAllocation>(node, {config});
return node;
}
YGNodeRef YGNodeClone(YGNodeConstRef oldNodeRef) {
auto oldNode = resolveRef(oldNodeRef);
const auto node = new yoga::Node(*oldNode);
Event::publish<Event::NodeAllocation>(node, {node->getConfig()});
node->setOwner(nullptr);
return node;
}
void YGNodeFree(const YGNodeRef nodeRef) {
const auto node = resolveRef(nodeRef);
if (auto owner = node->getOwner()) {
owner->removeChild(node);
node->setOwner(nullptr);
}
const size_t childCount = node->getChildCount();
for (size_t i = 0; i < childCount; i++) {
auto child = node->getChild(i);
child->setOwner(nullptr);
}
node->clearChildren();
Event::publish<Event::NodeDeallocation>(node, {YGNodeGetConfig(node)});
delete resolveRef(node);
}
void YGNodeFreeRecursive(YGNodeRef rootRef) {
const auto root = resolveRef(rootRef);
size_t skipped = 0;
while (root->getChildCount() > skipped) {
const auto child = root->getChild(skipped);
if (child->getOwner() != root) {
// Don't free shared nodes that we don't own.
skipped += 1;
} else {
YGNodeRemoveChild(root, child);
YGNodeFreeRecursive(child);
}
}
YGNodeFree(root);
}
void YGNodeFinalize(const YGNodeRef node) {
Event::publish<Event::NodeDeallocation>(node, {YGNodeGetConfig(node)});
delete resolveRef(node);
}
void YGNodeReset(YGNodeRef node) {
resolveRef(node)->reset();
}
void YGNodeCalculateLayout(
const YGNodeRef node,
const float ownerWidth,
const float ownerHeight,
const YGDirection ownerDirection) {
yoga::calculateLayout(
resolveRef(node), ownerWidth, ownerHeight, scopedEnum(ownerDirection));
}
bool YGNodeGetHasNewLayout(YGNodeConstRef node) {
return resolveRef(node)->getHasNewLayout();
}
void YGNodeSetHasNewLayout(YGNodeRef node, bool hasNewLayout) {
resolveRef(node)->setHasNewLayout(hasNewLayout);
}
bool YGNodeIsDirty(YGNodeConstRef node) {
return resolveRef(node)->isDirty();
}
void YGNodeMarkDirty(const YGNodeRef nodeRef) {
const auto node = resolveRef(nodeRef);
yoga::assertFatalWithNode(
node,
node->hasMeasureFunc(),
"Only leaf nodes with custom measure functions "
"should manually mark themselves as dirty");
node->markDirtyAndPropagate();
}
void YGNodeSetDirtiedFunc(YGNodeRef node, YGDirtiedFunc dirtiedFunc) {
resolveRef(node)->setDirtiedFunc(dirtiedFunc);
}
YGDirtiedFunc YGNodeGetDirtiedFunc(YGNodeConstRef node) {
return resolveRef(node)->getDirtiedFunc();
}
void YGNodeInsertChild(
const YGNodeRef ownerRef,
const YGNodeRef childRef,
const size_t index) {
auto owner = resolveRef(ownerRef);
auto child = resolveRef(childRef);
yoga::assertFatalWithNode(
owner,
child->getOwner() == nullptr,
"Child already has a owner, it must be removed first.");
yoga::assertFatalWithNode(
owner,
!owner->hasMeasureFunc(),
"Cannot add child: Nodes with measure functions cannot have children.");
owner->insertChild(child, index);
child->setOwner(owner);
owner->markDirtyAndPropagate();
}
void YGNodeSwapChild(
const YGNodeRef ownerRef,
const YGNodeRef childRef,
const size_t index) {
auto owner = resolveRef(ownerRef);
auto child = resolveRef(childRef);
owner->replaceChild(child, index);
child->setOwner(owner);
}
void YGNodeRemoveChild(
const YGNodeRef ownerRef,
const YGNodeRef excludedChildRef) {
auto owner = resolveRef(ownerRef);
auto excludedChild = resolveRef(excludedChildRef);
if (owner->getChildCount() == 0) {
// This is an empty set. Nothing to remove.
return;
}
// Children may be shared between parents, which is indicated by not having an
// owner. We only want to reset the child completely if it is owned
// exclusively by one node.
auto childOwner = excludedChild->getOwner();
if (owner->removeChild(excludedChild)) {
if (owner == childOwner) {
excludedChild->setLayout({}); // layout is no longer valid
excludedChild->setOwner(nullptr);
}
owner->markDirtyAndPropagate();
}
}
void YGNodeRemoveAllChildren(const YGNodeRef ownerRef) {
auto owner = resolveRef(ownerRef);
const size_t childCount = owner->getChildCount();
if (childCount == 0) {
// This is an empty set already. Nothing to do.
return;
}
auto* firstChild = owner->getChild(0);
if (firstChild->getOwner() == owner) {
// If the first child has this node as its owner, we assume that this child
// set is unique.
for (size_t i = 0; i < childCount; i++) {
yoga::Node* oldChild = owner->getChild(i);
oldChild->setLayout({}); // layout is no longer valid
oldChild->setOwner(nullptr);
}
owner->clearChildren();
owner->markDirtyAndPropagate();
return;
}
// Otherwise, we are not the owner of the child set. We don't have to do
// anything to clear it.
owner->setChildren({});
owner->markDirtyAndPropagate();
}
void YGNodeSetChildren(
const YGNodeRef ownerRef,
const YGNodeRef* childrenRefs,
const size_t count) {
auto owner = resolveRef(ownerRef);
auto children = reinterpret_cast<yoga::Node* const*>(childrenRefs);
if (!owner) {
return;
}
const std::vector<yoga::Node*> childrenVector = {children, children + count};
if (childrenVector.size() == 0) {
if (owner->getChildCount() > 0) {
for (auto* child : owner->getChildren()) {
child->setLayout({});
child->setOwner(nullptr);
}
owner->setChildren({});
owner->markDirtyAndPropagate();
}
} else {
if (owner->getChildCount() > 0) {
for (auto* oldChild : owner->getChildren()) {
// Our new children may have nodes in common with the old children. We
// don't reset these common nodes.
if (std::find(childrenVector.begin(), childrenVector.end(), oldChild) ==
childrenVector.end()) {
oldChild->setLayout({});
oldChild->setOwner(nullptr);
}
}
}
owner->setChildren(childrenVector);
for (yoga::Node* child : childrenVector) {
child->setOwner(owner);
}
owner->markDirtyAndPropagate();
}
}
YGNodeRef YGNodeGetChild(const YGNodeRef nodeRef, const size_t index) {
const auto node = resolveRef(nodeRef);
if (index < node->getChildren().size()) {
return node->getChild(index);
}
return nullptr;
}
size_t YGNodeGetChildCount(const YGNodeConstRef node) {
return resolveRef(node)->getChildren().size();
}
YGNodeRef YGNodeGetOwner(const YGNodeRef node) {
return resolveRef(node)->getOwner();
}
YGNodeRef YGNodeGetParent(const YGNodeRef node) {
return resolveRef(node)->getOwner();
}
void YGNodeSetConfig(YGNodeRef node, YGConfigRef config) {
resolveRef(node)->setConfig(resolveRef(config));
}
YGConfigConstRef YGNodeGetConfig(YGNodeRef node) {
return resolveRef(node)->getConfig();
}
void YGNodeSetContext(YGNodeRef node, void* context) {
return resolveRef(node)->setContext(context);
}
void* YGNodeGetContext(YGNodeConstRef node) {
return resolveRef(node)->getContext();
}
void YGNodeSetMeasureFunc(YGNodeRef node, YGMeasureFunc measureFunc) {
resolveRef(node)->setMeasureFunc(measureFunc);
}
bool YGNodeHasMeasureFunc(YGNodeConstRef node) {
return resolveRef(node)->hasMeasureFunc();
}
void YGNodeSetBaselineFunc(YGNodeRef node, YGBaselineFunc baselineFunc) {
resolveRef(node)->setBaselineFunc(baselineFunc);
}
bool YGNodeHasBaselineFunc(YGNodeConstRef node) {
return resolveRef(node)->hasBaselineFunc();
}
void YGNodeSetIsReferenceBaseline(YGNodeRef nodeRef, bool isReferenceBaseline) {
const auto node = resolveRef(nodeRef);
if (node->isReferenceBaseline() != isReferenceBaseline) {
node->setIsReferenceBaseline(isReferenceBaseline);
node->markDirtyAndPropagate();
}
}
bool YGNodeIsReferenceBaseline(YGNodeConstRef node) {
return resolveRef(node)->isReferenceBaseline();
}
void YGNodeSetNodeType(YGNodeRef node, YGNodeType nodeType) {
return resolveRef(node)->setNodeType(scopedEnum(nodeType));
}
YGNodeType YGNodeGetNodeType(YGNodeConstRef node) {
return unscopedEnum(resolveRef(node)->getNodeType());
}
void YGNodeSetPrintFunc(YGNodeRef node, YGPrintFunc printFunc) {
resolveRef(node)->setPrintFunc(printFunc);
}
void YGNodeSetAlwaysFormsContainingBlock(
YGNodeRef node,
bool alwaysFormsContainingBlock) {
resolveRef(node)->setAlwaysFormsContainingBlock(alwaysFormsContainingBlock);
}
#ifdef DEBUG
void YGNodePrint(const YGNodeConstRef node, const YGPrintOptions options) {
yoga::print(resolveRef(node), scopedEnum(options));
}
#endif
// TODO: This leaks internal details to the public API. Remove after removing
// ComponentKit usage of it.
bool YGNodeCanUseCachedMeasurement(
YGMeasureMode widthMode,
float availableWidth,
YGMeasureMode heightMode,
float availableHeight,
YGMeasureMode lastWidthMode,
float lastAvailableWidth,
YGMeasureMode lastHeightMode,
float lastAvailableHeight,
float lastComputedWidth,
float lastComputedHeight,
float marginRow,
float marginColumn,
YGConfigRef config) {
return yoga::canUseCachedMeasurement(
sizingMode(scopedEnum(widthMode)),
availableWidth,
sizingMode(scopedEnum(heightMode)),
availableHeight,
sizingMode(scopedEnum(lastWidthMode)),
lastAvailableWidth,
sizingMode(scopedEnum(lastHeightMode)),
lastAvailableHeight,
lastComputedWidth,
lastComputedHeight,
marginRow,
marginColumn,
resolveRef(config));
}