Summary: X-link: https://github.com/facebook/react-native/pull/41390 Pull Request resolved: https://github.com/facebook/yoga/pull/1460 Yoga passes `MeasureMode`/`YGMeasureMode` to express constraints in how a box should be measured, given definite or indefinite available space. This is modeled after Android [MeasureSpec](https://developer.android.com/reference/android/view/View.MeasureSpec), with a table above `calculateLayoutImpl()` explaining the CSS terms they map to. This can be confusing when flipping between the spec, and code. This switches internal usages to the CSS terms, but leaves around `YGMeasureMode` since it is the public API passed to measure functions. Reviewed By: joevilches Differential Revision: D51068417 fbshipit-source-id: 0a76266a4e7e0cc39996164607229c3c41de2818
368 lines
9.9 KiB
C++
368 lines
9.9 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);
|
|
}
|
|
|
|
#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));
|
|
}
|