Compare commits

...

9 Commits

Author SHA1 Message Date
potato520hjj
b7421b1270 fix: remove non-ASCII comment to resolve C4819 encoding warning in VS2022 (zh-CN)
warning C4819: The file contains a character that cannot be represented in the current code page (936). Save the file in Unicode format to prevent data loss.
2025-06-08 01:31:57 +08:00
Nick Gerleman
1232761571 YGPersistentNodeCloningTest (#1813)
Summary:
Pull Request resolved: https://github.com/facebook/yoga/pull/1813

This adds a unit test to Yoga, which emulates the model of "persistent Yoga nodes" and cloning used by React Fabric, including the private (but relied on) Yoga APIs.

It models the over-invalidation exposed in D75287261, which reproduces (due to Yoga incorrectly measuring flex-basis under fit-content, and that constraint changing when sibling changes) but this test for now sets a definite height on A, to show that we only clone what is neccesary, when measure constraints do not have to change.

Having a minimal version of Fabric's model in Yoga unit tests should make some of these interesting interactions a bit easier to debug.

Changelog: [Internal]

Reviewed By: javache

Differential Revision: D75572762

fbshipit-source-id: cda8b3fdd6e538a55dd100494518688c864bd233
2025-06-02 19:32:45 -07:00
Nick Gerleman
c935fd5e10 Resubmit: Expose Unsnapped Dimensions (#1811)
Summary:
Pull Request resolved: https://github.com/facebook/yoga/pull/1811

X-link: https://github.com/facebook/react-native/pull/51298

## Resubmit

This was backed out due to being up the stack from another change that was backed out, but should be safe by itself.

## Original

We want to know if an artifact created during measurement can fully be reused after final layout, but the final layout is allowed to be slightly larger due to pixel grid rounding (while still allowing reuse). It's hard to tell after the fact, whether it is larger because of this rounding (though the measure is used), or if it may be a pixel larger for valid reasons.

We can expose the unsnapped dimensions of a node to give us this information, and to correlate measurement artifacts.

This is most of the time the same as the layout's measured dimension, though I don't think it's safe to use this, since anything else measuring the node after could clobber this (I think `YGNodeLayoutGetOverflow` may also be prone to this as a bug).

Changelog: [Internal]

Reviewed By: joevilches

Differential Revision: D74673119

fbshipit-source-id: 06d2eb21e28b76458ec88f4dfcaec809707d0390
2025-05-13 18:21:04 -07:00
Yannick Loriot
624325302c Revert D74292949: Expose Unsnapped Dimensions
Differential Revision:
D74292949

Original commit changeset: 05011c66a9a9

Original Phabricator Diff: D74292949

fbshipit-source-id: c6ca51c7b882950d54b6a43e206973774db40429
2025-05-09 01:45:16 -07:00
Nick Gerleman
37a94a86de Expose Unsnapped Dimensions (#1809)
Summary:
Pull Request resolved: https://github.com/facebook/yoga/pull/1809

X-link: https://github.com/facebook/react-native/pull/51181

We want to know if an artifact created during measurement can fully be reused after final layout, but the final layout is allowed to be slightly larger due to pixel grid rounding (while still allowing reuse). It's hard to tell after the fact, whether it is larger because of this rounding (though the measure is used), or if it may be a pixel larger for valid reasons.

We can expose the unsnapped dimensions of a node to give us this information, and to correlate measurement artifacts.

This is most of the time the same as the layout's measured dimension, though I don't think it's safe to use this, since anything else measuring the node after could clobber this (I think `YGNodeLayoutGetOverflow` may also be prone to this as a bug).

Changelog: [Internal]

Reviewed By: rshest

Differential Revision: D74292949

fbshipit-source-id: 05011c66a9a9480544313eb1dfe2c46bf7742bac
2025-05-08 17:45:16 -07:00
Santhosh Kumar
4abc1a7d5f Fix documentation of 'alignContent' property configured with spaces (#1808)
Summary:
The documentation shall be corrected to specify in which axis the spaces are distributed when flex container is configured 'alignContent' property.

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

Reviewed By: joevilches

Differential Revision: D74347272

Pulled By: philIip

fbshipit-source-id: 1b05840028bca774c9d4a68562bcf537d5a72500
2025-05-07 14:40:02 -07:00
Yurii Nakonechnyi
51e6095005 LayoutData - added explicit default fields values initialization (#1802)
Summary:
X-link: https://github.com/facebook/react-native/pull/50227

Explicit defaults add 'strictness' and take the guesswork out of what's going on in places like this:

6455a848a7/yoga/algorithm/CalculateLayout.cpp (L2345)

Changelog: [Internal]

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

Reviewed By: javache

Differential Revision: D71687310

Pulled By: NickGerleman

fbshipit-source-id: f11d18aa68ce7ccd17fb1d5af0e729e8c0711cf9
2025-03-24 12:23:46 -07:00
Yurii Nakonechnyi
79cef614ce fatalWithMessage() - added warning suppression: unused 'message' argument (#1803)
Summary:
X-link: https://github.com/facebook/react-native/pull/50148

## Changelog:

[Internal] - suppression: unused 'message' argument

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

Reviewed By: NickGerleman

Differential Revision: D71492528

Pulled By: lunaleaps

fbshipit-source-id: 6fc7a665066351d15e09f0b6c82ed1fe3f688a94
2025-03-19 19:06:50 -07:00
Rob Hogan
6455a848a7 Update GHA actions/upload-artifact to v4, unbreak CI (#1799)
Summary:
Pull Request resolved: https://github.com/facebook/yoga/pull/1799

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

`actions/upload-artifact@v3` is deprecated and will no longer execute, causing CI to fail - eg:

https://github.com/facebook/yoga/actions/runs/13789185831/job/38564343959

See https://github.blog/changelog/2024-04-16-deprecation-notice-v3-of-the-artifact-actions/ for context

Reviewed By: NickGerleman

Differential Revision: D70986391

fbshipit-source-id: 66cec50bb485e89c0948c752ba7dc2a4f42617d6
2025-03-11 13:40:51 -07:00
14 changed files with 252 additions and 19 deletions

View File

@@ -24,7 +24,7 @@ jobs:
ORG_GRADLE_PROJECT_SIGNING_PWD: ${{ secrets.ORG_GRADLE_PROJECT_SIGNING_PWD }} ORG_GRADLE_PROJECT_SIGNING_PWD: ${{ secrets.ORG_GRADLE_PROJECT_SIGNING_PWD }}
- name: Upload Build Artifacts - name: Upload Build Artifacts
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: 'snapshot-artifacts' name: 'snapshot-artifacts'
path: '~/.m2/repository/' path: '~/.m2/repository/'

View File

@@ -23,7 +23,7 @@ jobs:
ORG_GRADLE_PROJECT_USE_SNAPSHOT: true ORG_GRADLE_PROJECT_USE_SNAPSHOT: true
- name: Upload Build Artifacts - name: Upload Build Artifacts
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: 'snapshot-artifacts' name: 'snapshot-artifacts'
path: '~/.m2/repository/' path: '~/.m2/repository/'

View File

@@ -110,7 +110,7 @@ jobs:
run: yarn pack --filename yoga-layout.tar.gz run: yarn pack --filename yoga-layout.tar.gz
working-directory: javascript working-directory: javascript
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v4
with: with:
name: npm-package name: npm-package
path: javascript/yoga-layout.tar.gz path: javascript/yoga-layout.tar.gz

View File

@@ -0,0 +1,184 @@
/*
* 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 <gtest/gtest.h>
#include <yoga/Yoga.h>
#include <yoga/config/Config.h>
#include <yoga/node/Node.h>
#include <functional>
#include <memory>
#include <vector>
namespace facebook::yoga {
struct YGPersistentNodeCloningTest : public ::testing::Test {
struct NodeWrapper {
explicit NodeWrapper(
YGConfigRef config,
std::vector<std::shared_ptr<NodeWrapper>> children = {})
: node{YGNodeNewWithConfig(config)}, children{std::move(children)} {
YGNodeSetContext(node, this);
auto privateNode = resolveRef(node);
for (const auto& child : this->children) {
auto privateChild = resolveRef(child->node);
// Claim first ownership of not yet owned nodes, to avoid immediately
// cloning them
if (YGNodeGetOwner(child->node) == nullptr) {
privateChild->setOwner(privateNode);
}
privateNode->insertChild(privateChild, privateNode->getChildCount());
}
}
// Clone, with current children, for mutation
NodeWrapper(const NodeWrapper& other)
: node{YGNodeClone(other.node)}, children{other.children} {
YGNodeSetContext(node, this);
auto privateNode = resolveRef(node);
privateNode->setOwner(nullptr);
}
// Clone, with new children
NodeWrapper(
const NodeWrapper& other,
std::vector<std::shared_ptr<NodeWrapper>> children)
: node{YGNodeClone(other.node)}, children{std::move(children)} {
YGNodeSetContext(node, this);
auto privateNode = resolveRef(node);
privateNode->setOwner(nullptr);
privateNode->setChildren({});
privateNode->setDirty(true);
for (const auto& child : this->children) {
auto privateChild = resolveRef(child->node);
// Claim first ownership of not yet owned nodes, to avoid immediately
// cloning them
if (YGNodeGetOwner(child->node) == nullptr) {
privateChild->setOwner(privateNode);
}
privateNode->insertChild(privateChild, privateNode->getChildCount());
}
}
NodeWrapper(NodeWrapper&&) = delete;
~NodeWrapper() {
YGNodeFree(node);
}
NodeWrapper& operator=(const NodeWrapper& other) = delete;
NodeWrapper& operator=(NodeWrapper&& other) = delete;
YGNodeRef node;
std::vector<std::shared_ptr<NodeWrapper>> children;
};
struct ConfigWrapper {
ConfigWrapper() {
YGConfigSetCloneNodeFunc(
config,
[](YGNodeConstRef oldNode, YGNodeConstRef owner, size_t childIndex) {
onClone(oldNode, owner, childIndex);
auto wrapper = static_cast<NodeWrapper*>(YGNodeGetContext(owner));
auto old = static_cast<NodeWrapper*>(YGNodeGetContext(oldNode));
wrapper->children[childIndex] = std::make_shared<NodeWrapper>(*old);
return wrapper->children[childIndex]->node;
});
}
ConfigWrapper(const ConfigWrapper&) = delete;
ConfigWrapper(ConfigWrapper&&) = delete;
~ConfigWrapper() {
YGConfigFree(config);
}
ConfigWrapper& operator=(const ConfigWrapper&) = delete;
ConfigWrapper& operator=(ConfigWrapper&&) = delete;
YGConfigRef config{YGConfigNew()};
};
ConfigWrapper configWrapper;
YGConfigRef config{configWrapper.config};
void SetUp() override {
onClone = [](...) {};
}
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
static inline std::function<void(YGNodeConstRef, YGNodeConstRef, size_t)>
onClone;
};
TEST_F(
YGPersistentNodeCloningTest,
changing_sibling_height_does_not_clone_neighbors) {
// <ScrollView>
// <View id="Sibling" style={{ height: 1 }} />
// <View id="A" style={{ height: 1 }}>
// <View id="B">
// <View id="C">
// <View id="D"/>
// </View>
// </View>
// </View>
// </ScrollView>
auto sibling = std::make_shared<NodeWrapper>(config);
YGNodeStyleSetHeight(sibling->node, 1);
auto d = std::make_shared<NodeWrapper>(config);
auto c = std::make_shared<NodeWrapper>(config, std::vector{d});
auto b = std::make_shared<NodeWrapper>(config, std::vector{c});
auto a = std::make_shared<NodeWrapper>(config, std::vector{b});
YGNodeStyleSetHeight(a->node, 1);
auto scrollContentView =
std::make_shared<NodeWrapper>(config, std::vector{sibling, a});
YGNodeStyleSetPositionType(scrollContentView->node, YGPositionTypeAbsolute);
auto scrollView =
std::make_shared<NodeWrapper>(config, std::vector{scrollContentView});
YGNodeStyleSetWidth(scrollView->node, 100);
YGNodeStyleSetHeight(scrollView->node, 100);
// We don't expect any cloning during the first layout
onClone = [](...) { FAIL(); };
YGNodeCalculateLayout(
scrollView->node, YGUndefined, YGUndefined, YGDirectionLTR);
auto siblingPrime = std::make_shared<NodeWrapper>(config);
YGNodeStyleSetHeight(siblingPrime->node, 2);
auto scrollContentViewPrime = std::make_shared<NodeWrapper>(
*scrollContentView, std::vector{siblingPrime, a});
auto scrollViewPrime = std::make_shared<NodeWrapper>(
*scrollView, std::vector{scrollContentViewPrime});
std::vector<NodeWrapper*> nodesCloned;
// We should only need to clone "A"
onClone = [&](YGNodeConstRef oldNode,
YGNodeConstRef /*owner*/,
size_t /*childIndex*/) {
nodesCloned.push_back(static_cast<NodeWrapper*>(YGNodeGetContext(oldNode)));
};
YGNodeCalculateLayout(
scrollViewPrime->node, YGUndefined, YGUndefined, YGDirectionLTR);
EXPECT_EQ(nodesCloned.size(), 1);
EXPECT_EQ(nodesCloned[0], a.get());
}
} // namespace facebook::yoga

View File

@@ -161,3 +161,23 @@ TEST(YogaTest, per_node_point_scale_factor) {
YGConfigFree(config2); YGConfigFree(config2);
YGConfigFree(config3); YGConfigFree(config3);
} }
TEST(YogaTest, raw_layout_dimensions) {
YGConfigRef config = YGConfigNew();
YGConfigSetPointScaleFactor(config, 0.5f);
YGNodeRef root = YGNodeNewWithConfig(config);
YGNodeStyleSetWidth(root, 11.5f);
YGNodeStyleSetHeight(root, 9.5f);
YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR);
ASSERT_EQ(YGNodeLayoutGetWidth(root), 12.0f);
ASSERT_EQ(YGNodeLayoutGetHeight(root), 10.0f);
ASSERT_EQ(YGNodeLayoutGetRawWidth(root), 11.5f);
ASSERT_EQ(YGNodeLayoutGetRawHeight(root), 9.5f);
YGNodeFreeRecursive(root);
YGConfigFree(config);
}

View File

@@ -17,15 +17,15 @@ has effect when items are wrapped to multiple lines using [flex wrap](/docs/styl
**Center**: Align wrapped lines in the center of the container's cross axis. **Center**: Align wrapped lines in the center of the container's cross axis.
**Space between**: Evenly space wrapped lines across the container's main axis, distributing **Space between**: Evenly space wrapped lines across the container's cross axis, distributing
remaining space between the lines. remaining space between the lines.
**Space around**: Evenly space wrapped lines across the container's main axis, distributing **Space around**: Evenly space wrapped lines across the container's cross axis, distributing
remaining space around the lines. Compared to space between using remaining space around the lines. Compared to space between using
space around will result in space being distributed to the beginning of space around will result in space being distributed to the beginning of
the first lines and end of the last line. the first lines and end of the last line.
**Space evenly**: Evenly space wrapped lines across the container's main axis, distributing **Space evenly**: Evenly space wrapped lines across the container's cross axis, distributing
remaining space around the lines. Compared to space around, space evenly will not remaining space around the lines. Compared to space around, space evenly will not
double the gaps between children. The size of gaps between children and between double the gaps between children. The size of gaps between children and between
the parent's edges and the first/last child will all be equal. the parent's edges and the first/last child will all be equal.

View File

@@ -90,3 +90,11 @@ float YGNodeLayoutGetPadding(YGNodeConstRef node, YGEdge edge) {
return getResolvedLayoutProperty<&LayoutResults::padding>( return getResolvedLayoutProperty<&LayoutResults::padding>(
node, scopedEnum(edge)); node, scopedEnum(edge));
} }
float YGNodeLayoutGetRawHeight(YGNodeConstRef node) {
return resolveRef(node)->getLayout().rawDimension(Dimension::Height);
}
float YGNodeLayoutGetRawWidth(YGNodeConstRef node) {
return resolveRef(node)->getLayout().rawDimension(Dimension::Width);
}

View File

@@ -32,4 +32,14 @@ YG_EXPORT float YGNodeLayoutGetMargin(YGNodeConstRef node, YGEdge edge);
YG_EXPORT float YGNodeLayoutGetBorder(YGNodeConstRef node, YGEdge edge); YG_EXPORT float YGNodeLayoutGetBorder(YGNodeConstRef node, YGEdge edge);
YG_EXPORT float YGNodeLayoutGetPadding(YGNodeConstRef node, YGEdge edge); YG_EXPORT float YGNodeLayoutGetPadding(YGNodeConstRef node, YGEdge edge);
/**
* Return the measured height of the node, before layout rounding
*/
YG_EXPORT float YGNodeLayoutGetRawHeight(YGNodeConstRef node);
/**
* Return the measured width of the node, before layout rounding
*/
YG_EXPORT float YGNodeLayoutGetRawWidth(YGNodeConstRef node);
YG_EXTERN_C_END YG_EXTERN_C_END

View File

@@ -106,25 +106,25 @@ void roundLayoutResultsToPixelGrid(
const bool hasFractionalHeight = const bool hasFractionalHeight =
!yoga::inexactEquals(round(scaledNodeHeight), scaledNodeHeight); !yoga::inexactEquals(round(scaledNodeHeight), scaledNodeHeight);
node->setLayoutDimension( node->getLayout().setDimension(
Dimension::Width,
roundValueToPixelGrid( roundValueToPixelGrid(
absoluteNodeRight, absoluteNodeRight,
pointScaleFactor, pointScaleFactor,
(textRounding && hasFractionalWidth), (textRounding && hasFractionalWidth),
(textRounding && !hasFractionalWidth)) - (textRounding && !hasFractionalWidth)) -
roundValueToPixelGrid( roundValueToPixelGrid(
absoluteNodeLeft, pointScaleFactor, false, textRounding), absoluteNodeLeft, pointScaleFactor, false, textRounding));
Dimension::Width);
node->setLayoutDimension( node->getLayout().setDimension(
Dimension::Height,
roundValueToPixelGrid( roundValueToPixelGrid(
absoluteNodeBottom, absoluteNodeBottom,
pointScaleFactor, pointScaleFactor,
(textRounding && hasFractionalHeight), (textRounding && hasFractionalHeight),
(textRounding && !hasFractionalHeight)) - (textRounding && !hasFractionalHeight)) -
roundValueToPixelGrid( roundValueToPixelGrid(
absoluteNodeTop, pointScaleFactor, false, textRounding), absoluteNodeTop, pointScaleFactor, false, textRounding));
Dimension::Height);
} }
for (yoga::Node* child : node->getChildren()) { for (yoga::Node* child : node->getChildren()) {

View File

@@ -27,7 +27,7 @@ enum class SizingMode {
StretchFit, StretchFit,
/** /**
* A boxs ideal size in a given axis when given infinite available space. * A box's "ideal" size in a given axis when given infinite available space.
* Usually this is the smallest size the box could take in that axis while * Usually this is the smallest size the box could take in that axis while
* still fitting around its contents, i.e. minimizing unfilled space while * still fitting around its contents, i.e. minimizing unfilled space while
* avoiding overflow. * avoiding overflow.

View File

@@ -19,6 +19,7 @@ namespace facebook::yoga {
#if defined(__cpp_exceptions) #if defined(__cpp_exceptions)
throw std::logic_error(message); throw std::logic_error(message);
#else #else
static_cast<void>(message); // Unused
std::terminate(); std::terminate();
#endif #endif
} }

View File

@@ -36,12 +36,12 @@ enum struct LayoutPassReason : int {
}; };
struct LayoutData { struct LayoutData {
int layouts; int layouts = 0;
int measures; int measures = 0;
uint32_t maxMeasureCache; uint32_t maxMeasureCache = 0;
int cachedLayouts; int cachedLayouts = 0;
int cachedMeasures; int cachedMeasures = 0;
int measureCallbacks; int measureCallbacks = 0;
std::array<int, static_cast<uint8_t>(LayoutPassReason::COUNT)> std::array<int, static_cast<uint8_t>(LayoutPassReason::COUNT)>
measureCallbackReasonsCount; measureCallbackReasonsCount;
}; };

View File

@@ -66,10 +66,18 @@ struct LayoutResults {
return measuredDimensions_[yoga::to_underlying(axis)]; return measuredDimensions_[yoga::to_underlying(axis)];
} }
float rawDimension(Dimension axis) const {
return rawDimensions_[yoga::to_underlying(axis)];
}
void setMeasuredDimension(Dimension axis, float dimension) { void setMeasuredDimension(Dimension axis, float dimension) {
measuredDimensions_[yoga::to_underlying(axis)] = dimension; measuredDimensions_[yoga::to_underlying(axis)] = dimension;
} }
void setRawDimension(Dimension axis, float dimension) {
rawDimensions_[yoga::to_underlying(axis)] = dimension;
}
float position(PhysicalEdge physicalEdge) const { float position(PhysicalEdge physicalEdge) const {
return position_[yoga::to_underlying(physicalEdge)]; return position_[yoga::to_underlying(physicalEdge)];
} }
@@ -113,6 +121,7 @@ struct LayoutResults {
std::array<float, 2> dimensions_ = {{YGUndefined, YGUndefined}}; std::array<float, 2> dimensions_ = {{YGUndefined, YGUndefined}};
std::array<float, 2> measuredDimensions_ = {{YGUndefined, YGUndefined}}; std::array<float, 2> measuredDimensions_ = {{YGUndefined, YGUndefined}};
std::array<float, 2> rawDimensions_ = {{YGUndefined, YGUndefined}};
std::array<float, 4> position_ = {}; std::array<float, 4> position_ = {};
std::array<float, 4> margin_ = {}; std::array<float, 4> margin_ = {};
std::array<float, 4> border_ = {}; std::array<float, 4> border_ = {};

View File

@@ -247,6 +247,7 @@ void Node::setLayoutHadOverflow(bool hadOverflow) {
void Node::setLayoutDimension(float lengthValue, Dimension dimension) { void Node::setLayoutDimension(float lengthValue, Dimension dimension) {
layout_.setDimension(dimension, lengthValue); layout_.setDimension(dimension, lengthValue);
layout_.setRawDimension(dimension, lengthValue);
} }
// If both left and right are defined, then use left. Otherwise return +left or // If both left and right are defined, then use left. Otherwise return +left or