diff --git a/tests/YGTraversalTest.cpp b/tests/YGTraversalTest.cpp new file mode 100644 index 00000000..3c4771da --- /dev/null +++ b/tests/YGTraversalTest.cpp @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include +#include + +TEST(YogaTest, pre_order_traversal) { + YGNodeRef const root = YGNodeNew(); + YGNodeRef const root_child0 = YGNodeNew(); + YGNodeRef const root_child1 = YGNodeNew(); + YGNodeRef const root_child0_child0 = YGNodeNew(); + + YGNodeSetChildren(root, {root_child0, root_child1}); + YGNodeInsertChild(root_child0, root_child0_child0, 0); + + std::vector visited; + YGTraversePreOrder(root, [&visited](YGNodeRef node) { + visited.push_back(node); + }); + + const std::vector expected = { + root, + root_child0, + root_child0_child0, + root_child1 + }; + ASSERT_EQ(visited, expected); + + YGNodeFreeRecursive(root); +} diff --git a/tests/YGTreeMutationTest.cpp b/tests/YGTreeMutationTest.cpp new file mode 100644 index 00000000..0f62b29f --- /dev/null +++ b/tests/YGTreeMutationTest.cpp @@ -0,0 +1,111 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include +#include + +static std::vector getChildren(YGNodeRef const node) +{ + const uint32_t count = YGNodeGetChildCount(node); + std::vector children; + children.reserve(count); + for (uint32_t i = 0 ; i < count ; i++) { + children.push_back(YGNodeGetChild(node, i)); + } + return children; +} + +TEST(YogaTest, set_children_adds_children_to_parent) { + YGNodeRef const root = YGNodeNew(); + YGNodeRef const root_child0 = YGNodeNew(); + YGNodeRef const root_child1 = YGNodeNew(); + + YGNodeSetChildren(root, {root_child0, root_child1}); + + const std::vector children = getChildren(root); + const std::vector expectedChildren = {root_child0, root_child1}; + ASSERT_EQ(children, expectedChildren); + + const std::vector parents = {YGNodeGetParent(root_child0), YGNodeGetParent(root_child1)}; + const std::vector expectedParents = {root, root}; + ASSERT_EQ(parents, expectedParents); + + YGNodeFreeRecursive(root); +} + +TEST(YogaTest, set_children_to_empty_removes_old_children) { + YGNodeRef const root = YGNodeNew(); + YGNodeRef const root_child0 = YGNodeNew(); + YGNodeRef const root_child1 = YGNodeNew(); + + YGNodeSetChildren(root, {root_child0, root_child1}); + YGNodeSetChildren(root, {}); + + const std::vector children = getChildren(root); + const std::vector expectedChildren = {}; + ASSERT_EQ(children, expectedChildren); + + const std::vector parents = {YGNodeGetParent(root_child0), YGNodeGetParent(root_child1)}; + const std::vector expectedParents = {nullptr, nullptr}; + ASSERT_EQ(parents, expectedParents); + + YGNodeFreeRecursive(root); +} + +TEST(YogaTest, set_children_replaces_non_common_children) { + YGNodeRef const root = YGNodeNew(); + YGNodeRef const root_child0 = YGNodeNew(); + YGNodeRef const root_child1 = YGNodeNew(); + + YGNodeSetChildren(root, {root_child0, root_child1}); + + YGNodeRef const root_child2 = YGNodeNew(); + YGNodeRef const root_child3 = YGNodeNew(); + + YGNodeSetChildren(root, {root_child2, root_child3}); + + const std::vector children = getChildren(root); + const std::vector expectedChildren = {root_child2, root_child3}; + ASSERT_EQ(children, expectedChildren); + + const std::vector parents = {YGNodeGetParent(root_child0), YGNodeGetParent(root_child1)}; + const std::vector expectedParents = {nullptr, nullptr}; + ASSERT_EQ(parents, expectedParents); + + YGNodeFreeRecursive(root); + YGNodeFree(root_child0); + YGNodeFree(root_child1); +} + +TEST(YogaTest, set_children_keeps_and_reorders_common_children) { + YGNodeRef const root = YGNodeNew(); + YGNodeRef const root_child0 = YGNodeNew(); + YGNodeRef const root_child1 = YGNodeNew(); + YGNodeRef const root_child2 = YGNodeNew(); + + YGNodeSetChildren(root, {root_child0, root_child1, root_child2}); + + YGNodeRef const root_child3 = YGNodeNew(); + + YGNodeSetChildren(root, {root_child2, root_child1, root_child3}); + + const std::vector children = getChildren(root); + const std::vector expectedChildren = {root_child2, root_child1, root_child3}; + ASSERT_EQ(children, expectedChildren); + + const std::vector parents = { + YGNodeGetParent(root_child0), + YGNodeGetParent(root_child1), + YGNodeGetParent(root_child2), + YGNodeGetParent(root_child3) + }; + const std::vector expectedParents = {nullptr, root, root, root}; + ASSERT_EQ(parents, expectedParents); + + YGNodeFreeRecursive(root); + YGNodeFree(root_child0); +} diff --git a/yoga/Yoga.cpp b/yoga/Yoga.cpp index 570f7ab9..1600b1b0 100644 --- a/yoga/Yoga.cpp +++ b/yoga/Yoga.cpp @@ -295,8 +295,8 @@ static YGNodeRef YGNodeDeepClone(YGNodeRef oldNode) { } void YGNodeFree(const YGNodeRef node) { - if (node->getParent()) { - node->getParent()->removeChild(node); + if (YGNodeRef parent = node->getParent()) { + parent->removeChild(node); node->setParent(nullptr); } @@ -477,6 +477,48 @@ void YGNodeRemoveAllChildren(const YGNodeRef parent) { parent->markDirtyAndPropogate(); } +static void YGNodeSetChildrenInternal(YGNodeRef const parent, const std::vector &children) +{ + if (!parent) { + return; + } + if (children.size() == 0) { + if (YGNodeGetChildCount(parent) > 0) { + for (YGNodeRef const child : parent->getChildren()) { + child->setLayout(YGLayout()); + child->setParent(nullptr); + } + parent->setChildren(YGVector()); + parent->markDirtyAndPropogate(); + } + } else { + if (YGNodeGetChildCount(parent) > 0) { + for (YGNodeRef const oldChild : parent->getChildren()) { + // Our new children may have nodes in common with the old children. We don't reset these common nodes. + if (std::find(children.begin(), children.end(), oldChild) == children.end()) { + oldChild->setLayout(YGLayout()); + oldChild->setParent(nullptr); + } + } + } + parent->setChildren(children); + for (YGNodeRef child : children) { + child->setParent(parent); + } + parent->markDirtyAndPropogate(); + } +} + +void YGNodeSetChildren(YGNodeRef const parent, const YGNodeRef c[], const uint32_t count) { + const YGVector children = {c, c + count}; + YGNodeSetChildrenInternal(parent, children); +} + +void YGNodeSetChildren(YGNodeRef const parent, const std::vector &children) +{ + YGNodeSetChildrenInternal(parent, children); +} + YGNodeRef YGNodeGetChild(const YGNodeRef node, const uint32_t index) { if (index < node->getChildren().size()) { return node->getChild(index); @@ -3925,3 +3967,18 @@ void *YGConfigGetContext(const YGConfigRef config) { void YGConfigSetNodeClonedFunc(const YGConfigRef config, const YGNodeClonedFunc callback) { config->cloneNodeCallback = callback; } + +static void YGTraverseChildrenPreOrder(const YGVector& children, const std::function& f) { + for (YGNodeRef node : children) { + f(node); + YGTraverseChildrenPreOrder(node->getChildren(), f); + } +} + +void YGTraversePreOrder(YGNodeRef const node, std::function&& f) { + if (!node) { + return; + } + f(node); + YGTraverseChildrenPreOrder(node->getChildren(), f); +} diff --git a/yoga/Yoga.h b/yoga/Yoga.h index 2ec6a687..1a985fa5 100644 --- a/yoga/Yoga.h +++ b/yoga/Yoga.h @@ -84,6 +84,7 @@ WIN_EXPORT void YGNodeRemoveAllChildren(const YGNodeRef node); WIN_EXPORT YGNodeRef YGNodeGetChild(const YGNodeRef node, const uint32_t index); WIN_EXPORT YGNodeRef YGNodeGetParent(const YGNodeRef node); WIN_EXPORT uint32_t YGNodeGetChildCount(const YGNodeRef node); +WIN_EXPORT void YGNodeSetChildren(YGNodeRef const parent, const YGNodeRef children[], const uint32_t count); WIN_EXPORT void YGNodeCalculateLayout(const YGNodeRef node, const float availableWidth, @@ -298,3 +299,15 @@ WIN_EXPORT float YGRoundValueToPixelGrid( const bool forceFloor); YG_EXTERN_C_END + +#ifdef __cplusplus + +#include +#include + +// Calls f on each node in the tree including the given node argument. +extern void YGTraversePreOrder(YGNodeRef const node, std::function&& f); + +extern void YGNodeSetChildren(YGNodeRef const parent, const std::vector &children); + +#endif