aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorSanthosh Kumar <[email protected]>2025-03-24 12:44:47 +0100
committerJan Arve Sæther <[email protected]>2025-05-28 03:29:34 +0200
commit6617c64b8f00ce99abcf255520456f2bc5867737 (patch)
tree9dcf99bcf5c908a04ed926f6baea9c8414504c88 /src
parentb1fc47a467562340278b90acb36c3ea12c12bfa9 (diff)
Support Flexbox layout in Qt QuickHEADdev
The Flexbox component allows the arrangement of the items within the layout in a more flexible way. There is a CSS standard defined for the flexbox layout https://www.w3.org/TR/CSS3-flexbox/. This can be achieved in qt-quick using the yoga library (https://github.com/facebook/yoga.git). [ChangeLog][Third-Party Code] Added MIT LICENSE from the third-party Facebook yoga source (https://github.com/facebook/yoga/blob/main/LICENSE) to enable its usage in Qt QuickLayouts. Task-number: QTBUG-133633 Change-Id: I2187dba031cb4842baef1c5a84c7132eb8c63137 Reviewed-by: Jan Arve Sæther <[email protected]>
Diffstat (limited to 'src')
-rw-r--r--src/3rdparty/yoga/BitUtils.h73
-rw-r--r--src/3rdparty/yoga/CMakeLists.txt40
-rw-r--r--src/3rdparty/yoga/CompactValue.h214
-rw-r--r--src/3rdparty/yoga/LICENSE21
-rw-r--r--src/3rdparty/yoga/Utils.cpp81
-rw-r--r--src/3rdparty/yoga/Utils.h146
-rw-r--r--src/3rdparty/yoga/YGConfig.cpp149
-rw-r--r--src/3rdparty/yoga/YGConfig.h110
-rw-r--r--src/3rdparty/yoga/YGEnums.cpp256
-rw-r--r--src/3rdparty/yoga/YGEnums.h143
-rw-r--r--src/3rdparty/yoga/YGFloatOptional.h70
-rw-r--r--src/3rdparty/yoga/YGLayout.cpp40
-rw-r--r--src/3rdparty/yoga/YGLayout.h63
-rw-r--r--src/3rdparty/yoga/YGMacros.h138
-rw-r--r--src/3rdparty/yoga/YGNode.cpp582
-rw-r--r--src/3rdparty/yoga/YGNode.h335
-rw-r--r--src/3rdparty/yoga/YGNodePrint.cpp245
-rw-r--r--src/3rdparty/yoga/YGNodePrint.h28
-rw-r--r--src/3rdparty/yoga/YGStyle.cpp54
-rw-r--r--src/3rdparty/yoga/YGStyle.h238
-rw-r--r--src/3rdparty/yoga/YGValue.cpp10
-rw-r--r--src/3rdparty/yoga/YGValue.h59
-rw-r--r--src/3rdparty/yoga/Yoga-internal.h160
-rw-r--r--src/3rdparty/yoga/Yoga.cpp4345
-rw-r--r--src/3rdparty/yoga/Yoga.h381
-rw-r--r--src/3rdparty/yoga/event/event.cpp84
-rw-r--r--src/3rdparty/yoga/event/event.h144
-rw-r--r--src/3rdparty/yoga/log.cpp62
-rw-r--r--src/3rdparty/yoga/log.h38
-rw-r--r--src/3rdparty/yoga/qt_attribution.json13
-rw-r--r--src/quick/doc/snippets/layouts/simpleFlexboxLayout.qml47
-rw-r--r--src/quicklayouts/CMakeLists.txt23
-rw-r--r--src/quicklayouts/qquickflexboxlayout.cpp662
-rw-r--r--src/quicklayouts/qquickflexboxlayout_p.h251
-rw-r--r--src/quicklayouts/qquickflexboxlayoutengine.cpp327
-rw-r--r--src/quicklayouts/qquickflexboxlayoutengine_p.h57
-rw-r--r--src/quicklayouts/qquickflexboxlayoutitem.cpp294
-rw-r--r--src/quicklayouts/qquickflexboxlayoutitem_p.h87
38 files changed, 10070 insertions, 0 deletions
diff --git a/src/3rdparty/yoga/BitUtils.h b/src/3rdparty/yoga/BitUtils.h
new file mode 100644
index 0000000000..d8d38a02d8
--- /dev/null
+++ b/src/3rdparty/yoga/BitUtils.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2016 The Qt Company Ltd.
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#pragma once
+
+#include <bitset>
+#include <cstdio>
+#include <cstdint>
+
+#include <yoga/YGEnums.h>
+
+namespace facebook {
+namespace yoga {
+
+namespace detail {
+
+// std::bitset with one bit for each option defined in YG_ENUM_SEQ_DECL
+template <typename Enum>
+using EnumBitset = std::bitset<facebook::yoga::enums::count<Enum>()>;
+
+constexpr size_t log2ceilFn(size_t n) {
+ return n < 1 ? 0 : (1 + log2ceilFn(n / 2));
+}
+
+constexpr int mask(size_t bitWidth, size_t index) {
+ return ((1 << bitWidth) - 1) << index;
+}
+
+// The number of bits necessary to represent enums defined with YG_ENUM_SEQ_DECL
+template <typename Enum>
+constexpr size_t bitWidthFn() {
+ static_assert(
+ enums::count<Enum>() > 0, "Enums must have at least one entries");
+ return log2ceilFn(enums::count<Enum>() - 1);
+}
+
+template <typename Enum>
+constexpr Enum getEnumData(int flags, size_t index) {
+ return static_cast<Enum>((flags & mask(bitWidthFn<Enum>(), index)) >> index);
+}
+
+template <typename Enum>
+void setEnumData(uint32_t& flags, size_t index, int newValue) {
+ flags = (flags & ~mask(bitWidthFn<Enum>(), index)) |
+ ((newValue << index) & (mask(bitWidthFn<Enum>(), index)));
+}
+
+template <typename Enum>
+void setEnumData(uint8_t& flags, size_t index, int newValue) {
+ flags = (flags & ~static_cast<uint8_t>(mask(bitWidthFn<Enum>(), index))) |
+ ((newValue << index) &
+ (static_cast<uint8_t>(mask(bitWidthFn<Enum>(), index))));
+}
+
+constexpr bool getBooleanData(int flags, size_t index) {
+ return (flags >> index) & 1;
+}
+
+inline void setBooleanData(uint8_t& flags, size_t index, bool value) {
+ if (value) {
+ flags |= 1 << index;
+ } else {
+ flags &= ~(1 << index);
+ }
+}
+
+} // namespace detail
+} // namespace yoga
+} // namespace facebook
diff --git a/src/3rdparty/yoga/CMakeLists.txt b/src/3rdparty/yoga/CMakeLists.txt
new file mode 100644
index 0000000000..fac5dd9cd5
--- /dev/null
+++ b/src/3rdparty/yoga/CMakeLists.txt
@@ -0,0 +1,40 @@
+# Copyright (C) 2016 The Qt Company Ltd.
+# Copyright (c) Meta Platforms, Inc. and affiliates.
+#
+# SPDX-License-Identifier: MIT
+
+cmake_minimum_required(VERSION 3.13...3.26)
+project(yogacore)
+set(CMAKE_VERBOSE_MAKEFILE on)
+
+if(TARGET yogacore)
+ return()
+endif()
+
+include(CheckIPOSupported)
+
+set(YOGA_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/..)
+include(${YOGA_ROOT}/cmake/project-defaults.cmake)
+
+
+file(GLOB SOURCES CONFIGURE_DEPENDS
+ ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/**/*.cpp)
+
+add_library(yogacore STATIC ${SOURCES})
+
+# Yoga conditionally uses <android/log> when building for Android
+if (ANDROID)
+ target_link_libraries(yogacore log)
+endif()
+
+check_ipo_supported(RESULT result)
+if(result)
+ set_target_properties(yogacore PROPERTIES
+ CMAKE_INTERPROCEDURAL_OPTIMIZATION true)
+endif()
+
+target_include_directories(yogacore
+ PUBLIC
+ $<BUILD_INTERFACE:${YOGA_ROOT}>
+ $<INSTALL_INTERFACE:${CMAKE_INSTALL_PREFIX}/include/yoga>)
diff --git a/src/3rdparty/yoga/CompactValue.h b/src/3rdparty/yoga/CompactValue.h
new file mode 100644
index 0000000000..457a745887
--- /dev/null
+++ b/src/3rdparty/yoga/CompactValue.h
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2016 The Qt Company Ltd.
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#pragma once
+
+#include <cmath>
+#include <cstdint>
+#include <limits>
+
+#include <yoga/YGMacros.h>
+#include <yoga/YGValue.h>
+
+#if defined(__has_include) && __has_include(<version>)
+// needed to be able to evaluate defined(__cpp_lib_bit_cast)
+#include <version>
+#else
+// needed to be able to evaluate defined(__cpp_lib_bit_cast)
+#include <ciso646>
+#endif
+
+#ifdef __cpp_lib_bit_cast
+#include <bit>
+#else
+#include <cstring>
+#endif
+
+static_assert(
+ std::numeric_limits<float>::is_iec559,
+ "facebook::yoga::detail::CompactValue only works with IEEE754 floats");
+
+#ifdef YOGA_COMPACT_VALUE_TEST
+#define VISIBLE_FOR_TESTING public:
+#else
+#define VISIBLE_FOR_TESTING private:
+#endif
+
+namespace facebook {
+namespace yoga {
+namespace detail {
+
+// This class stores YGValue in 32 bits.
+// - The value does not matter for Undefined and Auto. NaNs are used for their
+// representation.
+// - To differentiate between Point and Percent, one exponent bit is used.
+// Supported the range [0x40, 0xbf] (0xbf is inclusive for point, but
+// exclusive for percent).
+// - Value ranges:
+// points: 1.08420217e-19f to 36893485948395847680
+// 0x00000000 0x3fffffff
+// percent: 1.08420217e-19f to 18446742974197923840
+// 0x40000000 0x7f7fffff
+// - Zero is supported, negative zero is not
+// - values outside of the representable range are clamped
+class YOGA_EXPORT CompactValue {
+ friend constexpr bool operator==(CompactValue, CompactValue) noexcept;
+
+public:
+ static constexpr auto LOWER_BOUND = 1.08420217e-19f;
+ static constexpr auto UPPER_BOUND_POINT = 36893485948395847680.0f;
+ static constexpr auto UPPER_BOUND_PERCENT = 18446742974197923840.0f;
+
+ template <YGUnit Unit>
+ static CompactValue of(float value) noexcept {
+ if (value == 0.0f || (value < LOWER_BOUND && value > -LOWER_BOUND)) {
+ constexpr auto zero =
+ Unit == YGUnitPercent ? ZERO_BITS_PERCENT : ZERO_BITS_POINT;
+ return {zero};
+ }
+
+ constexpr auto upperBound =
+ Unit == YGUnitPercent ? UPPER_BOUND_PERCENT : UPPER_BOUND_POINT;
+ if (value > upperBound || value < -upperBound) {
+ value = copysignf(upperBound, value);
+ }
+
+ uint32_t unitBit = Unit == YGUnitPercent ? PERCENT_BIT : 0;
+ auto data = asU32(value);
+ data -= BIAS;
+ data |= unitBit;
+ return {data};
+ }
+
+ template <YGUnit Unit>
+ static CompactValue ofMaybe(float value) noexcept {
+ return std::isnan(value) || std::isinf(value) ? ofUndefined()
+ : of<Unit>(value);
+ }
+
+ static constexpr CompactValue ofZero() noexcept {
+ return CompactValue{ZERO_BITS_POINT};
+ }
+
+ static constexpr CompactValue ofUndefined() noexcept {
+ return CompactValue{};
+ }
+
+ static constexpr CompactValue ofAuto() noexcept {
+ return CompactValue{AUTO_BITS};
+ }
+
+ constexpr CompactValue() noexcept : repr_(0x7FC00000) {}
+
+ CompactValue(const YGValue& x) noexcept : repr_(uint32_t{0}) {
+ switch (x.unit) {
+ case YGUnitUndefined:
+ *this = ofUndefined();
+ break;
+ case YGUnitAuto:
+ *this = ofAuto();
+ break;
+ case YGUnitPoint:
+ *this = of<YGUnitPoint>(x.value);
+ break;
+ case YGUnitPercent:
+ *this = of<YGUnitPercent>(x.value);
+ break;
+ }
+ }
+
+ operator YGValue() const noexcept {
+ switch (repr_) {
+ case AUTO_BITS:
+ return YGValueAuto;
+ case ZERO_BITS_POINT:
+ return YGValue{0.0f, YGUnitPoint};
+ case ZERO_BITS_PERCENT:
+ return YGValue{0.0f, YGUnitPercent};
+ }
+
+ if (std::isnan(asFloat(repr_))) {
+ return YGValueUndefined;
+ }
+
+ auto data = repr_;
+ data &= ~PERCENT_BIT;
+ data += BIAS;
+
+ return YGValue{
+ asFloat(data), repr_ & 0x40000000 ? YGUnitPercent : YGUnitPoint};
+ }
+
+ bool isUndefined() const noexcept {
+ return (
+ repr_ != AUTO_BITS && repr_ != ZERO_BITS_POINT &&
+ repr_ != ZERO_BITS_PERCENT && std::isnan(asFloat(repr_)));
+ }
+
+ bool isAuto() const noexcept { return repr_ == AUTO_BITS; }
+
+private:
+ uint32_t repr_;
+
+ static constexpr uint32_t BIAS = 0x20000000;
+ static constexpr uint32_t PERCENT_BIT = 0x40000000;
+
+ // these are signaling NaNs with specific bit pattern as payload they will be
+ // silenced whenever going through an FPU operation on ARM + x86
+ static constexpr uint32_t AUTO_BITS = 0x7faaaaaa;
+ static constexpr uint32_t ZERO_BITS_POINT = 0x7f8f0f0f;
+ static constexpr uint32_t ZERO_BITS_PERCENT = 0x7f80f0f0;
+
+ constexpr CompactValue(uint32_t data) noexcept : repr_(data) {}
+
+ VISIBLE_FOR_TESTING uint32_t repr() { return repr_; }
+
+ static uint32_t asU32(float f) {
+#ifdef __cpp_lib_bit_cast
+ return std::bit_cast<uint32_t>(f);
+#else
+ uint32_t u;
+ static_assert(
+ sizeof(u) == sizeof(f), "uint32_t and float must have the same size");
+ std::memcpy(&u, &f, sizeof(f));
+ return u;
+#endif
+ }
+
+ static float asFloat(uint32_t u) {
+#ifdef __cpp_lib_bit_cast
+ return std::bit_cast<float>(u);
+#else
+ float f;
+ static_assert(
+ sizeof(f) == sizeof(u), "uint32_t and float must have the same size");
+ std::memcpy(&f, &u, sizeof(u));
+ return f;
+#endif
+ }
+};
+
+template <>
+CompactValue CompactValue::of<YGUnitUndefined>(float) noexcept = delete;
+template <>
+CompactValue CompactValue::of<YGUnitAuto>(float) noexcept = delete;
+template <>
+CompactValue CompactValue::ofMaybe<YGUnitUndefined>(float) noexcept = delete;
+template <>
+CompactValue CompactValue::ofMaybe<YGUnitAuto>(float) noexcept = delete;
+
+constexpr bool operator==(CompactValue a, CompactValue b) noexcept {
+ return a.repr_ == b.repr_;
+}
+
+constexpr bool operator!=(CompactValue a, CompactValue b) noexcept {
+ return !(a == b);
+}
+
+} // namespace detail
+} // namespace yoga
+} // namespace facebook
diff --git a/src/3rdparty/yoga/LICENSE b/src/3rdparty/yoga/LICENSE
new file mode 100644
index 0000000000..b96dcb0480
--- /dev/null
+++ b/src/3rdparty/yoga/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) Facebook, Inc. and its affiliates.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/src/3rdparty/yoga/Utils.cpp b/src/3rdparty/yoga/Utils.cpp
new file mode 100644
index 0000000000..80b0af9927
--- /dev/null
+++ b/src/3rdparty/yoga/Utils.cpp
@@ -0,0 +1,81 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// Copyright (c) Meta Platforms, Inc. and affiliates.
+//
+// SPDX-License-Identifier: MIT
+
+#include "Utils.h"
+#include <stdexcept>
+
+using namespace facebook;
+
+YGFlexDirection YGFlexDirectionCross(
+ const YGFlexDirection flexDirection,
+ const YGDirection direction) {
+ return YGFlexDirectionIsColumn(flexDirection)
+ ? YGResolveFlexDirection(YGFlexDirectionRow, direction)
+ : YGFlexDirectionColumn;
+}
+
+float YGFloatMax(const float a, const float b) {
+ if (!yoga::isUndefined(a) && !yoga::isUndefined(b)) {
+ return fmaxf(a, b);
+ }
+ return yoga::isUndefined(a) ? b : a;
+}
+
+float YGFloatMin(const float a, const float b) {
+ if (!yoga::isUndefined(a) && !yoga::isUndefined(b)) {
+ return fminf(a, b);
+ }
+
+ return yoga::isUndefined(a) ? b : a;
+}
+
+bool YGValueEqual(const YGValue& a, const YGValue& b) {
+ if (a.unit != b.unit) {
+ return false;
+ }
+
+ if (a.unit == YGUnitUndefined ||
+ (yoga::isUndefined(a.value) && yoga::isUndefined(b.value))) {
+ return true;
+ }
+
+ return fabs(a.value - b.value) < 0.0001f;
+}
+
+bool YGFloatsEqual(const float a, const float b) {
+ if (!yoga::isUndefined(a) && !yoga::isUndefined(b)) {
+ return fabs(a - b) < 0.0001f;
+ }
+ return yoga::isUndefined(a) && yoga::isUndefined(b);
+}
+
+bool YGDoubleEqual(const double a, const double b) {
+ if (!yoga::isUndefined(a) && !yoga::isUndefined(b)) {
+ return fabs(a - b) < 0.0001;
+ }
+ return yoga::isUndefined(a) && yoga::isUndefined(b);
+}
+
+float YGFloatSanitize(const float val) {
+ return yoga::isUndefined(val) ? 0 : val;
+}
+
+YGFloatOptional YGFloatOptionalMax(YGFloatOptional op1, YGFloatOptional op2) {
+ if (op1 >= op2) {
+ return op1;
+ }
+ if (op2 > op1) {
+ return op2;
+ }
+ return op1.isUndefined() ? op2 : op1;
+}
+
+void yoga::throwLogicalErrorWithMessage([[maybe_unused]]const char* message) {
+#if defined(__cpp_exceptions)
+ throw std::logic_error(message);
+#else // !defined(__cpp_exceptions)
+ std::terminate();
+#endif // defined(__cpp_exceptions)
+}
diff --git a/src/3rdparty/yoga/Utils.h b/src/3rdparty/yoga/Utils.h
new file mode 100644
index 0000000000..b0a38566f1
--- /dev/null
+++ b/src/3rdparty/yoga/Utils.h
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2016 The Qt Company Ltd.
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#pragma once
+
+#include "YGNode.h"
+#include "Yoga-internal.h"
+#include "CompactValue.h"
+
+// This struct is an helper model to hold the data for step 4 of flexbox algo,
+// which is collecting the flex items in a line.
+//
+// - itemsOnLine: Number of items which can fit in a line considering the
+// available Inner dimension, the flex items computed flexbasis and their
+// margin. It may be different than the difference between start and end
+// indicates because we skip over absolute-positioned items.
+//
+// - sizeConsumedOnCurrentLine: It is accumulation of the dimensions and margin
+// of all the children on the current line. This will be used in order to
+// either set the dimensions of the node if none already exist or to compute
+// the remaining space left for the flexible children.
+//
+// - totalFlexGrowFactors: total flex grow factors of flex items which are to be
+// laid in the current line
+//
+// - totalFlexShrinkFactors: total flex shrink factors of flex items which are
+// to be laid in the current line
+//
+// - endOfLineIndex: Its the end index of the last flex item which was examined
+// and it may or may not be part of the current line(as it may be absolutely
+// positioned or including it may have caused to overshoot availableInnerDim)
+//
+// - relativeChildren: Maintain a vector of the child nodes that can shrink
+// and/or grow.
+
+struct YGCollectFlexItemsRowValues {
+ uint32_t itemsOnLine;
+ float sizeConsumedOnCurrentLine;
+ float totalFlexGrowFactors;
+ float totalFlexShrinkScaledFactors;
+ uint32_t endOfLineIndex;
+ std::vector<YGNodeRef> relativeChildren;
+ float remainingFreeSpace;
+ // The size of the mainDim for the row after considering size, padding, margin
+ // and border of flex items. This is used to calculate maxLineDim after going
+ // through all the rows to decide on the main axis size of owner.
+ float mainDim;
+ // The size of the crossDim for the row after considering size, padding,
+ // margin and border of flex items. Used for calculating containers crossSize.
+ float crossDim;
+};
+
+bool YGValueEqual(const YGValue& a, const YGValue& b);
+inline bool YGValueEqual(
+ facebook::yoga::detail::CompactValue a,
+ facebook::yoga::detail::CompactValue b) {
+ return YGValueEqual((YGValue) a, (YGValue) b);
+}
+
+// This custom float equality function returns true if either absolute
+// difference between two floats is less than 0.0001f or both are undefined.
+bool YGFloatsEqual(const float a, const float b);
+
+bool YGDoubleEqual(const double a, const double b);
+
+float YGFloatMax(const float a, const float b);
+
+YGFloatOptional YGFloatOptionalMax(
+ const YGFloatOptional op1,
+ const YGFloatOptional op2);
+
+float YGFloatMin(const float a, const float b);
+
+// This custom float comparison function compares the array of float with
+// YGFloatsEqual, as the default float comparison operator will not work(Look
+// at the comments of YGFloatsEqual function).
+template <std::size_t size>
+bool YGFloatArrayEqual(
+ const std::array<float, size>& val1,
+ const std::array<float, size>& val2) {
+ bool areEqual = true;
+ for (std::size_t i = 0; i < size && areEqual; ++i) {
+ areEqual = YGFloatsEqual(val1[i], val2[i]);
+ }
+ return areEqual;
+}
+
+// This function returns 0 if YGFloatIsUndefined(val) is true and val otherwise
+float YGFloatSanitize(const float val);
+
+YGFlexDirection YGFlexDirectionCross(
+ const YGFlexDirection flexDirection,
+ const YGDirection direction);
+
+inline bool YGFlexDirectionIsRow(const YGFlexDirection flexDirection) {
+ return flexDirection == YGFlexDirectionRow ||
+ flexDirection == YGFlexDirectionRowReverse;
+}
+
+inline YGFloatOptional YGResolveValue(
+ const YGValue value,
+ const float ownerSize) {
+ switch (value.unit) {
+ case YGUnitPoint:
+ return YGFloatOptional{value.value};
+ case YGUnitPercent:
+ return YGFloatOptional{value.value * ownerSize * 0.01f};
+ default:
+ return YGFloatOptional{};
+ }
+}
+
+inline YGFloatOptional YGResolveValue(
+ facebook::yoga::detail::CompactValue value,
+ float ownerSize) {
+ return YGResolveValue((YGValue) value, ownerSize);
+}
+
+inline bool YGFlexDirectionIsColumn(const YGFlexDirection flexDirection) {
+ return flexDirection == YGFlexDirectionColumn ||
+ flexDirection == YGFlexDirectionColumnReverse;
+}
+
+inline YGFlexDirection YGResolveFlexDirection(
+ const YGFlexDirection flexDirection,
+ const YGDirection direction) {
+ if (direction == YGDirectionRTL) {
+ if (flexDirection == YGFlexDirectionRow) {
+ return YGFlexDirectionRowReverse;
+ } else if (flexDirection == YGFlexDirectionRowReverse) {
+ return YGFlexDirectionRow;
+ }
+ }
+
+ return flexDirection;
+}
+
+inline YGFloatOptional YGResolveValueMargin(
+ facebook::yoga::detail::CompactValue value,
+ const float ownerSize) {
+ return value.isAuto() ? YGFloatOptional{0} : YGResolveValue(value, ownerSize);
+}
diff --git a/src/3rdparty/yoga/YGConfig.cpp b/src/3rdparty/yoga/YGConfig.cpp
new file mode 100644
index 0000000000..eee23ec66d
--- /dev/null
+++ b/src/3rdparty/yoga/YGConfig.cpp
@@ -0,0 +1,149 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// Copyright (c) Meta Platforms, Inc. and affiliates.
+//
+// SPDX-License-Identifier: MIT
+
+#include "YGConfig.h"
+
+using namespace facebook::yoga;
+
+namespace facebook {
+namespace yoga {
+bool configUpdateInvalidatesLayout(YGConfigRef a, YGConfigRef b) {
+ return a->getErrata() != b->getErrata() ||
+ a->getEnabledExperiments() != b->getEnabledExperiments() ||
+ a->getPointScaleFactor() != b->getPointScaleFactor() ||
+ a->useWebDefaults() != b->useWebDefaults();
+}
+} // namespace yoga
+} // namespace facebook
+
+YGConfig::YGConfig(YGLogger logger) : cloneNodeCallback_{nullptr} {
+ setLogger(logger);
+}
+
+void YGConfig::setUseWebDefaults(bool useWebDefaults) {
+ flags_.useWebDefaults = useWebDefaults;
+}
+
+bool YGConfig::useWebDefaults() const {
+ return flags_.useWebDefaults;
+}
+
+void YGConfig::setShouldPrintTree(bool printTree) {
+ flags_.printTree = printTree;
+}
+
+bool YGConfig::shouldPrintTree() const {
+ return flags_.printTree;
+}
+
+void YGConfig::setExperimentalFeatureEnabled(
+ YGExperimentalFeature feature,
+ bool enabled) {
+ experimentalFeatures_.set(feature, enabled);
+}
+
+bool YGConfig::isExperimentalFeatureEnabled(
+ YGExperimentalFeature feature) const {
+ return experimentalFeatures_.test(feature);
+}
+
+ExperimentalFeatureSet YGConfig::getEnabledExperiments() const {
+ return experimentalFeatures_;
+}
+
+void YGConfig::setErrata(YGErrata errata) {
+ errata_ = errata;
+}
+
+void YGConfig::addErrata(YGErrata errata) {
+ errata_ |= errata;
+}
+
+void YGConfig::removeErrata(YGErrata errata) {
+ errata_ &= (~errata);
+}
+
+YGErrata YGConfig::getErrata() const {
+ return errata_;
+}
+
+bool YGConfig::hasErrata(YGErrata errata) const {
+ return (errata_ & errata) != YGErrataNone;
+}
+
+void YGConfig::setPointScaleFactor(float pointScaleFactor) {
+ pointScaleFactor_ = pointScaleFactor;
+}
+
+float YGConfig::getPointScaleFactor() const {
+ return pointScaleFactor_;
+}
+
+void YGConfig::setContext(void* context) {
+ context_ = context;
+}
+
+void* YGConfig::getContext() const {
+ return context_;
+}
+
+void YGConfig::setLogger(YGLogger logger) {
+ logger_.noContext = logger;
+ flags_.loggerUsesContext = false;
+}
+
+void YGConfig::setLogger(LogWithContextFn logger) {
+ logger_.withContext = logger;
+ flags_.loggerUsesContext = true;
+}
+
+void YGConfig::setLogger(std::nullptr_t) {
+ setLogger(YGLogger{nullptr});
+}
+
+void YGConfig::log(
+ YGConfig* config,
+ YGNode* node,
+ YGLogLevel logLevel,
+ void* logContext,
+ const char* format,
+ va_list args) const {
+ if (flags_.loggerUsesContext) {
+ logger_.withContext(config, node, logLevel, logContext, format, args);
+ } else {
+ logger_.noContext(config, node, logLevel, format, args);
+ }
+}
+
+void YGConfig::setCloneNodeCallback(YGCloneNodeFunc cloneNode) {
+ cloneNodeCallback_.noContext = cloneNode;
+ flags_.cloneNodeUsesContext = false;
+}
+
+void YGConfig::setCloneNodeCallback(CloneWithContextFn cloneNode) {
+ cloneNodeCallback_.withContext = cloneNode;
+ flags_.cloneNodeUsesContext = true;
+}
+
+void YGConfig::setCloneNodeCallback(std::nullptr_t) {
+ setCloneNodeCallback(YGCloneNodeFunc{nullptr});
+}
+
+YGNodeRef YGConfig::cloneNode(
+ YGNodeRef node,
+ YGNodeRef owner,
+ int childIndex,
+ void* cloneContext) const {
+ YGNodeRef clone = nullptr;
+ if (cloneNodeCallback_.noContext != nullptr) {
+ clone = flags_.cloneNodeUsesContext
+ ? cloneNodeCallback_.withContext(node, owner, childIndex, cloneContext)
+ : cloneNodeCallback_.noContext(node, owner, childIndex);
+ }
+ if (clone == nullptr) {
+ clone = YGNodeClone(node);
+ }
+ return clone;
+}
diff --git a/src/3rdparty/yoga/YGConfig.h b/src/3rdparty/yoga/YGConfig.h
new file mode 100644
index 0000000000..a946867f77
--- /dev/null
+++ b/src/3rdparty/yoga/YGConfig.h
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2016 The Qt Company Ltd.
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#pragma once
+
+#include <yoga/Yoga.h>
+
+#include "BitUtils.h"
+#include "Yoga-internal.h"
+
+namespace facebook {
+namespace yoga {
+
+// Whether moving a node from config "a" to config "b" should dirty previously
+// calculated layout results.
+bool configUpdateInvalidatesLayout(YGConfigRef a, YGConfigRef b);
+
+// Internal variants of log functions, currently used only by JNI bindings.
+// TODO: Reconcile this with the public API
+using LogWithContextFn = int (*)(
+ YGConfigRef config,
+ YGNodeRef node,
+ YGLogLevel level,
+ void* context,
+ const char* format,
+ va_list args);
+using CloneWithContextFn = YGNodeRef (*)(
+ YGNodeRef node,
+ YGNodeRef owner,
+ int childIndex,
+ void* cloneContext);
+
+using ExperimentalFeatureSet =
+ facebook::yoga::detail::EnumBitset<YGExperimentalFeature>;
+
+#pragma pack(push)
+#pragma pack(1)
+// Packed structure of <32-bit options to miminize size per node.
+struct YGConfigFlags {
+ bool useWebDefaults : 1;
+ bool printTree : 1;
+ bool cloneNodeUsesContext : 1;
+ bool loggerUsesContext : 1;
+};
+#pragma pack(pop)
+
+} // namespace yoga
+} // namespace facebook
+
+struct YOGA_EXPORT YGConfig {
+ YGConfig(YGLogger logger);
+
+ void setUseWebDefaults(bool useWebDefaults);
+ bool useWebDefaults() const;
+
+ void setShouldPrintTree(bool printTree);
+ bool shouldPrintTree() const;
+
+ void setExperimentalFeatureEnabled(
+ YGExperimentalFeature feature,
+ bool enabled);
+ bool isExperimentalFeatureEnabled(YGExperimentalFeature feature) const;
+ facebook::yoga::ExperimentalFeatureSet getEnabledExperiments() const;
+
+ void setErrata(YGErrata errata);
+ void addErrata(YGErrata errata);
+ void removeErrata(YGErrata errata);
+ YGErrata getErrata() const;
+ bool hasErrata(YGErrata errata) const;
+
+ void setPointScaleFactor(float pointScaleFactor);
+ float getPointScaleFactor() const;
+
+ void setContext(void* context);
+ void* getContext() const;
+
+ void setLogger(YGLogger logger);
+ void setLogger(facebook::yoga::LogWithContextFn logger);
+ void setLogger(std::nullptr_t);
+ void log(YGConfig*, YGNode*, YGLogLevel, void*, const char*, va_list) const;
+
+ void setCloneNodeCallback(YGCloneNodeFunc cloneNode);
+ void setCloneNodeCallback(facebook::yoga::CloneWithContextFn cloneNode);
+ void setCloneNodeCallback(std::nullptr_t);
+ YGNodeRef cloneNode(
+ YGNodeRef node,
+ YGNodeRef owner,
+ int childIndex,
+ void* cloneContext) const;
+
+private:
+ union {
+ facebook::yoga::CloneWithContextFn withContext;
+ YGCloneNodeFunc noContext;
+ } cloneNodeCallback_;
+ union {
+ facebook::yoga::LogWithContextFn withContext;
+ YGLogger noContext;
+ } logger_;
+
+ facebook::yoga::YGConfigFlags flags_{};
+ facebook::yoga::ExperimentalFeatureSet experimentalFeatures_{};
+ YGErrata errata_ = YGErrataNone;
+ float pointScaleFactor_ = 1.0f;
+ void* context_ = nullptr;
+};
diff --git a/src/3rdparty/yoga/YGEnums.cpp b/src/3rdparty/yoga/YGEnums.cpp
new file mode 100644
index 0000000000..1a4706a8d1
--- /dev/null
+++ b/src/3rdparty/yoga/YGEnums.cpp
@@ -0,0 +1,256 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// Copyright (c) Meta Platforms, Inc. and affiliates.
+//
+// SPDX-License-Identifier: MIT
+
+#include <yoga/YGEnums.h>
+
+const char* YGAlignToString(const YGAlign value) {
+ switch (value) {
+ case YGAlignAuto:
+ return "auto";
+ case YGAlignFlexStart:
+ return "flex-start";
+ case YGAlignCenter:
+ return "center";
+ case YGAlignFlexEnd:
+ return "flex-end";
+ case YGAlignStretch:
+ return "stretch";
+ case YGAlignBaseline:
+ return "baseline";
+ case YGAlignSpaceBetween:
+ return "space-between";
+ case YGAlignSpaceAround:
+ return "space-around";
+ }
+ return "unknown";
+}
+
+const char* YGDimensionToString(const YGDimension value) {
+ switch (value) {
+ case YGDimensionWidth:
+ return "width";
+ case YGDimensionHeight:
+ return "height";
+ }
+ return "unknown";
+}
+
+const char* YGDirectionToString(const YGDirection value) {
+ switch (value) {
+ case YGDirectionInherit:
+ return "inherit";
+ case YGDirectionLTR:
+ return "ltr";
+ case YGDirectionRTL:
+ return "rtl";
+ }
+ return "unknown";
+}
+
+const char* YGDisplayToString(const YGDisplay value) {
+ switch (value) {
+ case YGDisplayFlex:
+ return "flex";
+ case YGDisplayNone:
+ return "none";
+ }
+ return "unknown";
+}
+
+const char* YGEdgeToString(const YGEdge value) {
+ switch (value) {
+ case YGEdgeLeft:
+ return "left";
+ case YGEdgeTop:
+ return "top";
+ case YGEdgeRight:
+ return "right";
+ case YGEdgeBottom:
+ return "bottom";
+ case YGEdgeStart:
+ return "start";
+ case YGEdgeEnd:
+ return "end";
+ case YGEdgeHorizontal:
+ return "horizontal";
+ case YGEdgeVertical:
+ return "vertical";
+ case YGEdgeAll:
+ return "all";
+ }
+ return "unknown";
+}
+
+const char* YGErrataToString(const YGErrata value) {
+ switch (value) {
+ case YGErrataNone:
+ return "none";
+ case YGErrataStretchFlexBasis:
+ return "stretch-flex-basis";
+ case YGErrataAll:
+ return "all";
+ case YGErrataClassic:
+ return "classic";
+ }
+ return "unknown";
+}
+
+const char* YGExperimentalFeatureToString(const YGExperimentalFeature value) {
+ switch (value) {
+ case YGExperimentalFeatureWebFlexBasis:
+ return "web-flex-basis";
+ case YGExperimentalFeatureAbsolutePercentageAgainstPaddingEdge:
+ return "absolute-percentage-against-padding-edge";
+ case YGExperimentalFeatureFixJNILocalRefOverflows:
+ return "fix-jnilocal-ref-overflows";
+ }
+ return "unknown";
+}
+
+const char* YGFlexDirectionToString(const YGFlexDirection value) {
+ switch (value) {
+ case YGFlexDirectionColumn:
+ return "column";
+ case YGFlexDirectionColumnReverse:
+ return "column-reverse";
+ case YGFlexDirectionRow:
+ return "row";
+ case YGFlexDirectionRowReverse:
+ return "row-reverse";
+ }
+ return "unknown";
+}
+
+const char* YGGutterToString(const YGGutter value) {
+ switch (value) {
+ case YGGutterColumn:
+ return "column";
+ case YGGutterRow:
+ return "row";
+ case YGGutterAll:
+ return "all";
+ }
+ return "unknown";
+}
+
+const char* YGJustifyToString(const YGJustify value) {
+ switch (value) {
+ case YGJustifyFlexStart:
+ return "flex-start";
+ case YGJustifyCenter:
+ return "center";
+ case YGJustifyFlexEnd:
+ return "flex-end";
+ case YGJustifySpaceBetween:
+ return "space-between";
+ case YGJustifySpaceAround:
+ return "space-around";
+ case YGJustifySpaceEvenly:
+ return "space-evenly";
+ }
+ return "unknown";
+}
+
+const char* YGLogLevelToString(const YGLogLevel value) {
+ switch (value) {
+ case YGLogLevelError:
+ return "error";
+ case YGLogLevelWarn:
+ return "warn";
+ case YGLogLevelInfo:
+ return "info";
+ case YGLogLevelDebug:
+ return "debug";
+ case YGLogLevelVerbose:
+ return "verbose";
+ case YGLogLevelFatal:
+ return "fatal";
+ }
+ return "unknown";
+}
+
+const char* YGMeasureModeToString(const YGMeasureMode value) {
+ switch (value) {
+ case YGMeasureModeUndefined:
+ return "undefined";
+ case YGMeasureModeExactly:
+ return "exactly";
+ case YGMeasureModeAtMost:
+ return "at-most";
+ }
+ return "unknown";
+}
+
+const char* YGNodeTypeToString(const YGNodeType value) {
+ switch (value) {
+ case YGNodeTypeDefault:
+ return "default";
+ case YGNodeTypeText:
+ return "text";
+ }
+ return "unknown";
+}
+
+const char* YGOverflowToString(const YGOverflow value) {
+ switch (value) {
+ case YGOverflowVisible:
+ return "visible";
+ case YGOverflowHidden:
+ return "hidden";
+ case YGOverflowScroll:
+ return "scroll";
+ }
+ return "unknown";
+}
+
+const char* YGPositionTypeToString(const YGPositionType value) {
+ switch (value) {
+ case YGPositionTypeStatic:
+ return "static";
+ case YGPositionTypeRelative:
+ return "relative";
+ case YGPositionTypeAbsolute:
+ return "absolute";
+ }
+ return "unknown";
+}
+
+const char* YGPrintOptionsToString(const YGPrintOptions value) {
+ switch (value) {
+ case YGPrintOptionsLayout:
+ return "layout";
+ case YGPrintOptionsStyle:
+ return "style";
+ case YGPrintOptionsChildren:
+ return "children";
+ }
+ return "unknown";
+}
+
+const char* YGUnitToString(const YGUnit value) {
+ switch (value) {
+ case YGUnitUndefined:
+ return "undefined";
+ case YGUnitPoint:
+ return "point";
+ case YGUnitPercent:
+ return "percent";
+ case YGUnitAuto:
+ return "auto";
+ }
+ return "unknown";
+}
+
+const char* YGWrapToString(const YGWrap value) {
+ switch (value) {
+ case YGWrapNoWrap:
+ return "no-wrap";
+ case YGWrapWrap:
+ return "wrap";
+ case YGWrapWrapReverse:
+ return "wrap-reverse";
+ }
+ return "unknown";
+}
diff --git a/src/3rdparty/yoga/YGEnums.h b/src/3rdparty/yoga/YGEnums.h
new file mode 100644
index 0000000000..e03ca0cd97
--- /dev/null
+++ b/src/3rdparty/yoga/YGEnums.h
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2016 The Qt Company Ltd.
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#pragma once
+#include <yoga/YGMacros.h>
+
+// clang-format off
+
+
+YG_EXTERN_C_BEGIN
+
+YG_ENUM_SEQ_DECL(
+ YGAlign,
+ YGAlignAuto,
+ YGAlignFlexStart,
+ YGAlignCenter,
+ YGAlignFlexEnd,
+ YGAlignStretch,
+ YGAlignBaseline,
+ YGAlignSpaceBetween,
+ YGAlignSpaceAround)
+
+YG_ENUM_SEQ_DECL(
+ YGDimension,
+ YGDimensionWidth,
+ YGDimensionHeight)
+
+YG_ENUM_SEQ_DECL(
+ YGDirection,
+ YGDirectionInherit,
+ YGDirectionLTR,
+ YGDirectionRTL)
+
+YG_ENUM_SEQ_DECL(
+ YGDisplay,
+ YGDisplayFlex,
+ YGDisplayNone)
+
+YG_ENUM_SEQ_DECL(
+ YGEdge,
+ YGEdgeLeft,
+ YGEdgeTop,
+ YGEdgeRight,
+ YGEdgeBottom,
+ YGEdgeStart,
+ YGEdgeEnd,
+ YGEdgeHorizontal,
+ YGEdgeVertical,
+ YGEdgeAll)
+
+YG_ENUM_DECL(
+ YGErrata,
+ YGErrataNone = 0,
+ YGErrataStretchFlexBasis = 1,
+ YGErrataAll = 2147483647,
+ YGErrataClassic = 2147483646)
+YG_DEFINE_ENUM_FLAG_OPERATORS(YGErrata)
+
+YG_ENUM_SEQ_DECL(
+ YGExperimentalFeature,
+ YGExperimentalFeatureWebFlexBasis,
+ YGExperimentalFeatureAbsolutePercentageAgainstPaddingEdge,
+ YGExperimentalFeatureFixJNILocalRefOverflows)
+
+YG_ENUM_SEQ_DECL(
+ YGFlexDirection,
+ YGFlexDirectionColumn,
+ YGFlexDirectionColumnReverse,
+ YGFlexDirectionRow,
+ YGFlexDirectionRowReverse)
+
+YG_ENUM_SEQ_DECL(
+ YGGutter,
+ YGGutterColumn,
+ YGGutterRow,
+ YGGutterAll)
+
+YG_ENUM_SEQ_DECL(
+ YGJustify,
+ YGJustifyFlexStart,
+ YGJustifyCenter,
+ YGJustifyFlexEnd,
+ YGJustifySpaceBetween,
+ YGJustifySpaceAround,
+ YGJustifySpaceEvenly)
+
+YG_ENUM_SEQ_DECL(
+ YGLogLevel,
+ YGLogLevelError,
+ YGLogLevelWarn,
+ YGLogLevelInfo,
+ YGLogLevelDebug,
+ YGLogLevelVerbose,
+ YGLogLevelFatal)
+
+YG_ENUM_SEQ_DECL(
+ YGMeasureMode,
+ YGMeasureModeUndefined,
+ YGMeasureModeExactly,
+ YGMeasureModeAtMost)
+
+YG_ENUM_SEQ_DECL(
+ YGNodeType,
+ YGNodeTypeDefault,
+ YGNodeTypeText)
+
+YG_ENUM_SEQ_DECL(
+ YGOverflow,
+ YGOverflowVisible,
+ YGOverflowHidden,
+ YGOverflowScroll)
+
+YG_ENUM_SEQ_DECL(
+ YGPositionType,
+ YGPositionTypeStatic,
+ YGPositionTypeRelative,
+ YGPositionTypeAbsolute)
+
+YG_ENUM_DECL(
+ YGPrintOptions,
+ YGPrintOptionsLayout = 1,
+ YGPrintOptionsStyle = 2,
+ YGPrintOptionsChildren = 4)
+YG_DEFINE_ENUM_FLAG_OPERATORS(YGPrintOptions)
+
+YG_ENUM_SEQ_DECL(
+ YGUnit,
+ YGUnitUndefined,
+ YGUnitPoint,
+ YGUnitPercent,
+ YGUnitAuto)
+
+YG_ENUM_SEQ_DECL(
+ YGWrap,
+ YGWrapNoWrap,
+ YGWrapWrap,
+ YGWrapWrapReverse)
+
+YG_EXTERN_C_END
diff --git a/src/3rdparty/yoga/YGFloatOptional.h b/src/3rdparty/yoga/YGFloatOptional.h
new file mode 100644
index 0000000000..1768efdcb2
--- /dev/null
+++ b/src/3rdparty/yoga/YGFloatOptional.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2016 The Qt Company Ltd.
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#pragma once
+
+#include <cmath>
+#include <limits>
+#include "Yoga-internal.h"
+
+struct YGFloatOptional {
+private:
+ float value_ = std::numeric_limits<float>::quiet_NaN();
+
+public:
+ explicit constexpr YGFloatOptional(float value) : value_(value) {}
+ constexpr YGFloatOptional() = default;
+
+ // returns the wrapped value, or a value x with YGIsUndefined(x) == true
+ constexpr float unwrap() const { return value_; }
+
+ bool isUndefined() const { return std::isnan(value_); }
+};
+
+// operators take YGFloatOptional by value, as it is a 32bit value
+
+inline bool operator==(YGFloatOptional lhs, YGFloatOptional rhs) {
+ return lhs.unwrap() == rhs.unwrap() ||
+ (lhs.isUndefined() && rhs.isUndefined());
+}
+inline bool operator!=(YGFloatOptional lhs, YGFloatOptional rhs) {
+ return !(lhs == rhs);
+}
+
+inline bool operator==(YGFloatOptional lhs, float rhs) {
+ return lhs == YGFloatOptional{rhs};
+}
+inline bool operator!=(YGFloatOptional lhs, float rhs) {
+ return !(lhs == rhs);
+}
+
+inline bool operator==(float lhs, YGFloatOptional rhs) {
+ return rhs == lhs;
+}
+inline bool operator!=(float lhs, YGFloatOptional rhs) {
+ return !(lhs == rhs);
+}
+
+inline YGFloatOptional operator+(YGFloatOptional lhs, YGFloatOptional rhs) {
+ return YGFloatOptional{lhs.unwrap() + rhs.unwrap()};
+}
+
+inline bool operator>(YGFloatOptional lhs, YGFloatOptional rhs) {
+ return lhs.unwrap() > rhs.unwrap();
+}
+
+inline bool operator<(YGFloatOptional lhs, YGFloatOptional rhs) {
+ return lhs.unwrap() < rhs.unwrap();
+}
+
+inline bool operator>=(YGFloatOptional lhs, YGFloatOptional rhs) {
+ return lhs > rhs || lhs == rhs;
+}
+
+inline bool operator<=(YGFloatOptional lhs, YGFloatOptional rhs) {
+ return lhs < rhs || lhs == rhs;
+}
diff --git a/src/3rdparty/yoga/YGLayout.cpp b/src/3rdparty/yoga/YGLayout.cpp
new file mode 100644
index 0000000000..7c1af6de0f
--- /dev/null
+++ b/src/3rdparty/yoga/YGLayout.cpp
@@ -0,0 +1,40 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// Copyright (c) Meta Platforms, Inc. and affiliates.
+//
+// SPDX-License-Identifier: MIT
+
+#include "YGLayout.h"
+#include "Utils.h"
+
+using namespace facebook;
+
+bool YGLayout::operator==(YGLayout layout) const {
+ bool isEqual = YGFloatArrayEqual(position, layout.position) &&
+ YGFloatArrayEqual(dimensions, layout.dimensions) &&
+ YGFloatArrayEqual(margin, layout.margin) &&
+ YGFloatArrayEqual(border, layout.border) &&
+ YGFloatArrayEqual(padding, layout.padding) &&
+ direction() == layout.direction() &&
+ hadOverflow() == layout.hadOverflow() &&
+ lastOwnerDirection == layout.lastOwnerDirection &&
+ nextCachedMeasurementsIndex == layout.nextCachedMeasurementsIndex &&
+ cachedLayout == layout.cachedLayout &&
+ computedFlexBasis == layout.computedFlexBasis;
+
+ for (uint32_t i = 0; i < YG_MAX_CACHED_RESULT_COUNT && isEqual; ++i) {
+ isEqual = isEqual && cachedMeasurements[i] == layout.cachedMeasurements[i];
+ }
+
+ if (!yoga::isUndefined(measuredDimensions[0]) ||
+ !yoga::isUndefined(layout.measuredDimensions[0])) {
+ isEqual =
+ isEqual && (measuredDimensions[0] == layout.measuredDimensions[0]);
+ }
+ if (!yoga::isUndefined(measuredDimensions[1]) ||
+ !yoga::isUndefined(layout.measuredDimensions[1])) {
+ isEqual =
+ isEqual && (measuredDimensions[1] == layout.measuredDimensions[1]);
+ }
+
+ return isEqual;
+}
diff --git a/src/3rdparty/yoga/YGLayout.h b/src/3rdparty/yoga/YGLayout.h
new file mode 100644
index 0000000000..1864f86822
--- /dev/null
+++ b/src/3rdparty/yoga/YGLayout.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2016 The Qt Company Ltd.
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#pragma once
+
+#include "BitUtils.h"
+#include "YGFloatOptional.h"
+#include "Yoga-internal.h"
+
+struct YGLayout {
+ std::array<float, 4> position = {};
+ std::array<float, 2> dimensions = {{YGUndefined, YGUndefined}};
+ std::array<float, 4> margin = {};
+ std::array<float, 4> border = {};
+ std::array<float, 4> padding = {};
+
+private:
+ static constexpr size_t directionOffset = 0;
+ static constexpr size_t hadOverflowOffset =
+ directionOffset + facebook::yoga::detail::bitWidthFn<YGDirection>();
+ uint8_t flags = 0;
+
+public:
+ uint32_t computedFlexBasisGeneration = 0;
+ YGFloatOptional computedFlexBasis = {};
+
+ // Instead of recomputing the entire layout every single time, we cache some
+ // information to break early when nothing changed
+ uint32_t generationCount = 0;
+ YGDirection lastOwnerDirection = YGDirectionInherit;
+
+ uint32_t nextCachedMeasurementsIndex = 0;
+ std::array<YGCachedMeasurement, YG_MAX_CACHED_RESULT_COUNT>
+ cachedMeasurements = {};
+ std::array<float, 2> measuredDimensions = {{YGUndefined, YGUndefined}};
+
+ YGCachedMeasurement cachedLayout = YGCachedMeasurement();
+
+ YGDirection direction() const {
+ return facebook::yoga::detail::getEnumData<YGDirection>(
+ flags, directionOffset);
+ }
+
+ void setDirection(YGDirection direction) {
+ facebook::yoga::detail::setEnumData<YGDirection>(
+ flags, directionOffset, direction);
+ }
+
+ bool hadOverflow() const {
+ return facebook::yoga::detail::getBooleanData(flags, hadOverflowOffset);
+ }
+ void setHadOverflow(bool hadOverflow) {
+ facebook::yoga::detail::setBooleanData(
+ flags, hadOverflowOffset, hadOverflow);
+ }
+
+ bool operator==(YGLayout layout) const;
+ bool operator!=(YGLayout layout) const { return !(*this == layout); }
+};
diff --git a/src/3rdparty/yoga/YGMacros.h b/src/3rdparty/yoga/YGMacros.h
new file mode 100644
index 0000000000..a675c4d88e
--- /dev/null
+++ b/src/3rdparty/yoga/YGMacros.h
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2016 The Qt Company Ltd.
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#pragma once
+
+#ifdef __cplusplus
+#include <type_traits>
+#endif
+
+#ifdef __cplusplus
+#define YG_EXTERN_C_BEGIN extern "C" {
+#define YG_EXTERN_C_END }
+#else
+#define YG_EXTERN_C_BEGIN
+#define YG_EXTERN_C_END
+#endif
+
+#if defined(__cplusplus)
+#define YG_DEPRECATED(message) [[deprecated(message)]]
+#elif defined(_MSC_VER)
+#define YG_DEPRECATED(message) __declspec(deprecated(message))
+#else
+#define YG_DEPRECATED(message) __attribute__((deprecated(message)))
+#endif
+
+#ifdef _WINDLL
+#define WIN_EXPORT __declspec(dllexport)
+#else
+#define WIN_EXPORT
+#endif
+
+#ifndef YOGA_EXPORT
+#ifdef _MSC_VER
+#define YOGA_EXPORT
+#else
+#define YOGA_EXPORT __attribute__((visibility("default")))
+#endif
+#endif
+
+#ifdef NS_ENUM
+// Cannot use NSInteger as NSInteger has a different size than int (which is the
+// default type of a enum). Therefor when linking the Yoga C library into obj-c
+// the header is a mismatch for the Yoga ABI.
+#define YG_ENUM_BEGIN(name) NS_ENUM(int, name)
+#define YG_ENUM_END(name)
+#else
+#define YG_ENUM_BEGIN(name) enum name
+#define YG_ENUM_END(name) name
+#endif
+
+#ifdef __cplusplus
+#define YG_DEFINE_ENUM_FLAG_OPERATORS(name) \
+ extern "C++" { \
+ constexpr inline name operator~(name a) { \
+ return static_cast<name>( \
+ ~static_cast<std::underlying_type<name>::type>(a)); \
+ } \
+ constexpr inline name operator|(name a, name b) { \
+ return static_cast<name>( \
+ static_cast<std::underlying_type<name>::type>(a) | \
+ static_cast<std::underlying_type<name>::type>(b)); \
+ } \
+ constexpr inline name operator&(name a, name b) { \
+ return static_cast<name>( \
+ static_cast<std::underlying_type<name>::type>(a) & \
+ static_cast<std::underlying_type<name>::type>(b)); \
+ } \
+ constexpr inline name operator^(name a, name b) { \
+ return static_cast<name>( \
+ static_cast<std::underlying_type<name>::type>(a) ^ \
+ static_cast<std::underlying_type<name>::type>(b)); \
+ } \
+ inline name& operator|=(name& a, name b) { \
+ return reinterpret_cast<name&>( \
+ reinterpret_cast<std::underlying_type<name>::type&>(a) |= \
+ static_cast<std::underlying_type<name>::type>(b)); \
+ } \
+ inline name& operator&=(name& a, name b) { \
+ return reinterpret_cast<name&>( \
+ reinterpret_cast<std::underlying_type<name>::type&>(a) &= \
+ static_cast<std::underlying_type<name>::type>(b)); \
+ } \
+ inline name& operator^=(name& a, name b) { \
+ return reinterpret_cast<name&>( \
+ reinterpret_cast<std::underlying_type<name>::type&>(a) ^= \
+ static_cast<std::underlying_type<name>::type>(b)); \
+ } \
+ }
+#else
+#define YG_DEFINE_ENUM_FLAG_OPERATORS(name)
+#endif
+
+#ifdef __cplusplus
+namespace facebook {
+namespace yoga {
+namespace enums {
+
+template <typename T>
+constexpr int count(); // can't use `= delete` due to a defect in clang < 3.9
+
+namespace detail {
+template <int... xs>
+constexpr int n() {
+ return sizeof...(xs);
+}
+} // namespace detail
+
+} // namespace enums
+} // namespace yoga
+} // namespace facebook
+#endif
+
+#define YG_ENUM_DECL(NAME, ...) \
+ typedef YG_ENUM_BEGIN(NAME){__VA_ARGS__} YG_ENUM_END(NAME); \
+ WIN_EXPORT const char* NAME##ToString(NAME);
+
+#ifdef __cplusplus
+#define YG_ENUM_SEQ_DECL(NAME, ...) \
+ YG_ENUM_DECL(NAME, __VA_ARGS__) \
+ YG_EXTERN_C_END \
+ namespace facebook { \
+ namespace yoga { \
+ namespace enums { \
+ template <> \
+ constexpr int count<NAME>() { \
+ return detail::n<__VA_ARGS__>(); \
+ } \
+ } \
+ } \
+ } \
+ YG_EXTERN_C_BEGIN
+#else
+#define YG_ENUM_SEQ_DECL YG_ENUM_DECL
+#endif
diff --git a/src/3rdparty/yoga/YGNode.cpp b/src/3rdparty/yoga/YGNode.cpp
new file mode 100644
index 0000000000..b914c7dbcd
--- /dev/null
+++ b/src/3rdparty/yoga/YGNode.cpp
@@ -0,0 +1,582 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// Copyright (c) Meta Platforms, Inc. and affiliates.
+//
+// SPDX-License-Identifier: MIT
+
+#include "YGNode.h"
+#include <algorithm>
+#include <iostream>
+#include "Utils.h"
+
+using namespace facebook;
+using facebook::yoga::detail::CompactValue;
+
+YGNode::YGNode(const YGConfigRef config) : config_{config} {
+ YGAssert(
+ config != nullptr, "Attempting to construct YGNode with null config");
+
+ flags_.hasNewLayout = true;
+ if (config->useWebDefaults()) {
+ useWebDefaults();
+ }
+}
+
+YGNode::YGNode(YGNode&& node) {
+ context_ = node.context_;
+ flags_ = node.flags_;
+ measure_ = node.measure_;
+ baseline_ = node.baseline_;
+ print_ = node.print_;
+ dirtied_ = node.dirtied_;
+ style_ = node.style_;
+ layout_ = node.layout_;
+ lineIndex_ = node.lineIndex_;
+ owner_ = node.owner_;
+ children_ = std::move(node.children_);
+ config_ = node.config_;
+ resolvedDimensions_ = node.resolvedDimensions_;
+ for (auto c : children_) {
+ c->setOwner(this);
+ }
+}
+
+void YGNode::print(void* printContext) {
+ if (print_.noContext != nullptr) {
+ if (flags_.printUsesContext) {
+ print_.withContext(this, printContext);
+ } else {
+ print_.noContext(this);
+ }
+ }
+}
+
+CompactValue YGNode::computeEdgeValueForRow(
+ const YGStyle::Edges& edges,
+ YGEdge rowEdge,
+ YGEdge edge,
+ CompactValue defaultValue) {
+ if (!edges[rowEdge].isUndefined()) {
+ return edges[rowEdge];
+ } else if (!edges[edge].isUndefined()) {
+ return edges[edge];
+ } else if (!edges[YGEdgeHorizontal].isUndefined()) {
+ return edges[YGEdgeHorizontal];
+ } else if (!edges[YGEdgeAll].isUndefined()) {
+ return edges[YGEdgeAll];
+ } else {
+ return defaultValue;
+ }
+}
+
+CompactValue YGNode::computeEdgeValueForColumn(
+ const YGStyle::Edges& edges,
+ YGEdge edge,
+ CompactValue defaultValue) {
+ if (!edges[edge].isUndefined()) {
+ return edges[edge];
+ } else if (!edges[YGEdgeVertical].isUndefined()) {
+ return edges[YGEdgeVertical];
+ } else if (!edges[YGEdgeAll].isUndefined()) {
+ return edges[YGEdgeAll];
+ } else {
+ return defaultValue;
+ }
+}
+
+CompactValue YGNode::computeRowGap(
+ const YGStyle::Gutters& gutters,
+ CompactValue defaultValue) {
+ if (!gutters[YGGutterRow].isUndefined()) {
+ return gutters[YGGutterRow];
+ } else if (!gutters[YGGutterAll].isUndefined()) {
+ return gutters[YGGutterAll];
+ } else {
+ return defaultValue;
+ }
+}
+
+CompactValue YGNode::computeColumnGap(
+ const YGStyle::Gutters& gutters,
+ CompactValue defaultValue) {
+ if (!gutters[YGGutterColumn].isUndefined()) {
+ return gutters[YGGutterColumn];
+ } else if (!gutters[YGGutterAll].isUndefined()) {
+ return gutters[YGGutterAll];
+ } else {
+ return defaultValue;
+ }
+}
+
+YGFloatOptional YGNode::getLeadingPosition(
+ const YGFlexDirection axis,
+ const float axisSize) const {
+ auto leadingPosition = YGFlexDirectionIsRow(axis)
+ ? computeEdgeValueForRow(
+ style_.position(),
+ YGEdgeStart,
+ leading[axis],
+ CompactValue::ofZero())
+ : computeEdgeValueForColumn(
+ style_.position(), leading[axis], CompactValue::ofZero());
+ return YGResolveValue(leadingPosition, axisSize);
+}
+
+YGFloatOptional YGNode::getTrailingPosition(
+ const YGFlexDirection axis,
+ const float axisSize) const {
+ auto trailingPosition = YGFlexDirectionIsRow(axis)
+ ? computeEdgeValueForRow(
+ style_.position(),
+ YGEdgeEnd,
+ trailing[axis],
+ CompactValue::ofZero())
+ : computeEdgeValueForColumn(
+ style_.position(), trailing[axis], CompactValue::ofZero());
+ return YGResolveValue(trailingPosition, axisSize);
+}
+
+bool YGNode::isLeadingPositionDefined(const YGFlexDirection axis) const {
+ auto leadingPosition = YGFlexDirectionIsRow(axis)
+ ? computeEdgeValueForRow(
+ style_.position(),
+ YGEdgeStart,
+ leading[axis],
+ CompactValue::ofUndefined())
+ : computeEdgeValueForColumn(
+ style_.position(), leading[axis], CompactValue::ofUndefined());
+ return !leadingPosition.isUndefined();
+}
+
+bool YGNode::isTrailingPosDefined(const YGFlexDirection axis) const {
+ auto trailingPosition = YGFlexDirectionIsRow(axis)
+ ? computeEdgeValueForRow(
+ style_.position(),
+ YGEdgeEnd,
+ trailing[axis],
+ CompactValue::ofUndefined())
+ : computeEdgeValueForColumn(
+ style_.position(), trailing[axis], CompactValue::ofUndefined());
+ return !trailingPosition.isUndefined();
+}
+
+YGFloatOptional YGNode::getLeadingMargin(
+ const YGFlexDirection axis,
+ const float widthSize) const {
+ auto leadingMargin = YGFlexDirectionIsRow(axis)
+ ? computeEdgeValueForRow(
+ style_.margin(), YGEdgeStart, leading[axis], CompactValue::ofZero())
+ : computeEdgeValueForColumn(
+ style_.margin(), leading[axis], CompactValue::ofZero());
+ return YGResolveValueMargin(leadingMargin, widthSize);
+}
+
+YGFloatOptional YGNode::getTrailingMargin(
+ const YGFlexDirection axis,
+ const float widthSize) const {
+ auto trailingMargin = YGFlexDirectionIsRow(axis)
+ ? computeEdgeValueForRow(
+ style_.margin(), YGEdgeEnd, trailing[axis], CompactValue::ofZero())
+ : computeEdgeValueForColumn(
+ style_.margin(), trailing[axis], CompactValue::ofZero());
+ return YGResolveValueMargin(trailingMargin, widthSize);
+}
+
+YGFloatOptional YGNode::getMarginForAxis(
+ const YGFlexDirection axis,
+ const float widthSize) const {
+ return getLeadingMargin(axis, widthSize) + getTrailingMargin(axis, widthSize);
+}
+
+YGFloatOptional YGNode::getGapForAxis(
+ const YGFlexDirection axis,
+ const float widthSize) const {
+ auto gap = YGFlexDirectionIsRow(axis)
+ ? computeColumnGap(style_.gap(), CompactValue::ofZero())
+ : computeRowGap(style_.gap(), CompactValue::ofZero());
+ return YGResolveValue(gap, widthSize);
+}
+
+YGSize YGNode::measure(
+ float width,
+ YGMeasureMode widthMode,
+ float height,
+ YGMeasureMode heightMode,
+ void* layoutContext) {
+ return flags_.measureUsesContext
+ ? measure_.withContext(
+ this, width, widthMode, height, heightMode, layoutContext)
+ : measure_.noContext(this, width, widthMode, height, heightMode);
+}
+
+float YGNode::baseline(float width, float height, void* layoutContext) {
+ return flags_.baselineUsesContext
+ ? baseline_.withContext(this, width, height, layoutContext)
+ : baseline_.noContext(this, width, height);
+}
+
+// Setters
+
+void YGNode::setMeasureFunc(decltype(YGNode::measure_) measureFunc) {
+ if (measureFunc.noContext == nullptr) {
+ // TODO: t18095186 Move nodeType to opt-in function and mark appropriate
+ // places in Litho
+ setNodeType(YGNodeTypeDefault);
+ } else {
+ YGAssertWithNode(
+ this,
+ children_.size() == 0,
+ "Cannot set measure function: Nodes with measure functions cannot have "
+ "children.");
+ // TODO: t18095186 Move nodeType to opt-in function and mark appropriate
+ // places in Litho
+ setNodeType(YGNodeTypeText);
+ }
+
+ measure_ = measureFunc;
+}
+
+void YGNode::setMeasureFunc(YGMeasureFunc measureFunc) {
+ flags_.measureUsesContext = false;
+ decltype(YGNode::measure_) m;
+ m.noContext = measureFunc;
+ setMeasureFunc(m);
+}
+
+YOGA_EXPORT void YGNode::setMeasureFunc(MeasureWithContextFn measureFunc) {
+ flags_.measureUsesContext = true;
+ decltype(YGNode::measure_) m;
+ m.withContext = measureFunc;
+ setMeasureFunc(m);
+}
+
+void YGNode::replaceChild(YGNodeRef child, uint32_t index) {
+ children_[index] = child;
+}
+
+void YGNode::replaceChild(YGNodeRef oldChild, YGNodeRef newChild) {
+ std::replace(children_.begin(), children_.end(), oldChild, newChild);
+}
+
+void YGNode::insertChild(YGNodeRef child, uint32_t index) {
+ children_.insert(children_.begin() + index, child);
+}
+
+void YGNode::setConfig(YGConfigRef config) {
+ YGAssert(config != nullptr, "Attempting to set a null config on a YGNode");
+ YGAssertWithConfig(
+ config,
+ config->useWebDefaults() == config_->useWebDefaults(),
+ "UseWebDefaults may not be changed after constructing a YGNode");
+
+ if (yoga::configUpdateInvalidatesLayout(config_, config)) {
+ markDirtyAndPropagate();
+ }
+
+ config_ = config;
+}
+
+void YGNode::setDirty(bool isDirty) {
+ if (isDirty == flags_.isDirty) {
+ return;
+ }
+ flags_.isDirty = isDirty;
+ if (isDirty && dirtied_) {
+ dirtied_(this);
+ }
+}
+
+bool YGNode::removeChild(YGNodeRef child) {
+ std::vector<YGNodeRef>::iterator p =
+ std::find(children_.begin(), children_.end(), child);
+ if (p != children_.end()) {
+ children_.erase(p);
+ return true;
+ }
+ return false;
+}
+
+void YGNode::removeChild(uint32_t index) {
+ children_.erase(children_.begin() + index);
+}
+
+void YGNode::setLayoutDirection(YGDirection direction) {
+ layout_.setDirection(direction);
+}
+
+void YGNode::setLayoutMargin(float margin, int index) {
+ layout_.margin[index] = margin;
+}
+
+void YGNode::setLayoutBorder(float border, int index) {
+ layout_.border[index] = border;
+}
+
+void YGNode::setLayoutPadding(float padding, int index) {
+ layout_.padding[index] = padding;
+}
+
+void YGNode::setLayoutLastOwnerDirection(YGDirection direction) {
+ layout_.lastOwnerDirection = direction;
+}
+
+void YGNode::setLayoutComputedFlexBasis(
+ const YGFloatOptional computedFlexBasis) {
+ layout_.computedFlexBasis = computedFlexBasis;
+}
+
+void YGNode::setLayoutPosition(float position, int index) {
+ layout_.position[index] = position;
+}
+
+void YGNode::setLayoutComputedFlexBasisGeneration(
+ uint32_t computedFlexBasisGeneration) {
+ layout_.computedFlexBasisGeneration = computedFlexBasisGeneration;
+}
+
+void YGNode::setLayoutMeasuredDimension(float measuredDimension, int index) {
+ layout_.measuredDimensions[index] = measuredDimension;
+}
+
+void YGNode::setLayoutHadOverflow(bool hadOverflow) {
+ layout_.setHadOverflow(hadOverflow);
+}
+
+void YGNode::setLayoutDimension(float dimension, int index) {
+ layout_.dimensions[index] = dimension;
+}
+
+// If both left and right are defined, then use left. Otherwise return +left or
+// -right depending on which is defined.
+YGFloatOptional YGNode::relativePosition(
+ const YGFlexDirection axis,
+ const float axisSize) const {
+ if (isLeadingPositionDefined(axis)) {
+ return getLeadingPosition(axis, axisSize);
+ }
+
+ YGFloatOptional trailingPosition = getTrailingPosition(axis, axisSize);
+ if (!trailingPosition.isUndefined()) {
+ trailingPosition = YGFloatOptional{-1 * trailingPosition.unwrap()};
+ }
+ return trailingPosition;
+}
+
+void YGNode::setPosition(
+ const YGDirection direction,
+ const float mainSize,
+ const float crossSize,
+ const float ownerWidth) {
+ /* Root nodes should be always layouted as LTR, so we don't return negative
+ * values. */
+ const YGDirection directionRespectingRoot =
+ owner_ != nullptr ? direction : YGDirectionLTR;
+ const YGFlexDirection mainAxis =
+ YGResolveFlexDirection(style_.flexDirection(), directionRespectingRoot);
+ const YGFlexDirection crossAxis =
+ YGFlexDirectionCross(mainAxis, directionRespectingRoot);
+
+ // Here we should check for `YGPositionTypeStatic` and in this case zero inset
+ // properties (left, right, top, bottom, begin, end).
+ // https://www.w3.org/TR/css-position-3/#valdef-position-static
+ const YGFloatOptional relativePositionMain =
+ relativePosition(mainAxis, mainSize);
+ const YGFloatOptional relativePositionCross =
+ relativePosition(crossAxis, crossSize);
+
+ setLayoutPosition(
+ (getLeadingMargin(mainAxis, ownerWidth) + relativePositionMain).unwrap(),
+ leading[mainAxis]);
+ setLayoutPosition(
+ (getTrailingMargin(mainAxis, ownerWidth) + relativePositionMain).unwrap(),
+ trailing[mainAxis]);
+ setLayoutPosition(
+ (getLeadingMargin(crossAxis, ownerWidth) + relativePositionCross)
+ .unwrap(),
+ leading[crossAxis]);
+ setLayoutPosition(
+ (getTrailingMargin(crossAxis, ownerWidth) + relativePositionCross)
+ .unwrap(),
+ trailing[crossAxis]);
+}
+
+YGValue YGNode::marginLeadingValue(const YGFlexDirection axis) const {
+ if (YGFlexDirectionIsRow(axis) &&
+ !style_.margin()[YGEdgeStart].isUndefined()) {
+ return style_.margin()[YGEdgeStart];
+ } else {
+ return style_.margin()[leading[axis]];
+ }
+}
+
+YGValue YGNode::marginTrailingValue(const YGFlexDirection axis) const {
+ if (YGFlexDirectionIsRow(axis) && !style_.margin()[YGEdgeEnd].isUndefined()) {
+ return style_.margin()[YGEdgeEnd];
+ } else {
+ return style_.margin()[trailing[axis]];
+ }
+}
+
+YGValue YGNode::resolveFlexBasisPtr() const {
+ YGValue flexBasis = style_.flexBasis();
+ if (flexBasis.unit != YGUnitAuto && flexBasis.unit != YGUnitUndefined) {
+ return flexBasis;
+ }
+ if (!style_.flex().isUndefined() && style_.flex().unwrap() > 0.0f) {
+ return config_->useWebDefaults() ? YGValueAuto : YGValueZero;
+ }
+ return YGValueAuto;
+}
+
+void YGNode::resolveDimension() {
+ using namespace yoga;
+ const YGStyle& style = getStyle();
+ for (auto dim : {YGDimensionWidth, YGDimensionHeight}) {
+ if (!style.maxDimensions()[dim].isUndefined() &&
+ YGValueEqual(style.maxDimensions()[dim], style.minDimensions()[dim])) {
+ resolvedDimensions_[dim] = style.maxDimensions()[dim];
+ } else {
+ resolvedDimensions_[dim] = style.dimensions()[dim];
+ }
+ }
+}
+
+YGDirection YGNode::resolveDirection(const YGDirection ownerDirection) {
+ if (style_.direction() == YGDirectionInherit) {
+ return ownerDirection > YGDirectionInherit ? ownerDirection
+ : YGDirectionLTR;
+ } else {
+ return style_.direction();
+ }
+}
+
+YOGA_EXPORT void YGNode::clearChildren() {
+ children_.clear();
+ children_.shrink_to_fit();
+}
+
+// Other Methods
+
+void YGNode::cloneChildrenIfNeeded(void* cloneContext) {
+ iterChildrenAfterCloningIfNeeded([](YGNodeRef, void*) {}, cloneContext);
+}
+
+void YGNode::markDirtyAndPropagate() {
+ if (!flags_.isDirty) {
+ setDirty(true);
+ setLayoutComputedFlexBasis(YGFloatOptional());
+ if (owner_) {
+ owner_->markDirtyAndPropagate();
+ }
+ }
+}
+
+void YGNode::markDirtyAndPropagateDownwards() {
+ flags_.isDirty = true;
+ for_each(children_.begin(), children_.end(), [](YGNodeRef childNode) {
+ childNode->markDirtyAndPropagateDownwards();
+ });
+}
+
+float YGNode::resolveFlexGrow() const {
+ // Root nodes flexGrow should always be 0
+ if (owner_ == nullptr) {
+ return 0.0;
+ }
+ if (!style_.flexGrow().isUndefined()) {
+ return style_.flexGrow().unwrap();
+ }
+ if (!style_.flex().isUndefined() && style_.flex().unwrap() > 0.0f) {
+ return style_.flex().unwrap();
+ }
+ return kDefaultFlexGrow;
+}
+
+float YGNode::resolveFlexShrink() const {
+ if (owner_ == nullptr) {
+ return 0.0;
+ }
+ if (!style_.flexShrink().isUndefined()) {
+ return style_.flexShrink().unwrap();
+ }
+ if (!config_->useWebDefaults() && !style_.flex().isUndefined() &&
+ style_.flex().unwrap() < 0.0f) {
+ return -style_.flex().unwrap();
+ }
+ return config_->useWebDefaults() ? kWebDefaultFlexShrink : kDefaultFlexShrink;
+}
+
+bool YGNode::isNodeFlexible() {
+ return (
+ (style_.positionType() != YGPositionTypeAbsolute) &&
+ (resolveFlexGrow() != 0 || resolveFlexShrink() != 0));
+}
+
+float YGNode::getLeadingBorder(const YGFlexDirection axis) const {
+ YGValue leadingBorder = YGFlexDirectionIsRow(axis)
+ ? computeEdgeValueForRow(
+ style_.border(), YGEdgeStart, leading[axis], CompactValue::ofZero())
+ : computeEdgeValueForColumn(
+ style_.border(), leading[axis], CompactValue::ofZero());
+ return fmaxf(leadingBorder.value, 0.0f);
+}
+
+float YGNode::getTrailingBorder(const YGFlexDirection axis) const {
+ YGValue trailingBorder = YGFlexDirectionIsRow(axis)
+ ? computeEdgeValueForRow(
+ style_.border(), YGEdgeEnd, trailing[axis], CompactValue::ofZero())
+ : computeEdgeValueForColumn(
+ style_.border(), trailing[axis], CompactValue::ofZero());
+ return fmaxf(trailingBorder.value, 0.0f);
+}
+
+YGFloatOptional YGNode::getLeadingPadding(
+ const YGFlexDirection axis,
+ const float widthSize) const {
+ auto leadingPadding = YGFlexDirectionIsRow(axis)
+ ? computeEdgeValueForRow(
+ style_.padding(),
+ YGEdgeStart,
+ leading[axis],
+ CompactValue::ofZero())
+ : computeEdgeValueForColumn(
+ style_.padding(), leading[axis], CompactValue::ofZero());
+ return YGFloatOptionalMax(
+ YGResolveValue(leadingPadding, widthSize), YGFloatOptional(0.0f));
+}
+
+YGFloatOptional YGNode::getTrailingPadding(
+ const YGFlexDirection axis,
+ const float widthSize) const {
+ auto trailingPadding = YGFlexDirectionIsRow(axis)
+ ? computeEdgeValueForRow(
+ style_.padding(), YGEdgeEnd, trailing[axis], CompactValue::ofZero())
+ : computeEdgeValueForColumn(
+ style_.padding(), trailing[axis], CompactValue::ofZero());
+ return YGFloatOptionalMax(
+ YGResolveValue(trailingPadding, widthSize), YGFloatOptional(0.0f));
+}
+
+YGFloatOptional YGNode::getLeadingPaddingAndBorder(
+ const YGFlexDirection axis,
+ const float widthSize) const {
+ return getLeadingPadding(axis, widthSize) +
+ YGFloatOptional(getLeadingBorder(axis));
+}
+
+YGFloatOptional YGNode::getTrailingPaddingAndBorder(
+ const YGFlexDirection axis,
+ const float widthSize) const {
+ return getTrailingPadding(axis, widthSize) +
+ YGFloatOptional(getTrailingBorder(axis));
+}
+
+void YGNode::reset() {
+ YGAssertWithNode(
+ this,
+ children_.size() == 0,
+ "Cannot reset a node which still has children attached");
+ YGAssertWithNode(
+ this, owner_ == nullptr, "Cannot reset a node still attached to a owner");
+
+ *this = YGNode{getConfig()};
+}
diff --git a/src/3rdparty/yoga/YGNode.h b/src/3rdparty/yoga/YGNode.h
new file mode 100644
index 0000000000..c4eba5cafd
--- /dev/null
+++ b/src/3rdparty/yoga/YGNode.h
@@ -0,0 +1,335 @@
+/*
+ * Copyright (C) 2016 The Qt Company Ltd.
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#pragma once
+
+#include <cstdint>
+#include <stdio.h>
+#include "CompactValue.h"
+#include "YGConfig.h"
+#include "YGLayout.h"
+#include "YGStyle.h"
+#include "Yoga-internal.h"
+
+YGConfigRef YGConfigGetDefault();
+
+#pragma pack(push)
+#pragma pack(1)
+struct YGNodeFlags {
+ bool hasNewLayout : 1;
+ bool isReferenceBaseline : 1;
+ bool isDirty : 1;
+ uint8_t nodeType : 1;
+ bool measureUsesContext : 1;
+ bool baselineUsesContext : 1;
+ bool printUsesContext : 1;
+};
+#pragma pack(pop)
+
+struct YOGA_EXPORT YGNode {
+ using MeasureWithContextFn =
+ YGSize (*)(YGNode*, float, YGMeasureMode, float, YGMeasureMode, void*);
+ using BaselineWithContextFn = float (*)(YGNode*, float, float, void*);
+ using PrintWithContextFn = void (*)(YGNode*, void*);
+
+private:
+ void* context_ = nullptr;
+ YGNodeFlags flags_ = {};
+ union {
+ YGMeasureFunc noContext;
+ MeasureWithContextFn withContext;
+ } measure_ = {nullptr};
+ union {
+ YGBaselineFunc noContext;
+ BaselineWithContextFn withContext;
+ } baseline_ = {nullptr};
+ union {
+ YGPrintFunc noContext;
+ PrintWithContextFn withContext;
+ } print_ = {nullptr};
+ YGDirtiedFunc dirtied_ = nullptr;
+ YGStyle style_ = {};
+ YGLayout layout_ = {};
+ uint32_t lineIndex_ = 0;
+ YGNodeRef owner_ = nullptr;
+ YGVector children_ = {};
+ YGConfigRef config_;
+ std::array<YGValue, 2> resolvedDimensions_ = {
+ {YGValueUndefined, YGValueUndefined}};
+
+ YGFloatOptional relativePosition(
+ const YGFlexDirection axis,
+ const float axisSize) const;
+
+ void setMeasureFunc(decltype(measure_));
+ void setBaselineFunc(decltype(baseline_));
+
+ void useWebDefaults() {
+ style_.flexDirection() = YGFlexDirectionRow;
+ style_.alignContent() = YGAlignStretch;
+ }
+
+ // DANGER DANGER DANGER!
+ // If the node assigned to has children, we'd either have to deallocate
+ // them (potentially incorrect) or ignore them (danger of leaks). Only ever
+ // use this after checking that there are no children.
+ // DO NOT CHANGE THE VISIBILITY OF THIS METHOD!
+ YGNode& operator=(YGNode&&) = default;
+
+ using CompactValue = facebook::yoga::detail::CompactValue;
+
+public:
+ YGNode() : YGNode{YGConfigGetDefault()} { flags_.hasNewLayout = true; }
+ explicit YGNode(const YGConfigRef config);
+ ~YGNode() = default; // cleanup of owner/children relationships in YGNodeFree
+
+ YGNode(YGNode&&);
+
+ // Does not expose true value semantics, as children are not cloned eagerly.
+ // Should we remove this?
+ YGNode(const YGNode& node) = default;
+
+ // assignment means potential leaks of existing children, or alternatively
+ // freeing unowned memory, double free, or freeing stack memory.
+ YGNode& operator=(const YGNode&) = delete;
+
+ // Getters
+ void* getContext() const { return context_; }
+
+ void print(void*);
+
+ bool getHasNewLayout() const { return flags_.hasNewLayout; }
+
+ YGNodeType getNodeType() const {
+ return static_cast<YGNodeType>(flags_.nodeType);
+ }
+
+ bool hasMeasureFunc() const noexcept { return measure_.noContext != nullptr; }
+
+ YGSize measure(float, YGMeasureMode, float, YGMeasureMode, void*);
+
+ bool hasBaselineFunc() const noexcept {
+ return baseline_.noContext != nullptr;
+ }
+
+ float baseline(float width, float height, void* layoutContext);
+
+ bool hasErrata(YGErrata errata) const { return config_->hasErrata(errata); }
+
+ YGDirtiedFunc getDirtied() const { return dirtied_; }
+
+ // For Performance reasons passing as reference.
+ YGStyle& getStyle() { return style_; }
+
+ const YGStyle& getStyle() const { return style_; }
+
+ // For Performance reasons passing as reference.
+ YGLayout& getLayout() { return layout_; }
+
+ const YGLayout& getLayout() const { return layout_; }
+
+ uint32_t getLineIndex() const { return lineIndex_; }
+
+ bool isReferenceBaseline() { return flags_.isReferenceBaseline; }
+
+ // returns the YGNodeRef that owns this YGNode. An owner is used to identify
+ // the YogaTree that a YGNode belongs to. This method will return the parent
+ // of the YGNode when a YGNode only belongs to one YogaTree or nullptr when
+ // the YGNode is shared between two or more YogaTrees.
+ YGNodeRef getOwner() const { return owner_; }
+
+ // Deprecated, use getOwner() instead.
+ YGNodeRef getParent() const { return getOwner(); }
+
+ const YGVector& getChildren() const { return children_; }
+
+ // Applies a callback to all children, after cloning them if they are not
+ // owned.
+ template <typename T>
+ void iterChildrenAfterCloningIfNeeded(T callback, void* cloneContext) {
+ int i = 0;
+ for (YGNodeRef& child : children_) {
+ if (child->getOwner() != this) {
+ child = config_->cloneNode(child, this, i, cloneContext);
+ child->setOwner(this);
+ }
+ i += 1;
+
+ callback(child, cloneContext);
+ }
+ }
+
+ YGNodeRef getChild(uint32_t index) const { return children_.at(index); }
+
+ YGConfigRef getConfig() const { return config_; }
+
+ bool isDirty() const { return flags_.isDirty; }
+
+ std::array<YGValue, 2> getResolvedDimensions() const {
+ return resolvedDimensions_;
+ }
+
+ YGValue getResolvedDimension(int index) const {
+ return resolvedDimensions_[index];
+ }
+
+ static CompactValue computeEdgeValueForColumn(
+ const YGStyle::Edges& edges,
+ YGEdge edge,
+ CompactValue defaultValue);
+
+ static CompactValue computeEdgeValueForRow(
+ const YGStyle::Edges& edges,
+ YGEdge rowEdge,
+ YGEdge edge,
+ CompactValue defaultValue);
+
+ static CompactValue computeRowGap(
+ const YGStyle::Gutters& gutters,
+ CompactValue defaultValue);
+
+ static CompactValue computeColumnGap(
+ const YGStyle::Gutters& gutters,
+ CompactValue defaultValue);
+
+ // Methods related to positions, margin, padding and border
+ YGFloatOptional getLeadingPosition(
+ const YGFlexDirection axis,
+ const float axisSize) const;
+ bool isLeadingPositionDefined(const YGFlexDirection axis) const;
+ bool isTrailingPosDefined(const YGFlexDirection axis) const;
+ YGFloatOptional getTrailingPosition(
+ const YGFlexDirection axis,
+ const float axisSize) const;
+ YGFloatOptional getLeadingMargin(
+ const YGFlexDirection axis,
+ const float widthSize) const;
+ YGFloatOptional getTrailingMargin(
+ const YGFlexDirection axis,
+ const float widthSize) const;
+ float getLeadingBorder(const YGFlexDirection flexDirection) const;
+ float getTrailingBorder(const YGFlexDirection flexDirection) const;
+ YGFloatOptional getLeadingPadding(
+ const YGFlexDirection axis,
+ const float widthSize) const;
+ YGFloatOptional getTrailingPadding(
+ const YGFlexDirection axis,
+ const float widthSize) const;
+ YGFloatOptional getLeadingPaddingAndBorder(
+ const YGFlexDirection axis,
+ const float widthSize) const;
+ YGFloatOptional getTrailingPaddingAndBorder(
+ const YGFlexDirection axis,
+ const float widthSize) const;
+ YGFloatOptional getMarginForAxis(
+ const YGFlexDirection axis,
+ const float widthSize) const;
+ YGFloatOptional getGapForAxis(
+ const YGFlexDirection axis,
+ const float widthSize) const;
+ // Setters
+
+ void setContext(void* context) { context_ = context; }
+
+ void setPrintFunc(YGPrintFunc printFunc) {
+ print_.noContext = printFunc;
+ flags_.printUsesContext = false;
+ }
+ void setPrintFunc(PrintWithContextFn printFunc) {
+ print_.withContext = printFunc;
+ flags_.printUsesContext = true;
+ }
+ void setPrintFunc(std::nullptr_t) { setPrintFunc(YGPrintFunc{nullptr}); }
+
+ void setHasNewLayout(bool hasNewLayout) {
+ flags_.hasNewLayout = hasNewLayout;
+ }
+
+ void setNodeType(YGNodeType nodeType) {
+ flags_.nodeType = static_cast<uint8_t>(nodeType);
+ }
+
+ void setMeasureFunc(YGMeasureFunc measureFunc);
+ void setMeasureFunc(MeasureWithContextFn);
+ void setMeasureFunc(std::nullptr_t) {
+ return setMeasureFunc(YGMeasureFunc{nullptr});
+ }
+
+ void setBaselineFunc(YGBaselineFunc baseLineFunc) {
+ flags_.baselineUsesContext = false;
+ baseline_.noContext = baseLineFunc;
+ }
+ void setBaselineFunc(BaselineWithContextFn baseLineFunc) {
+ flags_.baselineUsesContext = true;
+ baseline_.withContext = baseLineFunc;
+ }
+ void setBaselineFunc(std::nullptr_t) {
+ return setBaselineFunc(YGBaselineFunc{nullptr});
+ }
+
+ void setDirtiedFunc(YGDirtiedFunc dirtiedFunc) { dirtied_ = dirtiedFunc; }
+
+ void setStyle(const YGStyle& style) { style_ = style; }
+
+ void setLayout(const YGLayout& layout) { layout_ = layout; }
+
+ void setLineIndex(uint32_t lineIndex) { lineIndex_ = lineIndex; }
+
+ void setIsReferenceBaseline(bool isReferenceBaseline) {
+ flags_.isReferenceBaseline = isReferenceBaseline;
+ }
+
+ void setOwner(YGNodeRef owner) { owner_ = owner; }
+
+ void setChildren(const YGVector& children) { children_ = children; }
+
+ // TODO: rvalue override for setChildren
+
+ void setConfig(YGConfigRef config);
+
+ void setDirty(bool isDirty);
+ void setLayoutLastOwnerDirection(YGDirection direction);
+ void setLayoutComputedFlexBasis(const YGFloatOptional computedFlexBasis);
+ void setLayoutComputedFlexBasisGeneration(
+ uint32_t computedFlexBasisGeneration);
+ void setLayoutMeasuredDimension(float measuredDimension, int index);
+ void setLayoutHadOverflow(bool hadOverflow);
+ void setLayoutDimension(float dimension, int index);
+ void setLayoutDirection(YGDirection direction);
+ void setLayoutMargin(float margin, int index);
+ void setLayoutBorder(float border, int index);
+ void setLayoutPadding(float padding, int index);
+ void setLayoutPosition(float position, int index);
+ void setPosition(
+ const YGDirection direction,
+ const float mainSize,
+ const float crossSize,
+ const float ownerWidth);
+ void markDirtyAndPropagateDownwards();
+
+ // Other methods
+ YGValue marginLeadingValue(const YGFlexDirection axis) const;
+ YGValue marginTrailingValue(const YGFlexDirection axis) const;
+ YGValue resolveFlexBasisPtr() const;
+ void resolveDimension();
+ YGDirection resolveDirection(const YGDirection ownerDirection);
+ void clearChildren();
+ /// Replaces the occurrences of oldChild with newChild
+ void replaceChild(YGNodeRef oldChild, YGNodeRef newChild);
+ void replaceChild(YGNodeRef child, uint32_t index);
+ void insertChild(YGNodeRef child, uint32_t index);
+ /// Removes the first occurrence of child
+ bool removeChild(YGNodeRef child);
+ void removeChild(uint32_t index);
+
+ void cloneChildrenIfNeeded(void*);
+ void markDirtyAndPropagate();
+ float resolveFlexGrow() const;
+ float resolveFlexShrink() const;
+ bool isNodeFlexible();
+ void reset();
+};
diff --git a/src/3rdparty/yoga/YGNodePrint.cpp b/src/3rdparty/yoga/YGNodePrint.cpp
new file mode 100644
index 0000000000..cf0a2e44ed
--- /dev/null
+++ b/src/3rdparty/yoga/YGNodePrint.cpp
@@ -0,0 +1,245 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// Copyright (c) Meta Platforms, Inc. and affiliates.
+//
+// SPDX-License-Identifier: MIT
+
+#ifdef DEBUG
+
+#include <stdarg.h>
+
+#include <yoga/YGEnums.h>
+
+#include "YGNodePrint.h"
+#include "YGNode.h"
+#include "Yoga-internal.h"
+#include "Utils.h"
+
+namespace facebook {
+namespace yoga {
+typedef std::string string;
+
+static void indent(string& base, uint32_t level) {
+ for (uint32_t i = 0; i < level; ++i) {
+ base.append(" ");
+ }
+}
+
+static bool areFourValuesEqual(const YGStyle::Edges& four) {
+ return YGValueEqual(four[0], four[1]) && YGValueEqual(four[0], four[2]) &&
+ YGValueEqual(four[0], four[3]);
+}
+
+static void appendFormattedString(string& str, const char* fmt, ...) {
+ va_list args;
+ va_start(args, fmt);
+ va_list argsCopy;
+ va_copy(argsCopy, args);
+ std::vector<char> buf(1 + vsnprintf(NULL, 0, fmt, args));
+ va_end(args);
+ vsnprintf(buf.data(), buf.size(), fmt, argsCopy);
+ va_end(argsCopy);
+ string result = string(buf.begin(), buf.end() - 1);
+ str.append(result);
+}
+
+static void appendFloatOptionalIfDefined(
+ string& base,
+ const string key,
+ const YGFloatOptional num) {
+ if (!num.isUndefined()) {
+ appendFormattedString(base, "%s: %g; ", key.c_str(), num.unwrap());
+ }
+}
+
+static void appendNumberIfNotUndefined(
+ string& base,
+ const string key,
+ const YGValue number) {
+ if (number.unit != YGUnitUndefined) {
+ if (number.unit == YGUnitAuto) {
+ base.append(key + ": auto; ");
+ } else {
+ string unit = number.unit == YGUnitPoint ? "px" : "%%";
+ appendFormattedString(
+ base, "%s: %g%s; ", key.c_str(), number.value, unit.c_str());
+ }
+ }
+}
+
+static void appendNumberIfNotAuto(
+ string& base,
+ const string& key,
+ const YGValue number) {
+ if (number.unit != YGUnitAuto) {
+ appendNumberIfNotUndefined(base, key, number);
+ }
+}
+
+static void appendNumberIfNotZero(
+ string& base,
+ const string& str,
+ const YGValue number) {
+ if (number.unit == YGUnitAuto) {
+ base.append(str + ": auto; ");
+ } else if (!YGFloatsEqual(number.value, 0)) {
+ appendNumberIfNotUndefined(base, str, number);
+ }
+}
+
+static void appendEdges(
+ string& base,
+ const string& key,
+ const YGStyle::Edges& edges) {
+ if (areFourValuesEqual(edges)) {
+ auto edgeValue = YGNode::computeEdgeValueForColumn(
+ edges, YGEdgeLeft, detail::CompactValue::ofZero());
+ appendNumberIfNotZero(base, key, edgeValue);
+ } else {
+ for (int edge = YGEdgeLeft; edge != YGEdgeAll; ++edge) {
+ string str = key + "-" + YGEdgeToString(static_cast<YGEdge>(edge));
+ appendNumberIfNotZero(base, str, edges[edge]);
+ }
+ }
+}
+
+static void appendEdgeIfNotUndefined(
+ string& base,
+ const string& str,
+ const YGStyle::Edges& edges,
+ const YGEdge edge) {
+ // TODO: this doesn't take RTL / YGEdgeStart / YGEdgeEnd into account
+ auto value = (edge == YGEdgeLeft || edge == YGEdgeRight)
+ ? YGNode::computeEdgeValueForRow(
+ edges, edge, edge, detail::CompactValue::ofUndefined())
+ : YGNode::computeEdgeValueForColumn(
+ edges, edge, detail::CompactValue::ofUndefined());
+ appendNumberIfNotUndefined(base, str, value);
+}
+
+void YGNodeToString(
+ std::string& str,
+ YGNodeRef node,
+ YGPrintOptions options,
+ uint32_t level) {
+ indent(str, level);
+ appendFormattedString(str, "<div ");
+
+ if (options & YGPrintOptionsLayout) {
+ appendFormattedString(str, "layout=\"");
+ appendFormattedString(
+ str, "width: %g; ", node->getLayout().dimensions[YGDimensionWidth]);
+ appendFormattedString(
+ str, "height: %g; ", node->getLayout().dimensions[YGDimensionHeight]);
+ appendFormattedString(
+ str, "top: %g; ", node->getLayout().position[YGEdgeTop]);
+ appendFormattedString(
+ str, "left: %g;", node->getLayout().position[YGEdgeLeft]);
+ appendFormattedString(str, "\" ");
+ }
+
+ if (options & YGPrintOptionsStyle) {
+ appendFormattedString(str, "style=\"");
+ const auto& style = node->getStyle();
+ if (style.flexDirection() != YGNode().getStyle().flexDirection()) {
+ appendFormattedString(
+ str,
+ "flex-direction: %s; ",
+ YGFlexDirectionToString(style.flexDirection()));
+ }
+ if (style.justifyContent() != YGNode().getStyle().justifyContent()) {
+ appendFormattedString(
+ str,
+ "justify-content: %s; ",
+ YGJustifyToString(style.justifyContent()));
+ }
+ if (style.alignItems() != YGNode().getStyle().alignItems()) {
+ appendFormattedString(
+ str, "align-items: %s; ", YGAlignToString(style.alignItems()));
+ }
+ if (style.alignContent() != YGNode().getStyle().alignContent()) {
+ appendFormattedString(
+ str, "align-content: %s; ", YGAlignToString(style.alignContent()));
+ }
+ if (style.alignSelf() != YGNode().getStyle().alignSelf()) {
+ appendFormattedString(
+ str, "align-self: %s; ", YGAlignToString(style.alignSelf()));
+ }
+ appendFloatOptionalIfDefined(str, "flex-grow", style.flexGrow());
+ appendFloatOptionalIfDefined(str, "flex-shrink", style.flexShrink());
+ appendNumberIfNotAuto(str, "flex-basis", style.flexBasis());
+ appendFloatOptionalIfDefined(str, "flex", style.flex());
+
+ if (style.flexWrap() != YGNode().getStyle().flexWrap()) {
+ appendFormattedString(
+ str, "flex-wrap: %s; ", YGWrapToString(style.flexWrap()));
+ }
+
+ if (style.overflow() != YGNode().getStyle().overflow()) {
+ appendFormattedString(
+ str, "overflow: %s; ", YGOverflowToString(style.overflow()));
+ }
+
+ if (style.display() != YGNode().getStyle().display()) {
+ appendFormattedString(
+ str, "display: %s; ", YGDisplayToString(style.display()));
+ }
+ appendEdges(str, "margin", style.margin());
+ appendEdges(str, "padding", style.padding());
+ appendEdges(str, "border", style.border());
+
+ if (YGNode::computeColumnGap(
+ style.gap(), detail::CompactValue::ofUndefined()) !=
+ YGNode::computeColumnGap(
+ YGNode().getStyle().gap(), detail::CompactValue::ofUndefined())) {
+ appendNumberIfNotUndefined(
+ str, "column-gap", style.gap()[YGGutterColumn]);
+ }
+ if (YGNode::computeRowGap(
+ style.gap(), detail::CompactValue::ofUndefined()) !=
+ YGNode::computeRowGap(
+ YGNode().getStyle().gap(), detail::CompactValue::ofUndefined())) {
+ appendNumberIfNotUndefined(str, "row-gap", style.gap()[YGGutterRow]);
+ }
+
+ appendNumberIfNotAuto(str, "width", style.dimensions()[YGDimensionWidth]);
+ appendNumberIfNotAuto(str, "height", style.dimensions()[YGDimensionHeight]);
+ appendNumberIfNotAuto(
+ str, "max-width", style.maxDimensions()[YGDimensionWidth]);
+ appendNumberIfNotAuto(
+ str, "max-height", style.maxDimensions()[YGDimensionHeight]);
+ appendNumberIfNotAuto(
+ str, "min-width", style.minDimensions()[YGDimensionWidth]);
+ appendNumberIfNotAuto(
+ str, "min-height", style.minDimensions()[YGDimensionHeight]);
+
+ if (style.positionType() != YGNode().getStyle().positionType()) {
+ appendFormattedString(
+ str, "position: %s; ", YGPositionTypeToString(style.positionType()));
+ }
+
+ appendEdgeIfNotUndefined(str, "left", style.position(), YGEdgeLeft);
+ appendEdgeIfNotUndefined(str, "right", style.position(), YGEdgeRight);
+ appendEdgeIfNotUndefined(str, "top", style.position(), YGEdgeTop);
+ appendEdgeIfNotUndefined(str, "bottom", style.position(), YGEdgeBottom);
+ appendFormattedString(str, "\" ");
+
+ if (node->hasMeasureFunc()) {
+ appendFormattedString(str, "has-custom-measure=\"true\"");
+ }
+ }
+ appendFormattedString(str, ">");
+
+ const uint32_t childCount = static_cast<uint32_t>(node->getChildren().size());
+ if (options & YGPrintOptionsChildren && childCount > 0) {
+ for (uint32_t i = 0; i < childCount; i++) {
+ appendFormattedString(str, "\n");
+ YGNodeToString(str, YGNodeGetChild(node, i), options, level + 1);
+ }
+ appendFormattedString(str, "\n");
+ indent(str, level);
+ }
+ appendFormattedString(str, "</div>");
+}
+} // namespace yoga
+} // namespace facebook
+#endif
diff --git a/src/3rdparty/yoga/YGNodePrint.h b/src/3rdparty/yoga/YGNodePrint.h
new file mode 100644
index 0000000000..04f1c0a08b
--- /dev/null
+++ b/src/3rdparty/yoga/YGNodePrint.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2016 The Qt Company Ltd.
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#ifdef DEBUG
+
+#pragma once
+
+#include <string>
+
+#include <yoga/Yoga.h>
+
+namespace facebook {
+namespace yoga {
+
+void YGNodeToString(
+ std::string& str,
+ YGNodeRef node,
+ YGPrintOptions options,
+ uint32_t level);
+
+} // namespace yoga
+} // namespace facebook
+
+#endif
diff --git a/src/3rdparty/yoga/YGStyle.cpp b/src/3rdparty/yoga/YGStyle.cpp
new file mode 100644
index 0000000000..b680800d23
--- /dev/null
+++ b/src/3rdparty/yoga/YGStyle.cpp
@@ -0,0 +1,54 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// Copyright (c) Meta Platforms, Inc. and affiliates.
+//
+// SPDX-License-Identifier: MIT
+
+#include "YGStyle.h"
+#include "Utils.h"
+
+// Yoga specific properties, not compatible with flexbox specification
+bool operator==(const YGStyle& lhs, const YGStyle& rhs) {
+ bool areNonFloatValuesEqual = lhs.direction() == rhs.direction() &&
+ lhs.flexDirection() == rhs.flexDirection() &&
+ lhs.justifyContent() == rhs.justifyContent() &&
+ lhs.alignContent() == rhs.alignContent() &&
+ lhs.alignItems() == rhs.alignItems() &&
+ lhs.alignSelf() == rhs.alignSelf() &&
+ lhs.positionType() == rhs.positionType() &&
+ lhs.flexWrap() == rhs.flexWrap() && lhs.overflow() == rhs.overflow() &&
+ lhs.display() == rhs.display() &&
+ YGValueEqual(lhs.flexBasis(), rhs.flexBasis()) &&
+ lhs.margin() == rhs.margin() && lhs.position() == rhs.position() &&
+ lhs.padding() == rhs.padding() && lhs.border() == rhs.border() &&
+ lhs.gap() == rhs.gap() && lhs.dimensions() == rhs.dimensions() &&
+ lhs.minDimensions() == rhs.minDimensions() &&
+ lhs.maxDimensions() == rhs.maxDimensions();
+
+ areNonFloatValuesEqual = areNonFloatValuesEqual &&
+ lhs.flex().isUndefined() == rhs.flex().isUndefined();
+ if (areNonFloatValuesEqual && !lhs.flex().isUndefined() &&
+ !rhs.flex().isUndefined()) {
+ areNonFloatValuesEqual = areNonFloatValuesEqual && lhs.flex() == rhs.flex();
+ }
+
+ areNonFloatValuesEqual = areNonFloatValuesEqual &&
+ lhs.flexGrow().isUndefined() == rhs.flexGrow().isUndefined();
+ if (areNonFloatValuesEqual && !lhs.flexGrow().isUndefined()) {
+ areNonFloatValuesEqual =
+ areNonFloatValuesEqual && lhs.flexGrow() == rhs.flexGrow();
+ }
+
+ areNonFloatValuesEqual = areNonFloatValuesEqual &&
+ lhs.flexShrink().isUndefined() == rhs.flexShrink().isUndefined();
+ if (areNonFloatValuesEqual && !rhs.flexShrink().isUndefined()) {
+ areNonFloatValuesEqual =
+ areNonFloatValuesEqual && lhs.flexShrink() == rhs.flexShrink();
+ }
+
+ if (!(lhs.aspectRatio().isUndefined() && rhs.aspectRatio().isUndefined())) {
+ areNonFloatValuesEqual =
+ areNonFloatValuesEqual && lhs.aspectRatio() == rhs.aspectRatio();
+ }
+
+ return areNonFloatValuesEqual;
+}
diff --git a/src/3rdparty/yoga/YGStyle.h b/src/3rdparty/yoga/YGStyle.h
new file mode 100644
index 0000000000..f998b81cd3
--- /dev/null
+++ b/src/3rdparty/yoga/YGStyle.h
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2016 The Qt Company Ltd.
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#pragma once
+
+#include <algorithm>
+#include <array>
+#include <cstdint>
+#include <type_traits>
+
+#include <yoga/Yoga.h>
+
+#include "CompactValue.h"
+#include "YGFloatOptional.h"
+#include "Yoga-internal.h"
+#include "BitUtils.h"
+
+class YOGA_EXPORT YGStyle {
+ template <typename Enum>
+ using Values =
+ facebook::yoga::detail::Values<facebook::yoga::enums::count<Enum>()>;
+ using CompactValue = facebook::yoga::detail::CompactValue;
+
+public:
+ using Dimensions = Values<YGDimension>;
+ using Edges = Values<YGEdge>;
+ using Gutters = Values<YGGutter>;
+
+ template <typename T>
+ struct BitfieldRef {
+ YGStyle& style;
+ size_t offset;
+ operator T() const {
+ return facebook::yoga::detail::getEnumData<T>(style.flags, offset);
+ }
+ BitfieldRef<T>& operator=(T x) {
+ facebook::yoga::detail::setEnumData<T>(style.flags, offset, x);
+ return *this;
+ }
+ };
+
+ template <typename T, T YGStyle::*Prop>
+ struct Ref {
+ YGStyle& style;
+ operator T() const { return style.*Prop; }
+ Ref<T, Prop>& operator=(T value) {
+ style.*Prop = value;
+ return *this;
+ }
+ };
+
+ template <typename Idx, Values<Idx> YGStyle::*Prop>
+ struct IdxRef {
+ struct Ref {
+ YGStyle& style;
+ Idx idx;
+ operator CompactValue() const { return (style.*Prop)[idx]; }
+ operator YGValue() const { return (style.*Prop)[idx]; }
+ Ref& operator=(CompactValue value) {
+ (style.*Prop)[idx] = value;
+ return *this;
+ }
+ };
+
+ YGStyle& style;
+ IdxRef<Idx, Prop>& operator=(const Values<Idx>& values) {
+ style.*Prop = values;
+ return *this;
+ }
+ operator const Values<Idx>&() const { return style.*Prop; }
+ Ref operator[](Idx idx) { return {style, idx}; }
+ CompactValue operator[](Idx idx) const { return (style.*Prop)[idx]; }
+ };
+
+ YGStyle() {
+ alignContent() = YGAlignFlexStart;
+ alignItems() = YGAlignStretch;
+ }
+ ~YGStyle() = default;
+
+private:
+ static constexpr size_t directionOffset = 0;
+ static constexpr size_t flexdirectionOffset =
+ directionOffset + facebook::yoga::detail::bitWidthFn<YGDirection>();
+ static constexpr size_t justifyContentOffset = flexdirectionOffset +
+ facebook::yoga::detail::bitWidthFn<YGFlexDirection>();
+ static constexpr size_t alignContentOffset =
+ justifyContentOffset + facebook::yoga::detail::bitWidthFn<YGJustify>();
+ static constexpr size_t alignItemsOffset =
+ alignContentOffset + facebook::yoga::detail::bitWidthFn<YGAlign>();
+ static constexpr size_t alignSelfOffset =
+ alignItemsOffset + facebook::yoga::detail::bitWidthFn<YGAlign>();
+ static constexpr size_t positionTypeOffset =
+ alignSelfOffset + facebook::yoga::detail::bitWidthFn<YGAlign>();
+ static constexpr size_t flexWrapOffset =
+ positionTypeOffset + facebook::yoga::detail::bitWidthFn<YGPositionType>();
+ static constexpr size_t overflowOffset =
+ flexWrapOffset + facebook::yoga::detail::bitWidthFn<YGWrap>();
+ static constexpr size_t displayOffset =
+ overflowOffset + facebook::yoga::detail::bitWidthFn<YGOverflow>();
+
+ uint32_t flags = 0;
+
+ YGFloatOptional flex_ = {};
+ YGFloatOptional flexGrow_ = {};
+ YGFloatOptional flexShrink_ = {};
+ CompactValue flexBasis_ = CompactValue::ofAuto();
+ Edges margin_ = {};
+ Edges position_ = {};
+ Edges padding_ = {};
+ Edges border_ = {};
+ Gutters gap_ = {};
+ Dimensions dimensions_{CompactValue::ofAuto()};
+ Dimensions minDimensions_ = {};
+ Dimensions maxDimensions_ = {};
+ // Yoga specific properties, not compatible with flexbox specification
+ YGFloatOptional aspectRatio_ = {};
+
+public:
+ // for library users needing a type
+ using ValueRepr = std::remove_reference<decltype(margin_[0])>::type;
+
+ YGDirection direction() const {
+ return facebook::yoga::detail::getEnumData<YGDirection>(
+ flags, directionOffset);
+ }
+ BitfieldRef<YGDirection> direction() { return {*this, directionOffset}; }
+
+ YGFlexDirection flexDirection() const {
+ return facebook::yoga::detail::getEnumData<YGFlexDirection>(
+ flags, flexdirectionOffset);
+ }
+ BitfieldRef<YGFlexDirection> flexDirection() {
+ return {*this, flexdirectionOffset};
+ }
+
+ YGJustify justifyContent() const {
+ return facebook::yoga::detail::getEnumData<YGJustify>(
+ flags, justifyContentOffset);
+ }
+ BitfieldRef<YGJustify> justifyContent() {
+ return {*this, justifyContentOffset};
+ }
+
+ YGAlign alignContent() const {
+ return facebook::yoga::detail::getEnumData<YGAlign>(
+ flags, alignContentOffset);
+ }
+ BitfieldRef<YGAlign> alignContent() { return {*this, alignContentOffset}; }
+
+ YGAlign alignItems() const {
+ return facebook::yoga::detail::getEnumData<YGAlign>(
+ flags, alignItemsOffset);
+ }
+ BitfieldRef<YGAlign> alignItems() { return {*this, alignItemsOffset}; }
+
+ YGAlign alignSelf() const {
+ return facebook::yoga::detail::getEnumData<YGAlign>(flags, alignSelfOffset);
+ }
+ BitfieldRef<YGAlign> alignSelf() { return {*this, alignSelfOffset}; }
+
+ YGPositionType positionType() const {
+ return facebook::yoga::detail::getEnumData<YGPositionType>(
+ flags, positionTypeOffset);
+ }
+ BitfieldRef<YGPositionType> positionType() {
+ return {*this, positionTypeOffset};
+ }
+
+ YGWrap flexWrap() const {
+ return facebook::yoga::detail::getEnumData<YGWrap>(flags, flexWrapOffset);
+ }
+ BitfieldRef<YGWrap> flexWrap() { return {*this, flexWrapOffset}; }
+
+ YGOverflow overflow() const {
+ return facebook::yoga::detail::getEnumData<YGOverflow>(
+ flags, overflowOffset);
+ }
+ BitfieldRef<YGOverflow> overflow() { return {*this, overflowOffset}; }
+
+ YGDisplay display() const {
+ return facebook::yoga::detail::getEnumData<YGDisplay>(flags, displayOffset);
+ }
+ BitfieldRef<YGDisplay> display() { return {*this, displayOffset}; }
+
+ YGFloatOptional flex() const { return flex_; }
+ Ref<YGFloatOptional, &YGStyle::flex_> flex() { return {*this}; }
+
+ YGFloatOptional flexGrow() const { return flexGrow_; }
+ Ref<YGFloatOptional, &YGStyle::flexGrow_> flexGrow() { return {*this}; }
+
+ YGFloatOptional flexShrink() const { return flexShrink_; }
+ Ref<YGFloatOptional, &YGStyle::flexShrink_> flexShrink() { return {*this}; }
+
+ CompactValue flexBasis() const { return flexBasis_; }
+ Ref<CompactValue, &YGStyle::flexBasis_> flexBasis() { return {*this}; }
+
+ const Edges& margin() const { return margin_; }
+ IdxRef<YGEdge, &YGStyle::margin_> margin() { return {*this}; }
+
+ const Edges& position() const { return position_; }
+ IdxRef<YGEdge, &YGStyle::position_> position() { return {*this}; }
+
+ const Edges& padding() const { return padding_; }
+ IdxRef<YGEdge, &YGStyle::padding_> padding() { return {*this}; }
+
+ const Edges& border() const { return border_; }
+ IdxRef<YGEdge, &YGStyle::border_> border() { return {*this}; }
+
+ const Gutters& gap() const { return gap_; }
+ IdxRef<YGGutter, &YGStyle::gap_> gap() { return {*this}; }
+
+ const Dimensions& dimensions() const { return dimensions_; }
+ IdxRef<YGDimension, &YGStyle::dimensions_> dimensions() { return {*this}; }
+
+ const Dimensions& minDimensions() const { return minDimensions_; }
+ IdxRef<YGDimension, &YGStyle::minDimensions_> minDimensions() {
+ return {*this};
+ }
+
+ const Dimensions& maxDimensions() const { return maxDimensions_; }
+ IdxRef<YGDimension, &YGStyle::maxDimensions_> maxDimensions() {
+ return {*this};
+ }
+
+ // Yoga specific properties, not compatible with flexbox specification
+ YGFloatOptional aspectRatio() const { return aspectRatio_; }
+ Ref<YGFloatOptional, &YGStyle::aspectRatio_> aspectRatio() { return {*this}; }
+};
+
+YOGA_EXPORT bool operator==(const YGStyle& lhs, const YGStyle& rhs);
+YOGA_EXPORT inline bool operator!=(const YGStyle& lhs, const YGStyle& rhs) {
+ return !(lhs == rhs);
+}
diff --git a/src/3rdparty/yoga/YGValue.cpp b/src/3rdparty/yoga/YGValue.cpp
new file mode 100644
index 0000000000..db34760ac6
--- /dev/null
+++ b/src/3rdparty/yoga/YGValue.cpp
@@ -0,0 +1,10 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// Copyright (c) Meta Platforms, Inc. and affiliates.
+//
+// SPDX-License-Identifier: MIT
+
+#include <yoga/YGValue.h>
+
+const YGValue YGValueZero = {0, YGUnitPoint};
+const YGValue YGValueUndefined = {YGUndefined, YGUnitUndefined};
+const YGValue YGValueAuto = {YGUndefined, YGUnitAuto};
diff --git a/src/3rdparty/yoga/YGValue.h b/src/3rdparty/yoga/YGValue.h
new file mode 100644
index 0000000000..a409653bb5
--- /dev/null
+++ b/src/3rdparty/yoga/YGValue.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2016 The Qt Company Ltd.
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#pragma once
+
+#include <yoga/YGEnums.h>
+#include <yoga/YGMacros.h>
+
+YG_EXTERN_C_BEGIN
+
+typedef struct YGValue {
+ float value;
+ YGUnit unit;
+} YGValue;
+
+YOGA_EXPORT extern const YGValue YGValueAuto;
+YOGA_EXPORT extern const YGValue YGValueUndefined;
+YOGA_EXPORT extern const YGValue YGValueZero;
+
+YG_EXTERN_C_END
+
+#ifdef __cplusplus
+#include <limits>
+constexpr float YGUndefined = std::numeric_limits<float>::quiet_NaN();
+#else
+#include <math.h>
+#define YGUndefined NAN
+#endif
+
+#ifdef __cplusplus
+inline bool operator==(const YGValue& lhs, const YGValue& rhs) {
+ if (lhs.unit != rhs.unit) {
+ return false;
+ }
+
+ switch (lhs.unit) {
+ case YGUnitUndefined:
+ case YGUnitAuto:
+ return true;
+ case YGUnitPoint:
+ case YGUnitPercent:
+ return lhs.value == rhs.value;
+ }
+
+ return false;
+}
+
+inline bool operator!=(const YGValue& lhs, const YGValue& rhs) {
+ return !(lhs == rhs);
+}
+
+inline YGValue operator-(const YGValue& value) {
+ return {-value.value, value.unit};
+}
+#endif
diff --git a/src/3rdparty/yoga/Yoga-internal.h b/src/3rdparty/yoga/Yoga-internal.h
new file mode 100644
index 0000000000..95de6fe2bb
--- /dev/null
+++ b/src/3rdparty/yoga/Yoga-internal.h
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2016 The Qt Company Ltd.
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#pragma once
+
+#include <algorithm>
+#include <array>
+#include <cmath>
+#include <vector>
+
+#include <yoga/Yoga.h>
+
+#include "CompactValue.h"
+
+using YGVector = std::vector<YGNodeRef>;
+
+YG_EXTERN_C_BEGIN
+
+void YGNodeCalculateLayoutWithContext(
+ YGNodeRef node,
+ float availableWidth,
+ float availableHeight,
+ YGDirection ownerDirection,
+ void* layoutContext);
+
+// Deallocates a Yoga Node. Unlike YGNodeFree, does not remove the node from
+// its parent or children.
+void YGNodeDeallocate(YGNodeRef node);
+
+YG_EXTERN_C_END
+
+namespace facebook {
+namespace yoga {
+
+inline bool isUndefined(float value) {
+ return std::isnan(value);
+}
+
+inline bool isUndefined(double value) {
+ return std::isnan(value);
+}
+
+void throwLogicalErrorWithMessage(const char* message);
+
+} // namespace yoga
+} // namespace facebook
+
+extern const std::array<YGEdge, 4> trailing;
+extern const std::array<YGEdge, 4> leading;
+extern const YGValue YGValueUndefined;
+extern const YGValue YGValueAuto;
+extern const YGValue YGValueZero;
+
+struct YGCachedMeasurement {
+ float availableWidth;
+ float availableHeight;
+ YGMeasureMode widthMeasureMode;
+ YGMeasureMode heightMeasureMode;
+
+ float computedWidth;
+ float computedHeight;
+
+ YGCachedMeasurement()
+ : availableWidth(-1),
+ availableHeight(-1),
+ widthMeasureMode(YGMeasureModeUndefined),
+ heightMeasureMode(YGMeasureModeUndefined),
+ computedWidth(-1),
+ computedHeight(-1) {}
+
+ bool operator==(YGCachedMeasurement measurement) const {
+ using namespace facebook;
+
+ bool isEqual = widthMeasureMode == measurement.widthMeasureMode &&
+ heightMeasureMode == measurement.heightMeasureMode;
+
+ if (!yoga::isUndefined(availableWidth) ||
+ !yoga::isUndefined(measurement.availableWidth)) {
+ isEqual = isEqual && availableWidth == measurement.availableWidth;
+ }
+ if (!yoga::isUndefined(availableHeight) ||
+ !yoga::isUndefined(measurement.availableHeight)) {
+ isEqual = isEqual && availableHeight == measurement.availableHeight;
+ }
+ if (!yoga::isUndefined(computedWidth) ||
+ !yoga::isUndefined(measurement.computedWidth)) {
+ isEqual = isEqual && computedWidth == measurement.computedWidth;
+ }
+ if (!yoga::isUndefined(computedHeight) ||
+ !yoga::isUndefined(measurement.computedHeight)) {
+ isEqual = isEqual && computedHeight == measurement.computedHeight;
+ }
+
+ return isEqual;
+ }
+};
+
+// This value was chosen based on empirical data:
+// 98% of analyzed layouts require less than 8 entries.
+#define YG_MAX_CACHED_RESULT_COUNT 8
+
+namespace facebook {
+namespace yoga {
+namespace detail {
+
+template <size_t Size>
+class Values {
+private:
+ std::array<CompactValue, Size> values_;
+
+public:
+ Values() = default;
+ Values(const Values& other) = default;
+
+ explicit Values(const YGValue& defaultValue) noexcept {
+ values_.fill(defaultValue);
+ }
+
+ const CompactValue& operator[](size_t i) const noexcept { return values_[i]; }
+ CompactValue& operator[](size_t i) noexcept { return values_[i]; }
+
+ template <size_t I>
+ YGValue get() const noexcept {
+ return std::get<I>(values_);
+ }
+
+ template <size_t I>
+ void set(YGValue& value) noexcept {
+ std::get<I>(values_) = value;
+ }
+
+ template <size_t I>
+ void set(YGValue&& value) noexcept {
+ set<I>(value);
+ }
+
+ bool operator==(const Values& other) const noexcept {
+ for (size_t i = 0; i < Size; ++i) {
+ if (values_[i] != other.values_[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ Values& operator=(const Values& other) = default;
+};
+} // namespace detail
+} // namespace yoga
+} // namespace facebook
+
+static const float kDefaultFlexGrow = 0.0f;
+static const float kDefaultFlexShrink = 0.0f;
+static const float kWebDefaultFlexShrink = 1.0f;
+
+extern bool YGFloatsEqual(const float a, const float b);
diff --git a/src/3rdparty/yoga/Yoga.cpp b/src/3rdparty/yoga/Yoga.cpp
new file mode 100644
index 0000000000..55546dae6c
--- /dev/null
+++ b/src/3rdparty/yoga/Yoga.cpp
@@ -0,0 +1,4345 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// Copyright (c) Meta Platforms, Inc. and affiliates.
+//
+// SPDX-License-Identifier: MIT
+
+#include <float.h>
+#include <string.h>
+#include <algorithm>
+#include <atomic>
+#include <memory>
+
+#include <yoga/Yoga.h>
+
+#include "log.h"
+#include "Utils.h"
+#include "YGNode.h"
+#include "YGNodePrint.h"
+#include "Yoga-internal.h"
+#include "event/event.h"
+
+using namespace facebook::yoga;
+using detail::Log;
+
+#ifdef ANDROID
+static int YGAndroidLog(
+ const YGConfigRef config,
+ const YGNodeRef node,
+ YGLogLevel level,
+ const char* format,
+ va_list args);
+#else
+static int YGDefaultLog(
+ const YGConfigRef config,
+ const YGNodeRef node,
+ YGLogLevel level,
+ const char* format,
+ va_list args);
+#endif
+
+#ifdef ANDROID
+#include <android/log.h>
+static int YGAndroidLog(
+ const YGConfigRef /*config*/,
+ const YGNodeRef /*node*/,
+ YGLogLevel level,
+ const char* format,
+ va_list args) {
+ int androidLevel = YGLogLevelDebug;
+ switch (level) {
+ case YGLogLevelFatal:
+ androidLevel = ANDROID_LOG_FATAL;
+ break;
+ case YGLogLevelError:
+ androidLevel = ANDROID_LOG_ERROR;
+ break;
+ case YGLogLevelWarn:
+ androidLevel = ANDROID_LOG_WARN;
+ break;
+ case YGLogLevelInfo:
+ androidLevel = ANDROID_LOG_INFO;
+ break;
+ case YGLogLevelDebug:
+ androidLevel = ANDROID_LOG_DEBUG;
+ break;
+ case YGLogLevelVerbose:
+ androidLevel = ANDROID_LOG_VERBOSE;
+ break;
+ }
+ const int result = __android_log_vprint(androidLevel, "yoga", format, args);
+ return result;
+}
+#else
+#define YG_UNUSED(x) (void) (x);
+
+static int YGDefaultLog(
+ const YGConfigRef config,
+ const YGNodeRef node,
+ YGLogLevel level,
+ const char* format,
+ va_list args) {
+ YG_UNUSED(config);
+ YG_UNUSED(node);
+ switch (level) {
+ case YGLogLevelError:
+ case YGLogLevelFatal:
+ return vfprintf(stderr, format, args);
+ case YGLogLevelWarn:
+ case YGLogLevelInfo:
+ case YGLogLevelDebug:
+ case YGLogLevelVerbose:
+ default:
+ return vprintf(format, args);
+ }
+}
+
+#undef YG_UNUSED
+#endif
+
+static inline bool YGDoubleIsUndefined(const double value) {
+ return facebook::yoga::isUndefined(value);
+}
+
+YOGA_EXPORT bool YGFloatIsUndefined(const float value) {
+ return facebook::yoga::isUndefined(value);
+}
+
+YOGA_EXPORT void* YGNodeGetContext(YGNodeRef node) {
+ return node->getContext();
+}
+
+YOGA_EXPORT void YGNodeSetContext(YGNodeRef node, void* context) {
+ return node->setContext(context);
+}
+
+YOGA_EXPORT YGConfigRef YGNodeGetConfig(YGNodeRef node) {
+ return node->getConfig();
+}
+
+YOGA_EXPORT void YGNodeSetConfig(YGNodeRef node, YGConfigRef config) {
+ node->setConfig(config);
+}
+
+YOGA_EXPORT bool YGNodeHasMeasureFunc(YGNodeRef node) {
+ return node->hasMeasureFunc();
+}
+
+YOGA_EXPORT void YGNodeSetMeasureFunc(
+ YGNodeRef node,
+ YGMeasureFunc measureFunc) {
+ node->setMeasureFunc(measureFunc);
+}
+
+YOGA_EXPORT bool YGNodeHasBaselineFunc(YGNodeRef node) {
+ return node->hasBaselineFunc();
+}
+
+YOGA_EXPORT void YGNodeSetBaselineFunc(
+ YGNodeRef node,
+ YGBaselineFunc baselineFunc) {
+ node->setBaselineFunc(baselineFunc);
+}
+
+YOGA_EXPORT YGDirtiedFunc YGNodeGetDirtiedFunc(YGNodeRef node) {
+ return node->getDirtied();
+}
+
+YOGA_EXPORT void YGNodeSetDirtiedFunc(
+ YGNodeRef node,
+ YGDirtiedFunc dirtiedFunc) {
+ node->setDirtiedFunc(dirtiedFunc);
+}
+
+YOGA_EXPORT void YGNodeSetPrintFunc(YGNodeRef node, YGPrintFunc printFunc) {
+ node->setPrintFunc(printFunc);
+}
+
+YOGA_EXPORT bool YGNodeGetHasNewLayout(YGNodeRef node) {
+ return node->getHasNewLayout();
+}
+
+YOGA_EXPORT void YGConfigSetPrintTreeFlag(YGConfigRef config, bool enabled) {
+ config->setShouldPrintTree(enabled);
+}
+
+YOGA_EXPORT void YGNodeSetHasNewLayout(YGNodeRef node, bool hasNewLayout) {
+ node->setHasNewLayout(hasNewLayout);
+}
+
+YOGA_EXPORT YGNodeType YGNodeGetNodeType(YGNodeRef node) {
+ return node->getNodeType();
+}
+
+YOGA_EXPORT void YGNodeSetNodeType(YGNodeRef node, YGNodeType nodeType) {
+ return node->setNodeType(nodeType);
+}
+
+YOGA_EXPORT bool YGNodeIsDirty(YGNodeRef node) {
+ return node->isDirty();
+}
+
+YOGA_EXPORT void YGNodeMarkDirtyAndPropagateToDescendants(
+ const YGNodeRef node) {
+ return node->markDirtyAndPropagateDownwards();
+}
+
+int32_t gConfigInstanceCount = 0;
+
+YOGA_EXPORT WIN_EXPORT YGNodeRef YGNodeNewWithConfig(const YGConfigRef config) {
+ const YGNodeRef node = new YGNode{config};
+ YGAssert(config != nullptr, "Tried to construct YGNode with null config");
+ YGAssertWithConfig(
+ config, node != nullptr, "Could not allocate memory for node");
+ Event::publish<Event::NodeAllocation>(node, {config});
+
+ return node;
+}
+
+YOGA_EXPORT YGConfigRef YGConfigGetDefault() {
+ static YGConfigRef defaultConfig = YGConfigNew();
+ return defaultConfig;
+}
+
+YOGA_EXPORT YGNodeRef YGNodeNew(void) {
+ return YGNodeNewWithConfig(YGConfigGetDefault());
+}
+
+YOGA_EXPORT YGNodeRef YGNodeClone(YGNodeRef oldNode) {
+ YGNodeRef node = new YGNode(*oldNode);
+ YGAssertWithConfig(
+ oldNode->getConfig(),
+ node != nullptr,
+ "Could not allocate memory for node");
+ Event::publish<Event::NodeAllocation>(node, {node->getConfig()});
+ node->setOwner(nullptr);
+ return node;
+}
+
+YOGA_EXPORT void YGNodeFree(const YGNodeRef node) {
+ if (YGNodeRef owner = node->getOwner()) {
+ owner->removeChild(node);
+ node->setOwner(nullptr);
+ }
+
+ const uint32_t childCount = YGNodeGetChildCount(node);
+ for (uint32_t i = 0; i < childCount; i++) {
+ const YGNodeRef child = YGNodeGetChild(node, i);
+ child->setOwner(nullptr);
+ }
+
+ node->clearChildren();
+ YGNodeDeallocate(node);
+}
+
+YOGA_EXPORT void YGNodeDeallocate(const YGNodeRef node) {
+ Event::publish<Event::NodeDeallocation>(node, {node->getConfig()});
+ delete node;
+}
+
+YOGA_EXPORT void YGNodeFreeRecursiveWithCleanupFunc(
+ const YGNodeRef root,
+ YGNodeCleanupFunc cleanup) {
+ uint32_t skipped = 0;
+ while (YGNodeGetChildCount(root) > skipped) {
+ const YGNodeRef child = YGNodeGetChild(root, skipped);
+ if (child->getOwner() != root) {
+ // Don't free shared nodes that we don't own.
+ skipped += 1;
+ } else {
+ YGNodeRemoveChild(root, child);
+ YGNodeFreeRecursive(child);
+ }
+ }
+ if (cleanup != nullptr) {
+ cleanup(root);
+ }
+ YGNodeFree(root);
+}
+
+YOGA_EXPORT void YGNodeFreeRecursive(const YGNodeRef root) {
+ return YGNodeFreeRecursiveWithCleanupFunc(root, nullptr);
+}
+
+YOGA_EXPORT void YGNodeReset(YGNodeRef node) {
+ node->reset();
+}
+
+YOGA_EXPORT int32_t YGConfigGetInstanceCount(void) {
+ return gConfigInstanceCount;
+}
+
+YOGA_EXPORT YGConfigRef YGConfigNew(void) {
+#ifdef ANDROID
+ const YGConfigRef config = new YGConfig(YGAndroidLog);
+#else
+ const YGConfigRef config = new YGConfig(YGDefaultLog);
+#endif
+ gConfigInstanceCount++;
+ return config;
+}
+
+YOGA_EXPORT void YGConfigFree(const YGConfigRef config) {
+ delete config;
+ gConfigInstanceCount--;
+}
+
+void YGConfigCopy(const YGConfigRef dest, const YGConfigRef src) {
+ memcpy(dest, src, sizeof(YGConfig));
+}
+
+YOGA_EXPORT void YGNodeSetIsReferenceBaseline(
+ YGNodeRef node,
+ bool isReferenceBaseline) {
+ if (node->isReferenceBaseline() != isReferenceBaseline) {
+ node->setIsReferenceBaseline(isReferenceBaseline);
+ node->markDirtyAndPropagate();
+ }
+}
+
+YOGA_EXPORT bool YGNodeIsReferenceBaseline(YGNodeRef node) {
+ return node->isReferenceBaseline();
+}
+
+YOGA_EXPORT void YGNodeInsertChild(
+ const YGNodeRef owner,
+ const YGNodeRef child,
+ const uint32_t index) {
+ YGAssertWithNode(
+ owner,
+ child->getOwner() == nullptr,
+ "Child already has a owner, it must be removed first.");
+
+ YGAssertWithNode(
+ owner,
+ !owner->hasMeasureFunc(),
+ "Cannot add child: Nodes with measure functions cannot have children.");
+
+ owner->insertChild(child, index);
+ child->setOwner(owner);
+ owner->markDirtyAndPropagate();
+}
+
+YOGA_EXPORT void YGNodeSwapChild(
+ const YGNodeRef owner,
+ const YGNodeRef child,
+ const uint32_t index) {
+ owner->replaceChild(child, index);
+ child->setOwner(owner);
+}
+
+YOGA_EXPORT void YGNodeRemoveChild(
+ const YGNodeRef owner,
+ const YGNodeRef excludedChild) {
+ if (YGNodeGetChildCount(owner) == 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();
+ }
+}
+
+YOGA_EXPORT void YGNodeRemoveAllChildren(const YGNodeRef owner) {
+ const uint32_t childCount = YGNodeGetChildCount(owner);
+ if (childCount == 0) {
+ // This is an empty set already. Nothing to do.
+ return;
+ }
+ const YGNodeRef firstChild = YGNodeGetChild(owner, 0);
+ if (firstChild->getOwner() == owner) {
+ // If the first child has this node as its owner, we assume that this child
+ // set is unique.
+ for (uint32_t i = 0; i < childCount; i++) {
+ const YGNodeRef oldChild = YGNodeGetChild(owner, i);
+ oldChild->setLayout(YGNode().getLayout()); // 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(YGVector());
+ owner->markDirtyAndPropagate();
+}
+
+YOGA_EXPORT void YGNodeSetChildren(
+ const YGNodeRef owner,
+ const YGNodeRef* children,
+ const uint32_t count) {
+ if (!owner) {
+ return;
+ }
+
+ const YGVector childrenVector = {children, children + count};
+ if (childrenVector.size() == 0) {
+ if (YGNodeGetChildCount(owner) > 0) {
+ for (YGNodeRef const child : owner->getChildren()) {
+ child->setLayout(YGLayout());
+ child->setOwner(nullptr);
+ }
+ owner->setChildren(YGVector());
+ owner->markDirtyAndPropagate();
+ }
+ } else {
+ if (YGNodeGetChildCount(owner) > 0) {
+ for (YGNodeRef const 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(YGLayout());
+ oldChild->setOwner(nullptr);
+ }
+ }
+ }
+ owner->setChildren(childrenVector);
+ for (YGNodeRef child : childrenVector) {
+ child->setOwner(owner);
+ }
+ owner->markDirtyAndPropagate();
+ }
+}
+
+YOGA_EXPORT YGNodeRef
+YGNodeGetChild(const YGNodeRef node, const uint32_t index) {
+ if (index < node->getChildren().size()) {
+ return node->getChild(index);
+ }
+ return nullptr;
+}
+
+YOGA_EXPORT uint32_t YGNodeGetChildCount(const YGNodeRef node) {
+ return static_cast<uint32_t>(node->getChildren().size());
+}
+
+YOGA_EXPORT YGNodeRef YGNodeGetOwner(const YGNodeRef node) {
+ return node->getOwner();
+}
+
+YOGA_EXPORT YGNodeRef YGNodeGetParent(const YGNodeRef node) {
+ return node->getOwner();
+}
+
+YOGA_EXPORT void YGNodeMarkDirty(const YGNodeRef node) {
+ YGAssertWithNode(
+ node,
+ node->hasMeasureFunc(),
+ "Only leaf nodes with custom measure functions "
+ "should manually mark themselves as dirty");
+
+ node->markDirtyAndPropagate();
+}
+
+YOGA_EXPORT void YGNodeCopyStyle(
+ const YGNodeRef dstNode,
+ const YGNodeRef srcNode) {
+ if (!(dstNode->getStyle() == srcNode->getStyle())) {
+ dstNode->setStyle(srcNode->getStyle());
+ dstNode->markDirtyAndPropagate();
+ }
+}
+
+YOGA_EXPORT float YGNodeStyleGetFlexGrow(const YGNodeConstRef node) {
+ return node->getStyle().flexGrow().isUndefined()
+ ? kDefaultFlexGrow
+ : node->getStyle().flexGrow().unwrap();
+}
+
+YOGA_EXPORT float YGNodeStyleGetFlexShrink(const YGNodeConstRef node) {
+ return node->getStyle().flexShrink().isUndefined()
+ ? (node->getConfig()->useWebDefaults() ? kWebDefaultFlexShrink
+ : kDefaultFlexShrink)
+ : node->getStyle().flexShrink().unwrap();
+}
+
+namespace {
+
+template <typename T, typename NeedsUpdate, typename Update>
+void updateStyle(
+ YGNode* node,
+ T value,
+ NeedsUpdate&& needsUpdate,
+ Update&& update) {
+ if (needsUpdate(node->getStyle(), value)) {
+ update(node->getStyle(), value);
+ node->markDirtyAndPropagate();
+ }
+}
+
+template <typename Ref, typename T>
+void updateStyle(YGNode* node, Ref (YGStyle::*prop)(), T value) {
+ updateStyle(
+ node,
+ value,
+ [prop](YGStyle& s, T x) { return (s.*prop)() != x; },
+ [prop](YGStyle& s, T x) { (s.*prop)() = x; });
+}
+
+template <typename Ref, typename Idx>
+void updateIndexedStyleProp(
+ YGNode* node,
+ Ref (YGStyle::*prop)(),
+ Idx idx,
+ detail::CompactValue value) {
+ using detail::CompactValue;
+ updateStyle(
+ node,
+ value,
+ [idx, prop](YGStyle& s, CompactValue x) { return (s.*prop)()[idx] != x; },
+ [idx, prop](YGStyle& s, CompactValue x) { (s.*prop)()[idx] = x; });
+}
+
+} // namespace
+
+// MSVC has trouble inferring the return type of pointer to member functions
+// with const and non-const overloads, instead of preferring the non-const
+// overload like clang and GCC. For the purposes of updateStyle(), we can help
+// MSVC by specifying that return type explicitly. In combination with
+// decltype, MSVC will prefer the non-const version.
+#define MSVC_HINT(PROP) decltype(YGStyle{}.PROP())
+
+YOGA_EXPORT void YGNodeStyleSetDirection(
+ const YGNodeRef node,
+ const YGDirection value) {
+ updateStyle<MSVC_HINT(direction)>(node, &YGStyle::direction, value);
+}
+YOGA_EXPORT YGDirection YGNodeStyleGetDirection(const YGNodeConstRef node) {
+ return node->getStyle().direction();
+}
+
+YOGA_EXPORT void YGNodeStyleSetFlexDirection(
+ const YGNodeRef node,
+ const YGFlexDirection flexDirection) {
+ updateStyle<MSVC_HINT(flexDirection)>(
+ node, &YGStyle::flexDirection, flexDirection);
+}
+YOGA_EXPORT YGFlexDirection
+YGNodeStyleGetFlexDirection(const YGNodeConstRef node) {
+ return node->getStyle().flexDirection();
+}
+
+YOGA_EXPORT void YGNodeStyleSetJustifyContent(
+ const YGNodeRef node,
+ const YGJustify justifyContent) {
+ updateStyle<MSVC_HINT(justifyContent)>(
+ node, &YGStyle::justifyContent, justifyContent);
+}
+YOGA_EXPORT YGJustify YGNodeStyleGetJustifyContent(const YGNodeConstRef node) {
+ return node->getStyle().justifyContent();
+}
+
+YOGA_EXPORT void YGNodeStyleSetAlignContent(
+ const YGNodeRef node,
+ const YGAlign alignContent) {
+ updateStyle<MSVC_HINT(alignContent)>(
+ node, &YGStyle::alignContent, alignContent);
+}
+YOGA_EXPORT YGAlign YGNodeStyleGetAlignContent(const YGNodeConstRef node) {
+ return node->getStyle().alignContent();
+}
+
+YOGA_EXPORT void YGNodeStyleSetAlignItems(
+ const YGNodeRef node,
+ const YGAlign alignItems) {
+ updateStyle<MSVC_HINT(alignItems)>(node, &YGStyle::alignItems, alignItems);
+}
+YOGA_EXPORT YGAlign YGNodeStyleGetAlignItems(const YGNodeConstRef node) {
+ return node->getStyle().alignItems();
+}
+
+YOGA_EXPORT void YGNodeStyleSetAlignSelf(
+ const YGNodeRef node,
+ const YGAlign alignSelf) {
+ updateStyle<MSVC_HINT(alignSelf)>(node, &YGStyle::alignSelf, alignSelf);
+}
+YOGA_EXPORT YGAlign YGNodeStyleGetAlignSelf(const YGNodeConstRef node) {
+ return node->getStyle().alignSelf();
+}
+
+YOGA_EXPORT void YGNodeStyleSetPositionType(
+ const YGNodeRef node,
+ const YGPositionType positionType) {
+ updateStyle<MSVC_HINT(positionType)>(
+ node, &YGStyle::positionType, positionType);
+}
+YOGA_EXPORT YGPositionType
+YGNodeStyleGetPositionType(const YGNodeConstRef node) {
+ return node->getStyle().positionType();
+}
+
+YOGA_EXPORT void YGNodeStyleSetFlexWrap(
+ const YGNodeRef node,
+ const YGWrap flexWrap) {
+ updateStyle<MSVC_HINT(flexWrap)>(node, &YGStyle::flexWrap, flexWrap);
+}
+YOGA_EXPORT YGWrap YGNodeStyleGetFlexWrap(const YGNodeConstRef node) {
+ return node->getStyle().flexWrap();
+}
+
+YOGA_EXPORT void YGNodeStyleSetOverflow(
+ const YGNodeRef node,
+ const YGOverflow overflow) {
+ updateStyle<MSVC_HINT(overflow)>(node, &YGStyle::overflow, overflow);
+}
+YOGA_EXPORT YGOverflow YGNodeStyleGetOverflow(const YGNodeConstRef node) {
+ return node->getStyle().overflow();
+}
+
+YOGA_EXPORT void YGNodeStyleSetDisplay(
+ const YGNodeRef node,
+ const YGDisplay display) {
+ updateStyle<MSVC_HINT(display)>(node, &YGStyle::display, display);
+}
+YOGA_EXPORT YGDisplay YGNodeStyleGetDisplay(const YGNodeConstRef node) {
+ return node->getStyle().display();
+}
+
+// TODO(T26792433): Change the API to accept YGFloatOptional.
+YOGA_EXPORT void YGNodeStyleSetFlex(const YGNodeRef node, const float flex) {
+ updateStyle<MSVC_HINT(flex)>(node, &YGStyle::flex, YGFloatOptional{flex});
+}
+
+// TODO(T26792433): Change the API to accept YGFloatOptional.
+YOGA_EXPORT float YGNodeStyleGetFlex(const YGNodeConstRef node) {
+ return node->getStyle().flex().isUndefined()
+ ? YGUndefined
+ : node->getStyle().flex().unwrap();
+}
+
+// TODO(T26792433): Change the API to accept YGFloatOptional.
+YOGA_EXPORT void YGNodeStyleSetFlexGrow(
+ const YGNodeRef node,
+ const float flexGrow) {
+ updateStyle<MSVC_HINT(flexGrow)>(
+ node, &YGStyle::flexGrow, YGFloatOptional{flexGrow});
+}
+
+// TODO(T26792433): Change the API to accept YGFloatOptional.
+YOGA_EXPORT void YGNodeStyleSetFlexShrink(
+ const YGNodeRef node,
+ const float flexShrink) {
+ updateStyle<MSVC_HINT(flexShrink)>(
+ node, &YGStyle::flexShrink, YGFloatOptional{flexShrink});
+}
+
+YOGA_EXPORT YGValue YGNodeStyleGetFlexBasis(const YGNodeConstRef node) {
+ YGValue flexBasis = node->getStyle().flexBasis();
+ if (flexBasis.unit == YGUnitUndefined || flexBasis.unit == YGUnitAuto) {
+ // TODO(T26792433): Get rid off the use of YGUndefined at client side
+ flexBasis.value = YGUndefined;
+ }
+ return flexBasis;
+}
+
+YOGA_EXPORT void YGNodeStyleSetFlexBasis(
+ const YGNodeRef node,
+ const float flexBasis) {
+ auto value = detail::CompactValue::ofMaybe<YGUnitPoint>(flexBasis);
+ updateStyle<MSVC_HINT(flexBasis)>(node, &YGStyle::flexBasis, value);
+}
+
+YOGA_EXPORT void YGNodeStyleSetFlexBasisPercent(
+ const YGNodeRef node,
+ const float flexBasisPercent) {
+ auto value = detail::CompactValue::ofMaybe<YGUnitPercent>(flexBasisPercent);
+ updateStyle<MSVC_HINT(flexBasis)>(node, &YGStyle::flexBasis, value);
+}
+
+YOGA_EXPORT void YGNodeStyleSetFlexBasisAuto(const YGNodeRef node) {
+ updateStyle<MSVC_HINT(flexBasis)>(
+ node, &YGStyle::flexBasis, detail::CompactValue::ofAuto());
+}
+
+YOGA_EXPORT void YGNodeStyleSetPosition(
+ YGNodeRef node,
+ YGEdge edge,
+ float points) {
+ auto value = detail::CompactValue::ofMaybe<YGUnitPoint>(points);
+ updateIndexedStyleProp<MSVC_HINT(position)>(
+ node, &YGStyle::position, edge, value);
+}
+YOGA_EXPORT void YGNodeStyleSetPositionPercent(
+ YGNodeRef node,
+ YGEdge edge,
+ float percent) {
+ auto value = detail::CompactValue::ofMaybe<YGUnitPercent>(percent);
+ updateIndexedStyleProp<MSVC_HINT(position)>(
+ node, &YGStyle::position, edge, value);
+}
+YOGA_EXPORT YGValue YGNodeStyleGetPosition(YGNodeConstRef node, YGEdge edge) {
+ return node->getStyle().position()[edge];
+}
+
+YOGA_EXPORT void YGNodeStyleSetMargin(
+ YGNodeRef node,
+ YGEdge edge,
+ float points) {
+ auto value = detail::CompactValue::ofMaybe<YGUnitPoint>(points);
+ updateIndexedStyleProp<MSVC_HINT(margin)>(
+ node, &YGStyle::margin, edge, value);
+}
+YOGA_EXPORT void YGNodeStyleSetMarginPercent(
+ YGNodeRef node,
+ YGEdge edge,
+ float percent) {
+ auto value = detail::CompactValue::ofMaybe<YGUnitPercent>(percent);
+ updateIndexedStyleProp<MSVC_HINT(margin)>(
+ node, &YGStyle::margin, edge, value);
+}
+YOGA_EXPORT void YGNodeStyleSetMarginAuto(YGNodeRef node, YGEdge edge) {
+ updateIndexedStyleProp<MSVC_HINT(margin)>(
+ node, &YGStyle::margin, edge, detail::CompactValue::ofAuto());
+}
+YOGA_EXPORT YGValue YGNodeStyleGetMargin(YGNodeConstRef node, YGEdge edge) {
+ return node->getStyle().margin()[edge];
+}
+
+YOGA_EXPORT void YGNodeStyleSetPadding(
+ YGNodeRef node,
+ YGEdge edge,
+ float points) {
+ auto value = detail::CompactValue::ofMaybe<YGUnitPoint>(points);
+ updateIndexedStyleProp<MSVC_HINT(padding)>(
+ node, &YGStyle::padding, edge, value);
+}
+YOGA_EXPORT void YGNodeStyleSetPaddingPercent(
+ YGNodeRef node,
+ YGEdge edge,
+ float percent) {
+ auto value = detail::CompactValue::ofMaybe<YGUnitPercent>(percent);
+ updateIndexedStyleProp<MSVC_HINT(padding)>(
+ node, &YGStyle::padding, edge, value);
+}
+YOGA_EXPORT YGValue YGNodeStyleGetPadding(YGNodeConstRef node, YGEdge edge) {
+ return node->getStyle().padding()[edge];
+}
+
+// TODO(T26792433): Change the API to accept YGFloatOptional.
+YOGA_EXPORT void YGNodeStyleSetBorder(
+ const YGNodeRef node,
+ const YGEdge edge,
+ const float border) {
+ auto value = detail::CompactValue::ofMaybe<YGUnitPoint>(border);
+ updateIndexedStyleProp<MSVC_HINT(border)>(
+ node, &YGStyle::border, edge, value);
+}
+
+YOGA_EXPORT float YGNodeStyleGetBorder(
+ const YGNodeConstRef node,
+ const YGEdge edge) {
+ auto border = node->getStyle().border()[edge];
+ if (border.isUndefined() || border.isAuto()) {
+ // TODO(T26792433): Rather than returning YGUndefined, change the api to
+ // return YGFloatOptional.
+ return YGUndefined;
+ }
+
+ return static_cast<YGValue>(border).value;
+}
+
+YOGA_EXPORT void YGNodeStyleSetGap(
+ const YGNodeRef node,
+ const YGGutter gutter,
+ const float gapLength) {
+ auto length = detail::CompactValue::ofMaybe<YGUnitPoint>(gapLength);
+ updateIndexedStyleProp<MSVC_HINT(gap)>(node, &YGStyle::gap, gutter, length);
+}
+
+YOGA_EXPORT float YGNodeStyleGetGap(
+ const YGNodeConstRef node,
+ const YGGutter gutter) {
+ auto gapLength = node->getStyle().gap()[gutter];
+ if (gapLength.isUndefined() || gapLength.isAuto()) {
+ // TODO(T26792433): Rather than returning YGUndefined, change the api to
+ // return YGFloatOptional.
+ return YGUndefined;
+ }
+
+ return static_cast<YGValue>(gapLength).value;
+}
+
+// Yoga specific properties, not compatible with flexbox specification
+
+// TODO(T26792433): Change the API to accept YGFloatOptional.
+YOGA_EXPORT float YGNodeStyleGetAspectRatio(const YGNodeConstRef node) {
+ const YGFloatOptional op = node->getStyle().aspectRatio();
+ return op.isUndefined() ? YGUndefined : op.unwrap();
+}
+
+// TODO(T26792433): Change the API to accept YGFloatOptional.
+YOGA_EXPORT void YGNodeStyleSetAspectRatio(
+ const YGNodeRef node,
+ const float aspectRatio) {
+ updateStyle<MSVC_HINT(aspectRatio)>(
+ node, &YGStyle::aspectRatio, YGFloatOptional{aspectRatio});
+}
+
+YOGA_EXPORT void YGNodeStyleSetWidth(YGNodeRef node, float points) {
+ auto value = detail::CompactValue::ofMaybe<YGUnitPoint>(points);
+ updateIndexedStyleProp<MSVC_HINT(dimensions)>(
+ node, &YGStyle::dimensions, YGDimensionWidth, value);
+}
+YOGA_EXPORT void YGNodeStyleSetWidthPercent(YGNodeRef node, float percent) {
+ auto value = detail::CompactValue::ofMaybe<YGUnitPercent>(percent);
+ updateIndexedStyleProp<MSVC_HINT(dimensions)>(
+ node, &YGStyle::dimensions, YGDimensionWidth, value);
+}
+YOGA_EXPORT void YGNodeStyleSetWidthAuto(YGNodeRef node) {
+ updateIndexedStyleProp<MSVC_HINT(dimensions)>(
+ node,
+ &YGStyle::dimensions,
+ YGDimensionWidth,
+ detail::CompactValue::ofAuto());
+}
+YOGA_EXPORT YGValue YGNodeStyleGetWidth(YGNodeConstRef node) {
+ return node->getStyle().dimensions()[YGDimensionWidth];
+}
+
+YOGA_EXPORT void YGNodeStyleSetHeight(YGNodeRef node, float points) {
+ auto value = detail::CompactValue::ofMaybe<YGUnitPoint>(points);
+ updateIndexedStyleProp<MSVC_HINT(dimensions)>(
+ node, &YGStyle::dimensions, YGDimensionHeight, value);
+}
+YOGA_EXPORT void YGNodeStyleSetHeightPercent(YGNodeRef node, float percent) {
+ auto value = detail::CompactValue::ofMaybe<YGUnitPercent>(percent);
+ updateIndexedStyleProp<MSVC_HINT(dimensions)>(
+ node, &YGStyle::dimensions, YGDimensionHeight, value);
+}
+YOGA_EXPORT void YGNodeStyleSetHeightAuto(YGNodeRef node) {
+ updateIndexedStyleProp<MSVC_HINT(dimensions)>(
+ node,
+ &YGStyle::dimensions,
+ YGDimensionHeight,
+ detail::CompactValue::ofAuto());
+}
+YOGA_EXPORT YGValue YGNodeStyleGetHeight(YGNodeConstRef node) {
+ return node->getStyle().dimensions()[YGDimensionHeight];
+}
+
+YOGA_EXPORT void YGNodeStyleSetMinWidth(
+ const YGNodeRef node,
+ const float minWidth) {
+ auto value = detail::CompactValue::ofMaybe<YGUnitPoint>(minWidth);
+ updateIndexedStyleProp<MSVC_HINT(minDimensions)>(
+ node, &YGStyle::minDimensions, YGDimensionWidth, value);
+}
+YOGA_EXPORT void YGNodeStyleSetMinWidthPercent(
+ const YGNodeRef node,
+ const float minWidth) {
+ auto value = detail::CompactValue::ofMaybe<YGUnitPercent>(minWidth);
+ updateIndexedStyleProp<MSVC_HINT(minDimensions)>(
+ node, &YGStyle::minDimensions, YGDimensionWidth, value);
+}
+YOGA_EXPORT YGValue YGNodeStyleGetMinWidth(const YGNodeConstRef node) {
+ return node->getStyle().minDimensions()[YGDimensionWidth];
+}
+
+YOGA_EXPORT void YGNodeStyleSetMinHeight(
+ const YGNodeRef node,
+ const float minHeight) {
+ auto value = detail::CompactValue::ofMaybe<YGUnitPoint>(minHeight);
+ updateIndexedStyleProp<MSVC_HINT(minDimensions)>(
+ node, &YGStyle::minDimensions, YGDimensionHeight, value);
+}
+YOGA_EXPORT void YGNodeStyleSetMinHeightPercent(
+ const YGNodeRef node,
+ const float minHeight) {
+ auto value = detail::CompactValue::ofMaybe<YGUnitPercent>(minHeight);
+ updateIndexedStyleProp<MSVC_HINT(minDimensions)>(
+ node, &YGStyle::minDimensions, YGDimensionHeight, value);
+}
+YOGA_EXPORT YGValue YGNodeStyleGetMinHeight(const YGNodeConstRef node) {
+ return node->getStyle().minDimensions()[YGDimensionHeight];
+}
+
+YOGA_EXPORT void YGNodeStyleSetMaxWidth(
+ const YGNodeRef node,
+ const float maxWidth) {
+ auto value = detail::CompactValue::ofMaybe<YGUnitPoint>(maxWidth);
+ updateIndexedStyleProp<MSVC_HINT(maxDimensions)>(
+ node, &YGStyle::maxDimensions, YGDimensionWidth, value);
+}
+YOGA_EXPORT void YGNodeStyleSetMaxWidthPercent(
+ const YGNodeRef node,
+ const float maxWidth) {
+ auto value = detail::CompactValue::ofMaybe<YGUnitPercent>(maxWidth);
+ updateIndexedStyleProp<MSVC_HINT(maxDimensions)>(
+ node, &YGStyle::maxDimensions, YGDimensionWidth, value);
+}
+YOGA_EXPORT YGValue YGNodeStyleGetMaxWidth(const YGNodeConstRef node) {
+ return node->getStyle().maxDimensions()[YGDimensionWidth];
+}
+
+YOGA_EXPORT void YGNodeStyleSetMaxHeight(
+ const YGNodeRef node,
+ const float maxHeight) {
+ auto value = detail::CompactValue::ofMaybe<YGUnitPoint>(maxHeight);
+ updateIndexedStyleProp<MSVC_HINT(maxDimensions)>(
+ node, &YGStyle::maxDimensions, YGDimensionHeight, value);
+}
+YOGA_EXPORT void YGNodeStyleSetMaxHeightPercent(
+ const YGNodeRef node,
+ const float maxHeight) {
+ auto value = detail::CompactValue::ofMaybe<YGUnitPercent>(maxHeight);
+ updateIndexedStyleProp<MSVC_HINT(maxDimensions)>(
+ node, &YGStyle::maxDimensions, YGDimensionHeight, value);
+}
+YOGA_EXPORT YGValue YGNodeStyleGetMaxHeight(const YGNodeConstRef node) {
+ return node->getStyle().maxDimensions()[YGDimensionHeight];
+}
+
+#define YG_NODE_LAYOUT_PROPERTY_IMPL(type, name, instanceName) \
+ YOGA_EXPORT type YGNodeLayoutGet##name(const YGNodeRef node) { \
+ return node->getLayout().instanceName; \
+ }
+
+#define YG_NODE_LAYOUT_RESOLVED_PROPERTY_IMPL(type, name, instanceName) \
+ YOGA_EXPORT type YGNodeLayoutGet##name( \
+ const YGNodeRef node, const YGEdge edge) { \
+ YGAssertWithNode( \
+ node, \
+ edge <= YGEdgeEnd, \
+ "Cannot get layout properties of multi-edge shorthands"); \
+ \
+ if (edge == YGEdgeStart) { \
+ if (node->getLayout().direction() == YGDirectionRTL) { \
+ return node->getLayout().instanceName[YGEdgeRight]; \
+ } else { \
+ return node->getLayout().instanceName[YGEdgeLeft]; \
+ } \
+ } \
+ \
+ if (edge == YGEdgeEnd) { \
+ if (node->getLayout().direction() == YGDirectionRTL) { \
+ return node->getLayout().instanceName[YGEdgeLeft]; \
+ } else { \
+ return node->getLayout().instanceName[YGEdgeRight]; \
+ } \
+ } \
+ \
+ return node->getLayout().instanceName[edge]; \
+ }
+
+YG_NODE_LAYOUT_PROPERTY_IMPL(float, Left, position[YGEdgeLeft])
+YG_NODE_LAYOUT_PROPERTY_IMPL(float, Top, position[YGEdgeTop])
+YG_NODE_LAYOUT_PROPERTY_IMPL(float, Right, position[YGEdgeRight])
+YG_NODE_LAYOUT_PROPERTY_IMPL(float, Bottom, position[YGEdgeBottom])
+YG_NODE_LAYOUT_PROPERTY_IMPL(float, Width, dimensions[YGDimensionWidth])
+YG_NODE_LAYOUT_PROPERTY_IMPL(float, Height, dimensions[YGDimensionHeight])
+YG_NODE_LAYOUT_PROPERTY_IMPL(YGDirection, Direction, direction())
+YG_NODE_LAYOUT_PROPERTY_IMPL(bool, HadOverflow, hadOverflow())
+
+YG_NODE_LAYOUT_RESOLVED_PROPERTY_IMPL(float, Margin, margin)
+YG_NODE_LAYOUT_RESOLVED_PROPERTY_IMPL(float, Border, border)
+YG_NODE_LAYOUT_RESOLVED_PROPERTY_IMPL(float, Padding, padding)
+
+std::atomic<uint32_t> gCurrentGenerationCount(0);
+
+bool YGLayoutNodeInternal(
+ const YGNodeRef node,
+ const float availableWidth,
+ const float availableHeight,
+ const YGDirection ownerDirection,
+ const YGMeasureMode widthMeasureMode,
+ const YGMeasureMode heightMeasureMode,
+ const float ownerWidth,
+ const float ownerHeight,
+ const bool performLayout,
+ const LayoutPassReason reason,
+ const YGConfigRef config,
+ LayoutData& layoutMarkerData,
+ void* const layoutContext,
+ const uint32_t depth,
+ const uint32_t generationCount);
+
+#ifdef DEBUG
+static void YGNodePrintInternal(
+ const YGNodeRef node,
+ const YGPrintOptions options) {
+ std::string str;
+ facebook::yoga::YGNodeToString(str, node, options, 0);
+ Log::log(node, YGLogLevelDebug, nullptr, str.c_str());
+}
+
+YOGA_EXPORT void YGNodePrint(
+ const YGNodeRef node,
+ const YGPrintOptions options) {
+ YGNodePrintInternal(node, options);
+}
+#endif
+
+const std::array<YGEdge, 4> leading = {
+ {YGEdgeTop, YGEdgeBottom, YGEdgeLeft, YGEdgeRight}};
+
+const std::array<YGEdge, 4> trailing = {
+ {YGEdgeBottom, YGEdgeTop, YGEdgeRight, YGEdgeLeft}};
+static const std::array<YGEdge, 4> pos = {{
+ YGEdgeTop,
+ YGEdgeBottom,
+ YGEdgeLeft,
+ YGEdgeRight,
+}};
+
+static const std::array<YGDimension, 4> dim = {
+ {YGDimensionHeight, YGDimensionHeight, YGDimensionWidth, YGDimensionWidth}};
+
+static inline float YGNodePaddingAndBorderForAxis(
+ const YGNodeConstRef node,
+ const YGFlexDirection axis,
+ const float widthSize) {
+ return (node->getLeadingPaddingAndBorder(axis, widthSize) +
+ node->getTrailingPaddingAndBorder(axis, widthSize))
+ .unwrap();
+}
+
+static inline YGAlign YGNodeAlignItem(const YGNode* node, const YGNode* child) {
+ const YGAlign align = child->getStyle().alignSelf() == YGAlignAuto
+ ? node->getStyle().alignItems()
+ : child->getStyle().alignSelf();
+ if (align == YGAlignBaseline &&
+ YGFlexDirectionIsColumn(node->getStyle().flexDirection())) {
+ return YGAlignFlexStart;
+ }
+ return align;
+}
+
+static float YGBaseline(const YGNodeRef node, void* layoutContext) {
+ if (node->hasBaselineFunc()) {
+
+ Event::publish<Event::NodeBaselineStart>(node);
+
+ const float baseline = node->baseline(
+ node->getLayout().measuredDimensions[YGDimensionWidth],
+ node->getLayout().measuredDimensions[YGDimensionHeight],
+ layoutContext);
+
+ Event::publish<Event::NodeBaselineEnd>(node);
+
+ YGAssertWithNode(
+ node,
+ !YGFloatIsUndefined(baseline),
+ "Expect custom baseline function to not return NaN");
+ return baseline;
+ }
+
+ YGNodeRef baselineChild = nullptr;
+ const uint32_t childCount = YGNodeGetChildCount(node);
+ for (uint32_t i = 0; i < childCount; i++) {
+ const YGNodeRef child = YGNodeGetChild(node, i);
+ if (child->getLineIndex() > 0) {
+ break;
+ }
+ if (child->getStyle().positionType() == YGPositionTypeAbsolute) {
+ continue;
+ }
+ if (YGNodeAlignItem(node, child) == YGAlignBaseline ||
+ child->isReferenceBaseline()) {
+ baselineChild = child;
+ break;
+ }
+
+ if (baselineChild == nullptr) {
+ baselineChild = child;
+ }
+ }
+
+ if (baselineChild == nullptr) {
+ return node->getLayout().measuredDimensions[YGDimensionHeight];
+ }
+
+ const float baseline = YGBaseline(baselineChild, layoutContext);
+ return baseline + baselineChild->getLayout().position[YGEdgeTop];
+}
+
+static bool YGIsBaselineLayout(const YGNodeRef node) {
+ if (YGFlexDirectionIsColumn(node->getStyle().flexDirection())) {
+ return false;
+ }
+ if (node->getStyle().alignItems() == YGAlignBaseline) {
+ return true;
+ }
+ const uint32_t childCount = YGNodeGetChildCount(node);
+ for (uint32_t i = 0; i < childCount; i++) {
+ const YGNodeRef child = YGNodeGetChild(node, i);
+ if (child->getStyle().positionType() != YGPositionTypeAbsolute &&
+ child->getStyle().alignSelf() == YGAlignBaseline) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static inline float YGNodeDimWithMargin(
+ const YGNodeRef node,
+ const YGFlexDirection axis,
+ const float widthSize) {
+ return node->getLayout().measuredDimensions[dim[axis]] +
+ (node->getLeadingMargin(axis, widthSize) +
+ node->getTrailingMargin(axis, widthSize))
+ .unwrap();
+}
+
+static inline bool YGNodeIsStyleDimDefined(
+ const YGNodeRef node,
+ const YGFlexDirection axis,
+ const float ownerSize) {
+ bool isUndefined =
+ YGFloatIsUndefined(node->getResolvedDimension(dim[axis]).value);
+ return !(
+ node->getResolvedDimension(dim[axis]).unit == YGUnitAuto ||
+ node->getResolvedDimension(dim[axis]).unit == YGUnitUndefined ||
+ (node->getResolvedDimension(dim[axis]).unit == YGUnitPoint &&
+ !isUndefined && node->getResolvedDimension(dim[axis]).value < 0.0f) ||
+ (node->getResolvedDimension(dim[axis]).unit == YGUnitPercent &&
+ !isUndefined &&
+ (node->getResolvedDimension(dim[axis]).value < 0.0f ||
+ YGFloatIsUndefined(ownerSize))));
+}
+
+static inline bool YGNodeIsLayoutDimDefined(
+ const YGNodeRef node,
+ const YGFlexDirection axis) {
+ const float value = node->getLayout().measuredDimensions[dim[axis]];
+ return !YGFloatIsUndefined(value) && value >= 0.0f;
+}
+
+static YGFloatOptional YGNodeBoundAxisWithinMinAndMax(
+ const YGNodeConstRef node,
+ const YGFlexDirection axis,
+ const YGFloatOptional value,
+ const float axisSize) {
+ YGFloatOptional min;
+ YGFloatOptional max;
+
+ if (YGFlexDirectionIsColumn(axis)) {
+ min = YGResolveValue(
+ node->getStyle().minDimensions()[YGDimensionHeight], axisSize);
+ max = YGResolveValue(
+ node->getStyle().maxDimensions()[YGDimensionHeight], axisSize);
+ } else if (YGFlexDirectionIsRow(axis)) {
+ min = YGResolveValue(
+ node->getStyle().minDimensions()[YGDimensionWidth], axisSize);
+ max = YGResolveValue(
+ node->getStyle().maxDimensions()[YGDimensionWidth], axisSize);
+ }
+
+ if (max >= YGFloatOptional{0} && value > max) {
+ return max;
+ }
+
+ if (min >= YGFloatOptional{0} && value < min) {
+ return min;
+ }
+
+ return value;
+}
+
+// Like YGNodeBoundAxisWithinMinAndMax but also ensures that the value doesn't
+// go below the padding and border amount.
+static inline float YGNodeBoundAxis(
+ const YGNodeRef node,
+ const YGFlexDirection axis,
+ const float value,
+ const float axisSize,
+ const float widthSize) {
+ return YGFloatMax(
+ YGNodeBoundAxisWithinMinAndMax(
+ node, axis, YGFloatOptional{value}, axisSize)
+ .unwrap(),
+ YGNodePaddingAndBorderForAxis(node, axis, widthSize));
+}
+
+static void YGNodeSetChildTrailingPosition(
+ const YGNodeRef node,
+ const YGNodeRef child,
+ const YGFlexDirection axis) {
+ const float size = child->getLayout().measuredDimensions[dim[axis]];
+ child->setLayoutPosition(
+ node->getLayout().measuredDimensions[dim[axis]] - size -
+ child->getLayout().position[pos[axis]],
+ trailing[axis]);
+}
+
+static void YGConstrainMaxSizeForMode(
+ const YGNodeConstRef node,
+ const enum YGFlexDirection axis,
+ const float ownerAxisSize,
+ const float ownerWidth,
+ YGMeasureMode* mode,
+ float* size) {
+ const YGFloatOptional maxSize =
+ YGResolveValue(
+ node->getStyle().maxDimensions()[dim[axis]], ownerAxisSize) +
+ YGFloatOptional(node->getMarginForAxis(axis, ownerWidth));
+ switch (*mode) {
+ case YGMeasureModeExactly:
+ case YGMeasureModeAtMost:
+ *size = (maxSize.isUndefined() || *size < maxSize.unwrap())
+ ? *size
+ : maxSize.unwrap();
+ break;
+ case YGMeasureModeUndefined:
+ if (!maxSize.isUndefined()) {
+ *mode = YGMeasureModeAtMost;
+ *size = maxSize.unwrap();
+ }
+ break;
+ }
+}
+
+static void YGNodeComputeFlexBasisForChild(
+ const YGNodeRef node,
+ const YGNodeRef child,
+ const float width,
+ const YGMeasureMode widthMode,
+ const float height,
+ const float ownerWidth,
+ const float ownerHeight,
+ const YGMeasureMode heightMode,
+ const YGDirection direction,
+ const YGConfigRef config,
+ LayoutData& layoutMarkerData,
+ void* const layoutContext,
+ const uint32_t depth,
+ const uint32_t generationCount) {
+ const YGFlexDirection mainAxis =
+ YGResolveFlexDirection(node->getStyle().flexDirection(), direction);
+ const bool isMainAxisRow = YGFlexDirectionIsRow(mainAxis);
+ const float mainAxisSize = isMainAxisRow ? width : height;
+ const float mainAxisownerSize = isMainAxisRow ? ownerWidth : ownerHeight;
+
+ float childWidth;
+ float childHeight;
+ YGMeasureMode childWidthMeasureMode;
+ YGMeasureMode childHeightMeasureMode;
+
+ const YGFloatOptional resolvedFlexBasis =
+ YGResolveValue(child->resolveFlexBasisPtr(), mainAxisownerSize);
+ const bool isRowStyleDimDefined =
+ YGNodeIsStyleDimDefined(child, YGFlexDirectionRow, ownerWidth);
+ const bool isColumnStyleDimDefined =
+ YGNodeIsStyleDimDefined(child, YGFlexDirectionColumn, ownerHeight);
+
+ if (!resolvedFlexBasis.isUndefined() && !YGFloatIsUndefined(mainAxisSize)) {
+ if (child->getLayout().computedFlexBasis.isUndefined() ||
+ (child->getConfig()->isExperimentalFeatureEnabled(
+ YGExperimentalFeatureWebFlexBasis) &&
+ child->getLayout().computedFlexBasisGeneration != generationCount)) {
+ const YGFloatOptional paddingAndBorder = YGFloatOptional(
+ YGNodePaddingAndBorderForAxis(child, mainAxis, ownerWidth));
+ child->setLayoutComputedFlexBasis(
+ YGFloatOptionalMax(resolvedFlexBasis, paddingAndBorder));
+ }
+ } else if (isMainAxisRow && isRowStyleDimDefined) {
+ // The width is definite, so use that as the flex basis.
+ const YGFloatOptional paddingAndBorder = YGFloatOptional(
+ YGNodePaddingAndBorderForAxis(child, YGFlexDirectionRow, ownerWidth));
+
+ child->setLayoutComputedFlexBasis(YGFloatOptionalMax(
+ YGResolveValue(
+ child->getResolvedDimensions()[YGDimensionWidth], ownerWidth),
+ paddingAndBorder));
+ } else if (!isMainAxisRow && isColumnStyleDimDefined) {
+ // The height is definite, so use that as the flex basis.
+ const YGFloatOptional paddingAndBorder =
+ YGFloatOptional(YGNodePaddingAndBorderForAxis(
+ child, YGFlexDirectionColumn, ownerWidth));
+ child->setLayoutComputedFlexBasis(YGFloatOptionalMax(
+ YGResolveValue(
+ child->getResolvedDimensions()[YGDimensionHeight], ownerHeight),
+ paddingAndBorder));
+ } else {
+ // Compute the flex basis and hypothetical main size (i.e. the clamped flex
+ // basis).
+ childWidth = YGUndefined;
+ childHeight = YGUndefined;
+ childWidthMeasureMode = YGMeasureModeUndefined;
+ childHeightMeasureMode = YGMeasureModeUndefined;
+
+ auto marginRow =
+ child->getMarginForAxis(YGFlexDirectionRow, ownerWidth).unwrap();
+ auto marginColumn =
+ child->getMarginForAxis(YGFlexDirectionColumn, ownerWidth).unwrap();
+
+ if (isRowStyleDimDefined) {
+ childWidth =
+ YGResolveValue(
+ child->getResolvedDimensions()[YGDimensionWidth], ownerWidth)
+ .unwrap() +
+ marginRow;
+ childWidthMeasureMode = YGMeasureModeExactly;
+ }
+ if (isColumnStyleDimDefined) {
+ childHeight =
+ YGResolveValue(
+ child->getResolvedDimensions()[YGDimensionHeight], ownerHeight)
+ .unwrap() +
+ marginColumn;
+ childHeightMeasureMode = YGMeasureModeExactly;
+ }
+
+ // The W3C spec doesn't say anything about the 'overflow' property, but all
+ // major browsers appear to implement the following logic.
+ if ((!isMainAxisRow && node->getStyle().overflow() == YGOverflowScroll) ||
+ node->getStyle().overflow() != YGOverflowScroll) {
+ if (YGFloatIsUndefined(childWidth) && !YGFloatIsUndefined(width)) {
+ childWidth = width;
+ childWidthMeasureMode = YGMeasureModeAtMost;
+ }
+ }
+
+ if ((isMainAxisRow && node->getStyle().overflow() == YGOverflowScroll) ||
+ node->getStyle().overflow() != YGOverflowScroll) {
+ if (YGFloatIsUndefined(childHeight) && !YGFloatIsUndefined(height)) {
+ childHeight = height;
+ childHeightMeasureMode = YGMeasureModeAtMost;
+ }
+ }
+
+ const auto& childStyle = child->getStyle();
+ if (!childStyle.aspectRatio().isUndefined()) {
+ if (!isMainAxisRow && childWidthMeasureMode == YGMeasureModeExactly) {
+ childHeight = marginColumn +
+ (childWidth - marginRow) / childStyle.aspectRatio().unwrap();
+ childHeightMeasureMode = YGMeasureModeExactly;
+ } else if (
+ isMainAxisRow && childHeightMeasureMode == YGMeasureModeExactly) {
+ childWidth = marginRow +
+ (childHeight - marginColumn) * childStyle.aspectRatio().unwrap();
+ childWidthMeasureMode = YGMeasureModeExactly;
+ }
+ }
+
+ // If child has no defined size in the cross axis and is set to stretch, set
+ // the cross axis to be measured exactly with the available inner width
+
+ const bool hasExactWidth =
+ !YGFloatIsUndefined(width) && widthMode == YGMeasureModeExactly;
+ const bool childWidthStretch =
+ YGNodeAlignItem(node, child) == YGAlignStretch &&
+ childWidthMeasureMode != YGMeasureModeExactly;
+ if (!isMainAxisRow && !isRowStyleDimDefined && hasExactWidth &&
+ childWidthStretch) {
+ childWidth = width;
+ childWidthMeasureMode = YGMeasureModeExactly;
+ if (!childStyle.aspectRatio().isUndefined()) {
+ childHeight =
+ (childWidth - marginRow) / childStyle.aspectRatio().unwrap();
+ childHeightMeasureMode = YGMeasureModeExactly;
+ }
+ }
+
+ const bool hasExactHeight =
+ !YGFloatIsUndefined(height) && heightMode == YGMeasureModeExactly;
+ const bool childHeightStretch =
+ YGNodeAlignItem(node, child) == YGAlignStretch &&
+ childHeightMeasureMode != YGMeasureModeExactly;
+ if (isMainAxisRow && !isColumnStyleDimDefined && hasExactHeight &&
+ childHeightStretch) {
+ childHeight = height;
+ childHeightMeasureMode = YGMeasureModeExactly;
+
+ if (!childStyle.aspectRatio().isUndefined()) {
+ childWidth =
+ (childHeight - marginColumn) * childStyle.aspectRatio().unwrap();
+ childWidthMeasureMode = YGMeasureModeExactly;
+ }
+ }
+
+ YGConstrainMaxSizeForMode(
+ child,
+ YGFlexDirectionRow,
+ ownerWidth,
+ ownerWidth,
+ &childWidthMeasureMode,
+ &childWidth);
+ YGConstrainMaxSizeForMode(
+ child,
+ YGFlexDirectionColumn,
+ ownerHeight,
+ ownerWidth,
+ &childHeightMeasureMode,
+ &childHeight);
+
+ // Measure the child
+ YGLayoutNodeInternal(
+ child,
+ childWidth,
+ childHeight,
+ direction,
+ childWidthMeasureMode,
+ childHeightMeasureMode,
+ ownerWidth,
+ ownerHeight,
+ false,
+ LayoutPassReason::kMeasureChild,
+ config,
+ layoutMarkerData,
+ layoutContext,
+ depth,
+ generationCount);
+
+ child->setLayoutComputedFlexBasis(YGFloatOptional(YGFloatMax(
+ child->getLayout().measuredDimensions[dim[mainAxis]],
+ YGNodePaddingAndBorderForAxis(child, mainAxis, ownerWidth))));
+ }
+ child->setLayoutComputedFlexBasisGeneration(generationCount);
+}
+
+static void YGNodeAbsoluteLayoutChild(
+ const YGNodeRef node,
+ const YGNodeRef child,
+ const float width,
+ const YGMeasureMode widthMode,
+ const float height,
+ const YGDirection direction,
+ const YGConfigRef config,
+ LayoutData& layoutMarkerData,
+ void* const layoutContext,
+ const uint32_t depth,
+ const uint32_t generationCount) {
+ const YGFlexDirection mainAxis =
+ YGResolveFlexDirection(node->getStyle().flexDirection(), direction);
+ const YGFlexDirection crossAxis = YGFlexDirectionCross(mainAxis, direction);
+ const bool isMainAxisRow = YGFlexDirectionIsRow(mainAxis);
+
+ float childWidth = YGUndefined;
+ float childHeight = YGUndefined;
+ YGMeasureMode childWidthMeasureMode = YGMeasureModeUndefined;
+ YGMeasureMode childHeightMeasureMode = YGMeasureModeUndefined;
+
+ auto marginRow = child->getMarginForAxis(YGFlexDirectionRow, width).unwrap();
+ auto marginColumn =
+ child->getMarginForAxis(YGFlexDirectionColumn, width).unwrap();
+
+ if (YGNodeIsStyleDimDefined(child, YGFlexDirectionRow, width)) {
+ childWidth =
+ YGResolveValue(child->getResolvedDimensions()[YGDimensionWidth], width)
+ .unwrap() +
+ marginRow;
+ } else {
+ // If the child doesn't have a specified width, compute the width based on
+ // the left/right offsets if they're defined.
+ if (child->isLeadingPositionDefined(YGFlexDirectionRow) &&
+ child->isTrailingPosDefined(YGFlexDirectionRow)) {
+ childWidth = node->getLayout().measuredDimensions[YGDimensionWidth] -
+ (node->getLeadingBorder(YGFlexDirectionRow) +
+ node->getTrailingBorder(YGFlexDirectionRow)) -
+ (child->getLeadingPosition(YGFlexDirectionRow, width) +
+ child->getTrailingPosition(YGFlexDirectionRow, width))
+ .unwrap();
+ childWidth =
+ YGNodeBoundAxis(child, YGFlexDirectionRow, childWidth, width, width);
+ }
+ }
+
+ if (YGNodeIsStyleDimDefined(child, YGFlexDirectionColumn, height)) {
+ childHeight = YGResolveValue(
+ child->getResolvedDimensions()[YGDimensionHeight], height)
+ .unwrap() +
+ marginColumn;
+ } else {
+ // If the child doesn't have a specified height, compute the height based on
+ // the top/bottom offsets if they're defined.
+ if (child->isLeadingPositionDefined(YGFlexDirectionColumn) &&
+ child->isTrailingPosDefined(YGFlexDirectionColumn)) {
+ childHeight = node->getLayout().measuredDimensions[YGDimensionHeight] -
+ (node->getLeadingBorder(YGFlexDirectionColumn) +
+ node->getTrailingBorder(YGFlexDirectionColumn)) -
+ (child->getLeadingPosition(YGFlexDirectionColumn, height) +
+ child->getTrailingPosition(YGFlexDirectionColumn, height))
+ .unwrap();
+ childHeight = YGNodeBoundAxis(
+ child, YGFlexDirectionColumn, childHeight, height, width);
+ }
+ }
+
+ // Exactly one dimension needs to be defined for us to be able to do aspect
+ // ratio calculation. One dimension being the anchor and the other being
+ // flexible.
+ const auto& childStyle = child->getStyle();
+ if (YGFloatIsUndefined(childWidth) ^ YGFloatIsUndefined(childHeight)) {
+ if (!childStyle.aspectRatio().isUndefined()) {
+ if (YGFloatIsUndefined(childWidth)) {
+ childWidth = marginRow +
+ (childHeight - marginColumn) * childStyle.aspectRatio().unwrap();
+ } else if (YGFloatIsUndefined(childHeight)) {
+ childHeight = marginColumn +
+ (childWidth - marginRow) / childStyle.aspectRatio().unwrap();
+ }
+ }
+ }
+
+ // If we're still missing one or the other dimension, measure the content.
+ if (YGFloatIsUndefined(childWidth) || YGFloatIsUndefined(childHeight)) {
+ childWidthMeasureMode = YGFloatIsUndefined(childWidth)
+ ? YGMeasureModeUndefined
+ : YGMeasureModeExactly;
+ childHeightMeasureMode = YGFloatIsUndefined(childHeight)
+ ? YGMeasureModeUndefined
+ : YGMeasureModeExactly;
+
+ // If the size of the owner is defined then try to constrain the absolute
+ // child to that size as well. This allows text within the absolute child to
+ // wrap to the size of its owner. This is the same behavior as many browsers
+ // implement.
+ if (!isMainAxisRow && YGFloatIsUndefined(childWidth) &&
+ widthMode != YGMeasureModeUndefined && !YGFloatIsUndefined(width) &&
+ width > 0) {
+ childWidth = width;
+ childWidthMeasureMode = YGMeasureModeAtMost;
+ }
+
+ YGLayoutNodeInternal(
+ child,
+ childWidth,
+ childHeight,
+ direction,
+ childWidthMeasureMode,
+ childHeightMeasureMode,
+ childWidth,
+ childHeight,
+ false,
+ LayoutPassReason::kAbsMeasureChild,
+ config,
+ layoutMarkerData,
+ layoutContext,
+ depth,
+ generationCount);
+ childWidth = child->getLayout().measuredDimensions[YGDimensionWidth] +
+ child->getMarginForAxis(YGFlexDirectionRow, width).unwrap();
+ childHeight = child->getLayout().measuredDimensions[YGDimensionHeight] +
+ child->getMarginForAxis(YGFlexDirectionColumn, width).unwrap();
+ }
+
+ YGLayoutNodeInternal(
+ child,
+ childWidth,
+ childHeight,
+ direction,
+ YGMeasureModeExactly,
+ YGMeasureModeExactly,
+ childWidth,
+ childHeight,
+ true,
+ LayoutPassReason::kAbsLayout,
+ config,
+ layoutMarkerData,
+ layoutContext,
+ depth,
+ generationCount);
+
+ if (child->isTrailingPosDefined(mainAxis) &&
+ !child->isLeadingPositionDefined(mainAxis)) {
+ child->setLayoutPosition(
+ node->getLayout().measuredDimensions[dim[mainAxis]] -
+ child->getLayout().measuredDimensions[dim[mainAxis]] -
+ node->getTrailingBorder(mainAxis) -
+ child->getTrailingMargin(mainAxis, isMainAxisRow ? width : height)
+ .unwrap() -
+ child->getTrailingPosition(mainAxis, isMainAxisRow ? width : height)
+ .unwrap(),
+ leading[mainAxis]);
+ } else if (
+ !child->isLeadingPositionDefined(mainAxis) &&
+ node->getStyle().justifyContent() == YGJustifyCenter) {
+ child->setLayoutPosition(
+ (node->getLayout().measuredDimensions[dim[mainAxis]] -
+ child->getLayout().measuredDimensions[dim[mainAxis]]) /
+ 2.0f,
+ leading[mainAxis]);
+ } else if (
+ !child->isLeadingPositionDefined(mainAxis) &&
+ node->getStyle().justifyContent() == YGJustifyFlexEnd) {
+ child->setLayoutPosition(
+ (node->getLayout().measuredDimensions[dim[mainAxis]] -
+ child->getLayout().measuredDimensions[dim[mainAxis]]),
+ leading[mainAxis]);
+ } else if (
+ node->getConfig()->isExperimentalFeatureEnabled(
+ YGExperimentalFeatureAbsolutePercentageAgainstPaddingEdge) &&
+ child->isLeadingPositionDefined(mainAxis)) {
+ child->setLayoutPosition(
+ child->getLeadingPosition(
+ mainAxis, node->getLayout().measuredDimensions[dim[mainAxis]])
+ .unwrap() +
+ node->getLeadingBorder(mainAxis) +
+ child
+ ->getLeadingMargin(
+ mainAxis,
+ node->getLayout().measuredDimensions[dim[mainAxis]])
+ .unwrap(),
+ leading[mainAxis]);
+ }
+
+ if (child->isTrailingPosDefined(crossAxis) &&
+ !child->isLeadingPositionDefined(crossAxis)) {
+ child->setLayoutPosition(
+ node->getLayout().measuredDimensions[dim[crossAxis]] -
+ child->getLayout().measuredDimensions[dim[crossAxis]] -
+ node->getTrailingBorder(crossAxis) -
+ child->getTrailingMargin(crossAxis, isMainAxisRow ? height : width)
+ .unwrap() -
+ child
+ ->getTrailingPosition(crossAxis, isMainAxisRow ? height : width)
+ .unwrap(),
+ leading[crossAxis]);
+
+ } else if (
+ !child->isLeadingPositionDefined(crossAxis) &&
+ YGNodeAlignItem(node, child) == YGAlignCenter) {
+ child->setLayoutPosition(
+ (node->getLayout().measuredDimensions[dim[crossAxis]] -
+ child->getLayout().measuredDimensions[dim[crossAxis]]) /
+ 2.0f,
+ leading[crossAxis]);
+ } else if (
+ !child->isLeadingPositionDefined(crossAxis) &&
+ ((YGNodeAlignItem(node, child) == YGAlignFlexEnd) ^
+ (node->getStyle().flexWrap() == YGWrapWrapReverse))) {
+ child->setLayoutPosition(
+ (node->getLayout().measuredDimensions[dim[crossAxis]] -
+ child->getLayout().measuredDimensions[dim[crossAxis]]),
+ leading[crossAxis]);
+ } else if (
+ node->getConfig()->isExperimentalFeatureEnabled(
+ YGExperimentalFeatureAbsolutePercentageAgainstPaddingEdge) &&
+ child->isLeadingPositionDefined(crossAxis)) {
+ child->setLayoutPosition(
+ child->getLeadingPosition(
+ crossAxis,
+ node->getLayout().measuredDimensions[dim[crossAxis]])
+ .unwrap() +
+ node->getLeadingBorder(crossAxis) +
+ child
+ ->getLeadingMargin(
+ crossAxis,
+ node->getLayout().measuredDimensions[dim[crossAxis]])
+ .unwrap(),
+ leading[crossAxis]);
+ }
+}
+
+static void YGNodeWithMeasureFuncSetMeasuredDimensions(
+ const YGNodeRef node,
+ float availableWidth,
+ float availableHeight,
+ const YGMeasureMode widthMeasureMode,
+ const YGMeasureMode heightMeasureMode,
+ const float ownerWidth,
+ const float ownerHeight,
+ LayoutData& layoutMarkerData,
+ void* const layoutContext,
+ const LayoutPassReason reason) {
+ YGAssertWithNode(
+ node,
+ node->hasMeasureFunc(),
+ "Expected node to have custom measure function");
+
+ if (widthMeasureMode == YGMeasureModeUndefined) {
+ availableWidth = YGUndefined;
+ }
+ if (heightMeasureMode == YGMeasureModeUndefined) {
+ availableHeight = YGUndefined;
+ }
+
+ const auto& padding = node->getLayout().padding;
+ const auto& border = node->getLayout().border;
+ const float paddingAndBorderAxisRow = padding[YGEdgeLeft] +
+ padding[YGEdgeRight] + border[YGEdgeLeft] + border[YGEdgeRight];
+ const float paddingAndBorderAxisColumn = padding[YGEdgeTop] +
+ padding[YGEdgeBottom] + border[YGEdgeTop] + border[YGEdgeBottom];
+
+ // We want to make sure we don't call measure with negative size
+ const float innerWidth = YGFloatIsUndefined(availableWidth)
+ ? availableWidth
+ : YGFloatMax(0, availableWidth - paddingAndBorderAxisRow);
+ const float innerHeight = YGFloatIsUndefined(availableHeight)
+ ? availableHeight
+ : YGFloatMax(0, availableHeight - paddingAndBorderAxisColumn);
+
+ if (widthMeasureMode == YGMeasureModeExactly &&
+ heightMeasureMode == YGMeasureModeExactly) {
+ // Don't bother sizing the text if both dimensions are already defined.
+ node->setLayoutMeasuredDimension(
+ YGNodeBoundAxis(
+ node, YGFlexDirectionRow, availableWidth, ownerWidth, ownerWidth),
+ YGDimensionWidth);
+ node->setLayoutMeasuredDimension(
+ YGNodeBoundAxis(
+ node,
+ YGFlexDirectionColumn,
+ availableHeight,
+ ownerHeight,
+ ownerWidth),
+ YGDimensionHeight);
+ } else {
+ Event::publish<Event::MeasureCallbackStart>(node);
+
+ // Measure the text under the current constraints.
+ const YGSize measuredSize = node->measure(
+ innerWidth,
+ widthMeasureMode,
+ innerHeight,
+ heightMeasureMode,
+ layoutContext);
+
+ layoutMarkerData.measureCallbacks += 1;
+ layoutMarkerData.measureCallbackReasonsCount[static_cast<size_t>(reason)] +=
+ 1;
+
+ Event::publish<Event::MeasureCallbackEnd>(
+ node,
+ {layoutContext,
+ innerWidth,
+ widthMeasureMode,
+ innerHeight,
+ heightMeasureMode,
+ measuredSize.width,
+ measuredSize.height,
+ reason});
+
+ node->setLayoutMeasuredDimension(
+ YGNodeBoundAxis(
+ node,
+ YGFlexDirectionRow,
+ (widthMeasureMode == YGMeasureModeUndefined ||
+ widthMeasureMode == YGMeasureModeAtMost)
+ ? measuredSize.width + paddingAndBorderAxisRow
+ : availableWidth,
+ ownerWidth,
+ ownerWidth),
+ YGDimensionWidth);
+
+ node->setLayoutMeasuredDimension(
+ YGNodeBoundAxis(
+ node,
+ YGFlexDirectionColumn,
+ (heightMeasureMode == YGMeasureModeUndefined ||
+ heightMeasureMode == YGMeasureModeAtMost)
+ ? measuredSize.height + paddingAndBorderAxisColumn
+ : availableHeight,
+ ownerHeight,
+ ownerWidth),
+ YGDimensionHeight);
+ }
+}
+
+// For nodes with no children, use the available values if they were provided,
+// or the minimum size as indicated by the padding and border sizes.
+static void YGNodeEmptyContainerSetMeasuredDimensions(
+ const YGNodeRef node,
+ const float availableWidth,
+ const float availableHeight,
+ const YGMeasureMode widthMeasureMode,
+ const YGMeasureMode heightMeasureMode,
+ const float ownerWidth,
+ const float ownerHeight) {
+ const auto& padding = node->getLayout().padding;
+ const auto& border = node->getLayout().border;
+
+ float width = availableWidth;
+ if (widthMeasureMode == YGMeasureModeUndefined ||
+ widthMeasureMode == YGMeasureModeAtMost) {
+ width = padding[YGEdgeLeft] + padding[YGEdgeRight] + border[YGEdgeLeft] +
+ border[YGEdgeRight];
+ }
+ node->setLayoutMeasuredDimension(
+ YGNodeBoundAxis(node, YGFlexDirectionRow, width, ownerWidth, ownerWidth),
+ YGDimensionWidth);
+
+ float height = availableHeight;
+ if (heightMeasureMode == YGMeasureModeUndefined ||
+ heightMeasureMode == YGMeasureModeAtMost) {
+ height = padding[YGEdgeTop] + padding[YGEdgeBottom] + border[YGEdgeTop] +
+ border[YGEdgeBottom];
+ }
+ node->setLayoutMeasuredDimension(
+ YGNodeBoundAxis(
+ node, YGFlexDirectionColumn, height, ownerHeight, ownerWidth),
+ YGDimensionHeight);
+}
+
+static bool YGNodeFixedSizeSetMeasuredDimensions(
+ const YGNodeRef node,
+ const float availableWidth,
+ const float availableHeight,
+ const YGMeasureMode widthMeasureMode,
+ const YGMeasureMode heightMeasureMode,
+ const float ownerWidth,
+ const float ownerHeight) {
+ if ((!YGFloatIsUndefined(availableWidth) &&
+ widthMeasureMode == YGMeasureModeAtMost && availableWidth <= 0.0f) ||
+ (!YGFloatIsUndefined(availableHeight) &&
+ heightMeasureMode == YGMeasureModeAtMost && availableHeight <= 0.0f) ||
+ (widthMeasureMode == YGMeasureModeExactly &&
+ heightMeasureMode == YGMeasureModeExactly)) {
+ node->setLayoutMeasuredDimension(
+ YGNodeBoundAxis(
+ node,
+ YGFlexDirectionRow,
+ YGFloatIsUndefined(availableWidth) ||
+ (widthMeasureMode == YGMeasureModeAtMost &&
+ availableWidth < 0.0f)
+ ? 0.0f
+ : availableWidth,
+ ownerWidth,
+ ownerWidth),
+ YGDimensionWidth);
+
+ node->setLayoutMeasuredDimension(
+ YGNodeBoundAxis(
+ node,
+ YGFlexDirectionColumn,
+ YGFloatIsUndefined(availableHeight) ||
+ (heightMeasureMode == YGMeasureModeAtMost &&
+ availableHeight < 0.0f)
+ ? 0.0f
+ : availableHeight,
+ ownerHeight,
+ ownerWidth),
+ YGDimensionHeight);
+ return true;
+ }
+
+ return false;
+}
+
+static void YGZeroOutLayoutRecursively(
+ const YGNodeRef node,
+ void* layoutContext) {
+ node->getLayout() = {};
+ node->setLayoutDimension(0, 0);
+ node->setLayoutDimension(0, 1);
+ node->setHasNewLayout(true);
+
+ node->iterChildrenAfterCloningIfNeeded(
+ YGZeroOutLayoutRecursively, layoutContext);
+}
+
+static float YGNodeCalculateAvailableInnerDim(
+ const YGNodeConstRef node,
+ const YGDimension dimension,
+ const float availableDim,
+ const float paddingAndBorder,
+ const float ownerDim) {
+ float availableInnerDim = availableDim - paddingAndBorder;
+ // Max dimension overrides predefined dimension value; Min dimension in turn
+ // overrides both of the above
+ if (!YGFloatIsUndefined(availableInnerDim)) {
+ // We want to make sure our available height does not violate min and max
+ // constraints
+ const YGFloatOptional minDimensionOptional =
+ YGResolveValue(node->getStyle().minDimensions()[dimension], ownerDim);
+ const float minInnerDim = minDimensionOptional.isUndefined()
+ ? 0.0f
+ : minDimensionOptional.unwrap() - paddingAndBorder;
+
+ const YGFloatOptional maxDimensionOptional =
+ YGResolveValue(node->getStyle().maxDimensions()[dimension], ownerDim);
+
+ const float maxInnerDim = maxDimensionOptional.isUndefined()
+ ? FLT_MAX
+ : maxDimensionOptional.unwrap() - paddingAndBorder;
+ availableInnerDim =
+ YGFloatMax(YGFloatMin(availableInnerDim, maxInnerDim), minInnerDim);
+ }
+
+ return availableInnerDim;
+}
+
+static float YGNodeComputeFlexBasisForChildren(
+ const YGNodeRef node,
+ const float availableInnerWidth,
+ const float availableInnerHeight,
+ YGMeasureMode widthMeasureMode,
+ YGMeasureMode heightMeasureMode,
+ YGDirection direction,
+ YGFlexDirection mainAxis,
+ const YGConfigRef config,
+ bool performLayout,
+ LayoutData& layoutMarkerData,
+ void* const layoutContext,
+ const uint32_t depth,
+ const uint32_t generationCount) {
+ float totalOuterFlexBasis = 0.0f;
+ YGNodeRef singleFlexChild = nullptr;
+ const YGVector& children = node->getChildren();
+ YGMeasureMode measureModeMainDim =
+ YGFlexDirectionIsRow(mainAxis) ? widthMeasureMode : heightMeasureMode;
+ // If there is only one child with flexGrow + flexShrink it means we can set
+ // the computedFlexBasis to 0 instead of measuring and shrinking / flexing the
+ // child to exactly match the remaining space
+ if (measureModeMainDim == YGMeasureModeExactly) {
+ for (auto child : children) {
+ if (child->isNodeFlexible()) {
+ if (singleFlexChild != nullptr ||
+ YGFloatsEqual(child->resolveFlexGrow(), 0.0f) ||
+ YGFloatsEqual(child->resolveFlexShrink(), 0.0f)) {
+ // There is already a flexible child, or this flexible child doesn't
+ // have flexGrow and flexShrink, abort
+ singleFlexChild = nullptr;
+ break;
+ } else {
+ singleFlexChild = child;
+ }
+ }
+ }
+ }
+
+ for (auto child : children) {
+ child->resolveDimension();
+ if (child->getStyle().display() == YGDisplayNone) {
+ YGZeroOutLayoutRecursively(child, layoutContext);
+ child->setHasNewLayout(true);
+ child->setDirty(false);
+ continue;
+ }
+ if (performLayout) {
+ // Set the initial position (relative to the owner).
+ const YGDirection childDirection = child->resolveDirection(direction);
+ const float mainDim = YGFlexDirectionIsRow(mainAxis)
+ ? availableInnerWidth
+ : availableInnerHeight;
+ const float crossDim = YGFlexDirectionIsRow(mainAxis)
+ ? availableInnerHeight
+ : availableInnerWidth;
+ child->setPosition(
+ childDirection, mainDim, crossDim, availableInnerWidth);
+ }
+
+ if (child->getStyle().positionType() == YGPositionTypeAbsolute) {
+ continue;
+ }
+ if (child == singleFlexChild) {
+ child->setLayoutComputedFlexBasisGeneration(generationCount);
+ child->setLayoutComputedFlexBasis(YGFloatOptional(0));
+ } else {
+ YGNodeComputeFlexBasisForChild(
+ node,
+ child,
+ availableInnerWidth,
+ widthMeasureMode,
+ availableInnerHeight,
+ availableInnerWidth,
+ availableInnerHeight,
+ heightMeasureMode,
+ direction,
+ config,
+ layoutMarkerData,
+ layoutContext,
+ depth,
+ generationCount);
+ }
+
+ totalOuterFlexBasis +=
+ (child->getLayout().computedFlexBasis +
+ child->getMarginForAxis(mainAxis, availableInnerWidth))
+ .unwrap();
+ }
+
+ return totalOuterFlexBasis;
+}
+
+// This function assumes that all the children of node have their
+// computedFlexBasis properly computed(To do this use
+// YGNodeComputeFlexBasisForChildren function). This function calculates
+// YGCollectFlexItemsRowMeasurement
+static YGCollectFlexItemsRowValues YGCalculateCollectFlexItemsRowValues(
+ const YGNodeRef& node,
+ const YGDirection ownerDirection,
+ const float mainAxisownerSize,
+ const float availableInnerWidth,
+ const float availableInnerMainDim,
+ const uint32_t startOfLineIndex,
+ const uint32_t lineCount) {
+ YGCollectFlexItemsRowValues flexAlgoRowMeasurement = {};
+ flexAlgoRowMeasurement.relativeChildren.reserve(node->getChildren().size());
+
+ float sizeConsumedOnCurrentLineIncludingMinConstraint = 0;
+ const YGFlexDirection mainAxis = YGResolveFlexDirection(
+ node->getStyle().flexDirection(), node->resolveDirection(ownerDirection));
+ const bool isNodeFlexWrap = node->getStyle().flexWrap() != YGWrapNoWrap;
+ const float gap = node->getGapForAxis(mainAxis, availableInnerWidth).unwrap();
+
+ // Add items to the current line until it's full or we run out of items.
+ uint32_t endOfLineIndex = startOfLineIndex;
+ for (; endOfLineIndex < node->getChildren().size(); endOfLineIndex++) {
+ const YGNodeRef child = node->getChild(endOfLineIndex);
+ if (child->getStyle().display() == YGDisplayNone ||
+ child->getStyle().positionType() == YGPositionTypeAbsolute) {
+ continue;
+ }
+
+ const bool isFirstElementInLine = (endOfLineIndex - startOfLineIndex) == 0;
+
+ child->setLineIndex(lineCount);
+ const float childMarginMainAxis =
+ child->getMarginForAxis(mainAxis, availableInnerWidth).unwrap();
+ const float childLeadingGapMainAxis = isFirstElementInLine ? 0.0f : gap;
+ const float flexBasisWithMinAndMaxConstraints =
+ YGNodeBoundAxisWithinMinAndMax(
+ child,
+ mainAxis,
+ child->getLayout().computedFlexBasis,
+ mainAxisownerSize)
+ .unwrap();
+
+ // If this is a multi-line flow and this item pushes us over the available
+ // size, we've hit the end of the current line. Break out of the loop and
+ // lay out the current line.
+ if (sizeConsumedOnCurrentLineIncludingMinConstraint +
+ flexBasisWithMinAndMaxConstraints + childMarginMainAxis +
+ childLeadingGapMainAxis >
+ availableInnerMainDim &&
+ isNodeFlexWrap && flexAlgoRowMeasurement.itemsOnLine > 0) {
+ break;
+ }
+
+ sizeConsumedOnCurrentLineIncludingMinConstraint +=
+ flexBasisWithMinAndMaxConstraints + childMarginMainAxis +
+ childLeadingGapMainAxis;
+ flexAlgoRowMeasurement.sizeConsumedOnCurrentLine +=
+ flexBasisWithMinAndMaxConstraints + childMarginMainAxis +
+ childLeadingGapMainAxis;
+ flexAlgoRowMeasurement.itemsOnLine++;
+
+ if (child->isNodeFlexible()) {
+ flexAlgoRowMeasurement.totalFlexGrowFactors += child->resolveFlexGrow();
+
+ // Unlike the grow factor, the shrink factor is scaled relative to the
+ // child dimension.
+ flexAlgoRowMeasurement.totalFlexShrinkScaledFactors +=
+ -child->resolveFlexShrink() *
+ child->getLayout().computedFlexBasis.unwrap();
+ }
+
+ flexAlgoRowMeasurement.relativeChildren.push_back(child);
+ }
+
+ // The total flex factor needs to be floored to 1.
+ if (flexAlgoRowMeasurement.totalFlexGrowFactors > 0 &&
+ flexAlgoRowMeasurement.totalFlexGrowFactors < 1) {
+ flexAlgoRowMeasurement.totalFlexGrowFactors = 1;
+ }
+
+ // The total flex shrink factor needs to be floored to 1.
+ if (flexAlgoRowMeasurement.totalFlexShrinkScaledFactors > 0 &&
+ flexAlgoRowMeasurement.totalFlexShrinkScaledFactors < 1) {
+ flexAlgoRowMeasurement.totalFlexShrinkScaledFactors = 1;
+ }
+ flexAlgoRowMeasurement.endOfLineIndex = endOfLineIndex;
+ return flexAlgoRowMeasurement;
+}
+
+// It distributes the free space to the flexible items and ensures that the size
+// of the flex items abide the min and max constraints. At the end of this
+// function the child nodes would have proper size. Prior using this function
+// please ensure that YGDistributeFreeSpaceFirstPass is called.
+static float YGDistributeFreeSpaceSecondPass(
+ YGCollectFlexItemsRowValues& collectedFlexItemsValues,
+ const YGNodeRef node,
+ const YGFlexDirection mainAxis,
+ const YGFlexDirection crossAxis,
+ const float mainAxisownerSize,
+ const float availableInnerMainDim,
+ const float availableInnerCrossDim,
+ const float availableInnerWidth,
+ const float availableInnerHeight,
+ const bool mainAxisOverflows,
+ const YGMeasureMode measureModeCrossDim,
+ const bool performLayout,
+ const YGConfigRef config,
+ LayoutData& layoutMarkerData,
+ void* const layoutContext,
+ const uint32_t depth,
+ const uint32_t generationCount) {
+ float childFlexBasis = 0;
+ float flexShrinkScaledFactor = 0;
+ float flexGrowFactor = 0;
+ float deltaFreeSpace = 0;
+ const bool isMainAxisRow = YGFlexDirectionIsRow(mainAxis);
+ const bool isNodeFlexWrap = node->getStyle().flexWrap() != YGWrapNoWrap;
+
+ for (auto currentRelativeChild : collectedFlexItemsValues.relativeChildren) {
+ childFlexBasis = YGNodeBoundAxisWithinMinAndMax(
+ currentRelativeChild,
+ mainAxis,
+ currentRelativeChild->getLayout().computedFlexBasis,
+ mainAxisownerSize)
+ .unwrap();
+ float updatedMainSize = childFlexBasis;
+
+ if (!YGFloatIsUndefined(collectedFlexItemsValues.remainingFreeSpace) &&
+ collectedFlexItemsValues.remainingFreeSpace < 0) {
+ flexShrinkScaledFactor =
+ -currentRelativeChild->resolveFlexShrink() * childFlexBasis;
+ // Is this child able to shrink?
+ if (flexShrinkScaledFactor != 0) {
+ float childSize;
+
+ if (!YGFloatIsUndefined(
+ collectedFlexItemsValues.totalFlexShrinkScaledFactors) &&
+ collectedFlexItemsValues.totalFlexShrinkScaledFactors == 0) {
+ childSize = childFlexBasis + flexShrinkScaledFactor;
+ } else {
+ childSize = childFlexBasis +
+ (collectedFlexItemsValues.remainingFreeSpace /
+ collectedFlexItemsValues.totalFlexShrinkScaledFactors) *
+ flexShrinkScaledFactor;
+ }
+
+ updatedMainSize = YGNodeBoundAxis(
+ currentRelativeChild,
+ mainAxis,
+ childSize,
+ availableInnerMainDim,
+ availableInnerWidth);
+ }
+ } else if (
+ !YGFloatIsUndefined(collectedFlexItemsValues.remainingFreeSpace) &&
+ collectedFlexItemsValues.remainingFreeSpace > 0) {
+ flexGrowFactor = currentRelativeChild->resolveFlexGrow();
+
+ // Is this child able to grow?
+ if (!YGFloatIsUndefined(flexGrowFactor) && flexGrowFactor != 0) {
+ updatedMainSize = YGNodeBoundAxis(
+ currentRelativeChild,
+ mainAxis,
+ childFlexBasis +
+ collectedFlexItemsValues.remainingFreeSpace /
+ collectedFlexItemsValues.totalFlexGrowFactors *
+ flexGrowFactor,
+ availableInnerMainDim,
+ availableInnerWidth);
+ }
+ }
+
+ deltaFreeSpace += updatedMainSize - childFlexBasis;
+
+ const float marginMain =
+ currentRelativeChild->getMarginForAxis(mainAxis, availableInnerWidth)
+ .unwrap();
+ const float marginCross =
+ currentRelativeChild->getMarginForAxis(crossAxis, availableInnerWidth)
+ .unwrap();
+
+ float childCrossSize;
+ float childMainSize = updatedMainSize + marginMain;
+ YGMeasureMode childCrossMeasureMode;
+ YGMeasureMode childMainMeasureMode = YGMeasureModeExactly;
+
+ const auto& childStyle = currentRelativeChild->getStyle();
+ if (!childStyle.aspectRatio().isUndefined()) {
+ childCrossSize = isMainAxisRow
+ ? (childMainSize - marginMain) / childStyle.aspectRatio().unwrap()
+ : (childMainSize - marginMain) * childStyle.aspectRatio().unwrap();
+ childCrossMeasureMode = YGMeasureModeExactly;
+
+ childCrossSize += marginCross;
+ } else if (
+ !YGFloatIsUndefined(availableInnerCrossDim) &&
+ !YGNodeIsStyleDimDefined(
+ currentRelativeChild, crossAxis, availableInnerCrossDim) &&
+ measureModeCrossDim == YGMeasureModeExactly &&
+ !(isNodeFlexWrap && mainAxisOverflows) &&
+ YGNodeAlignItem(node, currentRelativeChild) == YGAlignStretch &&
+ currentRelativeChild->marginLeadingValue(crossAxis).unit !=
+ YGUnitAuto &&
+ currentRelativeChild->marginTrailingValue(crossAxis).unit !=
+ YGUnitAuto) {
+ childCrossSize = availableInnerCrossDim;
+ childCrossMeasureMode = YGMeasureModeExactly;
+ } else if (!YGNodeIsStyleDimDefined(
+ currentRelativeChild, crossAxis, availableInnerCrossDim)) {
+ childCrossSize = availableInnerCrossDim;
+ childCrossMeasureMode = YGFloatIsUndefined(childCrossSize)
+ ? YGMeasureModeUndefined
+ : YGMeasureModeAtMost;
+ } else {
+ childCrossSize =
+ YGResolveValue(
+ currentRelativeChild->getResolvedDimension(dim[crossAxis]),
+ availableInnerCrossDim)
+ .unwrap() +
+ marginCross;
+ const bool isLoosePercentageMeasurement =
+ currentRelativeChild->getResolvedDimension(dim[crossAxis]).unit ==
+ YGUnitPercent &&
+ measureModeCrossDim != YGMeasureModeExactly;
+ childCrossMeasureMode =
+ YGFloatIsUndefined(childCrossSize) || isLoosePercentageMeasurement
+ ? YGMeasureModeUndefined
+ : YGMeasureModeExactly;
+ }
+
+ YGConstrainMaxSizeForMode(
+ currentRelativeChild,
+ mainAxis,
+ availableInnerMainDim,
+ availableInnerWidth,
+ &childMainMeasureMode,
+ &childMainSize);
+ YGConstrainMaxSizeForMode(
+ currentRelativeChild,
+ crossAxis,
+ availableInnerCrossDim,
+ availableInnerWidth,
+ &childCrossMeasureMode,
+ &childCrossSize);
+
+ const bool requiresStretchLayout =
+ !YGNodeIsStyleDimDefined(
+ currentRelativeChild, crossAxis, availableInnerCrossDim) &&
+ YGNodeAlignItem(node, currentRelativeChild) == YGAlignStretch &&
+ currentRelativeChild->marginLeadingValue(crossAxis).unit !=
+ YGUnitAuto &&
+ currentRelativeChild->marginTrailingValue(crossAxis).unit != YGUnitAuto;
+
+ const float childWidth = isMainAxisRow ? childMainSize : childCrossSize;
+ const float childHeight = !isMainAxisRow ? childMainSize : childCrossSize;
+
+ const YGMeasureMode childWidthMeasureMode =
+ isMainAxisRow ? childMainMeasureMode : childCrossMeasureMode;
+ const YGMeasureMode childHeightMeasureMode =
+ !isMainAxisRow ? childMainMeasureMode : childCrossMeasureMode;
+
+ const bool isLayoutPass = performLayout && !requiresStretchLayout;
+ // Recursively call the layout algorithm for this child with the updated
+ // main size.
+ YGLayoutNodeInternal(
+ currentRelativeChild,
+ childWidth,
+ childHeight,
+ node->getLayout().direction(),
+ childWidthMeasureMode,
+ childHeightMeasureMode,
+ availableInnerWidth,
+ availableInnerHeight,
+ isLayoutPass,
+ isLayoutPass ? LayoutPassReason::kFlexLayout
+ : LayoutPassReason::kFlexMeasure,
+ config,
+ layoutMarkerData,
+ layoutContext,
+ depth,
+ generationCount);
+ node->setLayoutHadOverflow(
+ node->getLayout().hadOverflow() ||
+ currentRelativeChild->getLayout().hadOverflow());
+ }
+ return deltaFreeSpace;
+}
+
+// It distributes the free space to the flexible items.For those flexible items
+// whose min and max constraints are triggered, those flex item's clamped size
+// is removed from the remaingfreespace.
+static void YGDistributeFreeSpaceFirstPass(
+ YGCollectFlexItemsRowValues& collectedFlexItemsValues,
+ const YGFlexDirection mainAxis,
+ const float mainAxisownerSize,
+ const float availableInnerMainDim,
+ const float availableInnerWidth) {
+ float flexShrinkScaledFactor = 0;
+ float flexGrowFactor = 0;
+ float baseMainSize = 0;
+ float boundMainSize = 0;
+ float deltaFreeSpace = 0;
+
+ for (auto currentRelativeChild : collectedFlexItemsValues.relativeChildren) {
+ float childFlexBasis =
+ YGNodeBoundAxisWithinMinAndMax(
+ currentRelativeChild,
+ mainAxis,
+ currentRelativeChild->getLayout().computedFlexBasis,
+ mainAxisownerSize)
+ .unwrap();
+
+ if (collectedFlexItemsValues.remainingFreeSpace < 0) {
+ flexShrinkScaledFactor =
+ -currentRelativeChild->resolveFlexShrink() * childFlexBasis;
+
+ // Is this child able to shrink?
+ if (!YGFloatIsUndefined(flexShrinkScaledFactor) &&
+ flexShrinkScaledFactor != 0) {
+ baseMainSize = childFlexBasis +
+ collectedFlexItemsValues.remainingFreeSpace /
+ collectedFlexItemsValues.totalFlexShrinkScaledFactors *
+ flexShrinkScaledFactor;
+ boundMainSize = YGNodeBoundAxis(
+ currentRelativeChild,
+ mainAxis,
+ baseMainSize,
+ availableInnerMainDim,
+ availableInnerWidth);
+ if (!YGFloatIsUndefined(baseMainSize) &&
+ !YGFloatIsUndefined(boundMainSize) &&
+ baseMainSize != boundMainSize) {
+ // By excluding this item's size and flex factor from remaining, this
+ // item's min/max constraints should also trigger in the second pass
+ // resulting in the item's size calculation being identical in the
+ // first and second passes.
+ deltaFreeSpace += boundMainSize - childFlexBasis;
+ collectedFlexItemsValues.totalFlexShrinkScaledFactors -=
+ (-currentRelativeChild->resolveFlexShrink() *
+ currentRelativeChild->getLayout().computedFlexBasis.unwrap());
+ }
+ }
+ } else if (
+ !YGFloatIsUndefined(collectedFlexItemsValues.remainingFreeSpace) &&
+ collectedFlexItemsValues.remainingFreeSpace > 0) {
+ flexGrowFactor = currentRelativeChild->resolveFlexGrow();
+
+ // Is this child able to grow?
+ if (!YGFloatIsUndefined(flexGrowFactor) && flexGrowFactor != 0) {
+ baseMainSize = childFlexBasis +
+ collectedFlexItemsValues.remainingFreeSpace /
+ collectedFlexItemsValues.totalFlexGrowFactors * flexGrowFactor;
+ boundMainSize = YGNodeBoundAxis(
+ currentRelativeChild,
+ mainAxis,
+ baseMainSize,
+ availableInnerMainDim,
+ availableInnerWidth);
+
+ if (!YGFloatIsUndefined(baseMainSize) &&
+ !YGFloatIsUndefined(boundMainSize) &&
+ baseMainSize != boundMainSize) {
+ // By excluding this item's size and flex factor from remaining, this
+ // item's min/max constraints should also trigger in the second pass
+ // resulting in the item's size calculation being identical in the
+ // first and second passes.
+ deltaFreeSpace += boundMainSize - childFlexBasis;
+ collectedFlexItemsValues.totalFlexGrowFactors -= flexGrowFactor;
+ }
+ }
+ }
+ }
+ collectedFlexItemsValues.remainingFreeSpace -= deltaFreeSpace;
+}
+
+// Do two passes over the flex items to figure out how to distribute the
+// remaining space.
+//
+// The first pass finds the items whose min/max constraints trigger, freezes
+// them at those sizes, and excludes those sizes from the remaining space.
+//
+// The second pass sets the size of each flexible item. It distributes the
+// remaining space amongst the items whose min/max constraints didn't trigger in
+// the first pass. For the other items, it sets their sizes by forcing their
+// min/max constraints to trigger again.
+//
+// This two pass approach for resolving min/max constraints deviates from the
+// spec. The spec
+// (https://www.w3.org/TR/CSS-flexbox-1/#resolve-flexible-lengths) describes a
+// process that needs to be repeated a variable number of times. The algorithm
+// implemented here won't handle all cases but it was simpler to implement and
+// it mitigates performance concerns because we know exactly how many passes
+// it'll do.
+//
+// At the end of this function the child nodes would have the proper size
+// assigned to them.
+//
+static void YGResolveFlexibleLength(
+ const YGNodeRef node,
+ YGCollectFlexItemsRowValues& collectedFlexItemsValues,
+ const YGFlexDirection mainAxis,
+ const YGFlexDirection crossAxis,
+ const float mainAxisownerSize,
+ const float availableInnerMainDim,
+ const float availableInnerCrossDim,
+ const float availableInnerWidth,
+ const float availableInnerHeight,
+ const bool mainAxisOverflows,
+ const YGMeasureMode measureModeCrossDim,
+ const bool performLayout,
+ const YGConfigRef config,
+ LayoutData& layoutMarkerData,
+ void* const layoutContext,
+ const uint32_t depth,
+ const uint32_t generationCount) {
+ const float originalFreeSpace = collectedFlexItemsValues.remainingFreeSpace;
+ // First pass: detect the flex items whose min/max constraints trigger
+ YGDistributeFreeSpaceFirstPass(
+ collectedFlexItemsValues,
+ mainAxis,
+ mainAxisownerSize,
+ availableInnerMainDim,
+ availableInnerWidth);
+
+ // Second pass: resolve the sizes of the flexible items
+ const float distributedFreeSpace = YGDistributeFreeSpaceSecondPass(
+ collectedFlexItemsValues,
+ node,
+ mainAxis,
+ crossAxis,
+ mainAxisownerSize,
+ availableInnerMainDim,
+ availableInnerCrossDim,
+ availableInnerWidth,
+ availableInnerHeight,
+ mainAxisOverflows,
+ measureModeCrossDim,
+ performLayout,
+ config,
+ layoutMarkerData,
+ layoutContext,
+ depth,
+ generationCount);
+
+ collectedFlexItemsValues.remainingFreeSpace =
+ originalFreeSpace - distributedFreeSpace;
+}
+
+static void YGJustifyMainAxis(
+ const YGNodeRef node,
+ YGCollectFlexItemsRowValues& collectedFlexItemsValues,
+ const uint32_t startOfLineIndex,
+ const YGFlexDirection mainAxis,
+ const YGFlexDirection crossAxis,
+ const YGMeasureMode measureModeMainDim,
+ const YGMeasureMode measureModeCrossDim,
+ const float mainAxisownerSize,
+ const float ownerWidth,
+ const float availableInnerMainDim,
+ const float availableInnerCrossDim,
+ const float availableInnerWidth,
+ const bool performLayout,
+ void* const layoutContext) {
+ const auto& style = node->getStyle();
+ const float leadingPaddingAndBorderMain =
+ node->getLeadingPaddingAndBorder(mainAxis, ownerWidth).unwrap();
+ const float trailingPaddingAndBorderMain =
+ node->getTrailingPaddingAndBorder(mainAxis, ownerWidth).unwrap();
+ const float gap = node->getGapForAxis(mainAxis, ownerWidth).unwrap();
+ // If we are using "at most" rules in the main axis, make sure that
+ // remainingFreeSpace is 0 when min main dimension is not given
+ if (measureModeMainDim == YGMeasureModeAtMost &&
+ collectedFlexItemsValues.remainingFreeSpace > 0) {
+ if (!style.minDimensions()[dim[mainAxis]].isUndefined() &&
+ !YGResolveValue(style.minDimensions()[dim[mainAxis]], mainAxisownerSize)
+ .isUndefined()) {
+ // This condition makes sure that if the size of main dimension(after
+ // considering child nodes main dim, leading and trailing padding etc)
+ // falls below min dimension, then the remainingFreeSpace is reassigned
+ // considering the min dimension
+
+ // `minAvailableMainDim` denotes minimum available space in which child
+ // can be laid out, it will exclude space consumed by padding and border.
+ const float minAvailableMainDim =
+ YGResolveValue(
+ style.minDimensions()[dim[mainAxis]], mainAxisownerSize)
+ .unwrap() -
+ leadingPaddingAndBorderMain - trailingPaddingAndBorderMain;
+ const float occupiedSpaceByChildNodes =
+ availableInnerMainDim - collectedFlexItemsValues.remainingFreeSpace;
+ collectedFlexItemsValues.remainingFreeSpace =
+ YGFloatMax(0, minAvailableMainDim - occupiedSpaceByChildNodes);
+ } else {
+ collectedFlexItemsValues.remainingFreeSpace = 0;
+ }
+ }
+
+ int numberOfAutoMarginsOnCurrentLine = 0;
+ for (uint32_t i = startOfLineIndex;
+ i < collectedFlexItemsValues.endOfLineIndex;
+ i++) {
+ const YGNodeRef child = node->getChild(i);
+ if (child->getStyle().positionType() != YGPositionTypeAbsolute) {
+ if (child->marginLeadingValue(mainAxis).unit == YGUnitAuto) {
+ numberOfAutoMarginsOnCurrentLine++;
+ }
+ if (child->marginTrailingValue(mainAxis).unit == YGUnitAuto) {
+ numberOfAutoMarginsOnCurrentLine++;
+ }
+ }
+ }
+
+ // In order to position the elements in the main axis, we have two controls.
+ // The space between the beginning and the first element and the space between
+ // each two elements.
+ float leadingMainDim = 0;
+ float betweenMainDim = gap;
+ const YGJustify justifyContent = node->getStyle().justifyContent();
+
+ if (numberOfAutoMarginsOnCurrentLine == 0) {
+ switch (justifyContent) {
+ case YGJustifyCenter:
+ leadingMainDim = collectedFlexItemsValues.remainingFreeSpace / 2;
+ break;
+ case YGJustifyFlexEnd:
+ leadingMainDim = collectedFlexItemsValues.remainingFreeSpace;
+ break;
+ case YGJustifySpaceBetween:
+ if (collectedFlexItemsValues.itemsOnLine > 1) {
+ betweenMainDim +=
+ YGFloatMax(collectedFlexItemsValues.remainingFreeSpace, 0) /
+ (collectedFlexItemsValues.itemsOnLine - 1);
+ }
+ break;
+ case YGJustifySpaceEvenly:
+ // Space is distributed evenly across all elements
+ leadingMainDim = collectedFlexItemsValues.remainingFreeSpace /
+ (collectedFlexItemsValues.itemsOnLine + 1);
+ betweenMainDim += leadingMainDim;
+ break;
+ case YGJustifySpaceAround:
+ // Space on the edges is half of the space between elements
+ leadingMainDim = 0.5f * collectedFlexItemsValues.remainingFreeSpace /
+ collectedFlexItemsValues.itemsOnLine;
+ betweenMainDim += leadingMainDim * 2;
+ break;
+ case YGJustifyFlexStart:
+ break;
+ }
+ }
+
+ collectedFlexItemsValues.mainDim =
+ leadingPaddingAndBorderMain + leadingMainDim;
+ collectedFlexItemsValues.crossDim = 0;
+
+ float maxAscentForCurrentLine = 0;
+ float maxDescentForCurrentLine = 0;
+ bool isNodeBaselineLayout = YGIsBaselineLayout(node);
+ for (uint32_t i = startOfLineIndex;
+ i < collectedFlexItemsValues.endOfLineIndex;
+ i++) {
+ const YGNodeRef child = node->getChild(i);
+ const YGStyle& childStyle = child->getStyle();
+ const YGLayout childLayout = child->getLayout();
+ const bool isLastChild = i == collectedFlexItemsValues.endOfLineIndex - 1;
+ // remove the gap if it is the last element of the line
+ if (isLastChild) {
+ betweenMainDim -= gap;
+ }
+ if (childStyle.display() == YGDisplayNone) {
+ continue;
+ }
+ if (childStyle.positionType() == YGPositionTypeAbsolute &&
+ child->isLeadingPositionDefined(mainAxis)) {
+ if (performLayout) {
+ // In case the child is position absolute and has left/top being
+ // defined, we override the position to whatever the user said (and
+ // margin/border).
+ child->setLayoutPosition(
+ child->getLeadingPosition(mainAxis, availableInnerMainDim)
+ .unwrap() +
+ node->getLeadingBorder(mainAxis) +
+ child->getLeadingMargin(mainAxis, availableInnerWidth).unwrap(),
+ pos[mainAxis]);
+ }
+ } else {
+ // Now that we placed the element, we need to update the variables.
+ // We need to do that only for relative elements. Absolute elements do not
+ // take part in that phase.
+ if (childStyle.positionType() != YGPositionTypeAbsolute) {
+ if (child->marginLeadingValue(mainAxis).unit == YGUnitAuto) {
+ collectedFlexItemsValues.mainDim +=
+ collectedFlexItemsValues.remainingFreeSpace /
+ numberOfAutoMarginsOnCurrentLine;
+ }
+
+ if (performLayout) {
+ child->setLayoutPosition(
+ childLayout.position[pos[mainAxis]] +
+ collectedFlexItemsValues.mainDim,
+ pos[mainAxis]);
+ }
+
+ if (child->marginTrailingValue(mainAxis).unit == YGUnitAuto) {
+ collectedFlexItemsValues.mainDim +=
+ collectedFlexItemsValues.remainingFreeSpace /
+ numberOfAutoMarginsOnCurrentLine;
+ }
+ bool canSkipFlex =
+ !performLayout && measureModeCrossDim == YGMeasureModeExactly;
+ if (canSkipFlex) {
+ // If we skipped the flex step, then we can't rely on the measuredDims
+ // because they weren't computed. This means we can't call
+ // YGNodeDimWithMargin.
+ collectedFlexItemsValues.mainDim += betweenMainDim +
+ child->getMarginForAxis(mainAxis, availableInnerWidth).unwrap() +
+ childLayout.computedFlexBasis.unwrap();
+ collectedFlexItemsValues.crossDim = availableInnerCrossDim;
+ } else {
+ // The main dimension is the sum of all the elements dimension plus
+ // the spacing.
+ collectedFlexItemsValues.mainDim += betweenMainDim +
+ YGNodeDimWithMargin(child, mainAxis, availableInnerWidth);
+
+ if (isNodeBaselineLayout) {
+ // If the child is baseline aligned then the cross dimension is
+ // calculated by adding maxAscent and maxDescent from the baseline.
+ const float ascent = YGBaseline(child, layoutContext) +
+ child
+ ->getLeadingMargin(
+ YGFlexDirectionColumn, availableInnerWidth)
+ .unwrap();
+ const float descent =
+ child->getLayout().measuredDimensions[YGDimensionHeight] +
+ child
+ ->getMarginForAxis(
+ YGFlexDirectionColumn, availableInnerWidth)
+ .unwrap() -
+ ascent;
+
+ maxAscentForCurrentLine =
+ YGFloatMax(maxAscentForCurrentLine, ascent);
+ maxDescentForCurrentLine =
+ YGFloatMax(maxDescentForCurrentLine, descent);
+ } else {
+ // The cross dimension is the max of the elements dimension since
+ // there can only be one element in that cross dimension in the case
+ // when the items are not baseline aligned
+ collectedFlexItemsValues.crossDim = YGFloatMax(
+ collectedFlexItemsValues.crossDim,
+ YGNodeDimWithMargin(child, crossAxis, availableInnerWidth));
+ }
+ }
+ } else if (performLayout) {
+ child->setLayoutPosition(
+ childLayout.position[pos[mainAxis]] +
+ node->getLeadingBorder(mainAxis) + leadingMainDim,
+ pos[mainAxis]);
+ }
+ }
+ }
+ collectedFlexItemsValues.mainDim += trailingPaddingAndBorderMain;
+
+ if (isNodeBaselineLayout) {
+ collectedFlexItemsValues.crossDim =
+ maxAscentForCurrentLine + maxDescentForCurrentLine;
+ }
+}
+
+//
+// This is the main routine that implements a subset of the flexbox layout
+// algorithm described in the W3C CSS documentation:
+// https://www.w3.org/TR/CSS3-flexbox/.
+//
+// Limitations of this algorithm, compared to the full standard:
+// * Display property is always assumed to be 'flex' except for Text nodes,
+// which are assumed to be 'inline-flex'.
+// * The 'zIndex' property (or any form of z ordering) is not supported. Nodes
+// are stacked in document order.
+// * The 'order' property is not supported. The order of flex items is always
+// defined by document order.
+// * The 'visibility' property is always assumed to be 'visible'. Values of
+// 'collapse' and 'hidden' are not supported.
+// * There is no support for forced breaks.
+// * It does not support vertical inline directions (top-to-bottom or
+// bottom-to-top text).
+//
+// Deviations from standard:
+// * Section 4.5 of the spec indicates that all flex items have a default
+// minimum main size. For text blocks, for example, this is the width of the
+// widest word. Calculating the minimum width is expensive, so we forego it
+// and assume a default minimum main size of 0.
+// * Min/Max sizes in the main axis are not honored when resolving flexible
+// lengths.
+// * The spec indicates that the default value for 'flexDirection' is 'row',
+// but the algorithm below assumes a default of 'column'.
+//
+// Input parameters:
+// - node: current node to be sized and laid out
+// - availableWidth & availableHeight: available size to be used for sizing
+// the node or YGUndefined if the size is not available; interpretation
+// depends on layout flags
+// - ownerDirection: the inline (text) direction within the owner
+// (left-to-right or right-to-left)
+// - widthMeasureMode: indicates the sizing rules for the width (see below
+// for explanation)
+// - heightMeasureMode: indicates the sizing rules for the height (see below
+// for explanation)
+// - performLayout: specifies whether the caller is interested in just the
+// dimensions of the node or it requires the entire node and its subtree to
+// be laid out (with final positions)
+//
+// Details:
+// This routine is called recursively to lay out subtrees of flexbox
+// elements. It uses the information in node.style, which is treated as a
+// read-only input. It is responsible for setting the layout.direction and
+// layout.measuredDimensions fields for the input node as well as the
+// layout.position and layout.lineIndex fields for its child nodes. The
+// layout.measuredDimensions field includes any border or padding for the
+// node but does not include margins.
+//
+// The spec describes four different layout modes: "fill available", "max
+// content", "min content", and "fit content". Of these, we don't use "min
+// content" because we don't support default minimum main sizes (see above
+// for details). Each of our measure modes maps to a layout mode from the
+// spec (https://www.w3.org/TR/CSS3-sizing/#terms):
+// - YGMeasureModeUndefined: max content
+// - YGMeasureModeExactly: fill available
+// - YGMeasureModeAtMost: fit content
+//
+// When calling YGNodelayoutImpl and YGLayoutNodeInternal, if the caller
+// passes an available size of undefined then it must also pass a measure
+// mode of YGMeasureModeUndefined in that dimension.
+//
+static void YGNodelayoutImpl(
+ const YGNodeRef node,
+ const float availableWidth,
+ const float availableHeight,
+ const YGDirection ownerDirection,
+ const YGMeasureMode widthMeasureMode,
+ const YGMeasureMode heightMeasureMode,
+ const float ownerWidth,
+ const float ownerHeight,
+ const bool performLayout,
+ const YGConfigRef config,
+ LayoutData& layoutMarkerData,
+ void* const layoutContext,
+ const uint32_t depth,
+ const uint32_t generationCount,
+ const LayoutPassReason reason) {
+ YGAssertWithNode(
+ node,
+ YGFloatIsUndefined(availableWidth)
+ ? widthMeasureMode == YGMeasureModeUndefined
+ : true,
+ "availableWidth is indefinite so widthMeasureMode must be "
+ "YGMeasureModeUndefined");
+ YGAssertWithNode(
+ node,
+ YGFloatIsUndefined(availableHeight)
+ ? heightMeasureMode == YGMeasureModeUndefined
+ : true,
+ "availableHeight is indefinite so heightMeasureMode must be "
+ "YGMeasureModeUndefined");
+
+ (performLayout ? layoutMarkerData.layouts : layoutMarkerData.measures) += 1;
+
+ // Set the resolved resolution in the node's layout.
+ const YGDirection direction = node->resolveDirection(ownerDirection);
+ node->setLayoutDirection(direction);
+
+ const YGFlexDirection flexRowDirection =
+ YGResolveFlexDirection(YGFlexDirectionRow, direction);
+ const YGFlexDirection flexColumnDirection =
+ YGResolveFlexDirection(YGFlexDirectionColumn, direction);
+
+ const YGEdge startEdge =
+ direction == YGDirectionLTR ? YGEdgeLeft : YGEdgeRight;
+ const YGEdge endEdge = direction == YGDirectionLTR ? YGEdgeRight : YGEdgeLeft;
+
+ const float marginRowLeading =
+ node->getLeadingMargin(flexRowDirection, ownerWidth).unwrap();
+ node->setLayoutMargin(marginRowLeading, startEdge);
+ const float marginRowTrailing =
+ node->getTrailingMargin(flexRowDirection, ownerWidth).unwrap();
+ node->setLayoutMargin(marginRowTrailing, endEdge);
+ const float marginColumnLeading =
+ node->getLeadingMargin(flexColumnDirection, ownerWidth).unwrap();
+ node->setLayoutMargin(marginColumnLeading, YGEdgeTop);
+ const float marginColumnTrailing =
+ node->getTrailingMargin(flexColumnDirection, ownerWidth).unwrap();
+ node->setLayoutMargin(marginColumnTrailing, YGEdgeBottom);
+
+ const float marginAxisRow = marginRowLeading + marginRowTrailing;
+ const float marginAxisColumn = marginColumnLeading + marginColumnTrailing;
+
+ node->setLayoutBorder(node->getLeadingBorder(flexRowDirection), startEdge);
+ node->setLayoutBorder(node->getTrailingBorder(flexRowDirection), endEdge);
+ node->setLayoutBorder(node->getLeadingBorder(flexColumnDirection), YGEdgeTop);
+ node->setLayoutBorder(
+ node->getTrailingBorder(flexColumnDirection), YGEdgeBottom);
+
+ node->setLayoutPadding(
+ node->getLeadingPadding(flexRowDirection, ownerWidth).unwrap(),
+ startEdge);
+ node->setLayoutPadding(
+ node->getTrailingPadding(flexRowDirection, ownerWidth).unwrap(), endEdge);
+ node->setLayoutPadding(
+ node->getLeadingPadding(flexColumnDirection, ownerWidth).unwrap(),
+ YGEdgeTop);
+ node->setLayoutPadding(
+ node->getTrailingPadding(flexColumnDirection, ownerWidth).unwrap(),
+ YGEdgeBottom);
+
+ if (node->hasMeasureFunc()) {
+ YGNodeWithMeasureFuncSetMeasuredDimensions(
+ node,
+ availableWidth - marginAxisRow,
+ availableHeight - marginAxisColumn,
+ widthMeasureMode,
+ heightMeasureMode,
+ ownerWidth,
+ ownerHeight,
+ layoutMarkerData,
+ layoutContext,
+ reason);
+ return;
+ }
+
+ const uint32_t childCount = YGNodeGetChildCount(node);
+ if (childCount == 0) {
+ YGNodeEmptyContainerSetMeasuredDimensions(
+ node,
+ availableWidth - marginAxisRow,
+ availableHeight - marginAxisColumn,
+ widthMeasureMode,
+ heightMeasureMode,
+ ownerWidth,
+ ownerHeight);
+ return;
+ }
+
+ // If we're not being asked to perform a full layout we can skip the algorithm
+ // if we already know the size
+ if (!performLayout &&
+ YGNodeFixedSizeSetMeasuredDimensions(
+ node,
+ availableWidth - marginAxisRow,
+ availableHeight - marginAxisColumn,
+ widthMeasureMode,
+ heightMeasureMode,
+ ownerWidth,
+ ownerHeight)) {
+ return;
+ }
+
+ // At this point we know we're going to perform work. Ensure that each child
+ // has a mutable copy.
+ node->cloneChildrenIfNeeded(layoutContext);
+ // Reset layout flags, as they could have changed.
+ node->setLayoutHadOverflow(false);
+
+ // STEP 1: CALCULATE VALUES FOR REMAINDER OF ALGORITHM
+ const YGFlexDirection mainAxis =
+ YGResolveFlexDirection(node->getStyle().flexDirection(), direction);
+ const YGFlexDirection crossAxis = YGFlexDirectionCross(mainAxis, direction);
+ const bool isMainAxisRow = YGFlexDirectionIsRow(mainAxis);
+ const bool isNodeFlexWrap = node->getStyle().flexWrap() != YGWrapNoWrap;
+
+ const float mainAxisownerSize = isMainAxisRow ? ownerWidth : ownerHeight;
+ const float crossAxisownerSize = isMainAxisRow ? ownerHeight : ownerWidth;
+
+ const float paddingAndBorderAxisMain =
+ YGNodePaddingAndBorderForAxis(node, mainAxis, ownerWidth);
+ const float leadingPaddingAndBorderCross =
+ node->getLeadingPaddingAndBorder(crossAxis, ownerWidth).unwrap();
+ const float trailingPaddingAndBorderCross =
+ node->getTrailingPaddingAndBorder(crossAxis, ownerWidth).unwrap();
+ const float paddingAndBorderAxisCross =
+ leadingPaddingAndBorderCross + trailingPaddingAndBorderCross;
+
+ YGMeasureMode measureModeMainDim =
+ isMainAxisRow ? widthMeasureMode : heightMeasureMode;
+ YGMeasureMode measureModeCrossDim =
+ isMainAxisRow ? heightMeasureMode : widthMeasureMode;
+
+ const float paddingAndBorderAxisRow =
+ isMainAxisRow ? paddingAndBorderAxisMain : paddingAndBorderAxisCross;
+ const float paddingAndBorderAxisColumn =
+ isMainAxisRow ? paddingAndBorderAxisCross : paddingAndBorderAxisMain;
+
+ // STEP 2: DETERMINE AVAILABLE SIZE IN MAIN AND CROSS DIRECTIONS
+
+ float availableInnerWidth = YGNodeCalculateAvailableInnerDim(
+ node,
+ YGDimensionWidth,
+ availableWidth - marginAxisRow,
+ paddingAndBorderAxisRow,
+ ownerWidth);
+ float availableInnerHeight = YGNodeCalculateAvailableInnerDim(
+ node,
+ YGDimensionHeight,
+ availableHeight - marginAxisColumn,
+ paddingAndBorderAxisColumn,
+ ownerHeight);
+
+ float availableInnerMainDim =
+ isMainAxisRow ? availableInnerWidth : availableInnerHeight;
+ const float availableInnerCrossDim =
+ isMainAxisRow ? availableInnerHeight : availableInnerWidth;
+
+ // STEP 3: DETERMINE FLEX BASIS FOR EACH ITEM
+
+ // Computed basis + margins + gap
+ float totalMainDim = 0;
+ totalMainDim += YGNodeComputeFlexBasisForChildren(
+ node,
+ availableInnerWidth,
+ availableInnerHeight,
+ widthMeasureMode,
+ heightMeasureMode,
+ direction,
+ mainAxis,
+ config,
+ performLayout,
+ layoutMarkerData,
+ layoutContext,
+ depth,
+ generationCount);
+
+ if (childCount > 1) {
+ totalMainDim +=
+ node->getGapForAxis(mainAxis, availableInnerCrossDim).unwrap() *
+ (childCount - 1);
+ }
+
+ const bool mainAxisOverflows =
+ (measureModeMainDim != YGMeasureModeUndefined) &&
+ totalMainDim > availableInnerMainDim;
+
+ if (isNodeFlexWrap && mainAxisOverflows &&
+ measureModeMainDim == YGMeasureModeAtMost) {
+ measureModeMainDim = YGMeasureModeExactly;
+ }
+ // STEP 4: COLLECT FLEX ITEMS INTO FLEX LINES
+
+ // Indexes of children that represent the first and last items in the line.
+ uint32_t startOfLineIndex = 0;
+ uint32_t endOfLineIndex = 0;
+
+ // Number of lines.
+ uint32_t lineCount = 0;
+
+ // Accumulated cross dimensions of all lines so far.
+ float totalLineCrossDim = 0;
+
+ const float crossAxisGap =
+ node->getGapForAxis(crossAxis, availableInnerCrossDim).unwrap();
+
+ // Max main dimension of all the lines.
+ float maxLineMainDim = 0;
+ YGCollectFlexItemsRowValues collectedFlexItemsValues;
+ for (; endOfLineIndex < childCount;
+ lineCount++, startOfLineIndex = endOfLineIndex) {
+ collectedFlexItemsValues = YGCalculateCollectFlexItemsRowValues(
+ node,
+ ownerDirection,
+ mainAxisownerSize,
+ availableInnerWidth,
+ availableInnerMainDim,
+ startOfLineIndex,
+ lineCount);
+ endOfLineIndex = collectedFlexItemsValues.endOfLineIndex;
+
+ // If we don't need to measure the cross axis, we can skip the entire flex
+ // step.
+ const bool canSkipFlex =
+ !performLayout && measureModeCrossDim == YGMeasureModeExactly;
+
+ // STEP 5: RESOLVING FLEXIBLE LENGTHS ON MAIN AXIS
+ // Calculate the remaining available space that needs to be allocated. If
+ // the main dimension size isn't known, it is computed based on the line
+ // length, so there's no more space left to distribute.
+
+ bool sizeBasedOnContent = false;
+ // If we don't measure with exact main dimension we want to ensure we don't
+ // violate min and max
+ if (measureModeMainDim != YGMeasureModeExactly) {
+ const auto& minDimensions = node->getStyle().minDimensions();
+ const auto& maxDimensions = node->getStyle().maxDimensions();
+ const float minInnerWidth =
+ YGResolveValue(minDimensions[YGDimensionWidth], ownerWidth).unwrap() -
+ paddingAndBorderAxisRow;
+ const float maxInnerWidth =
+ YGResolveValue(maxDimensions[YGDimensionWidth], ownerWidth).unwrap() -
+ paddingAndBorderAxisRow;
+ const float minInnerHeight =
+ YGResolveValue(minDimensions[YGDimensionHeight], ownerHeight)
+ .unwrap() -
+ paddingAndBorderAxisColumn;
+ const float maxInnerHeight =
+ YGResolveValue(maxDimensions[YGDimensionHeight], ownerHeight)
+ .unwrap() -
+ paddingAndBorderAxisColumn;
+
+ const float minInnerMainDim =
+ isMainAxisRow ? minInnerWidth : minInnerHeight;
+ const float maxInnerMainDim =
+ isMainAxisRow ? maxInnerWidth : maxInnerHeight;
+
+ if (!YGFloatIsUndefined(minInnerMainDim) &&
+ collectedFlexItemsValues.sizeConsumedOnCurrentLine <
+ minInnerMainDim) {
+ availableInnerMainDim = minInnerMainDim;
+ } else if (
+ !YGFloatIsUndefined(maxInnerMainDim) &&
+ collectedFlexItemsValues.sizeConsumedOnCurrentLine >
+ maxInnerMainDim) {
+ availableInnerMainDim = maxInnerMainDim;
+ } else {
+ bool useLegacyStretchBehaviour =
+ node->hasErrata(YGErrataStretchFlexBasis);
+
+ if (!useLegacyStretchBehaviour &&
+ ((!YGFloatIsUndefined(
+ collectedFlexItemsValues.totalFlexGrowFactors) &&
+ collectedFlexItemsValues.totalFlexGrowFactors == 0) ||
+ (!YGFloatIsUndefined(node->resolveFlexGrow()) &&
+ node->resolveFlexGrow() == 0))) {
+ // If we don't have any children to flex or we can't flex the node
+ // itself, space we've used is all space we need. Root node also
+ // should be shrunk to minimum
+ availableInnerMainDim =
+ collectedFlexItemsValues.sizeConsumedOnCurrentLine;
+ }
+
+ sizeBasedOnContent = !useLegacyStretchBehaviour;
+ }
+ }
+
+ if (!sizeBasedOnContent && !YGFloatIsUndefined(availableInnerMainDim)) {
+ collectedFlexItemsValues.remainingFreeSpace = availableInnerMainDim -
+ collectedFlexItemsValues.sizeConsumedOnCurrentLine;
+ } else if (collectedFlexItemsValues.sizeConsumedOnCurrentLine < 0) {
+ // availableInnerMainDim is indefinite which means the node is being sized
+ // based on its content. sizeConsumedOnCurrentLine is negative which means
+ // the node will allocate 0 points for its content. Consequently,
+ // remainingFreeSpace is 0 - sizeConsumedOnCurrentLine.
+ collectedFlexItemsValues.remainingFreeSpace =
+ -collectedFlexItemsValues.sizeConsumedOnCurrentLine;
+ }
+
+ if (!canSkipFlex) {
+ YGResolveFlexibleLength(
+ node,
+ collectedFlexItemsValues,
+ mainAxis,
+ crossAxis,
+ mainAxisownerSize,
+ availableInnerMainDim,
+ availableInnerCrossDim,
+ availableInnerWidth,
+ availableInnerHeight,
+ mainAxisOverflows,
+ measureModeCrossDim,
+ performLayout,
+ config,
+ layoutMarkerData,
+ layoutContext,
+ depth,
+ generationCount);
+ }
+
+ node->setLayoutHadOverflow(
+ node->getLayout().hadOverflow() |
+ (collectedFlexItemsValues.remainingFreeSpace < 0));
+
+ // STEP 6: MAIN-AXIS JUSTIFICATION & CROSS-AXIS SIZE DETERMINATION
+
+ // At this point, all the children have their dimensions set in the main
+ // axis. Their dimensions are also set in the cross axis with the exception
+ // of items that are aligned "stretch". We need to compute these stretch
+ // values and set the final positions.
+
+ YGJustifyMainAxis(
+ node,
+ collectedFlexItemsValues,
+ startOfLineIndex,
+ mainAxis,
+ crossAxis,
+ measureModeMainDim,
+ measureModeCrossDim,
+ mainAxisownerSize,
+ ownerWidth,
+ availableInnerMainDim,
+ availableInnerCrossDim,
+ availableInnerWidth,
+ performLayout,
+ layoutContext);
+
+ float containerCrossAxis = availableInnerCrossDim;
+ if (measureModeCrossDim == YGMeasureModeUndefined ||
+ measureModeCrossDim == YGMeasureModeAtMost) {
+ // Compute the cross axis from the max cross dimension of the children.
+ containerCrossAxis =
+ YGNodeBoundAxis(
+ node,
+ crossAxis,
+ collectedFlexItemsValues.crossDim + paddingAndBorderAxisCross,
+ crossAxisownerSize,
+ ownerWidth) -
+ paddingAndBorderAxisCross;
+ }
+
+ // If there's no flex wrap, the cross dimension is defined by the container.
+ if (!isNodeFlexWrap && measureModeCrossDim == YGMeasureModeExactly) {
+ collectedFlexItemsValues.crossDim = availableInnerCrossDim;
+ }
+
+ // Clamp to the min/max size specified on the container.
+ collectedFlexItemsValues.crossDim =
+ YGNodeBoundAxis(
+ node,
+ crossAxis,
+ collectedFlexItemsValues.crossDim + paddingAndBorderAxisCross,
+ crossAxisownerSize,
+ ownerWidth) -
+ paddingAndBorderAxisCross;
+
+ // STEP 7: CROSS-AXIS ALIGNMENT
+ // We can skip child alignment if we're just measuring the container.
+ if (performLayout) {
+ for (uint32_t i = startOfLineIndex; i < endOfLineIndex; i++) {
+ const YGNodeRef child = node->getChild(i);
+ if (child->getStyle().display() == YGDisplayNone) {
+ continue;
+ }
+ if (child->getStyle().positionType() == YGPositionTypeAbsolute) {
+ // If the child is absolutely positioned and has a
+ // top/left/bottom/right set, override all the previously computed
+ // positions to set it correctly.
+ const bool isChildLeadingPosDefined =
+ child->isLeadingPositionDefined(crossAxis);
+ if (isChildLeadingPosDefined) {
+ child->setLayoutPosition(
+ child->getLeadingPosition(crossAxis, availableInnerCrossDim)
+ .unwrap() +
+ node->getLeadingBorder(crossAxis) +
+ child->getLeadingMargin(crossAxis, availableInnerWidth)
+ .unwrap(),
+ pos[crossAxis]);
+ }
+ // If leading position is not defined or calculations result in Nan,
+ // default to border + margin
+ if (!isChildLeadingPosDefined ||
+ YGFloatIsUndefined(child->getLayout().position[pos[crossAxis]])) {
+ child->setLayoutPosition(
+ node->getLeadingBorder(crossAxis) +
+ child->getLeadingMargin(crossAxis, availableInnerWidth)
+ .unwrap(),
+ pos[crossAxis]);
+ }
+ } else {
+ float leadingCrossDim = leadingPaddingAndBorderCross;
+
+ // For a relative children, we're either using alignItems (owner) or
+ // alignSelf (child) in order to determine the position in the cross
+ // axis
+ const YGAlign alignItem = YGNodeAlignItem(node, child);
+
+ // If the child uses align stretch, we need to lay it out one more
+ // time, this time forcing the cross-axis size to be the computed
+ // cross size for the current line.
+ if (alignItem == YGAlignStretch &&
+ child->marginLeadingValue(crossAxis).unit != YGUnitAuto &&
+ child->marginTrailingValue(crossAxis).unit != YGUnitAuto) {
+ // If the child defines a definite size for its cross axis, there's
+ // no need to stretch.
+ if (!YGNodeIsStyleDimDefined(
+ child, crossAxis, availableInnerCrossDim)) {
+ float childMainSize =
+ child->getLayout().measuredDimensions[dim[mainAxis]];
+ const auto& childStyle = child->getStyle();
+ float childCrossSize = !childStyle.aspectRatio().isUndefined()
+ ? child->getMarginForAxis(crossAxis, availableInnerWidth)
+ .unwrap() +
+ (isMainAxisRow
+ ? childMainSize / childStyle.aspectRatio().unwrap()
+ : childMainSize * childStyle.aspectRatio().unwrap())
+ : collectedFlexItemsValues.crossDim;
+
+ childMainSize +=
+ child->getMarginForAxis(mainAxis, availableInnerWidth)
+ .unwrap();
+
+ YGMeasureMode childMainMeasureMode = YGMeasureModeExactly;
+ YGMeasureMode childCrossMeasureMode = YGMeasureModeExactly;
+ YGConstrainMaxSizeForMode(
+ child,
+ mainAxis,
+ availableInnerMainDim,
+ availableInnerWidth,
+ &childMainMeasureMode,
+ &childMainSize);
+ YGConstrainMaxSizeForMode(
+ child,
+ crossAxis,
+ availableInnerCrossDim,
+ availableInnerWidth,
+ &childCrossMeasureMode,
+ &childCrossSize);
+
+ const float childWidth =
+ isMainAxisRow ? childMainSize : childCrossSize;
+ const float childHeight =
+ !isMainAxisRow ? childMainSize : childCrossSize;
+
+ auto alignContent = node->getStyle().alignContent();
+ auto crossAxisDoesNotGrow =
+ alignContent != YGAlignStretch && isNodeFlexWrap;
+ const YGMeasureMode childWidthMeasureMode =
+ YGFloatIsUndefined(childWidth) ||
+ (!isMainAxisRow && crossAxisDoesNotGrow)
+ ? YGMeasureModeUndefined
+ : YGMeasureModeExactly;
+ const YGMeasureMode childHeightMeasureMode =
+ YGFloatIsUndefined(childHeight) ||
+ (isMainAxisRow && crossAxisDoesNotGrow)
+ ? YGMeasureModeUndefined
+ : YGMeasureModeExactly;
+
+ YGLayoutNodeInternal(
+ child,
+ childWidth,
+ childHeight,
+ direction,
+ childWidthMeasureMode,
+ childHeightMeasureMode,
+ availableInnerWidth,
+ availableInnerHeight,
+ true,
+ LayoutPassReason::kStretch,
+ config,
+ layoutMarkerData,
+ layoutContext,
+ depth,
+ generationCount);
+ }
+ } else {
+ const float remainingCrossDim = containerCrossAxis -
+ YGNodeDimWithMargin(child, crossAxis, availableInnerWidth);
+
+ if (child->marginLeadingValue(crossAxis).unit == YGUnitAuto &&
+ child->marginTrailingValue(crossAxis).unit == YGUnitAuto) {
+ leadingCrossDim += YGFloatMax(0.0f, remainingCrossDim / 2);
+ } else if (
+ child->marginTrailingValue(crossAxis).unit == YGUnitAuto) {
+ // No-Op
+ } else if (
+ child->marginLeadingValue(crossAxis).unit == YGUnitAuto) {
+ leadingCrossDim += YGFloatMax(0.0f, remainingCrossDim);
+ } else if (alignItem == YGAlignFlexStart) {
+ // No-Op
+ } else if (alignItem == YGAlignCenter) {
+ leadingCrossDim += remainingCrossDim / 2;
+ } else {
+ leadingCrossDim += remainingCrossDim;
+ }
+ }
+ // And we apply the position
+ child->setLayoutPosition(
+ child->getLayout().position[pos[crossAxis]] + totalLineCrossDim +
+ leadingCrossDim,
+ pos[crossAxis]);
+ }
+ }
+ }
+
+ const float appliedCrossGap = lineCount != 0 ? crossAxisGap : 0.0f;
+ totalLineCrossDim += collectedFlexItemsValues.crossDim + appliedCrossGap;
+ maxLineMainDim =
+ YGFloatMax(maxLineMainDim, collectedFlexItemsValues.mainDim);
+ }
+
+ // STEP 8: MULTI-LINE CONTENT ALIGNMENT
+ // currentLead stores the size of the cross dim
+ if (performLayout && (isNodeFlexWrap || YGIsBaselineLayout(node))) {
+ float crossDimLead = 0;
+ float currentLead = leadingPaddingAndBorderCross;
+ if (!YGFloatIsUndefined(availableInnerCrossDim)) {
+ const float remainingAlignContentDim =
+ availableInnerCrossDim - totalLineCrossDim;
+ switch (node->getStyle().alignContent()) {
+ case YGAlignFlexEnd:
+ currentLead += remainingAlignContentDim;
+ break;
+ case YGAlignCenter:
+ currentLead += remainingAlignContentDim / 2;
+ break;
+ case YGAlignStretch:
+ if (availableInnerCrossDim > totalLineCrossDim) {
+ crossDimLead = remainingAlignContentDim / lineCount;
+ }
+ break;
+ case YGAlignSpaceAround:
+ if (availableInnerCrossDim > totalLineCrossDim) {
+ currentLead += remainingAlignContentDim / (2 * lineCount);
+ if (lineCount > 1) {
+ crossDimLead = remainingAlignContentDim / lineCount;
+ }
+ } else {
+ currentLead += remainingAlignContentDim / 2;
+ }
+ break;
+ case YGAlignSpaceBetween:
+ if (availableInnerCrossDim > totalLineCrossDim && lineCount > 1) {
+ crossDimLead = remainingAlignContentDim / (lineCount - 1);
+ }
+ break;
+ case YGAlignAuto:
+ case YGAlignFlexStart:
+ case YGAlignBaseline:
+ break;
+ }
+ }
+ uint32_t endIndex = 0;
+ for (uint32_t i = 0; i < lineCount; i++) {
+ const uint32_t startIndex = endIndex;
+ uint32_t ii;
+
+ // compute the line's height and find the endIndex
+ float lineHeight = 0;
+ float maxAscentForCurrentLine = 0;
+ float maxDescentForCurrentLine = 0;
+ for (ii = startIndex; ii < childCount; ii++) {
+ const YGNodeRef child = node->getChild(ii);
+ if (child->getStyle().display() == YGDisplayNone) {
+ continue;
+ }
+ if (child->getStyle().positionType() != YGPositionTypeAbsolute) {
+ if (child->getLineIndex() != i) {
+ break;
+ }
+ if (YGNodeIsLayoutDimDefined(child, crossAxis)) {
+ lineHeight = YGFloatMax(
+ lineHeight,
+ child->getLayout().measuredDimensions[dim[crossAxis]] +
+ child->getMarginForAxis(crossAxis, availableInnerWidth)
+ .unwrap());
+ }
+ if (YGNodeAlignItem(node, child) == YGAlignBaseline) {
+ const float ascent = YGBaseline(child, layoutContext) +
+ child
+ ->getLeadingMargin(
+ YGFlexDirectionColumn, availableInnerWidth)
+ .unwrap();
+ const float descent =
+ child->getLayout().measuredDimensions[YGDimensionHeight] +
+ child
+ ->getMarginForAxis(
+ YGFlexDirectionColumn, availableInnerWidth)
+ .unwrap() -
+ ascent;
+ maxAscentForCurrentLine =
+ YGFloatMax(maxAscentForCurrentLine, ascent);
+ maxDescentForCurrentLine =
+ YGFloatMax(maxDescentForCurrentLine, descent);
+ lineHeight = YGFloatMax(
+ lineHeight, maxAscentForCurrentLine + maxDescentForCurrentLine);
+ }
+ }
+ }
+ endIndex = ii;
+ lineHeight += crossDimLead;
+ currentLead += i != 0 ? crossAxisGap : 0;
+
+ if (performLayout) {
+ for (ii = startIndex; ii < endIndex; ii++) {
+ const YGNodeRef child = node->getChild(ii);
+ if (child->getStyle().display() == YGDisplayNone) {
+ continue;
+ }
+ if (child->getStyle().positionType() != YGPositionTypeAbsolute) {
+ switch (YGNodeAlignItem(node, child)) {
+ case YGAlignFlexStart: {
+ child->setLayoutPosition(
+ currentLead +
+ child->getLeadingMargin(crossAxis, availableInnerWidth)
+ .unwrap(),
+ pos[crossAxis]);
+ break;
+ }
+ case YGAlignFlexEnd: {
+ child->setLayoutPosition(
+ currentLead + lineHeight -
+ child->getTrailingMargin(crossAxis, availableInnerWidth)
+ .unwrap() -
+ child->getLayout().measuredDimensions[dim[crossAxis]],
+ pos[crossAxis]);
+ break;
+ }
+ case YGAlignCenter: {
+ float childHeight =
+ child->getLayout().measuredDimensions[dim[crossAxis]];
+
+ child->setLayoutPosition(
+ currentLead + (lineHeight - childHeight) / 2,
+ pos[crossAxis]);
+ break;
+ }
+ case YGAlignStretch: {
+ child->setLayoutPosition(
+ currentLead +
+ child->getLeadingMargin(crossAxis, availableInnerWidth)
+ .unwrap(),
+ pos[crossAxis]);
+
+ // Remeasure child with the line height as it as been only
+ // measured with the owners height yet.
+ if (!YGNodeIsStyleDimDefined(
+ child, crossAxis, availableInnerCrossDim)) {
+ const float childWidth = isMainAxisRow
+ ? (child->getLayout()
+ .measuredDimensions[YGDimensionWidth] +
+ child->getMarginForAxis(mainAxis, availableInnerWidth)
+ .unwrap())
+ : lineHeight;
+
+ const float childHeight = !isMainAxisRow
+ ? (child->getLayout()
+ .measuredDimensions[YGDimensionHeight] +
+ child->getMarginForAxis(crossAxis, availableInnerWidth)
+ .unwrap())
+ : lineHeight;
+
+ if (!(YGFloatsEqual(
+ childWidth,
+ child->getLayout()
+ .measuredDimensions[YGDimensionWidth]) &&
+ YGFloatsEqual(
+ childHeight,
+ child->getLayout()
+ .measuredDimensions[YGDimensionHeight]))) {
+ YGLayoutNodeInternal(
+ child,
+ childWidth,
+ childHeight,
+ direction,
+ YGMeasureModeExactly,
+ YGMeasureModeExactly,
+ availableInnerWidth,
+ availableInnerHeight,
+ true,
+ LayoutPassReason::kMultilineStretch,
+ config,
+ layoutMarkerData,
+ layoutContext,
+ depth,
+ generationCount);
+ }
+ }
+ break;
+ }
+ case YGAlignBaseline: {
+ child->setLayoutPosition(
+ currentLead + maxAscentForCurrentLine -
+ YGBaseline(child, layoutContext) +
+ child
+ ->getLeadingPosition(
+ YGFlexDirectionColumn, availableInnerCrossDim)
+ .unwrap(),
+ YGEdgeTop);
+
+ break;
+ }
+ case YGAlignAuto:
+ case YGAlignSpaceBetween:
+ case YGAlignSpaceAround:
+ break;
+ }
+ }
+ }
+ }
+ currentLead += lineHeight;
+ }
+ }
+
+ // STEP 9: COMPUTING FINAL DIMENSIONS
+
+ node->setLayoutMeasuredDimension(
+ YGNodeBoundAxis(
+ node,
+ YGFlexDirectionRow,
+ availableWidth - marginAxisRow,
+ ownerWidth,
+ ownerWidth),
+ YGDimensionWidth);
+
+ node->setLayoutMeasuredDimension(
+ YGNodeBoundAxis(
+ node,
+ YGFlexDirectionColumn,
+ availableHeight - marginAxisColumn,
+ ownerHeight,
+ ownerWidth),
+ YGDimensionHeight);
+
+ // If the user didn't specify a width or height for the node, set the
+ // dimensions based on the children.
+ if (measureModeMainDim == YGMeasureModeUndefined ||
+ (node->getStyle().overflow() != YGOverflowScroll &&
+ measureModeMainDim == YGMeasureModeAtMost)) {
+ // Clamp the size to the min/max size, if specified, and make sure it
+ // doesn't go below the padding and border amount.
+ node->setLayoutMeasuredDimension(
+ YGNodeBoundAxis(
+ node, mainAxis, maxLineMainDim, mainAxisownerSize, ownerWidth),
+ dim[mainAxis]);
+
+ } else if (
+ measureModeMainDim == YGMeasureModeAtMost &&
+ node->getStyle().overflow() == YGOverflowScroll) {
+ node->setLayoutMeasuredDimension(
+ YGFloatMax(
+ YGFloatMin(
+ availableInnerMainDim + paddingAndBorderAxisMain,
+ YGNodeBoundAxisWithinMinAndMax(
+ node,
+ mainAxis,
+ YGFloatOptional{maxLineMainDim},
+ mainAxisownerSize)
+ .unwrap()),
+ paddingAndBorderAxisMain),
+ dim[mainAxis]);
+ }
+
+ if (measureModeCrossDim == YGMeasureModeUndefined ||
+ (node->getStyle().overflow() != YGOverflowScroll &&
+ measureModeCrossDim == YGMeasureModeAtMost)) {
+ // Clamp the size to the min/max size, if specified, and make sure it
+ // doesn't go below the padding and border amount.
+ node->setLayoutMeasuredDimension(
+ YGNodeBoundAxis(
+ node,
+ crossAxis,
+ totalLineCrossDim + paddingAndBorderAxisCross,
+ crossAxisownerSize,
+ ownerWidth),
+ dim[crossAxis]);
+
+ } else if (
+ measureModeCrossDim == YGMeasureModeAtMost &&
+ node->getStyle().overflow() == YGOverflowScroll) {
+ node->setLayoutMeasuredDimension(
+ YGFloatMax(
+ YGFloatMin(
+ availableInnerCrossDim + paddingAndBorderAxisCross,
+ YGNodeBoundAxisWithinMinAndMax(
+ node,
+ crossAxis,
+ YGFloatOptional{
+ totalLineCrossDim + paddingAndBorderAxisCross},
+ crossAxisownerSize)
+ .unwrap()),
+ paddingAndBorderAxisCross),
+ dim[crossAxis]);
+ }
+
+ // As we only wrapped in normal direction yet, we need to reverse the
+ // positions on wrap-reverse.
+ if (performLayout && node->getStyle().flexWrap() == YGWrapWrapReverse) {
+ for (uint32_t i = 0; i < childCount; i++) {
+ const YGNodeRef child = YGNodeGetChild(node, i);
+ if (child->getStyle().positionType() != YGPositionTypeAbsolute) {
+ child->setLayoutPosition(
+ node->getLayout().measuredDimensions[dim[crossAxis]] -
+ child->getLayout().position[pos[crossAxis]] -
+ child->getLayout().measuredDimensions[dim[crossAxis]],
+ pos[crossAxis]);
+ }
+ }
+ }
+
+ if (performLayout) {
+ // STEP 10: SIZING AND POSITIONING ABSOLUTE CHILDREN
+ for (auto child : node->getChildren()) {
+ if (child->getStyle().display() == YGDisplayNone ||
+ child->getStyle().positionType() != YGPositionTypeAbsolute) {
+ continue;
+ }
+ const bool absolutePercentageAgainstPaddingEdge =
+ node->getConfig()->isExperimentalFeatureEnabled(
+ YGExperimentalFeatureAbsolutePercentageAgainstPaddingEdge);
+
+ YGNodeAbsoluteLayoutChild(
+ node,
+ child,
+ absolutePercentageAgainstPaddingEdge
+ ? node->getLayout().measuredDimensions[YGDimensionWidth]
+ : availableInnerWidth,
+ isMainAxisRow ? measureModeMainDim : measureModeCrossDim,
+ absolutePercentageAgainstPaddingEdge
+ ? node->getLayout().measuredDimensions[YGDimensionHeight]
+ : availableInnerHeight,
+ direction,
+ config,
+ layoutMarkerData,
+ layoutContext,
+ depth,
+ generationCount);
+ }
+
+ // STEP 11: SETTING TRAILING POSITIONS FOR CHILDREN
+ const bool needsMainTrailingPos = mainAxis == YGFlexDirectionRowReverse ||
+ mainAxis == YGFlexDirectionColumnReverse;
+ const bool needsCrossTrailingPos = crossAxis == YGFlexDirectionRowReverse ||
+ crossAxis == YGFlexDirectionColumnReverse;
+
+ // Set trailing position if necessary.
+ if (needsMainTrailingPos || needsCrossTrailingPos) {
+ for (uint32_t i = 0; i < childCount; i++) {
+ const YGNodeRef child = node->getChild(i);
+ if (child->getStyle().display() == YGDisplayNone) {
+ continue;
+ }
+ if (needsMainTrailingPos) {
+ YGNodeSetChildTrailingPosition(node, child, mainAxis);
+ }
+
+ if (needsCrossTrailingPos) {
+ YGNodeSetChildTrailingPosition(node, child, crossAxis);
+ }
+ }
+ }
+ }
+}
+
+bool gPrintChanges = false;
+bool gPrintSkips = false;
+
+static const char* spacer =
+ " ";
+
+static const char* YGSpacer(const unsigned long level) {
+ const size_t spacerLen = strlen(spacer);
+ if (level > spacerLen) {
+ return &spacer[0];
+ } else {
+ return &spacer[spacerLen - level];
+ }
+}
+
+static const char* YGMeasureModeName(
+ const YGMeasureMode mode,
+ const bool performLayout) {
+ constexpr auto N = enums::count<YGMeasureMode>();
+ const char* kMeasureModeNames[N] = {"UNDEFINED", "EXACTLY", "AT_MOST"};
+ const char* kLayoutModeNames[N] = {
+ "LAY_UNDEFINED", "LAY_EXACTLY", "LAY_AT_MOST"};
+
+ if (mode >= N) {
+ return "";
+ }
+
+ return performLayout ? kLayoutModeNames[mode] : kMeasureModeNames[mode];
+}
+
+static inline bool YGMeasureModeSizeIsExactAndMatchesOldMeasuredSize(
+ YGMeasureMode sizeMode,
+ float size,
+ float lastComputedSize) {
+ return sizeMode == YGMeasureModeExactly &&
+ YGFloatsEqual(size, lastComputedSize);
+}
+
+static inline bool YGMeasureModeOldSizeIsUnspecifiedAndStillFits(
+ YGMeasureMode sizeMode,
+ float size,
+ YGMeasureMode lastSizeMode,
+ float lastComputedSize) {
+ return sizeMode == YGMeasureModeAtMost &&
+ lastSizeMode == YGMeasureModeUndefined &&
+ (size >= lastComputedSize || YGFloatsEqual(size, lastComputedSize));
+}
+
+static inline bool YGMeasureModeNewMeasureSizeIsStricterAndStillValid(
+ YGMeasureMode sizeMode,
+ float size,
+ YGMeasureMode lastSizeMode,
+ float lastSize,
+ float lastComputedSize) {
+ return lastSizeMode == YGMeasureModeAtMost &&
+ sizeMode == YGMeasureModeAtMost && !YGFloatIsUndefined(lastSize) &&
+ !YGFloatIsUndefined(size) && !YGFloatIsUndefined(lastComputedSize) &&
+ lastSize > size &&
+ (lastComputedSize <= size || YGFloatsEqual(size, lastComputedSize));
+}
+
+YOGA_EXPORT float YGRoundValueToPixelGrid(
+ const double value,
+ const double pointScaleFactor,
+ const bool forceCeil,
+ const bool forceFloor) {
+ double scaledValue = value * pointScaleFactor;
+ // We want to calculate `fractial` such that `floor(scaledValue) = scaledValue
+ // - fractial`.
+ double fractial = fmod(scaledValue, 1.0);
+ if (fractial < 0) {
+ // This branch is for handling negative numbers for `value`.
+ //
+ // Regarding `floor` and `ceil`. Note that for a number x, `floor(x) <= x <=
+ // ceil(x)` even for negative numbers. Here are a couple of examples:
+ // - x = 2.2: floor( 2.2) = 2, ceil( 2.2) = 3
+ // - x = -2.2: floor(-2.2) = -3, ceil(-2.2) = -2
+ //
+ // Regarding `fmodf`. For fractional negative numbers, `fmodf` returns a
+ // negative number. For example, `fmodf(-2.2) = -0.2`. However, we want
+ // `fractial` to be the number such that subtracting it from `value` will
+ // give us `floor(value)`. In the case of negative numbers, adding 1 to
+ // `fmodf(value)` gives us this. Let's continue the example from above:
+ // - fractial = fmodf(-2.2) = -0.2
+ // - Add 1 to the fraction: fractial2 = fractial + 1 = -0.2 + 1 = 0.8
+ // - Finding the `floor`: -2.2 - fractial2 = -2.2 - 0.8 = -3
+ ++fractial;
+ }
+ if (YGDoubleEqual(fractial, 0)) {
+ // First we check if the value is already rounded
+ scaledValue = scaledValue - fractial;
+ } else if (YGDoubleEqual(fractial, 1.0)) {
+ scaledValue = scaledValue - fractial + 1.0;
+ } else if (forceCeil) {
+ // Next we check if we need to use forced rounding
+ scaledValue = scaledValue - fractial + 1.0;
+ } else if (forceFloor) {
+ scaledValue = scaledValue - fractial;
+ } else {
+ // Finally we just round the value
+ scaledValue = scaledValue - fractial +
+ (!YGDoubleIsUndefined(fractial) &&
+ (fractial > 0.5 || YGDoubleEqual(fractial, 0.5))
+ ? 1.0
+ : 0.0);
+ }
+ return (YGDoubleIsUndefined(scaledValue) ||
+ YGDoubleIsUndefined(pointScaleFactor))
+ ? YGUndefined
+ : (float) (scaledValue / pointScaleFactor);
+}
+
+YOGA_EXPORT bool YGNodeCanUseCachedMeasurement(
+ const YGMeasureMode widthMode,
+ const float width,
+ const YGMeasureMode heightMode,
+ const float height,
+ const YGMeasureMode lastWidthMode,
+ const float lastWidth,
+ const YGMeasureMode lastHeightMode,
+ const float lastHeight,
+ const float lastComputedWidth,
+ const float lastComputedHeight,
+ const float marginRow,
+ const float marginColumn,
+ const YGConfigRef config) {
+ if ((!YGFloatIsUndefined(lastComputedHeight) && lastComputedHeight < 0) ||
+ (!YGFloatIsUndefined(lastComputedWidth) && lastComputedWidth < 0)) {
+ return false;
+ }
+ bool useRoundedComparison =
+ config != nullptr && config->getPointScaleFactor() != 0;
+ const float effectiveWidth = useRoundedComparison
+ ? YGRoundValueToPixelGrid(
+ width, config->getPointScaleFactor(), false, false)
+ : width;
+ const float effectiveHeight = useRoundedComparison
+ ? YGRoundValueToPixelGrid(
+ height, config->getPointScaleFactor(), false, false)
+ : height;
+ const float effectiveLastWidth = useRoundedComparison
+ ? YGRoundValueToPixelGrid(
+ lastWidth, config->getPointScaleFactor(), false, false)
+ : lastWidth;
+ const float effectiveLastHeight = useRoundedComparison
+ ? YGRoundValueToPixelGrid(
+ lastHeight, config->getPointScaleFactor(), false, false)
+ : lastHeight;
+
+ const bool hasSameWidthSpec = lastWidthMode == widthMode &&
+ YGFloatsEqual(effectiveLastWidth, effectiveWidth);
+ const bool hasSameHeightSpec = lastHeightMode == heightMode &&
+ YGFloatsEqual(effectiveLastHeight, effectiveHeight);
+
+ const bool widthIsCompatible =
+ hasSameWidthSpec ||
+ YGMeasureModeSizeIsExactAndMatchesOldMeasuredSize(
+ widthMode, width - marginRow, lastComputedWidth) ||
+ YGMeasureModeOldSizeIsUnspecifiedAndStillFits(
+ widthMode, width - marginRow, lastWidthMode, lastComputedWidth) ||
+ YGMeasureModeNewMeasureSizeIsStricterAndStillValid(
+ widthMode,
+ width - marginRow,
+ lastWidthMode,
+ lastWidth,
+ lastComputedWidth);
+
+ const bool heightIsCompatible =
+ hasSameHeightSpec ||
+ YGMeasureModeSizeIsExactAndMatchesOldMeasuredSize(
+ heightMode, height - marginColumn, lastComputedHeight) ||
+ YGMeasureModeOldSizeIsUnspecifiedAndStillFits(
+ heightMode,
+ height - marginColumn,
+ lastHeightMode,
+ lastComputedHeight) ||
+ YGMeasureModeNewMeasureSizeIsStricterAndStillValid(
+ heightMode,
+ height - marginColumn,
+ lastHeightMode,
+ lastHeight,
+ lastComputedHeight);
+
+ return widthIsCompatible && heightIsCompatible;
+}
+
+//
+// This is a wrapper around the YGNodelayoutImpl function. It determines whether
+// the layout request is redundant and can be skipped.
+//
+// Parameters:
+// Input parameters are the same as YGNodelayoutImpl (see above)
+// Return parameter is true if layout was performed, false if skipped
+//
+bool YGLayoutNodeInternal(
+ const YGNodeRef node,
+ const float availableWidth,
+ const float availableHeight,
+ const YGDirection ownerDirection,
+ const YGMeasureMode widthMeasureMode,
+ const YGMeasureMode heightMeasureMode,
+ const float ownerWidth,
+ const float ownerHeight,
+ const bool performLayout,
+ const LayoutPassReason reason,
+ const YGConfigRef config,
+ LayoutData& layoutMarkerData,
+ void* const layoutContext,
+ uint32_t depth,
+ const uint32_t generationCount) {
+ YGLayout* layout = &node->getLayout();
+
+ depth++;
+
+ const bool needToVisitNode =
+ (node->isDirty() && layout->generationCount != generationCount) ||
+ layout->lastOwnerDirection != ownerDirection;
+
+ if (needToVisitNode) {
+ // Invalidate the cached results.
+ layout->nextCachedMeasurementsIndex = 0;
+ layout->cachedLayout.availableWidth = -1;
+ layout->cachedLayout.availableHeight = -1;
+ layout->cachedLayout.widthMeasureMode = YGMeasureModeUndefined;
+ layout->cachedLayout.heightMeasureMode = YGMeasureModeUndefined;
+ layout->cachedLayout.computedWidth = -1;
+ layout->cachedLayout.computedHeight = -1;
+ }
+
+ YGCachedMeasurement* cachedResults = nullptr;
+
+ // Determine whether the results are already cached. We maintain a separate
+ // cache for layouts and measurements. A layout operation modifies the
+ // positions and dimensions for nodes in the subtree. The algorithm assumes
+ // that each node gets laid out a maximum of one time per tree layout, but
+ // multiple measurements may be required to resolve all of the flex
+ // dimensions. We handle nodes with measure functions specially here because
+ // they are the most expensive to measure, so it's worth avoiding redundant
+ // measurements if at all possible.
+ if (node->hasMeasureFunc()) {
+ const float marginAxisRow =
+ node->getMarginForAxis(YGFlexDirectionRow, ownerWidth).unwrap();
+ const float marginAxisColumn =
+ node->getMarginForAxis(YGFlexDirectionColumn, ownerWidth).unwrap();
+
+ // First, try to use the layout cache.
+ if (YGNodeCanUseCachedMeasurement(
+ widthMeasureMode,
+ availableWidth,
+ heightMeasureMode,
+ availableHeight,
+ layout->cachedLayout.widthMeasureMode,
+ layout->cachedLayout.availableWidth,
+ layout->cachedLayout.heightMeasureMode,
+ layout->cachedLayout.availableHeight,
+ layout->cachedLayout.computedWidth,
+ layout->cachedLayout.computedHeight,
+ marginAxisRow,
+ marginAxisColumn,
+ config)) {
+ cachedResults = &layout->cachedLayout;
+ } else {
+ // Try to use the measurement cache.
+ for (uint32_t i = 0; i < layout->nextCachedMeasurementsIndex; i++) {
+ if (YGNodeCanUseCachedMeasurement(
+ widthMeasureMode,
+ availableWidth,
+ heightMeasureMode,
+ availableHeight,
+ layout->cachedMeasurements[i].widthMeasureMode,
+ layout->cachedMeasurements[i].availableWidth,
+ layout->cachedMeasurements[i].heightMeasureMode,
+ layout->cachedMeasurements[i].availableHeight,
+ layout->cachedMeasurements[i].computedWidth,
+ layout->cachedMeasurements[i].computedHeight,
+ marginAxisRow,
+ marginAxisColumn,
+ config)) {
+ cachedResults = &layout->cachedMeasurements[i];
+ break;
+ }
+ }
+ }
+ } else if (performLayout) {
+ if (YGFloatsEqual(layout->cachedLayout.availableWidth, availableWidth) &&
+ YGFloatsEqual(layout->cachedLayout.availableHeight, availableHeight) &&
+ layout->cachedLayout.widthMeasureMode == widthMeasureMode &&
+ layout->cachedLayout.heightMeasureMode == heightMeasureMode) {
+ cachedResults = &layout->cachedLayout;
+ }
+ } else {
+ for (uint32_t i = 0; i < layout->nextCachedMeasurementsIndex; i++) {
+ if (YGFloatsEqual(
+ layout->cachedMeasurements[i].availableWidth, availableWidth) &&
+ YGFloatsEqual(
+ layout->cachedMeasurements[i].availableHeight, availableHeight) &&
+ layout->cachedMeasurements[i].widthMeasureMode == widthMeasureMode &&
+ layout->cachedMeasurements[i].heightMeasureMode ==
+ heightMeasureMode) {
+ cachedResults = &layout->cachedMeasurements[i];
+ break;
+ }
+ }
+ }
+
+ if (!needToVisitNode && cachedResults != nullptr) {
+ layout->measuredDimensions[YGDimensionWidth] = cachedResults->computedWidth;
+ layout->measuredDimensions[YGDimensionHeight] =
+ cachedResults->computedHeight;
+
+ (performLayout ? layoutMarkerData.cachedLayouts
+ : layoutMarkerData.cachedMeasures) += 1;
+
+ if (gPrintChanges && gPrintSkips) {
+ Log::log(
+ node,
+ YGLogLevelVerbose,
+ nullptr,
+ "%s%d.{[skipped] ",
+ YGSpacer(depth),
+ depth);
+ node->print(layoutContext);
+ Log::log(
+ node,
+ YGLogLevelVerbose,
+ nullptr,
+ "wm: %s, hm: %s, aw: %f ah: %f => d: (%f, %f) %s\n",
+ YGMeasureModeName(widthMeasureMode, performLayout),
+ YGMeasureModeName(heightMeasureMode, performLayout),
+ availableWidth,
+ availableHeight,
+ cachedResults->computedWidth,
+ cachedResults->computedHeight,
+ LayoutPassReasonToString(reason));
+ }
+ } else {
+ if (gPrintChanges) {
+ Log::log(
+ node,
+ YGLogLevelVerbose,
+ nullptr,
+ "%s%d.{%s",
+ YGSpacer(depth),
+ depth,
+ needToVisitNode ? "*" : "");
+ node->print(layoutContext);
+ Log::log(
+ node,
+ YGLogLevelVerbose,
+ nullptr,
+ "wm: %s, hm: %s, aw: %f ah: %f %s\n",
+ YGMeasureModeName(widthMeasureMode, performLayout),
+ YGMeasureModeName(heightMeasureMode, performLayout),
+ availableWidth,
+ availableHeight,
+ LayoutPassReasonToString(reason));
+ }
+
+ YGNodelayoutImpl(
+ node,
+ availableWidth,
+ availableHeight,
+ ownerDirection,
+ widthMeasureMode,
+ heightMeasureMode,
+ ownerWidth,
+ ownerHeight,
+ performLayout,
+ config,
+ layoutMarkerData,
+ layoutContext,
+ depth,
+ generationCount,
+ reason);
+
+ if (gPrintChanges) {
+ Log::log(
+ node,
+ YGLogLevelVerbose,
+ nullptr,
+ "%s%d.}%s",
+ YGSpacer(depth),
+ depth,
+ needToVisitNode ? "*" : "");
+ node->print(layoutContext);
+ Log::log(
+ node,
+ YGLogLevelVerbose,
+ nullptr,
+ "wm: %s, hm: %s, d: (%f, %f) %s\n",
+ YGMeasureModeName(widthMeasureMode, performLayout),
+ YGMeasureModeName(heightMeasureMode, performLayout),
+ layout->measuredDimensions[YGDimensionWidth],
+ layout->measuredDimensions[YGDimensionHeight],
+ LayoutPassReasonToString(reason));
+ }
+
+ layout->lastOwnerDirection = ownerDirection;
+
+ if (cachedResults == nullptr) {
+ if (layout->nextCachedMeasurementsIndex + 1 >
+ (uint32_t) layoutMarkerData.maxMeasureCache) {
+ layoutMarkerData.maxMeasureCache =
+ layout->nextCachedMeasurementsIndex + 1;
+ }
+ if (layout->nextCachedMeasurementsIndex == YG_MAX_CACHED_RESULT_COUNT) {
+ if (gPrintChanges) {
+ Log::log(node, YGLogLevelVerbose, nullptr, "Out of cache entries!\n");
+ }
+ layout->nextCachedMeasurementsIndex = 0;
+ }
+
+ YGCachedMeasurement* newCacheEntry;
+ if (performLayout) {
+ // Use the single layout cache entry.
+ newCacheEntry = &layout->cachedLayout;
+ } else {
+ // Allocate a new measurement cache entry.
+ newCacheEntry =
+ &layout->cachedMeasurements[layout->nextCachedMeasurementsIndex];
+ layout->nextCachedMeasurementsIndex++;
+ }
+
+ newCacheEntry->availableWidth = availableWidth;
+ newCacheEntry->availableHeight = availableHeight;
+ newCacheEntry->widthMeasureMode = widthMeasureMode;
+ newCacheEntry->heightMeasureMode = heightMeasureMode;
+ newCacheEntry->computedWidth =
+ layout->measuredDimensions[YGDimensionWidth];
+ newCacheEntry->computedHeight =
+ layout->measuredDimensions[YGDimensionHeight];
+ }
+ }
+
+ if (performLayout) {
+ node->setLayoutDimension(
+ node->getLayout().measuredDimensions[YGDimensionWidth],
+ YGDimensionWidth);
+ node->setLayoutDimension(
+ node->getLayout().measuredDimensions[YGDimensionHeight],
+ YGDimensionHeight);
+
+ node->setHasNewLayout(true);
+ node->setDirty(false);
+ }
+
+ layout->generationCount = generationCount;
+
+ LayoutType layoutType;
+ if (performLayout) {
+ layoutType = !needToVisitNode && cachedResults == &layout->cachedLayout
+ ? LayoutType::kCachedLayout
+ : LayoutType::kLayout;
+ } else {
+ layoutType = cachedResults != nullptr ? LayoutType::kCachedMeasure
+ : LayoutType::kMeasure;
+ }
+ Event::publish<Event::NodeLayout>(node, {layoutType, layoutContext});
+
+ return (needToVisitNode || cachedResults == nullptr);
+}
+
+YOGA_EXPORT void YGConfigSetPointScaleFactor(
+ const YGConfigRef config,
+ const float pixelsInPoint) {
+ YGAssertWithConfig(
+ config,
+ pixelsInPoint >= 0.0f,
+ "Scale factor should not be less than zero");
+
+ // We store points for Pixel as we will use it for rounding
+ if (pixelsInPoint == 0.0f) {
+ // Zero is used to skip rounding
+ config->setPointScaleFactor(0.0f);
+ } else {
+ config->setPointScaleFactor(pixelsInPoint);
+ }
+}
+
+YOGA_EXPORT float YGConfigGetPointScaleFactor(const YGConfigRef config) {
+ return config->getPointScaleFactor();
+}
+
+static void YGRoundToPixelGrid(
+ const YGNodeRef node,
+ const double pointScaleFactor,
+ const double absoluteLeft,
+ const double absoluteTop) {
+ if (pointScaleFactor == 0.0f) {
+ return;
+ }
+
+ const double nodeLeft = node->getLayout().position[YGEdgeLeft];
+ const double nodeTop = node->getLayout().position[YGEdgeTop];
+
+ const double nodeWidth = node->getLayout().dimensions[YGDimensionWidth];
+ const double nodeHeight = node->getLayout().dimensions[YGDimensionHeight];
+
+ const double absoluteNodeLeft = absoluteLeft + nodeLeft;
+ const double absoluteNodeTop = absoluteTop + nodeTop;
+
+ const double absoluteNodeRight = absoluteNodeLeft + nodeWidth;
+ const double absoluteNodeBottom = absoluteNodeTop + nodeHeight;
+
+ // If a node has a custom measure function we never want to round down its
+ // size as this could lead to unwanted text truncation.
+ const bool textRounding = node->getNodeType() == YGNodeTypeText;
+
+ node->setLayoutPosition(
+ YGRoundValueToPixelGrid(nodeLeft, pointScaleFactor, false, textRounding),
+ YGEdgeLeft);
+
+ node->setLayoutPosition(
+ YGRoundValueToPixelGrid(nodeTop, pointScaleFactor, false, textRounding),
+ YGEdgeTop);
+
+ // We multiply dimension by scale factor and if the result is close to the
+ // whole number, we don't have any fraction To verify if the result is close
+ // to whole number we want to check both floor and ceil numbers
+ const bool hasFractionalWidth =
+ !YGDoubleEqual(fmod(nodeWidth * pointScaleFactor, 1.0), 0) &&
+ !YGDoubleEqual(fmod(nodeWidth * pointScaleFactor, 1.0), 1.0);
+ const bool hasFractionalHeight =
+ !YGDoubleEqual(fmod(nodeHeight * pointScaleFactor, 1.0), 0) &&
+ !YGDoubleEqual(fmod(nodeHeight * pointScaleFactor, 1.0), 1.0);
+
+ node->setLayoutDimension(
+ YGRoundValueToPixelGrid(
+ absoluteNodeRight,
+ pointScaleFactor,
+ (textRounding && hasFractionalWidth),
+ (textRounding && !hasFractionalWidth)) -
+ YGRoundValueToPixelGrid(
+ absoluteNodeLeft, pointScaleFactor, false, textRounding),
+ YGDimensionWidth);
+
+ node->setLayoutDimension(
+ YGRoundValueToPixelGrid(
+ absoluteNodeBottom,
+ pointScaleFactor,
+ (textRounding && hasFractionalHeight),
+ (textRounding && !hasFractionalHeight)) -
+ YGRoundValueToPixelGrid(
+ absoluteNodeTop, pointScaleFactor, false, textRounding),
+ YGDimensionHeight);
+
+ const uint32_t childCount = YGNodeGetChildCount(node);
+ for (uint32_t i = 0; i < childCount; i++) {
+ YGRoundToPixelGrid(
+ YGNodeGetChild(node, i),
+ pointScaleFactor,
+ absoluteNodeLeft,
+ absoluteNodeTop);
+ }
+}
+
+YOGA_EXPORT void YGNodeCalculateLayoutWithContext(
+ const YGNodeRef node,
+ const float ownerWidth,
+ const float ownerHeight,
+ const YGDirection ownerDirection,
+ void* layoutContext) {
+
+ Event::publish<Event::LayoutPassStart>(node, {layoutContext});
+ LayoutData markerData = {};
+
+ // Increment the generation count. This will force the recursive routine to
+ // visit all dirty nodes at least once. Subsequent visits will be skipped if
+ // the input parameters don't change.
+ gCurrentGenerationCount.fetch_add(1, std::memory_order_relaxed);
+ node->resolveDimension();
+ float width = YGUndefined;
+ YGMeasureMode widthMeasureMode = YGMeasureModeUndefined;
+ const auto& maxDimensions = node->getStyle().maxDimensions();
+ if (YGNodeIsStyleDimDefined(node, YGFlexDirectionRow, ownerWidth)) {
+ width =
+ (YGResolveValue(
+ node->getResolvedDimension(dim[YGFlexDirectionRow]), ownerWidth) +
+ node->getMarginForAxis(YGFlexDirectionRow, ownerWidth))
+ .unwrap();
+ widthMeasureMode = YGMeasureModeExactly;
+ } else if (!YGResolveValue(maxDimensions[YGDimensionWidth], ownerWidth)
+ .isUndefined()) {
+ width =
+ YGResolveValue(maxDimensions[YGDimensionWidth], ownerWidth).unwrap();
+ widthMeasureMode = YGMeasureModeAtMost;
+ } else {
+ width = ownerWidth;
+ widthMeasureMode = YGFloatIsUndefined(width) ? YGMeasureModeUndefined
+ : YGMeasureModeExactly;
+ }
+
+ float height = YGUndefined;
+ YGMeasureMode heightMeasureMode = YGMeasureModeUndefined;
+ if (YGNodeIsStyleDimDefined(node, YGFlexDirectionColumn, ownerHeight)) {
+ height = (YGResolveValue(
+ node->getResolvedDimension(dim[YGFlexDirectionColumn]),
+ ownerHeight) +
+ node->getMarginForAxis(YGFlexDirectionColumn, ownerWidth))
+ .unwrap();
+ heightMeasureMode = YGMeasureModeExactly;
+ } else if (!YGResolveValue(maxDimensions[YGDimensionHeight], ownerHeight)
+ .isUndefined()) {
+ height =
+ YGResolveValue(maxDimensions[YGDimensionHeight], ownerHeight).unwrap();
+ heightMeasureMode = YGMeasureModeAtMost;
+ } else {
+ height = ownerHeight;
+ heightMeasureMode = YGFloatIsUndefined(height) ? YGMeasureModeUndefined
+ : YGMeasureModeExactly;
+ }
+ if (YGLayoutNodeInternal(
+ node,
+ width,
+ height,
+ ownerDirection,
+ widthMeasureMode,
+ heightMeasureMode,
+ ownerWidth,
+ ownerHeight,
+ true,
+ LayoutPassReason::kInitial,
+ node->getConfig(),
+ markerData,
+ layoutContext,
+ 0, // tree root
+ gCurrentGenerationCount.load(std::memory_order_relaxed))) {
+ node->setPosition(
+ node->getLayout().direction(), ownerWidth, ownerHeight, ownerWidth);
+ YGRoundToPixelGrid(
+ node, node->getConfig()->getPointScaleFactor(), 0.0f, 0.0f);
+
+#ifdef DEBUG
+ if (node->getConfig()->shouldPrintTree()) {
+ YGNodePrint(
+ node,
+ (YGPrintOptions) (YGPrintOptionsLayout | YGPrintOptionsChildren | YGPrintOptionsStyle));
+ }
+#endif
+ }
+
+ Event::publish<Event::LayoutPassEnd>(node, {layoutContext, &markerData});
+}
+
+YOGA_EXPORT void YGNodeCalculateLayout(
+ const YGNodeRef node,
+ const float ownerWidth,
+ const float ownerHeight,
+ const YGDirection ownerDirection) {
+ YGNodeCalculateLayoutWithContext(
+ node, ownerWidth, ownerHeight, ownerDirection, nullptr);
+}
+
+YOGA_EXPORT void YGConfigSetLogger(const YGConfigRef config, YGLogger logger) {
+ if (logger != nullptr) {
+ config->setLogger(logger);
+ } else {
+#ifdef ANDROID
+ config->setLogger(&YGAndroidLog);
+#else
+ config->setLogger(&YGDefaultLog);
+#endif
+ }
+}
+
+void YGAssert(const bool condition, const char* message) {
+ if (!condition) {
+ Log::log(YGNodeRef{nullptr}, YGLogLevelFatal, nullptr, "%s\n", message);
+ throwLogicalErrorWithMessage(message);
+ }
+}
+
+void YGAssertWithNode(
+ const YGNodeRef node,
+ const bool condition,
+ const char* message) {
+ if (!condition) {
+ Log::log(node, YGLogLevelFatal, nullptr, "%s\n", message);
+ throwLogicalErrorWithMessage(message);
+ }
+}
+
+void YGAssertWithConfig(
+ const YGConfigRef config,
+ const bool condition,
+ const char* message) {
+ if (!condition) {
+ Log::log(config, YGLogLevelFatal, nullptr, "%s\n", message);
+ throwLogicalErrorWithMessage(message);
+ }
+}
+
+YOGA_EXPORT void YGConfigSetExperimentalFeatureEnabled(
+ const YGConfigRef config,
+ const YGExperimentalFeature feature,
+ const bool enabled) {
+ config->setExperimentalFeatureEnabled(feature, enabled);
+}
+
+YOGA_EXPORT bool YGConfigIsExperimentalFeatureEnabled(
+ const YGConfigRef config,
+ const YGExperimentalFeature feature) {
+ return config->isExperimentalFeatureEnabled(feature);
+}
+
+YOGA_EXPORT void YGConfigSetUseWebDefaults(
+ const YGConfigRef config,
+ const bool enabled) {
+ config->setUseWebDefaults(enabled);
+}
+
+YOGA_EXPORT bool YGConfigGetUseLegacyStretchBehaviour(
+ const YGConfigRef config) {
+ return config->hasErrata(YGErrataStretchFlexBasis);
+}
+
+YOGA_EXPORT void YGConfigSetUseLegacyStretchBehaviour(
+ const YGConfigRef config,
+ const bool useLegacyStretchBehaviour) {
+ if (useLegacyStretchBehaviour) {
+ config->addErrata(YGErrataStretchFlexBasis);
+ } else {
+ config->removeErrata(YGErrataStretchFlexBasis);
+ }
+}
+
+bool YGConfigGetUseWebDefaults(const YGConfigRef config) {
+ return config->useWebDefaults();
+}
+
+YOGA_EXPORT void YGConfigSetContext(const YGConfigRef config, void* context) {
+ config->setContext(context);
+}
+
+YOGA_EXPORT void* YGConfigGetContext(const YGConfigRef config) {
+ return config->getContext();
+}
+
+YOGA_EXPORT void YGConfigSetErrata(YGConfigRef config, YGErrata errata) {
+ config->setErrata(errata);
+}
+
+YOGA_EXPORT YGErrata YGConfigGetErrata(YGConfigRef config) {
+ return config->getErrata();
+}
+
+YOGA_EXPORT void YGConfigSetCloneNodeFunc(
+ const YGConfigRef config,
+ const YGCloneNodeFunc callback) {
+ config->setCloneNodeCallback(callback);
+}
diff --git a/src/3rdparty/yoga/Yoga.h b/src/3rdparty/yoga/Yoga.h
new file mode 100644
index 0000000000..834a6c5747
--- /dev/null
+++ b/src/3rdparty/yoga/Yoga.h
@@ -0,0 +1,381 @@
+/*
+ * Copyright (C) 2016 The Qt Company Ltd.
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#pragma once
+
+#include <assert.h>
+#include <math.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#ifndef __cplusplus
+#include <stdbool.h>
+#endif
+
+#include <yoga/YGEnums.h>
+#include <yoga/YGMacros.h>
+#include <yoga/YGValue.h>
+
+YG_EXTERN_C_BEGIN
+
+typedef struct YGSize {
+ float width;
+ float height;
+} YGSize;
+
+typedef struct YGConfig* YGConfigRef;
+
+typedef struct YGNode* YGNodeRef;
+typedef const struct YGNode* YGNodeConstRef;
+
+typedef YGSize (*YGMeasureFunc)(
+ YGNodeRef node,
+ float width,
+ YGMeasureMode widthMode,
+ float height,
+ YGMeasureMode heightMode);
+typedef float (*YGBaselineFunc)(YGNodeRef node, float width, float height);
+typedef void (*YGDirtiedFunc)(YGNodeRef node);
+typedef void (*YGPrintFunc)(YGNodeRef node);
+typedef void (*YGNodeCleanupFunc)(YGNodeRef node);
+typedef int (*YGLogger)(
+ YGConfigRef config,
+ YGNodeRef node,
+ YGLogLevel level,
+ const char* format,
+ va_list args);
+typedef YGNodeRef (
+ *YGCloneNodeFunc)(YGNodeRef oldNode, YGNodeRef owner, int childIndex);
+
+// YGNode
+WIN_EXPORT YGNodeRef YGNodeNew(void);
+WIN_EXPORT YGNodeRef YGNodeNewWithConfig(YGConfigRef config);
+WIN_EXPORT YGNodeRef YGNodeClone(YGNodeRef node);
+WIN_EXPORT void YGNodeFree(YGNodeRef node);
+WIN_EXPORT void YGNodeFreeRecursiveWithCleanupFunc(
+ YGNodeRef node,
+ YGNodeCleanupFunc cleanup);
+WIN_EXPORT void YGNodeFreeRecursive(YGNodeRef node);
+WIN_EXPORT void YGNodeReset(YGNodeRef node);
+
+WIN_EXPORT void YGNodeInsertChild(
+ YGNodeRef node,
+ YGNodeRef child,
+ uint32_t index);
+
+WIN_EXPORT void YGNodeSwapChild(
+ YGNodeRef node,
+ YGNodeRef child,
+ uint32_t index);
+
+WIN_EXPORT void YGNodeRemoveChild(YGNodeRef node, YGNodeRef child);
+WIN_EXPORT void YGNodeRemoveAllChildren(YGNodeRef node);
+WIN_EXPORT YGNodeRef YGNodeGetChild(YGNodeRef node, uint32_t index);
+WIN_EXPORT YGNodeRef YGNodeGetOwner(YGNodeRef node);
+WIN_EXPORT YGNodeRef YGNodeGetParent(YGNodeRef node);
+WIN_EXPORT uint32_t YGNodeGetChildCount(YGNodeRef node);
+WIN_EXPORT void YGNodeSetChildren(
+ YGNodeRef owner,
+ const YGNodeRef* children,
+ uint32_t count);
+
+WIN_EXPORT void YGNodeSetIsReferenceBaseline(
+ YGNodeRef node,
+ bool isReferenceBaseline);
+
+WIN_EXPORT bool YGNodeIsReferenceBaseline(YGNodeRef node);
+
+WIN_EXPORT void YGNodeCalculateLayout(
+ YGNodeRef node,
+ float availableWidth,
+ float availableHeight,
+ YGDirection ownerDirection);
+
+// Mark a node as dirty. Only valid for nodes with a custom measure function
+// set.
+//
+// Yoga knows when to mark all other nodes as dirty but because nodes with
+// measure functions depend on information not known to Yoga they must perform
+// this dirty marking manually.
+WIN_EXPORT void YGNodeMarkDirty(YGNodeRef node);
+
+// Marks the current node and all its descendants as dirty.
+//
+// Intended to be used for Yoga benchmarks. Don't use in production, as calling
+// `YGCalculateLayout` will cause the recalculation of each and every node.
+WIN_EXPORT void YGNodeMarkDirtyAndPropagateToDescendants(YGNodeRef node);
+
+WIN_EXPORT void YGNodePrint(YGNodeRef node, YGPrintOptions options);
+
+WIN_EXPORT bool YGFloatIsUndefined(float value);
+
+WIN_EXPORT bool YGNodeCanUseCachedMeasurement(
+ YGMeasureMode widthMode,
+ float width,
+ YGMeasureMode heightMode,
+ float height,
+ YGMeasureMode lastWidthMode,
+ float lastWidth,
+ YGMeasureMode lastHeightMode,
+ float lastHeight,
+ float lastComputedWidth,
+ float lastComputedHeight,
+ float marginRow,
+ float marginColumn,
+ YGConfigRef config);
+
+WIN_EXPORT void YGNodeCopyStyle(YGNodeRef dstNode, YGNodeRef srcNode);
+
+WIN_EXPORT void* YGNodeGetContext(YGNodeRef node);
+WIN_EXPORT void YGNodeSetContext(YGNodeRef node, void* context);
+
+WIN_EXPORT YGConfigRef YGNodeGetConfig(YGNodeRef node);
+WIN_EXPORT void YGNodeSetConfig(YGNodeRef node, YGConfigRef config);
+
+void YGConfigSetPrintTreeFlag(YGConfigRef config, bool enabled);
+bool YGNodeHasMeasureFunc(YGNodeRef node);
+WIN_EXPORT void YGNodeSetMeasureFunc(YGNodeRef node, YGMeasureFunc measureFunc);
+bool YGNodeHasBaselineFunc(YGNodeRef node);
+void YGNodeSetBaselineFunc(YGNodeRef node, YGBaselineFunc baselineFunc);
+YGDirtiedFunc YGNodeGetDirtiedFunc(YGNodeRef node);
+void YGNodeSetDirtiedFunc(YGNodeRef node, YGDirtiedFunc dirtiedFunc);
+void YGNodeSetPrintFunc(YGNodeRef node, YGPrintFunc printFunc);
+WIN_EXPORT bool YGNodeGetHasNewLayout(YGNodeRef node);
+WIN_EXPORT void YGNodeSetHasNewLayout(YGNodeRef node, bool hasNewLayout);
+YGNodeType YGNodeGetNodeType(YGNodeRef node);
+void YGNodeSetNodeType(YGNodeRef node, YGNodeType nodeType);
+WIN_EXPORT bool YGNodeIsDirty(YGNodeRef node);
+
+WIN_EXPORT void YGNodeStyleSetDirection(YGNodeRef node, YGDirection direction);
+WIN_EXPORT YGDirection YGNodeStyleGetDirection(YGNodeConstRef node);
+
+WIN_EXPORT void YGNodeStyleSetFlexDirection(
+ YGNodeRef node,
+ YGFlexDirection flexDirection);
+WIN_EXPORT YGFlexDirection YGNodeStyleGetFlexDirection(YGNodeConstRef node);
+
+WIN_EXPORT void YGNodeStyleSetJustifyContent(
+ YGNodeRef node,
+ YGJustify justifyContent);
+WIN_EXPORT YGJustify YGNodeStyleGetJustifyContent(YGNodeConstRef node);
+
+WIN_EXPORT void YGNodeStyleSetAlignContent(
+ YGNodeRef node,
+ YGAlign alignContent);
+WIN_EXPORT YGAlign YGNodeStyleGetAlignContent(YGNodeConstRef node);
+
+WIN_EXPORT void YGNodeStyleSetAlignItems(YGNodeRef node, YGAlign alignItems);
+WIN_EXPORT YGAlign YGNodeStyleGetAlignItems(YGNodeConstRef node);
+
+WIN_EXPORT void YGNodeStyleSetAlignSelf(YGNodeRef node, YGAlign alignSelf);
+WIN_EXPORT YGAlign YGNodeStyleGetAlignSelf(YGNodeConstRef node);
+
+WIN_EXPORT void YGNodeStyleSetPositionType(
+ YGNodeRef node,
+ YGPositionType positionType);
+WIN_EXPORT YGPositionType YGNodeStyleGetPositionType(YGNodeConstRef node);
+
+WIN_EXPORT void YGNodeStyleSetFlexWrap(YGNodeRef node, YGWrap flexWrap);
+WIN_EXPORT YGWrap YGNodeStyleGetFlexWrap(YGNodeConstRef node);
+
+WIN_EXPORT void YGNodeStyleSetOverflow(YGNodeRef node, YGOverflow overflow);
+WIN_EXPORT YGOverflow YGNodeStyleGetOverflow(YGNodeConstRef node);
+
+WIN_EXPORT void YGNodeStyleSetDisplay(YGNodeRef node, YGDisplay display);
+WIN_EXPORT YGDisplay YGNodeStyleGetDisplay(YGNodeConstRef node);
+
+WIN_EXPORT void YGNodeStyleSetFlex(YGNodeRef node, float flex);
+WIN_EXPORT float YGNodeStyleGetFlex(YGNodeConstRef node);
+
+WIN_EXPORT void YGNodeStyleSetFlexGrow(YGNodeRef node, float flexGrow);
+WIN_EXPORT float YGNodeStyleGetFlexGrow(YGNodeConstRef node);
+
+WIN_EXPORT void YGNodeStyleSetFlexShrink(YGNodeRef node, float flexShrink);
+WIN_EXPORT float YGNodeStyleGetFlexShrink(YGNodeConstRef node);
+
+WIN_EXPORT void YGNodeStyleSetFlexBasis(YGNodeRef node, float flexBasis);
+WIN_EXPORT void YGNodeStyleSetFlexBasisPercent(YGNodeRef node, float flexBasis);
+WIN_EXPORT void YGNodeStyleSetFlexBasisAuto(YGNodeRef node);
+WIN_EXPORT YGValue YGNodeStyleGetFlexBasis(YGNodeConstRef node);
+
+WIN_EXPORT void YGNodeStyleSetPosition(
+ YGNodeRef node,
+ YGEdge edge,
+ float position);
+WIN_EXPORT void YGNodeStyleSetPositionPercent(
+ YGNodeRef node,
+ YGEdge edge,
+ float position);
+WIN_EXPORT YGValue YGNodeStyleGetPosition(YGNodeConstRef node, YGEdge edge);
+
+WIN_EXPORT void YGNodeStyleSetMargin(YGNodeRef node, YGEdge edge, float margin);
+WIN_EXPORT void YGNodeStyleSetMarginPercent(
+ YGNodeRef node,
+ YGEdge edge,
+ float margin);
+WIN_EXPORT void YGNodeStyleSetMarginAuto(YGNodeRef node, YGEdge edge);
+WIN_EXPORT YGValue YGNodeStyleGetMargin(YGNodeConstRef node, YGEdge edge);
+
+WIN_EXPORT void YGNodeStyleSetPadding(
+ YGNodeRef node,
+ YGEdge edge,
+ float padding);
+WIN_EXPORT void YGNodeStyleSetPaddingPercent(
+ YGNodeRef node,
+ YGEdge edge,
+ float padding);
+WIN_EXPORT YGValue YGNodeStyleGetPadding(YGNodeConstRef node, YGEdge edge);
+
+WIN_EXPORT void YGNodeStyleSetBorder(YGNodeRef node, YGEdge edge, float border);
+WIN_EXPORT float YGNodeStyleGetBorder(YGNodeConstRef node, YGEdge edge);
+
+WIN_EXPORT void YGNodeStyleSetGap(
+ YGNodeRef node,
+ YGGutter gutter,
+ float gapLength);
+WIN_EXPORT float YGNodeStyleGetGap(YGNodeConstRef node, YGGutter gutter);
+
+WIN_EXPORT void YGNodeStyleSetWidth(YGNodeRef node, float width);
+WIN_EXPORT void YGNodeStyleSetWidthPercent(YGNodeRef node, float width);
+WIN_EXPORT void YGNodeStyleSetWidthAuto(YGNodeRef node);
+WIN_EXPORT YGValue YGNodeStyleGetWidth(YGNodeConstRef node);
+
+WIN_EXPORT void YGNodeStyleSetHeight(YGNodeRef node, float height);
+WIN_EXPORT void YGNodeStyleSetHeightPercent(YGNodeRef node, float height);
+WIN_EXPORT void YGNodeStyleSetHeightAuto(YGNodeRef node);
+WIN_EXPORT YGValue YGNodeStyleGetHeight(YGNodeConstRef node);
+
+WIN_EXPORT void YGNodeStyleSetMinWidth(YGNodeRef node, float minWidth);
+WIN_EXPORT void YGNodeStyleSetMinWidthPercent(YGNodeRef node, float minWidth);
+WIN_EXPORT YGValue YGNodeStyleGetMinWidth(YGNodeConstRef node);
+
+WIN_EXPORT void YGNodeStyleSetMinHeight(YGNodeRef node, float minHeight);
+WIN_EXPORT void YGNodeStyleSetMinHeightPercent(YGNodeRef node, float minHeight);
+WIN_EXPORT YGValue YGNodeStyleGetMinHeight(YGNodeConstRef node);
+
+WIN_EXPORT void YGNodeStyleSetMaxWidth(YGNodeRef node, float maxWidth);
+WIN_EXPORT void YGNodeStyleSetMaxWidthPercent(YGNodeRef node, float maxWidth);
+WIN_EXPORT YGValue YGNodeStyleGetMaxWidth(YGNodeConstRef node);
+
+WIN_EXPORT void YGNodeStyleSetMaxHeight(YGNodeRef node, float maxHeight);
+WIN_EXPORT void YGNodeStyleSetMaxHeightPercent(YGNodeRef node, float maxHeight);
+WIN_EXPORT YGValue YGNodeStyleGetMaxHeight(YGNodeConstRef node);
+
+// Yoga specific properties, not compatible with flexbox specification Aspect
+// ratio control the size of the undefined dimension of a node. Aspect ratio is
+// encoded as a floating point value width/height. e.g. A value of 2 leads to a
+// node with a width twice the size of its height while a value of 0.5 gives the
+// opposite effect.
+//
+// - On a node with a set width/height aspect ratio control the size of the
+// unset dimension
+// - On a node with a set flex basis aspect ratio controls the size of the node
+// in the cross axis if unset
+// - On a node with a measure function aspect ratio works as though the measure
+// function measures the flex basis
+// - On a node with flex grow/shrink aspect ratio controls the size of the node
+// in the cross axis if unset
+// - Aspect ratio takes min/max dimensions into account
+WIN_EXPORT void YGNodeStyleSetAspectRatio(YGNodeRef node, float aspectRatio);
+WIN_EXPORT float YGNodeStyleGetAspectRatio(YGNodeConstRef node);
+
+WIN_EXPORT float YGNodeLayoutGetLeft(YGNodeRef node);
+WIN_EXPORT float YGNodeLayoutGetTop(YGNodeRef node);
+WIN_EXPORT float YGNodeLayoutGetRight(YGNodeRef node);
+WIN_EXPORT float YGNodeLayoutGetBottom(YGNodeRef node);
+WIN_EXPORT float YGNodeLayoutGetWidth(YGNodeRef node);
+WIN_EXPORT float YGNodeLayoutGetHeight(YGNodeRef node);
+WIN_EXPORT YGDirection YGNodeLayoutGetDirection(YGNodeRef node);
+WIN_EXPORT bool YGNodeLayoutGetHadOverflow(YGNodeRef node);
+
+// Get the computed values for these nodes after performing layout. If they were
+// set using point values then the returned value will be the same as
+// YGNodeStyleGetXXX. However if they were set using a percentage value then the
+// returned value is the computed value used during layout.
+WIN_EXPORT float YGNodeLayoutGetMargin(YGNodeRef node, YGEdge edge);
+WIN_EXPORT float YGNodeLayoutGetBorder(YGNodeRef node, YGEdge edge);
+WIN_EXPORT float YGNodeLayoutGetPadding(YGNodeRef node, YGEdge edge);
+
+WIN_EXPORT void YGConfigSetLogger(YGConfigRef config, YGLogger logger);
+WIN_EXPORT void YGAssert(bool condition, const char* message);
+WIN_EXPORT void YGAssertWithNode(
+ YGNodeRef node,
+ bool condition,
+ const char* message);
+WIN_EXPORT void YGAssertWithConfig(
+ YGConfigRef config,
+ bool condition,
+ const char* message);
+// Set this to number of pixels in 1 point to round calculation results If you
+// want to avoid rounding - set PointScaleFactor to 0
+WIN_EXPORT void YGConfigSetPointScaleFactor(
+ YGConfigRef config,
+ float pixelsInPoint);
+WIN_EXPORT float YGConfigGetPointScaleFactor(YGConfigRef config);
+
+// Yoga previously had an error where containers would take the maximum space
+// possible instead of the minimum like they are supposed to. In practice this
+// resulted in implicit behaviour similar to align-self: stretch; Because this
+// was such a long-standing bug we must allow legacy users to switch back to
+// this behaviour.
+WIN_EXPORT YG_DEPRECATED(
+ "Please use "
+ "\"YGConfigGetErrata()\"") bool YGConfigGetUseLegacyStretchBehaviour(YGConfigRef
+ config);
+WIN_EXPORT
+YG_DEPRECATED(
+ "\"YGConfigSetUseLegacyStretchBehaviour\" will be removed in the next "
+ "release. Usage should be replaced with \"YGConfigSetErrata(YGErrataAll)\" "
+ "to opt out of all future breaking conformance fixes, or "
+ "\"YGConfigSetErrata(YGErrataStretchFlexBasis)\" to opt out of the "
+ "specific conformance fix previously disabled by "
+ "\"UseLegacyStretchBehaviour\".")
+void YGConfigSetUseLegacyStretchBehaviour(
+ YGConfigRef config,
+ bool useLegacyStretchBehaviour);
+
+// YGConfig
+WIN_EXPORT YGConfigRef YGConfigNew(void);
+WIN_EXPORT void YGConfigFree(YGConfigRef config);
+WIN_EXPORT void YGConfigCopy(YGConfigRef dest, YGConfigRef src);
+WIN_EXPORT int32_t YGConfigGetInstanceCount(void);
+
+WIN_EXPORT void YGConfigSetExperimentalFeatureEnabled(
+ YGConfigRef config,
+ YGExperimentalFeature feature,
+ bool enabled);
+WIN_EXPORT bool YGConfigIsExperimentalFeatureEnabled(
+ YGConfigRef config,
+ YGExperimentalFeature feature);
+
+// Using the web defaults is the preferred configuration for new projects. Usage
+// of non web defaults should be considered as legacy.
+WIN_EXPORT void YGConfigSetUseWebDefaults(YGConfigRef config, bool enabled);
+WIN_EXPORT bool YGConfigGetUseWebDefaults(YGConfigRef config);
+
+WIN_EXPORT void YGConfigSetCloneNodeFunc(
+ YGConfigRef config,
+ YGCloneNodeFunc callback);
+
+// Export only for C#
+WIN_EXPORT YGConfigRef YGConfigGetDefault(void);
+
+WIN_EXPORT void YGConfigSetContext(YGConfigRef config, void* context);
+WIN_EXPORT void* YGConfigGetContext(YGConfigRef config);
+
+WIN_EXPORT void YGConfigSetErrata(YGConfigRef config, YGErrata errata);
+WIN_EXPORT YGErrata YGConfigGetErrata(YGConfigRef config);
+
+WIN_EXPORT float YGRoundValueToPixelGrid(
+ double value,
+ double pointScaleFactor,
+ bool forceCeil,
+ bool forceFloor);
+
+YG_EXTERN_C_END
diff --git a/src/3rdparty/yoga/event/event.cpp b/src/3rdparty/yoga/event/event.cpp
new file mode 100644
index 0000000000..dad7a9a082
--- /dev/null
+++ b/src/3rdparty/yoga/event/event.cpp
@@ -0,0 +1,84 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// Copyright (c) Meta Platforms, Inc. and affiliates.
+//
+// SPDX-License-Identifier: MIT
+
+#include "event.h"
+#include <atomic>
+#include <memory>
+
+namespace facebook {
+namespace yoga {
+
+const char* LayoutPassReasonToString(const LayoutPassReason value) {
+ switch (value) {
+ case LayoutPassReason::kInitial:
+ return "initial";
+ case LayoutPassReason::kAbsLayout:
+ return "abs_layout";
+ case LayoutPassReason::kStretch:
+ return "stretch";
+ case LayoutPassReason::kMultilineStretch:
+ return "multiline_stretch";
+ case LayoutPassReason::kFlexLayout:
+ return "flex_layout";
+ case LayoutPassReason::kMeasureChild:
+ return "measure";
+ case LayoutPassReason::kAbsMeasureChild:
+ return "abs_measure";
+ case LayoutPassReason::kFlexMeasure:
+ return "flex_measure";
+ default:
+ return "unknown";
+ }
+}
+
+namespace {
+
+struct Node {
+ std::function<Event::Subscriber> subscriber = nullptr;
+ Node* next = nullptr;
+
+ Node(std::function<Event::Subscriber>&& subscriber)
+ : subscriber{std::move(subscriber)} {}
+};
+
+std::atomic<Node*> subscribers{nullptr};
+
+Node* push(Node* newHead) {
+ Node* oldHead;
+ do {
+ oldHead = subscribers.load(std::memory_order_relaxed);
+ if (newHead != nullptr) {
+ newHead->next = oldHead;
+ }
+ } while (!subscribers.compare_exchange_weak(
+ oldHead, newHead, std::memory_order_release, std::memory_order_relaxed));
+ return oldHead;
+}
+
+} // namespace
+
+void Event::reset() {
+ auto head = push(nullptr);
+ while (head != nullptr) {
+ auto current = head;
+ head = head->next;
+ delete current;
+ }
+}
+
+void Event::subscribe(std::function<Subscriber>&& subscriber) {
+ push(new Node{std::move(subscriber)});
+}
+
+void Event::publish(const YGNode& node, Type eventType, const Data& eventData) {
+ for (auto subscriber = subscribers.load(std::memory_order_relaxed);
+ subscriber != nullptr;
+ subscriber = subscriber->next) {
+ subscriber->subscriber(node, eventType, eventData);
+ }
+}
+
+} // namespace yoga
+} // namespace facebook
diff --git a/src/3rdparty/yoga/event/event.h b/src/3rdparty/yoga/event/event.h
new file mode 100644
index 0000000000..f5f2a80e38
--- /dev/null
+++ b/src/3rdparty/yoga/event/event.h
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2016 The Qt Company Ltd.
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#pragma once
+
+#include <functional>
+#include <vector>
+#include <array>
+#include <yoga/YGEnums.h>
+#include <stdint.h>
+
+struct YGConfig;
+struct YGNode;
+
+namespace facebook {
+namespace yoga {
+
+enum struct LayoutType : int {
+ kLayout = 0,
+ kMeasure = 1,
+ kCachedLayout = 2,
+ kCachedMeasure = 3
+};
+
+enum struct LayoutPassReason : int {
+ kInitial = 0,
+ kAbsLayout = 1,
+ kStretch = 2,
+ kMultilineStretch = 3,
+ kFlexLayout = 4,
+ kMeasureChild = 5,
+ kAbsMeasureChild = 6,
+ kFlexMeasure = 7,
+ COUNT
+};
+
+struct LayoutData {
+ int layouts;
+ int measures;
+ int maxMeasureCache;
+ int cachedLayouts;
+ int cachedMeasures;
+ int measureCallbacks;
+ std::array<int, static_cast<uint8_t>(LayoutPassReason::COUNT)>
+ measureCallbackReasonsCount;
+};
+
+const char* LayoutPassReasonToString(const LayoutPassReason value);
+
+struct YOGA_EXPORT Event {
+ enum Type {
+ NodeAllocation,
+ NodeDeallocation,
+ NodeLayout,
+ LayoutPassStart,
+ LayoutPassEnd,
+ MeasureCallbackStart,
+ MeasureCallbackEnd,
+ NodeBaselineStart,
+ NodeBaselineEnd,
+ };
+ class Data;
+ using Subscriber = void(const YGNode&, Type, Data);
+ using Subscribers = std::vector<std::function<Subscriber>>;
+
+ template <Type E>
+ struct TypedData {};
+
+ class Data {
+ const void* data_;
+
+ public:
+ template <Type E>
+ Data(const TypedData<E>& data) : data_{&data} {}
+
+ template <Type E>
+ const TypedData<E>& get() const {
+ return *static_cast<const TypedData<E>*>(data_);
+ }
+ };
+
+ static void reset();
+
+ static void subscribe(std::function<Subscriber>&& subscriber);
+
+ template <Type E>
+ static void publish(const YGNode& node, const TypedData<E>& eventData = {}) {
+ publish(node, E, Data{eventData});
+ }
+
+ template <Type E>
+ static void publish(const YGNode* node, const TypedData<E>& eventData = {}) {
+ publish<E>(*node, eventData);
+ }
+
+private:
+ static void publish(const YGNode&, Type, const Data&);
+};
+
+template <>
+struct Event::TypedData<Event::NodeAllocation> {
+ YGConfig* config;
+};
+
+template <>
+struct Event::TypedData<Event::NodeDeallocation> {
+ YGConfig* config;
+};
+
+template <>
+struct Event::TypedData<Event::LayoutPassStart> {
+ void* layoutContext;
+};
+
+template <>
+struct Event::TypedData<Event::LayoutPassEnd> {
+ void* layoutContext;
+ LayoutData* layoutData;
+};
+
+template <>
+struct Event::TypedData<Event::MeasureCallbackEnd> {
+ void* layoutContext;
+ float width;
+ YGMeasureMode widthMeasureMode;
+ float height;
+ YGMeasureMode heightMeasureMode;
+ float measuredWidth;
+ float measuredHeight;
+ const LayoutPassReason reason;
+};
+
+template <>
+struct Event::TypedData<Event::NodeLayout> {
+ LayoutType layoutType;
+ void* layoutContext;
+};
+
+} // namespace yoga
+} // namespace facebook
diff --git a/src/3rdparty/yoga/log.cpp b/src/3rdparty/yoga/log.cpp
new file mode 100644
index 0000000000..454ac462e6
--- /dev/null
+++ b/src/3rdparty/yoga/log.cpp
@@ -0,0 +1,62 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// Copyright (c) Meta Platforms, Inc. and affiliates.
+//
+// SPDX-License-Identifier: MIT
+
+#include <yoga/Yoga.h>
+
+#include "log.h"
+#include "YGConfig.h"
+#include "YGNode.h"
+
+namespace facebook {
+namespace yoga {
+namespace detail {
+
+namespace {
+
+void vlog(
+ YGConfig* config,
+ YGNode* node,
+ YGLogLevel level,
+ void* context,
+ const char* format,
+ va_list args) {
+ YGConfig* logConfig = config != nullptr ? config : YGConfigGetDefault();
+ logConfig->log(logConfig, node, level, context, format, args);
+}
+} // namespace
+
+YOGA_EXPORT void Log::log(
+ YGNode* node,
+ YGLogLevel level,
+ void* context,
+ const char* format,
+ ...) noexcept {
+ va_list args;
+ va_start(args, format);
+ vlog(
+ node == nullptr ? nullptr : node->getConfig(),
+ node,
+ level,
+ context,
+ format,
+ args);
+ va_end(args);
+}
+
+void Log::log(
+ YGConfig* config,
+ YGLogLevel level,
+ void* context,
+ const char* format,
+ ...) noexcept {
+ va_list args;
+ va_start(args, format);
+ vlog(config, nullptr, level, context, format, args);
+ va_end(args);
+}
+
+} // namespace detail
+} // namespace yoga
+} // namespace facebook
diff --git a/src/3rdparty/yoga/log.h b/src/3rdparty/yoga/log.h
new file mode 100644
index 0000000000..2e6190d976
--- /dev/null
+++ b/src/3rdparty/yoga/log.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2016 The Qt Company Ltd.
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#pragma once
+
+#include <yoga/YGEnums.h>
+
+struct YGNode;
+struct YGConfig;
+
+namespace facebook {
+namespace yoga {
+
+namespace detail {
+
+struct Log {
+ static void log(
+ YGNode* node,
+ YGLogLevel level,
+ void*,
+ const char* message,
+ ...) noexcept;
+
+ static void log(
+ YGConfig* config,
+ YGLogLevel level,
+ void*,
+ const char* format,
+ ...) noexcept;
+};
+
+} // namespace detail
+} // namespace yoga
+} // namespace facebook
diff --git a/src/3rdparty/yoga/qt_attribution.json b/src/3rdparty/yoga/qt_attribution.json
new file mode 100644
index 0000000000..cf20b31000
--- /dev/null
+++ b/src/3rdparty/yoga/qt_attribution.json
@@ -0,0 +1,13 @@
+{
+ "Id": "yoga",
+ "Name": "Yoga",
+ "QDocModule": "qtquick",
+ "QtUsage": "Used in Qt Quick.Layouts",
+
+ "Homepage": "https://www.yogalayout.dev/",
+ "License": "MIT License",
+ "LicenseId": "MIT",
+ "LicenseFile": "LICENSE",
+ "Copyright": ["Copyright (c) Facebook, Inc. and its affiliates.",
+ "Copyright (c) Meta Platforms, Inc. and affiliates."]
+}
diff --git a/src/quick/doc/snippets/layouts/simpleFlexboxLayout.qml b/src/quick/doc/snippets/layouts/simpleFlexboxLayout.qml
new file mode 100644
index 0000000000..eea4b415f5
--- /dev/null
+++ b/src/quick/doc/snippets/layouts/simpleFlexboxLayout.qml
@@ -0,0 +1,47 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+
+ApplicationWindow {
+ width: 640
+ height: 480
+ visible: true
+ title: qsTr("QML Flexbox Layout")
+ //! [layout-definition]
+ FlexboxLayout {
+ id: flexLayout
+ anchors.fill: parent
+ wrap: FlexboxLayout.Wrap
+ direction: FlexboxLayout.Row
+ justifyContent: FlexboxLayout.JustifySpaceAround
+ Rectangle {
+ color: 'teal'
+ implicitWidth: 200
+ implicitHeight: 200
+ }
+ Rectangle {
+ color: 'plum'
+ implicitWidth: 200
+ implicitHeight: 200
+ }
+ Rectangle {
+ color: 'olive'
+ implicitWidth: 200
+ implicitHeight: 200
+ }
+ Rectangle {
+ color: 'beige'
+ implicitWidth: 200
+ implicitHeight: 200
+ }
+ Rectangle {
+ color: 'darkseagreen'
+ implicitWidth: 200
+ implicitHeight: 200
+ }
+ }
+ //! [layout-definition]
+}
diff --git a/src/quicklayouts/CMakeLists.txt b/src/quicklayouts/CMakeLists.txt
index 7dad1a0c33..9e37c50d83 100644
--- a/src/quicklayouts/CMakeLists.txt
+++ b/src/quicklayouts/CMakeLists.txt
@@ -25,3 +25,26 @@ qt_internal_add_qml_module(QuickLayouts
Qt::QuickPrivate
Qt::Qml
)
+
+qt_internal_extend_target(QuickLayouts CONDITION NOT INTEGRITY
+ INCLUDE_DIRECTORIES
+ ${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty
+ SOURCES
+ # Qt flexbox layout (uses inernally yoga engine to layout quick items)
+ qquickflexboxlayout.cpp qquickflexboxlayout_p.h
+ qquickflexboxlayoutengine.cpp qquickflexboxlayoutengine_p.h
+ qquickflexboxlayoutitem_p.h qquickflexboxlayoutitem.cpp
+ # Yoga library source
+ ${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/yoga/Yoga.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/yoga/event/event.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/yoga/YGValue.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/yoga/YGStyle.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/yoga/YGEnums.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/yoga/YGNodePrint.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/yoga/YGNode.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/yoga/YGLayout.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/yoga/YGEnums.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/yoga/YGConfig.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/yoga/Utils.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/yoga/log.cpp
+ )
diff --git a/src/quicklayouts/qquickflexboxlayout.cpp b/src/quicklayouts/qquickflexboxlayout.cpp
new file mode 100644
index 0000000000..d982a32eab
--- /dev/null
+++ b/src/quicklayouts/qquickflexboxlayout.cpp
@@ -0,0 +1,662 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include <QtQuickLayouts/private/qquickflexboxlayout_p.h>
+#include <QtQuickLayouts/private/qquickflexboxlayoutengine_p.h>
+#include <QtQml/qqmlinfo.h>
+
+/*!
+ \qmltype FlexboxLayout
+ //! \nativetype QQuickFlexboxLayout
+ \inherits Layout
+ \inqmlmodule QtQuick.Layouts
+ \ingroup layouts
+ \brief The FlexboxLayout QML construct provides a flex layout for the
+ quick items.
+ \since 6.10
+ \preliminary
+
+ The FlexboxLayout enables to layout the quick items in a flexible way as
+ similar to that of
+ \l {https://www.w3.org/TR/css-flexbox-1/}{CSS Flexible Box Layout}.
+ Internally Qt FlexboxLayout uses
+ the yoga engine to derive the geometry of the flex items. The
+ \l {https://www.yogalayout.dev/}{yoga library} is a subset of the
+ \l {https://www.w3.org/TR/css-flexbox-1/}{CSS Flexible Box Layout}. Thus
+ FlexboxLayout can be limited to the feature as supported in the
+ \l {https://www.yogalayout.dev/}{yoga library}.
+
+ \note The FlexboxLayout adheres to yoga library version 2.0 for its
+ features.
+
+ The items within the FlexboxLayout can be configured with preferred,
+ minimum and maximum sizes through the existing layout attached properties.
+ For instance, if the item within the FlexboxLayout need to be stretched,
+ the layout attached property \l {Layout.fillWidth} or
+ \l {Layout.fillHeight} can be set.
+
+ Items in a FlexboxLayout support these attached properties:
+ \list
+ \li \l{FlexboxLayout.alignSelf}
+ \li \l{Layout.minimumWidth}
+ \li \l{Layout.minimumHeight}
+ \li \l{Layout.preferredWidth}
+ \li \l{Layout.preferredHeight}
+ \li \l{Layout.maximumWidth}
+ \li \l{Layout.maximumHeight}
+ \li \l{Layout.fillWidth}
+ \li \l{Layout.fillHeight}
+ \endlist
+
+ Read more about attached properties \l{QML Object Attributes}{here}.
+ \sa ColumnLayout
+ \sa GridLayout
+ \sa RowLayout
+ \sa {QtQuick.Controls::StackView}{StackView}
+ \sa {Qt Quick Layouts Overview}
+
+ To be able to use this type more efficiently, it is recommended that you
+ understand the general mechanism of the Qt Quick Layouts module. Refer to
+ \l{Qt Quick Layouts Overview} for more information.
+
+ \section1 Example Usage
+
+ The following snippet shows the minimalistic example of using QML
+ FlexboxLayout to arrange the rectangle items in more flexible way
+
+ \snippet layouts/simpleFlexboxLayout.qml layout-definition
+
+ \note This API is considered tech preview and may change or be removed in
+ future versions of Qt.
+*/
+
+QT_BEGIN_NAMESPACE
+
+class QQuickFlexboxLayoutPrivate : public QQuickLayoutPrivate
+{
+ Q_DECLARE_PUBLIC(QQuickFlexboxLayout)
+
+public:
+ QQuickFlexboxLayoutPrivate() : QQuickLayoutPrivate() {}
+ const QQuickFlexboxLayoutEngine& getFlexEngine() const { return m_flexEngine; }
+
+private:
+ QQuickFlexboxLayoutEngine m_flexEngine;
+ QQuickFlexboxLayout::FlexboxDirection m_direction = QQuickFlexboxLayout::Row;
+ QQuickFlexboxLayout::FlexboxWrap m_wrap = QQuickFlexboxLayout::NoWrap;
+ QQuickFlexboxLayout::FlexboxAlignment m_alignItems = QQuickFlexboxLayout::AlignStart;
+ // Align items within the layout in the multi-line containter (i.e. with wrap enabled) and
+ // its aligned to the cross axis of the flexbox layout
+ QQuickFlexboxLayout::FlexboxAlignment m_alignContent = QQuickFlexboxLayout::AlignStart;
+ // Align content item in the multi-line containter and its aligned to the main axis of the
+ // flexbox layout
+ QQuickFlexboxLayout::FlexboxJustify m_justifyContent = QQuickFlexboxLayout::JustifyStart;
+ qreal m_gap = 0.;
+ qreal m_rowGap = 0.;
+ qreal m_columnGap = 0.;
+ std::bitset<QQuickFlexboxLayout::GapMax> m_gapBitSet;
+};
+
+static QQuickFlexboxLayoutAttached *attachedFlexboxLayoutObject(QQuickItem *item, bool create = false)
+{
+ return static_cast<QQuickFlexboxLayoutAttached*>(
+ qmlAttachedPropertiesObject<QQuickFlexboxLayout>(item, create));
+}
+
+QQuickFlexboxLayout::QQuickFlexboxLayout(QQuickItem *parent) :
+ QQuickLayout(*new QQuickFlexboxLayoutPrivate, parent)
+{
+ Q_D(QQuickFlexboxLayout);
+ d->m_flexEngine.setFlexboxParentItem(new QQuickFlexboxLayoutItem(this));
+}
+
+QQuickFlexboxLayout::~QQuickFlexboxLayout()
+{
+ Q_D(QQuickFlexboxLayout);
+ d->m_flexEngine.clearItems();
+}
+
+/*!
+ \qmlproperty enumeration FlexboxLayout::direction
+
+ This property holds the item layout direction within the flex box layout
+ and it defines the
+ \l {https://www.w3.org/TR/css-flexbox-1/#box-model}{main-axis}.
+
+ Possible values:
+
+ \value FlexboxLayout.Row (default) Items are laid out from
+ left to right.
+ \value FlexboxLayout.RowReversed Items are laid out from right to
+ left.
+ \value FlexboxLayout.Column Items are laid out from top to
+ bottom.
+ \value FlexboxLayout.ColumnReversed Items are laid out from bottom to
+ top.
+
+ The default value is \c FlexboxLayout.Row.
+*/
+QQuickFlexboxLayout::FlexboxDirection QQuickFlexboxLayout::direction() const
+{
+ Q_D(const QQuickFlexboxLayout);
+ return d->m_direction;
+}
+
+void QQuickFlexboxLayout::setDirection(QQuickFlexboxLayout::FlexboxDirection direction)
+{
+ Q_D(QQuickFlexboxLayout);
+ if (d->m_direction == direction)
+ return;
+ d->m_direction = direction;
+ invalidate();
+ emit directionChanged();
+}
+
+/*!
+ \qmlproperty enumeration FlexboxLayout::wrap
+
+ This property specifies that the items within the flex box layout can wrap
+ or not and it happens when the children overflow the size of the flex box
+ layout. If the items are wrapped, it will be placed in multiple lines
+ depending on overflow condition as stated. Each line takes up the
+ maximum size of the item along the
+ \l {https://www.w3.org/TR/css-flexbox-1/#box-model}{cross-axis}.
+
+ Possible values:
+
+ \value FlexboxLayout.Wrap Items are wrapped into multiple lines
+ within the flex box layout.
+ \value FlexboxLayout.NoWrap (default) Items are not wrapped and
+ laid out in single line within the
+ flex box layout.
+ \value FlexboxLayout.WrapReverse Items are wrapped into multiple lines
+ within the flex box layout in the
+ reverse direction.
+
+ The default value is \c FlexboxLayout.NoWrap.
+*/
+QQuickFlexboxLayout::FlexboxWrap QQuickFlexboxLayout::wrap() const
+{
+ Q_D(const QQuickFlexboxLayout);
+ return d->m_wrap;
+}
+
+void QQuickFlexboxLayout::setWrap(QQuickFlexboxLayout::FlexboxWrap wrapMode)
+{
+ Q_D(QQuickFlexboxLayout);
+ if (d->m_wrap == wrapMode)
+ return;
+ d->m_wrap = wrapMode;
+ invalidate();
+ emit wrapChanged();
+}
+
+/*!
+ \qmlproperty enumeration FlexboxLayout::alignItems
+
+ This property specifies the alignment of the items within the
+ \l {https://www.w3.org/TR/css-flexbox-1/#flex-lines}{flex lines} of the
+ flex box layout and its aligned along the
+ \l {https://www.w3.org/TR/css-flexbox-1/#box-model}{cross-axis}
+ (which is orthogonal to the main-axis, as defined by the property
+ \l {FlexboxLayout::direction}). This property can be overridden by the items
+ within the flex box layout through the property
+ \l {FlexboxLayoutAttached::alignSelf}.
+
+ Possible values:
+
+ \value FlexboxLayout.AlignStart (default) Items are aligned to the
+ start of the flex box layout
+ cross-axis.
+ \value FlexboxLayout.AlignCenter Items are aligned along the center
+ of the flex box layout cross-axis.
+ \value FlexboxLayout.AlignEnd Items are aligned to the end of the
+ flex box layout cross-axis.
+
+ \note The alignments mentioned in possible values are only applicable for
+ the \l {FlexboxLayout::alignItems} property
+
+ The default value is \c FlexboxLayout.AlignStart.
+*/
+QQuickFlexboxLayout::FlexboxAlignment QQuickFlexboxLayout::alignItems() const
+{
+ Q_D(const QQuickFlexboxLayout);
+ return d->m_alignItems;
+}
+
+void QQuickFlexboxLayout::setAlignItems(QQuickFlexboxLayout::FlexboxAlignment alignment)
+{
+ Q_D(QQuickFlexboxLayout);
+ if (alignment >= QQuickFlexboxLayout::AlignStretch || alignment <= QQuickFlexboxLayout::AlignAuto) {
+ qWarning("Not applicable for Flexbox layout container");
+ return;
+ }
+ if (d->m_alignItems == alignment)
+ return;
+ d->m_alignItems = alignment;
+ invalidate();
+ emit alignItemsChanged();
+}
+
+/*!
+ \qmlproperty enumeration FlexboxLayout::alignContent
+
+ This property specifies the distribution of the
+ \l {https://www.w3.org/TR/css-flexbox-1/#flex-lines}{flex lines} along the
+ \l {https://www.w3.org/TR/css-flexbox-1/#box-model}{cross-axis} of the
+ flex box layout.
+
+ Possible values:
+
+ \value FlexboxLayout.AlignStart (default) Flex lines are aligned to
+ the start of the flex box layout.
+ \value FlexboxLayout.AlignCenter Flex lines are aligned along the
+ center of the flex box layout.
+ \value FlexboxLayout.AlignEnd Flex lines are aligned to the end
+ of the flex box layout.
+ \value FlexboxLayout.AlignStretch Flex lines are stretched according
+ to the height of the flex box
+ layout.
+ \value FlexboxLayout.AlignSpaceBetween The spaces are evenly distributed
+ between the lines and no space
+ along the edge of the flex box
+ layout.
+ \value FlexboxLayout.AlignSpaceAround The spaces are evenly distributed
+ between the lines and half-size
+ space on the edges of the flex box
+ layout.
+ \value FlexboxLayout.AlignSpaceEvenly The spaces are evenly distributed
+ between the lines and the edges of
+ the flex box layout. Not supported
+ in Qt 6.10.
+
+ The default value is \c FlexboxLayout.AlignStart.
+*/
+QQuickFlexboxLayout::FlexboxAlignment QQuickFlexboxLayout::alignContent() const
+{
+ Q_D(const QQuickFlexboxLayout);
+ return d->m_alignContent;
+}
+
+void QQuickFlexboxLayout::setAlignContent(QQuickFlexboxLayout::FlexboxAlignment alignment)
+{
+ Q_D(QQuickFlexboxLayout);
+ if (alignment == QQuickFlexboxLayout::AlignSpaceEvenly) {
+ qmlWarning(this) << "Currently not supported for Flexbox layout container";
+ return;
+ }
+ if (d->m_alignContent == alignment)
+ return;
+ d->m_alignContent = alignment;
+ invalidate();
+ emit alignContentChanged();
+}
+
+/*!
+ \qmlproperty enumeration FlexboxLayout::justifyContent
+
+ This property specifies the distribution of the items along the
+ \l {https://www.w3.org/TR/css-flexbox-1/#box-model}{main-axis} of the
+ flex box layout.
+
+ Possible values:
+
+ \value FlexboxLayout.JustifyStart (default) Items are aligned to
+ the start of the flex box
+ layout.
+ \value FlexboxLayout.JustifyCenter Items are aligned along the
+ center of the flex box layout.
+ \value FlexboxLayout.JustifyEnd Items are aligned to the end of
+ the flex box layout.
+ \value FlexboxLayout.JustifySpaceBetween The spaces are evenly
+ distributed between the items
+ and no space along the edges
+ of the flex box layout.
+ \value FlexboxLayout.JustifySpaceAround The spaces are evenly
+ distributed between the items
+ and half-size space on the
+ edges of the flex box layout.
+ \value FlexboxLayout.JustiftSpaceEvenly The spaces are evenly
+ distributed between the items
+ and edges of the flex
+ box layout.
+
+ The default value is \c FlexboxLayout.JustifyStart.
+*/
+QQuickFlexboxLayout::FlexboxJustify QQuickFlexboxLayout::justifyContent() const
+{
+ Q_D(const QQuickFlexboxLayout);
+ return d->m_justifyContent;
+}
+
+void QQuickFlexboxLayout::setJustifyContent(QQuickFlexboxLayout::FlexboxJustify justifyContent)
+{
+ Q_D(QQuickFlexboxLayout);
+ if (d->m_justifyContent == justifyContent)
+ return;
+ d->m_justifyContent = justifyContent;
+ invalidate();
+ emit justifyContentChanged();
+}
+
+/*!
+ \qmlproperty real FlexboxLayout::gap
+
+ This property holds the amount of space that need to be applied
+ to the \l {FlexboxLayout} both along the
+ \l {https://www.w3.org/TR/css-align-3/#gaps}{inline axis and block axis}.
+
+ The default value is \c 0.
+*/
+qreal QQuickFlexboxLayout::gap() const
+{
+ Q_D(const QQuickFlexboxLayout);
+ return d->m_gap;
+}
+
+void QQuickFlexboxLayout::setGap(qreal gap)
+{
+ Q_D(QQuickFlexboxLayout);
+ if (d->m_gap == gap)
+ return;
+ d->m_gap = gap;
+ d->m_gapBitSet.set(GapAll);
+ invalidate();
+ emit gapChanged();
+ if (!isGapBitSet(GapRow))
+ emit rowGapChanged();
+ if (!isGapBitSet(GapColumn))
+ emit columnGapChanged();
+}
+
+void QQuickFlexboxLayout::resetGap()
+{
+ Q_D(QQuickFlexboxLayout);
+ d->m_gap = 0;
+ d->m_gapBitSet.reset(GapAll);
+ emit gapChanged();
+ if (!isGapBitSet(GapRow))
+ emit rowGapChanged();
+ if (!isGapBitSet(GapColumn))
+ emit columnGapChanged();
+}
+
+/*!
+ \qmlproperty real FlexboxLayout::rowGap
+
+ This property holds the amount of space that need to be applied to the
+ \l {FlexboxLayout} along the
+ \l {https://www.w3.org/TR/css-align-3/#gaps}{block axis}. Setting this
+ property override the \l {FlexboxLayout::gap} value affecting the
+ \l {https://www.w3.org/TR/css-align-3/#gaps}{block axis}.
+
+ The default value is \c 0.
+*/
+qreal QQuickFlexboxLayout::rowGap() const
+{
+ Q_D(const QQuickFlexboxLayout);
+ if (!isGapBitSet(GapRow))
+ return d->m_gap;
+ return d->m_rowGap;
+}
+
+void QQuickFlexboxLayout::setRowGap(qreal gap)
+{
+ Q_D(QQuickFlexboxLayout);
+ if (d->m_rowGap == gap)
+ return;
+ d->m_rowGap = gap;
+ d->m_gapBitSet.set(QQuickFlexboxLayout::GapRow);
+ invalidate();
+ emit rowGapChanged();
+}
+
+void QQuickFlexboxLayout::resetRowGap()
+{
+ Q_D(QQuickFlexboxLayout);
+ d->m_rowGap = 0;
+ d->m_gapBitSet.reset(GapRow);
+ emit rowGapChanged();
+}
+
+/*!
+ \qmlproperty real FlexboxLayout::columnGap
+
+ This property holds the amount of space that need to be applied
+ to the \l {FlexboxLayout} along the {inline axis}
+ {https://www.w3.org/TR/css-align-3/#gaps}. Setting this property
+ override the \l {FlexboxLayout::gap} value affecting the
+ \l {https://www.w3.org/TR/css-align-3/#gaps}{inline axis}.
+
+ The default value is \c 0.
+*/
+qreal QQuickFlexboxLayout::columnGap() const
+{
+ Q_D(const QQuickFlexboxLayout);
+ if (!isGapBitSet(GapColumn))
+ return d->m_gap;
+ return d->m_columnGap;
+}
+
+void QQuickFlexboxLayout::setColumnGap(qreal gap)
+{
+ Q_D(QQuickFlexboxLayout);
+ if (d->m_columnGap == gap)
+ return;
+ d->m_columnGap = gap;
+ d->m_gapBitSet.set(QQuickFlexboxLayout::GapColumn);
+ invalidate();
+ emit columnGapChanged();
+}
+
+void QQuickFlexboxLayout::resetColumnGap()
+{
+ Q_D(QQuickFlexboxLayout);
+ d->m_columnGap = 0;
+ d->m_gapBitSet.reset(GapColumn);
+ emit columnGapChanged();
+}
+
+void QQuickFlexboxLayout::updateLayoutItems()
+{
+ Q_D(QQuickFlexboxLayout);
+ // Clean all the items in the layout
+ d->m_flexEngine.clearItems();
+ // Update the parent item properties
+ if (auto *flexParentItem = d->m_flexEngine.getFlexboxParentItem()) {
+ flexParentItem->setFlexDirection(d->m_direction);
+ flexParentItem->setFlexWrap(d->m_wrap);
+ flexParentItem->setFlexAlignItemsProperty(d->m_alignItems);
+ flexParentItem->setFlexAlignContentProperty(d->m_alignContent);
+ flexParentItem->setFlexJustifyContentProperty(d->m_justifyContent);
+ if (isGapBitSet(QQuickFlexboxLayout::GapAll))
+ flexParentItem->setFlexGap(QQuickFlexboxLayout::GapAll, d->m_gap);
+ if (isGapBitSet(QQuickFlexboxLayout::GapRow))
+ flexParentItem->setFlexGap(QQuickFlexboxLayout::GapRow, d->m_rowGap);
+ if (isGapBitSet(QQuickFlexboxLayout::GapColumn))
+ flexParentItem->setFlexGap(QQuickFlexboxLayout::GapColumn, d->m_columnGap);
+ }
+
+ // Insert the items in the layout
+ const QList<QQuickItem *> items = childItems();
+ for (auto *childItem : items) {
+ Q_ASSERT(childItem);
+ checkAnchors(childItem);
+ if (shouldIgnoreItem(childItem))
+ continue;
+ // Create and set the attached properties of the flex item and add it as child
+ auto *flexLayoutItem = new QQuickFlexboxLayoutItem(childItem);
+ if (auto *flexItemAttachedProperties = attachedFlexboxLayoutObject(childItem))
+ flexLayoutItem->setFlexAlignSelfProperty(flexItemAttachedProperties->alignSelf());
+ d->m_flexEngine.insertItem(flexLayoutItem);
+ }
+}
+
+void QQuickFlexboxLayout::checkAnchors(QQuickItem *item) const
+{
+ QQuickAnchors *anchors = QQuickItemPrivate::get(item)->_anchors;
+ if (anchors && anchors->activeDirections())
+ qmlWarning(item) << "Detected anchors on an item that is managed by a layout. This is undefined behavior; use FlexboxLayout alignment properties instead.";
+}
+
+void QQuickFlexboxLayout::componentComplete()
+{
+ QQuickLayout::componentComplete();
+ ensureLayoutItemsUpdated(ApplySizeHints);
+ if (qobject_cast<QQuickLayout*>(parentItem()))
+ return;
+ rearrange(QSizeF(width(), height()));
+}
+
+void QQuickFlexboxLayout::itemVisibilityChanged(QQuickItem *item)
+{
+ if (!isReady())
+ return;
+ invalidate(item);
+}
+
+QSizeF QQuickFlexboxLayout::sizeHint(Qt::SizeHint whichSizeHint) const
+{
+ Q_D(const QQuickFlexboxLayout);
+ QSizeF sizeHint = d->m_flexEngine.sizeHint(whichSizeHint);
+ d->m_dirty = false;
+ return sizeHint;
+}
+
+QQuickFlexboxLayoutAttached *QQuickFlexboxLayout::qmlAttachedProperties(QObject *object)
+{
+ return new QQuickFlexboxLayoutAttached(object);
+}
+
+bool QQuickFlexboxLayout::isGapBitSet(QQuickFlexboxLayout::FlexboxGap gap) const
+{
+ Q_D(const QQuickFlexboxLayout);
+ if (gap < QQuickFlexboxLayout::GapRow || gap > QQuickFlexboxLayout::GapAll)
+ return false;
+ return d->m_gapBitSet[gap];
+}
+
+QQuickItem *QQuickFlexboxLayout::itemAt(int index) const
+{
+ const auto items = childItems();
+ for (QQuickItem *item : items) {
+ if (shouldIgnoreItem(item))
+ continue;
+ if (index == 0)
+ return item;
+ --index;
+ }
+ return nullptr;
+}
+
+int QQuickFlexboxLayout::itemCount() const
+{
+ int count = 0;
+ const auto items = childItems();
+ for (QQuickItem *item : items) {
+ if (shouldIgnoreItem(item))
+ continue;
+ ++count;
+ }
+ return count;
+}
+
+void QQuickFlexboxLayout::invalidate(QQuickItem *childItem)
+{
+ Q_D(QQuickFlexboxLayout);
+ d->m_flexEngine.invalidateItemSizeHint(childItem);
+ QQuickLayout::invalidate(this);
+ if (QQuickLayout *parentLayout = qobject_cast<QQuickLayout *>(parentItem()))
+ parentLayout->invalidate(this);
+}
+
+void QQuickFlexboxLayout::childItemsChanged()
+{
+ const int count = itemCount();
+ for (int i = 0; i < count; ++i) {
+ QQuickItem *child = itemAt(i);
+ checkAnchors(child);
+ }
+}
+
+void QQuickFlexboxLayout::rearrange(const QSizeF &newSize)
+{
+ Q_D(QQuickFlexboxLayout);
+ if (newSize.isNull() || !newSize.isValid())
+ return;
+ d->m_flexEngine.setGeometries(newSize);
+ QQuickLayout::rearrange(newSize);
+}
+
+void QQuickFlexboxLayout::itemSiblingOrderChanged(QQuickItem *)
+{
+ if (!isReady())
+ return;
+ invalidate();
+}
+
+QQuickFlexboxLayoutAttached::QQuickFlexboxLayoutAttached(QObject *object)
+{
+ auto item = qobject_cast<QQuickItem*>(object);
+ if (!item) {
+ qmlWarning(object) << "FlexboxLayout attached property must be attached to an object deriving from Item";
+ return;
+ }
+ setParent(object);
+ if (auto flexboxLayout = qobject_cast<QQuickFlexboxLayout*>(item->parentItem())) {
+ if (!flexboxLayout->isComponentComplete()) {
+ // Don't try to get the index if the FlexboxLayout itself hasn't
+ // loaded yet.
+ return;
+ }
+ // In case of lazy loading in loader, attachedProperties are created
+ // and updated for the object after adding the child object to the
+ // stack layout, which leads to entries with same index. Triggering
+ // childItemsChanged() resets to right index in the stack layout.
+ flexboxLayout->childItemsChanged();
+ }
+}
+
+/*!
+ \qmlattachedproperty enumeration FlexboxLayout::alignSelf
+
+ This attached property allows to align this item in the flex box layout
+ along the \l {https://www.w3.org/TR/css-flexbox-1/#box-model}{cross-axis}
+ and it overrides the parent flex box layout property
+ \l {alignItems} {FlexboxLayout::alignItems}.
+
+ By default, the child item inherit the alignment from the parent and it can
+ override the parent flex box layout
+ \l {alignItems} {FlexboxLayout::alignItems} property with the values
+ \l {FlexboxLayout::AlignStart}, \l {FlexboxLayout::AlignCenter}
+ and \l {FlexboxLayout::AlignEnd}.
+
+ The default value is \l {FlexboxLayout::AlignAuto}.
+*/
+QQuickFlexboxLayout::FlexboxAlignment QQuickFlexboxLayoutAttached::alignSelf() const
+{
+ return m_alignSelf;
+}
+
+void QQuickFlexboxLayoutAttached::setAlignSelf(QQuickFlexboxLayout::FlexboxAlignment alignment)
+{
+ if (m_alignSelf == alignment)
+ return;
+
+ m_alignSelf = alignment;
+ const auto *item = qobject_cast<QQuickItem*>(parent());
+ if (auto *flexLayout = qobject_cast<QQuickFlexboxLayout *>(item->parent())) {
+ auto *priv = dynamic_cast<QQuickFlexboxLayoutPrivate *>(QQuickLayoutPrivate::get(flexLayout));
+ auto &flexEngine = priv->getFlexEngine();
+ auto *item = qobject_cast<QQuickItem *>(parent());
+ if (auto *flexLayoutItem = flexEngine.findFlexboxLayoutItem(item)) {
+ flexLayoutItem->setFlexAlignSelfProperty(alignment);
+ flexLayout->invalidate();
+ }
+ }
+ emit alignSelfChanged();
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qquickflexboxlayout_p.cpp"
diff --git a/src/quicklayouts/qquickflexboxlayout_p.h b/src/quicklayouts/qquickflexboxlayout_p.h
new file mode 100644
index 0000000000..60e036a3c5
--- /dev/null
+++ b/src/quicklayouts/qquickflexboxlayout_p.h
@@ -0,0 +1,251 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QQUICKFLEXBOXLAYOUT_H
+#define QQUICKFLEXBOXLAYOUT_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <bitset>
+#include <QtQuickLayouts/private/qquicklayoutglobal_p.h>
+#include <QtQuickLayouts/private/qquicklayout_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QQuickFlexboxLayoutPrivate;
+class QQuickFlexboxLayoutAttached;
+
+class Q_QUICKLAYOUTS_EXPORT QQuickFlexboxLayout : public QQuickLayout
+{
+ Q_OBJECT
+
+ Q_PROPERTY(FlexboxDirection direction READ direction WRITE setDirection NOTIFY directionChanged FINAL)
+ Q_PROPERTY(FlexboxWrap wrap READ wrap WRITE setWrap NOTIFY wrapChanged FINAL)
+ Q_PROPERTY(FlexboxAlignment alignItems READ alignItems WRITE setAlignItems NOTIFY alignItemsChanged FINAL)
+ Q_PROPERTY(FlexboxAlignment alignContent READ alignContent WRITE setAlignContent NOTIFY alignContentChanged FINAL)
+ Q_PROPERTY(FlexboxJustify justifyContent READ justifyContent WRITE setJustifyContent NOTIFY justifyContentChanged FINAL)
+ Q_PROPERTY(qreal gap READ gap WRITE setGap NOTIFY gapChanged RESET resetGap FINAL)
+ Q_PROPERTY(qreal rowGap READ rowGap WRITE setRowGap NOTIFY rowGapChanged RESET resetRowGap FINAL)
+ Q_PROPERTY(qreal columnGap READ columnGap WRITE setColumnGap NOTIFY columnGapChanged RESET resetColumnGap FINAL)
+
+ QML_NAMED_ELEMENT(FlexboxLayout)
+ QML_ADDED_IN_VERSION(2, 0)
+ QML_ATTACHED(QQuickFlexboxLayoutAttached)
+
+public:
+ explicit QQuickFlexboxLayout(QQuickItem *parent = nullptr);
+ ~QQuickFlexboxLayout();
+
+ enum FlexboxDirection { // Used as similar to CSS standard
+ Column,
+ ColumnReverse,
+ Row,
+ RowReverse
+ };
+ Q_ENUM(FlexboxDirection);
+
+ enum FlexboxWrap {
+ NoWrap,
+ Wrap,
+ WrapReverse
+ };
+ Q_ENUM(FlexboxWrap);
+
+ // The alignments here can be mapped to the flexbox CSS assignments: align-items, align-content
+ //
+ // alignItems: AlignStart | AlignCenter | AlignEnd | AlignStretch
+ // Note: AlignSpace* not supported by the flexAlignItems
+ //
+ // alignContent: AlignStart | AlignEnd | AlignCenter | AlignStretch | AlignSpaceBetween |
+ // AlignSpaceAround
+ //
+ // For instance, consider placing the items are placed within the flex with flexDirection
+ // set to Row
+ //
+ // alignItems - This property causes flex items to be positioned as below with respective
+ // value set
+ //
+ // AlignStart - Flex items are positioned from the start of the cross axis
+ // [[Item1][Item2][Item3][Item4][Item5]...]
+ // AlignEnd - Flex items are positioned from the end of the cross axis
+ // [...[Item1][Item2][Item3][Item4][Item5]]
+ // AlignStretch - Flex items are stretched along the cross axis
+ // || | | | | ||
+ // ||Item1|Item2|Item3|Item4|Item5||
+ // || | | | | ||
+ // AlignCenter - Flex items are centered along the cross axis
+ // | | ||
+ // |[Item1][Item2][Item3][Item4]|Item5||
+ // | | ||
+ //
+ // alignContent - This property causes flex items to be positioned considering space around
+ // edge lines and in-between with respective value set
+ //
+ // AlignStart - Lines are packed towards the start of the container
+ // [[Item1][Item2][Item3][Item4][Item5]...]
+ // AlignEnd - Lines are packed towards the end of the container
+ // [...[Item1][Item2][Item3][Item4][Item5]]
+ // AlignCenter - Lines are packed towards the center
+ // || | | | | ||
+ // ||Item1|Item2|Item3|Item4|Item5||
+ // || | | | | ||
+ // AlignSpaceBetween - Lines are packed at the edges of the container and spaces
+ // are placed in-between rows of the flex items
+ // |[Item1][Item2][Item3]|
+ // | |
+ // |[Item4][Item5][Item6]|
+ // AlignSpaceAround - Spaces are placed in-between the rows of the flex items and
+ // would be shared around the edges (i.e. the space between the
+ // items and at the edge of the container will vary)
+ // | |
+ // |[Item1][Item2][Item3]|
+ // | |
+ // | |
+ // |[Item4][Item5][Item6]|
+ // | |
+ // AlignStretch - Lines are stretched and there will be no space in-between
+ // |[Item1][Item2][Item3]|
+ // |[Item4][Item5][Item6]|
+ enum FlexboxAlignment {
+ AlignAuto = 0,
+ AlignStart,
+ AlignCenter,
+ AlignEnd,
+ AlignStretch, // Same as Layout.fillHeight or Layout.fillWidth
+ AlignBaseline,
+ AlignSpaceBetween,
+ AlignSpaceAround,
+ AlignSpaceEvenly
+ };
+ Q_ENUM(FlexboxAlignment)
+
+ // The alignments can be used for justify-content
+ enum FlexboxJustify {
+ JustifyStart,
+ JustifyCenter,
+ JustifyEnd,
+ JustifySpaceBetween,
+ JustifySpaceAround,
+ JustifySpaceEvenly
+ };
+ Q_ENUM(FlexboxJustify)
+
+ // The alignments can be used for justify-content
+ enum FlexboxEdge {
+ EdgeLeft,
+ EdgeRight,
+ EdgeTop,
+ EdgeBottom,
+ EdgeAll,
+ EdgeMax
+ };
+ Q_ENUM(FlexboxEdge)
+
+ // The alignments can be used for justify-content
+ enum FlexboxGap {
+ GapRow,
+ GapColumn,
+ GapAll,
+ GapMax
+ };
+ Q_ENUM(FlexboxGap)
+
+ FlexboxDirection direction() const;
+ void setDirection(FlexboxDirection);
+
+ FlexboxWrap wrap() const;
+ void setWrap(FlexboxWrap);
+
+ FlexboxAlignment alignItems() const;
+ void setAlignItems(FlexboxAlignment);
+
+ FlexboxJustify justifyContent() const;
+ void setJustifyContent(FlexboxJustify);
+
+ FlexboxAlignment alignContent() const;
+ void setAlignContent(FlexboxAlignment);
+
+ qreal gap() const;
+ void setGap(qreal);
+ void resetGap();
+
+ qreal rowGap() const;
+ void setRowGap(qreal);
+ void resetRowGap();
+
+ qreal columnGap() const;
+ void setColumnGap(qreal);
+ void resetColumnGap();
+
+ void componentComplete() override;
+ QSizeF sizeHint(Qt::SizeHint whichSizeHint) const override;
+ void setAlignment(QQuickItem *, Qt::Alignment) override {}
+ void setStretchFactor(QQuickItem *, int, Qt::Orientation) override {}
+
+ void invalidate(QQuickItem *childItem = nullptr) override;
+ void updateLayoutItems() override;
+ void rearrange(const QSizeF &) override;
+
+ // iterator
+ QQuickItem *itemAt(int index) const override;
+ int itemCount() const override;
+
+ /* QQuickItemChangeListener */
+ void itemSiblingOrderChanged(QQuickItem *item) override;
+ void itemVisibilityChanged(QQuickItem *item) override;
+
+ /* internal */
+ static QQuickFlexboxLayoutAttached *qmlAttachedProperties(QObject *object);
+ bool isGapBitSet(QQuickFlexboxLayout::FlexboxGap gap) const;
+ void checkAnchors(QQuickItem *item) const;
+
+Q_SIGNALS:
+ void countChanged();
+ void directionChanged();
+ void wrapChanged();
+ void alignItemsChanged();
+ void alignContentChanged();
+ void justifyContentChanged();
+ void gapChanged();
+ void rowGapChanged();
+ void columnGapChanged();
+
+private:
+ void childItemsChanged();
+
+ friend class QQuickFlexboxLayoutAttached;
+ Q_DECLARE_PRIVATE(QQuickFlexboxLayout)
+};
+
+class Q_QUICKLAYOUTS_EXPORT QQuickFlexboxLayoutAttached : public QObject
+{
+ Q_OBJECT
+
+ Q_PROPERTY(QQuickFlexboxLayout::FlexboxAlignment alignSelf READ alignSelf WRITE setAlignSelf NOTIFY alignSelfChanged FINAL)
+
+public:
+ QQuickFlexboxLayoutAttached(QObject *object);
+
+ QQuickFlexboxLayout::FlexboxAlignment alignSelf() const;
+ void setAlignSelf(const QQuickFlexboxLayout::FlexboxAlignment);
+
+Q_SIGNALS:
+ void alignSelfChanged();
+
+private:
+ // The child item in the flex layout allowed to override the parent align-item property
+ QQuickFlexboxLayout::FlexboxAlignment m_alignSelf = QQuickFlexboxLayout::AlignAuto;
+};
+
+QT_END_NAMESPACE
+
+#endif // QQUICKFLEXBOXLAYOUT_H
diff --git a/src/quicklayouts/qquickflexboxlayoutengine.cpp b/src/quicklayouts/qquickflexboxlayoutengine.cpp
new file mode 100644
index 0000000000..e27c907084
--- /dev/null
+++ b/src/quicklayouts/qquickflexboxlayoutengine.cpp
@@ -0,0 +1,327 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include <QtQuickLayouts/private/qquickflexboxlayoutengine_p.h>
+
+QT_BEGIN_NAMESPACE
+
+QQuickFlexboxLayoutEngine::QQuickFlexboxLayoutEngine()
+{
+}
+
+QQuickFlexboxLayoutEngine::~QQuickFlexboxLayoutEngine()
+{
+ clearItems();
+}
+
+void QQuickFlexboxLayoutEngine::setFlexboxParentItem(QQuickFlexboxLayoutItem *item)
+{
+ Q_ASSERT(item != nullptr);
+ if (qobject_cast<QQuickFlexboxLayout *>(item->quickItem())) {
+ m_flexboxParentItem = item;
+ // Yoga parent item shouldn't have measure function
+ if (m_flexboxParentItem->hasMeasureFunc())
+ m_flexboxParentItem->resetMeasureFunc();
+ }
+}
+
+void QQuickFlexboxLayoutEngine::clearItems()
+{
+ for (auto &flexItem: m_flexLayoutItems)
+ delete flexItem;
+ m_flexLayoutItems.clear();
+ // Clear the size hints as we removed all the items from the flex layout
+ for (int hintIndex = 0; hintIndex < Qt::NSizeHints; hintIndex++)
+ m_cachedSizeHints[hintIndex] = QSizeF();
+}
+
+void QQuickFlexboxLayoutEngine::insertItem(QQuickFlexboxLayoutItem *item)
+{
+ m_flexboxParentItem->insertChild(item, m_flexLayoutItems.count());
+ m_flexLayoutItems.append(item);
+}
+
+int QQuickFlexboxLayoutEngine::itemCount() const
+{
+ return m_flexLayoutItems.count();
+}
+
+QQuickItem *QQuickFlexboxLayoutEngine::itemAt(int index) const
+{
+ if (index < 0 || index >= m_flexLayoutItems.count())
+ return nullptr;
+ return m_flexLayoutItems.at(index)->quickItem();
+}
+
+QQuickFlexboxLayoutItem *QQuickFlexboxLayoutEngine::findFlexboxLayoutItem(QQuickItem *item) const
+{
+ if (!item || (m_flexLayoutItems.count() <= 0))
+ return nullptr;
+ auto iterator = std::find_if(m_flexLayoutItems.cbegin(), m_flexLayoutItems.cend(),
+ [item] (QQuickFlexboxLayoutItem *flexLayoutItem){
+ return (flexLayoutItem->quickItem() == item);
+ });
+ return (iterator == m_flexLayoutItems.cend()) ? nullptr : *iterator;
+}
+
+void QQuickFlexboxLayoutEngine::collectItemSizeHints(QQuickFlexboxLayoutItem *flexItem, QSizeF *sizeHints) const
+{
+ QQuickLayoutAttached *info = nullptr;
+ QQuickLayout::effectiveSizeHints_helper(flexItem->quickItem(), sizeHints, &info, true);
+
+ if (!info)
+ return;
+
+ // Set layout margins to the flex item (Layout.margins)
+ if (info->isMarginsSet())
+ flexItem->setFlexMargin(QQuickFlexboxLayout::EdgeAll, info->margins());
+ if (info->isLeftMarginSet())
+ flexItem->setFlexMargin(QQuickFlexboxLayout::EdgeLeft, info->leftMargin());
+ if (info->isRightMarginSet())
+ flexItem->setFlexMargin(QQuickFlexboxLayout::EdgeRight, info->rightMargin());
+ if (info->isTopMarginSet())
+ flexItem->setFlexMargin(QQuickFlexboxLayout::EdgeTop, info->topMargin());
+ if (info->isBottomMarginSet())
+ flexItem->setFlexMargin(QQuickFlexboxLayout::EdgeBottom, info->bottomMargin());
+
+ // Set child item to grow, shrink and stretch depending on the layout
+ // properties.
+ // If Layout.fillWidth or Layout.fillHeight is set as true, then the child
+ // item within the layout can grow or shrink (considering the minimum and
+ // maximum sizes) along the main axis which depends upon the flex
+ // direction.
+ // If both Layout.fillWidth and Layout.fillHeight are set as true, then the
+ // child item within the layout need to grow or shrink in cross section and
+ // it require stretch need to be set for the yoga flex child item.
+ if (info->isFillWidthSet() || info->isFillHeightSet()) {
+ // Set stretch to child item both width and height
+ if (auto *parentLayoutItem = qobject_cast<QQuickFlexboxLayout *>(m_flexboxParentItem->quickItem())) {
+ if (parentLayoutItem->direction() == QQuickFlexboxLayout::Row ||
+ parentLayoutItem->direction() == QQuickFlexboxLayout::RowReverse) {
+ // If the Layout.fillHeight not been set, the preferred height
+ // will be set as height
+ if (!info->fillHeight())
+ flexItem->setHeight(sizeHints[Qt::PreferredSize].height());
+ flexItem->setFlexBasis(sizeHints[Qt::PreferredSize].width(), !info->fillWidth());
+ // Set child item to grow on main-axis (i.e. the flex
+ // direction)
+ flexItem->setItemGrowAlongMainAxis(info->fillWidth() ? 1.0f : 0.0f);
+ // Set child item to shrink on main-axis (i.e. the flex
+ // direction)
+ flexItem->setItemShrinkAlongMainAxis(info->fillWidth() ? 1.0f : 0.0f);
+ }
+ else {
+ // If the Layout.fillWidth not been set, the preferred width
+ // will be set as width
+ if (!info->fillWidth())
+ flexItem->setWidth(sizeHints[Qt::PreferredSize].width());
+ flexItem->setFlexBasis(sizeHints[Qt::PreferredSize].height(), !info->fillHeight());
+ // Set child item to grow on main-axis (i.e. the flex
+ // direction)
+ flexItem->setItemGrowAlongMainAxis(info->fillHeight() ? 1.0f : 0.0f);
+ // Set child item to shrink on main-axis (i.e. the flex
+ // direction)
+ flexItem->setItemShrinkAlongMainAxis(info->fillHeight() ? 1.0f : 0.0f);
+ }
+ }
+ // If the Layout.fillHeight not been set, the preferred height will be
+ // set as height in the previous condition. Otherwise (for
+ // Layout.fillHeight been set as true), make flex item to AlignStretch.
+ // Thus it can also grow vertically.
+ // Note: The same applies for Layout.fillWidth to grow horizontally.
+ if ((qt_is_nan(flexItem->size().width()) && info->fillWidth()) ||
+ (qt_is_nan(flexItem->size().height()) && info->fillHeight())) {
+ flexItem->setItemStretchAlongCrossSection();
+ } else {
+ flexItem->inheritItemStretchAlongCrossSection();
+ }
+ }
+}
+
+SizeHints &QQuickFlexboxLayoutEngine::cachedItemSizeHints(int index) const
+{
+ QQuickFlexboxLayoutItem *flexBoxLayoutItem = m_flexLayoutItems.at(index);
+ Q_ASSERT(flexBoxLayoutItem);
+ SizeHints &hints = flexBoxLayoutItem->cachedItemSizeHints();
+ if (!hints.min().isValid())
+ collectItemSizeHints(flexBoxLayoutItem, hints.array);
+ return hints;
+}
+
+QSizeF QQuickFlexboxLayoutEngine::sizeHint(Qt::SizeHint whichSizeHint) const
+{
+ QSizeF &askingFor = m_cachedSizeHints[whichSizeHint];
+ if (!askingFor.isValid()) {
+ QSizeF &minS = m_cachedSizeHints[Qt::MinimumSize];
+ QSizeF &prefS = m_cachedSizeHints[Qt::PreferredSize];
+ QSizeF &maxS = m_cachedSizeHints[Qt::MaximumSize];
+
+ minS = QSizeF(0,0);
+ prefS = QSizeF(0,0);
+ maxS = QSizeF(std::numeric_limits<qreal>::infinity(), std::numeric_limits<qreal>::infinity());
+
+ const int count = itemCount();
+ for (int i = 0; i < count; ++i) {
+ SizeHints &hints = cachedItemSizeHints(i);
+ auto &flexLayoutItem = m_flexLayoutItems.at(i);
+ flexLayoutItem->setMinSize(hints.min());
+ if (flexLayoutItem->isFlexBasisUndefined()) {
+ // If flex basis is undefined and item is still stretched, it
+ // meant the flex child item has a const width or height but
+ // want to stretch vertically or horizontally
+ if (flexLayoutItem->isItemStreched()) {
+ if (auto *parentLayoutItem = qobject_cast<QQuickFlexboxLayout *>(m_flexboxParentItem->quickItem())) {
+ // Reset the size of the child item if the parent sets
+ // its property 'align-item' to strecth
+ // Note: The child item can also override the parent
+ // align-item property through align-self
+ // (this is FlexboxLayout.alignItem for quick items)
+ flexLayoutItem->resetSize();
+ if (parentLayoutItem->direction() == QQuickFlexboxLayout::Row ||
+ parentLayoutItem->direction() == QQuickFlexboxLayout::RowReverse) {
+ flexLayoutItem->setWidth(hints.pref().width());
+ } else {
+ flexLayoutItem->setHeight(hints.pref().height());
+ }
+ }
+ } else {
+ flexLayoutItem->setSize(hints.pref());
+ }
+ }
+ flexLayoutItem->setMaxSize(hints.max());
+ // The preferred size, minimum and maximum size of the parent item
+ // will be calculated as follows
+ // If no wrap enabled in the flex layout:
+ // For flex direction Row or RowReversed:
+ // Parent pref, min and max width:
+ // Sum of the pref, min and max width of the child
+ // items
+ // Parent pref, min and max height:
+ // Max of pref, min and max height of the child
+ // items
+ // For flex direction Column or ColumnReversed:
+ // Parent pref, min and max width:
+ // Max of pref, min and max width of the child
+ // items
+ // Parent pref, min and max height:
+ // Sum of the pref, min and max height of the
+ // child items
+ // Else if wrap enabled in the flex layout: (either Wrap or
+ // WrapReversed)
+ // For flex direction Row or RowReversed or Column or
+ // ColumnReversed:
+ // Parent pref, min, max width/height:
+ // Sum of the pref, min and max width/height of
+ // the child items
+ if (auto *qFlexLayout = qobject_cast<QQuickFlexboxLayout *>(m_flexboxParentItem->quickItem())) {
+ if (qFlexLayout->wrap() == QQuickFlexboxLayout::NoWrap) {
+ if (qFlexLayout->direction() == QQuickFlexboxLayout::Row ||
+ qFlexLayout->direction() == QQuickFlexboxLayout::RowReverse) {
+ // Minimum size
+ minS.setWidth(minS.width() + hints.min().width());
+ minS.setHeight(qMax(minS.height(), hints.min().height()));
+ // Preferred size
+ prefS.setWidth(prefS.width() + hints.pref().width());
+ prefS.setHeight(qMax(prefS.height(), hints.pref().height()));
+ // Maximum size
+ maxS.setWidth(maxS.width() + hints.max().width());
+ maxS.setHeight(qMax(maxS.height(), hints.max().height()));
+ } else if (qFlexLayout->direction() == QQuickFlexboxLayout::Column ||
+ qFlexLayout->direction() == QQuickFlexboxLayout::ColumnReverse) {
+ // Minimum size
+ minS.setWidth(qMax(minS.width(), hints.min().width()));
+ minS.setHeight(minS.height() + hints.min().height());
+ // Preferred size
+ prefS.setWidth(qMax(prefS.width(), hints.pref().width()));
+ prefS.setHeight(prefS.height() + hints.pref().height());
+ // Maximum size
+ maxS.setWidth(qMax(maxS.width(), hints.max().width()));
+ maxS.setHeight(maxS.height() + hints.max().height());
+ }
+ } else if (qFlexLayout->wrap() == QQuickFlexboxLayout::Wrap ||
+ qFlexLayout->wrap() == QQuickFlexboxLayout::WrapReverse) {
+ minS += hints.min();
+ prefS += hints.pref();
+ maxS += hints.max();
+ }
+ }
+ }
+ }
+ return askingFor;
+}
+
+void QQuickFlexboxLayoutEngine::invalidateItemSizeHint(QQuickItem *item)
+{
+ if (auto *flexLayoutItem = findFlexboxLayoutItem(item)) {
+ SizeHints &hints = flexLayoutItem->cachedItemSizeHints();
+ hints.min() = QSizeF();
+ hints.pref() = QSizeF();
+ hints.max() = QSizeF();
+ }
+}
+
+void QQuickFlexboxLayoutEngine::setGeometries(const QSizeF &contentSize)
+{
+ m_flexboxParentItem->setSize(contentSize);
+ m_flexboxParentItem->computeLayout(contentSize);
+ for (auto *item : m_flexLayoutItems) {
+ item->quickItem()->setPosition(item->position());
+ QSizeF oldSize = item->quickItem()->size();
+ QSizeF newSize = item->size();
+ if (oldSize == newSize) {
+ // Enforce rearrange as the size remains the same.
+ // This can happen in a case where we add a child item to the layout
+ // (which is already a child to a layout)
+ if (auto *layout = qobject_cast<QQuickLayout *>(item->quickItem())) {
+ if (layout->invalidatedArrangement())
+ layout->rearrange(newSize);
+ }
+ } else {
+ item->quickItem()->setSize(newSize);
+ }
+ }
+}
+
+// TODO: Need to check whether its needed to get the size of the flex item
+// through the callback measure function
+// QSizeF QQuickFlexboxLayoutItem::getSizeHint(float width,
+// YGMeasureMode widthMode, float height, YGMeasureMode heightMode)
+// {
+// QSizeF newSize(width, height);
+// switch (widthMode) {
+// case YGMeasureModeAtMost:
+// newSize.setWidth(m_cachedSizeHint.max().width());
+// break;
+// case YGMeasureModeExactly:
+// case YGMeasureModeUndefined:
+// newSize.setWidth(m_cachedSizeHint.pref().width());
+// break;
+// default: break;
+// }
+// switch (heightMode) {
+// case YGMeasureModeAtMost:
+// newSize.setHeight(m_cachedSizeHint.max().height());
+// break;
+// case YGMeasureModeExactly:
+// case YGMeasureModeUndefined:
+// newSize.setHeight(m_cachedSizeHint.pref().height());
+// break;
+// default: break;
+// }
+// return newSize;
+// }
+
+// YGSize QQuickFlexboxLayoutItem::measureFunc(YGNodeRef nodeRef, float width,
+// YGMeasureMode widthMode, float height, YGMeasureMode heightMode)
+// {
+// YGSize defaultSize;
+// auto *layoutItem = static_cast<QQuickFlexboxLayoutItem *>(YGNodeGetContext(nodeRef));
+// if (layoutItem) {
+// QSizeF size = layoutItem->getSizeHint(width, widthMode, height, heightMode);
+// defaultSize.width = (qt_is_nan(size.width()) || qt_is_inf(size.width())) ? YGUndefined : size.width();
+// defaultSize.height = (qt_is_nan(size.height()) || qt_is_inf(size.height())) ? YGUndefined : size.height();
+// }
+// return defaultSize;
+// }
+
+QT_END_NAMESPACE
diff --git a/src/quicklayouts/qquickflexboxlayoutengine_p.h b/src/quicklayouts/qquickflexboxlayoutengine_p.h
new file mode 100644
index 0000000000..3eb9a67dc8
--- /dev/null
+++ b/src/quicklayouts/qquickflexboxlayoutengine_p.h
@@ -0,0 +1,57 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QQUICKFLEXBOXLAYOUTENGINE_H
+#define QQUICKFLEXBOXLAYOUTENGINE_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtQuickLayouts/private/qquickflexboxlayoutitem_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QQuickFlexboxLayoutEngine
+{
+public:
+ QQuickFlexboxLayoutEngine();
+ ~QQuickFlexboxLayoutEngine();
+
+ void setGeometries(const QSizeF &contentSize);
+ QSizeF sizeHint(Qt::SizeHint whichSizeHint) const;
+ void collectItemSizeHints(QQuickFlexboxLayoutItem *flexItem, QSizeF *sizeHints) const;
+ void removeItemSizeHint(QQuickItem *item);
+ void invalidateItemSizeHint(QQuickItem *item);
+ bool isChildOverflowingParent(QQuickItem *item);
+
+ void insertItem(QQuickFlexboxLayoutItem *item);
+ int itemCount() const;
+ QQuickItem *itemAt(int index) const;
+ int indexOf(QQuickItem *item) const;
+ QQuickFlexboxLayoutItem *findFlexboxLayoutItem(QQuickItem *item) const;
+ void clearItems();
+
+ void setFlexboxParentItem(QQuickFlexboxLayoutItem *parentItem);
+ QQuickFlexboxLayoutItem *getFlexboxParentItem() { return m_flexboxParentItem; }
+
+private:
+ mutable QSizeF m_cachedSizeHints[Qt::NSizeHints];
+ SizeHints &cachedItemSizeHints(int index) const;
+
+ QList<QQuickFlexboxLayoutItem *> m_flexLayoutItems;
+ QQuickFlexboxLayoutItem *m_flexboxParentItem;
+ int m_visualDirection;
+ QSizeF m_contentSize;
+};
+
+QT_END_NAMESPACE
+
+#endif // QQUICKFLEXBOXLAYOUTENGINE_H
diff --git a/src/quicklayouts/qquickflexboxlayoutitem.cpp b/src/quicklayouts/qquickflexboxlayoutitem.cpp
new file mode 100644
index 0000000000..300eec3b5b
--- /dev/null
+++ b/src/quicklayouts/qquickflexboxlayoutitem.cpp
@@ -0,0 +1,294 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include <QtCore/qloggingcategory.h>
+#include <QtQuickLayouts/private/qquickflexboxlayoutitem_p.h>
+#include <yoga/YGNode.h>
+
+QT_BEGIN_NAMESPACE
+
+Q_LOGGING_CATEGORY(lcQuickFlexLayoutItem, "qt.quick.flexlayouts.item")
+
+static constexpr YGWrap QtToYGFlexboxWrap(QQuickFlexboxLayout::FlexboxWrap qtWrap) {
+ switch (qtWrap) {
+ case QQuickFlexboxLayout::NoWrap: return YGWrap::YGWrapNoWrap;
+ case QQuickFlexboxLayout::Wrap: return YGWrap::YGWrapWrap;
+ case QQuickFlexboxLayout::WrapReverse: return YGWrap::YGWrapWrapReverse;
+ default: {
+ qWarning("Not a valid wrap");
+ return YGWrap{};
+ }
+ }
+};
+
+static constexpr YGFlexDirection QtToYGFlexboxDirection(QQuickFlexboxLayout::FlexboxDirection qtDirection) {
+ switch (qtDirection) {
+ case QQuickFlexboxLayout::Column: return YGFlexDirection::YGFlexDirectionColumn;
+ case QQuickFlexboxLayout::ColumnReverse: return YGFlexDirection::YGFlexDirectionColumnReverse;
+ case QQuickFlexboxLayout::Row: return YGFlexDirection::YGFlexDirectionRow;
+ case QQuickFlexboxLayout::RowReverse: return YGFlexDirection::YGFlexDirectionRowReverse;
+ default: {
+ qWarning("Not a valid direction");
+ return YGFlexDirection{};
+ }
+ }
+};
+
+static constexpr YGAlign QtToYGFlexboxAlignment(QQuickFlexboxLayout::FlexboxAlignment qtAlignment) {
+ switch (qtAlignment) {
+ case QQuickFlexboxLayout::AlignAuto: return YGAlign::YGAlignAuto;
+ case QQuickFlexboxLayout::AlignStart: return YGAlign::YGAlignFlexStart;
+ case QQuickFlexboxLayout::AlignCenter: return YGAlign::YGAlignCenter;
+ case QQuickFlexboxLayout::AlignEnd: return YGAlign::YGAlignFlexEnd;
+ case QQuickFlexboxLayout::AlignStretch: return YGAlign::YGAlignStretch;
+ case QQuickFlexboxLayout::AlignBaseline: return YGAlign::YGAlignBaseline;
+ case QQuickFlexboxLayout::AlignSpaceBetween: return YGAlign::YGAlignSpaceBetween;
+ case QQuickFlexboxLayout::AlignSpaceAround: return YGAlign::YGAlignSpaceAround;
+ case QQuickFlexboxLayout::AlignSpaceEvenly: {
+ return YGAlign{};
+ }
+ default: {
+ qWarning("Not a valid alignment");
+ return YGAlign{};
+ }
+ }
+};
+
+static constexpr YGJustify QtToYGFlexboxJustify(QQuickFlexboxLayout::FlexboxJustify qtJustify) {
+ switch (qtJustify) {
+ case QQuickFlexboxLayout::JustifyStart: return YGJustify::YGJustifyFlexStart;
+ case QQuickFlexboxLayout::JustifyCenter: return YGJustify::YGJustifyCenter;
+ case QQuickFlexboxLayout::JustifyEnd: return YGJustify::YGJustifyFlexEnd;
+ case QQuickFlexboxLayout::JustifySpaceBetween: return YGJustify::YGJustifySpaceBetween;
+ case QQuickFlexboxLayout::JustifySpaceAround: return YGJustify::YGJustifySpaceAround;
+ case QQuickFlexboxLayout::JustifySpaceEvenly: return YGJustify::YGJustifySpaceEvenly;
+ default: {
+ qWarning("Not a valid justify");
+ return YGJustify{};
+ }
+ }
+};
+
+static constexpr YGEdge QtToYGFlexboxEdge(QQuickFlexboxLayout::FlexboxEdge qtEdge) {
+ switch (qtEdge) {
+ case QQuickFlexboxLayout::EdgeLeft: return YGEdge::YGEdgeLeft;
+ case QQuickFlexboxLayout::EdgeRight: return YGEdge::YGEdgeRight;
+ case QQuickFlexboxLayout::EdgeTop: return YGEdge::YGEdgeTop;
+ case QQuickFlexboxLayout::EdgeBottom: return YGEdge::YGEdgeBottom;
+ case QQuickFlexboxLayout::EdgeAll: return YGEdge::YGEdgeAll;
+ default: {
+ qWarning("Not a valid edge");
+ return YGEdge{};
+ }
+ }
+};
+
+static constexpr YGGutter QtToYGFlexboxGap(QQuickFlexboxLayout::FlexboxGap qtGap) {
+ switch (qtGap) {
+ case QQuickFlexboxLayout::GapRow: return YGGutter::YGGutterRow;
+ case QQuickFlexboxLayout::GapColumn: return YGGutter::YGGutterColumn;
+ case QQuickFlexboxLayout::GapAll: return YGGutter::YGGutterAll;
+ default: {
+ qWarning("Not a valid gap");
+ return YGGutter{};
+ }
+ }
+};
+
+QQuickFlexboxLayoutItem::QQuickFlexboxLayoutItem(QQuickItem *item)
+ : m_item(item)
+{
+ Q_ASSERT(m_item != nullptr);
+ m_yogaNode = YGNodeNew();
+ resetDefault();
+}
+
+QQuickFlexboxLayoutItem::~QQuickFlexboxLayoutItem()
+{
+ YGNodeFree(m_yogaNode);
+}
+
+void QQuickFlexboxLayoutItem::setMinSize(const QSizeF &size)
+{
+ YGNodeStyleSetMinWidth(m_yogaNode, static_cast<float>(size.width()));
+ YGNodeStyleSetMinHeight(m_yogaNode, static_cast<float>(size.height()));
+}
+
+void QQuickFlexboxLayoutItem::setSize(const QSizeF &size)
+{
+ YGNodeStyleSetWidth(m_yogaNode, static_cast<float>(size.width()));
+ YGNodeStyleSetHeight(m_yogaNode, static_cast<float>(size.height()));
+}
+
+void QQuickFlexboxLayoutItem::setWidth(const qreal &width)
+{
+ YGNodeStyleSetWidth(m_yogaNode, static_cast<float>(width));
+}
+
+void QQuickFlexboxLayoutItem::setHeight(const qreal &height)
+{
+ YGNodeStyleSetHeight(m_yogaNode, static_cast<float>(height));
+}
+
+void QQuickFlexboxLayoutItem::setMaxSize(const QSizeF &size)
+{
+ YGNodeStyleSetMaxWidth(m_yogaNode, static_cast<float>(size.width()));
+ YGNodeStyleSetMaxHeight(m_yogaNode, static_cast<float>(size.height()));
+}
+
+void QQuickFlexboxLayoutItem::setFlexBasis(const qreal value, bool reset)
+{
+ YGNodeStyleSetFlexBasis(m_yogaNode, reset ? qQNaN() : value);
+}
+
+bool QQuickFlexboxLayoutItem::isFlexBasisUndefined() const
+{
+ float value = YGNodeStyleGetFlexBasis(m_yogaNode).value;
+ return (value == YGUndefined || qt_is_nan(value));
+}
+
+void QQuickFlexboxLayoutItem::setItemGrowAlongMainAxis(const qreal value)
+{
+ YGNodeStyleSetFlexGrow(m_yogaNode, value);
+}
+
+void QQuickFlexboxLayoutItem::setItemShrinkAlongMainAxis(const qreal value)
+{
+ YGNodeStyleSetFlexShrink(m_yogaNode, value);
+}
+
+void QQuickFlexboxLayoutItem::setItemStretchAlongCrossSection()
+{
+ YGNodeStyleSetAlignSelf(m_yogaNode, YGAlignStretch);
+}
+
+void QQuickFlexboxLayoutItem::setFlexDirection(QQuickFlexboxLayout::FlexboxDirection direction)
+{
+ YGNodeStyleSetFlexDirection(m_yogaNode, QtToYGFlexboxDirection(direction));
+}
+
+void QQuickFlexboxLayoutItem::setFlexWrap(QQuickFlexboxLayout::FlexboxWrap wrap)
+{
+ YGNodeStyleSetFlexWrap(m_yogaNode, QtToYGFlexboxWrap(wrap));
+}
+
+void QQuickFlexboxLayoutItem::setFlexAlignItemsProperty(QQuickFlexboxLayout::FlexboxAlignment align)
+{
+ YGNodeStyleSetAlignItems(m_yogaNode, QtToYGFlexboxAlignment(align));
+}
+
+void QQuickFlexboxLayoutItem::setFlexAlignContentProperty(QQuickFlexboxLayout::FlexboxAlignment align)
+{
+ YGNodeStyleSetAlignContent(m_yogaNode, QtToYGFlexboxAlignment(align));
+}
+
+void QQuickFlexboxLayoutItem::setFlexJustifyContentProperty(QQuickFlexboxLayout::FlexboxJustify justify)
+{
+ YGNodeStyleSetJustifyContent(m_yogaNode, QtToYGFlexboxJustify(justify));
+}
+
+void QQuickFlexboxLayoutItem::setFlexAlignSelfProperty(QQuickFlexboxLayout::FlexboxAlignment align)
+{
+ YGNodeStyleSetAlignSelf(m_yogaNode, QtToYGFlexboxAlignment(align));
+}
+
+void QQuickFlexboxLayoutItem::setFlexMargin(QQuickFlexboxLayout::FlexboxEdge edge, const qreal value)
+{
+ YGNodeStyleSetMargin(m_yogaNode, QtToYGFlexboxEdge(edge), value);
+}
+
+void QQuickFlexboxLayoutItem::setFlexPadding(QQuickFlexboxLayout::FlexboxEdge edge, const qreal value)
+{
+ YGNodeStyleSetPadding(m_yogaNode, QtToYGFlexboxEdge(edge), value);
+}
+
+void QQuickFlexboxLayoutItem::setFlexGap(QQuickFlexboxLayout::FlexboxGap gap, const qreal value)
+{
+ YGNodeStyleSetGap(m_yogaNode, QtToYGFlexboxGap(gap), value);
+}
+
+bool QQuickFlexboxLayoutItem::isItemStreched() const
+{
+ return ((YGNodeStyleGetAlignSelf(m_yogaNode) == YGAlignStretch) ||
+ ((YGNodeStyleGetAlignSelf(m_yogaNode) == YGAlignAuto) &&
+ (YGNodeStyleGetAlignItems(m_yogaNode->getParent()) == YGAlignStretch)));
+}
+
+void QQuickFlexboxLayoutItem::inheritItemStretchAlongCrossSection()
+{
+ YGNodeStyleSetAlignSelf(m_yogaNode, YGAlignAuto);
+}
+
+void QQuickFlexboxLayoutItem::resetMargins()
+{
+ YGNodeStyleSetMargin(m_yogaNode, YGEdgeLeft, 0);
+ YGNodeStyleSetMargin(m_yogaNode, YGEdgeTop, 0);
+ YGNodeStyleSetMargin(m_yogaNode, YGEdgeRight, 0);
+ YGNodeStyleSetMargin(m_yogaNode, YGEdgeBottom, 0);
+}
+
+void QQuickFlexboxLayoutItem::resetPaddings()
+{
+ YGNodeStyleSetPadding(m_yogaNode, YGEdgeAll, 0);
+}
+
+void QQuickFlexboxLayoutItem::resetSize()
+{
+ YGNodeStyleSetWidth(m_yogaNode, YGUndefined);
+ YGNodeStyleSetHeight(m_yogaNode, YGUndefined);
+}
+
+QPoint QQuickFlexboxLayoutItem::position() const
+{
+ return QPoint(YGNodeLayoutGetLeft(m_yogaNode), YGNodeLayoutGetTop(m_yogaNode));
+}
+
+QSizeF QQuickFlexboxLayoutItem::size() const
+{
+ return QSizeF(YGNodeLayoutGetWidth(m_yogaNode), YGNodeLayoutGetHeight(m_yogaNode));
+}
+
+void QQuickFlexboxLayoutItem::insertChild(QQuickFlexboxLayoutItem *child, int index)
+{
+ YGNodeInsertChild(m_yogaNode, child->yogaItem(), index);
+ // TODO: We may need this only for the text node?
+ // YGNodeSetMeasureFunc(m_yogaNode, &QQuickFlexboxLayoutItem::measureFunc);
+}
+
+void QQuickFlexboxLayoutItem::resetDefault()
+{
+ // Context object is required here for callback functionality
+ // For instance, the measurement function would be called to determine the
+ // layout size
+ YGNodeSetContext(m_yogaNode, this);
+ resetMargins();
+ resetPaddings();
+ YGNodeStyleSetFlexBasis(m_yogaNode, YGUndefined);
+}
+
+bool QQuickFlexboxLayoutItem::hasMeasureFunc() const
+{
+ return YGNodeHasMeasureFunc(m_yogaNode);
+}
+
+void QQuickFlexboxLayoutItem::resetMeasureFunc()
+{
+ YGNodeSetMeasureFunc(m_yogaNode, nullptr);
+}
+
+SizeHints &QQuickFlexboxLayoutItem::cachedItemSizeHints() const
+{
+ return m_cachedSizeHint;
+}
+
+void QQuickFlexboxLayoutItem::computeLayout(const QSizeF &size)
+{
+ // Consider either NaN or Inf as YGUndefined
+ const float width = (qt_is_nan(size.width()) || qt_is_inf(size.width()) ||
+ !size.width()) ? YGUndefined : size.width();
+ const float height = (qt_is_nan(size.height()) || qt_is_inf(size.height()) ||
+ !size.height()) ? YGUndefined : size.height();
+ YGNodeCalculateLayout(m_yogaNode, width, height, YGDirectionLTR);
+}
+
+QT_END_NAMESPACE
diff --git a/src/quicklayouts/qquickflexboxlayoutitem_p.h b/src/quicklayouts/qquickflexboxlayoutitem_p.h
new file mode 100644
index 0000000000..9223b05ad7
--- /dev/null
+++ b/src/quicklayouts/qquickflexboxlayoutitem_p.h
@@ -0,0 +1,87 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QQUICKFLEXBOXLAYOUTITEM_P_H
+#define QQUICKFLEXBOXLAYOUTITEM_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtQuick/private/qquickitem_p.h>
+#include <QtQuickLayouts/private/qquickflexboxlayout_p.h>
+#include <yoga/Yoga.h>
+
+QT_BEGIN_NAMESPACE
+
+struct SizeHints {
+ inline QSizeF &min() { return array[Qt::MinimumSize]; }
+ inline QSizeF &pref() { return array[Qt::PreferredSize]; }
+ inline QSizeF &max() { return array[Qt::MaximumSize]; }
+ QSizeF array[Qt::NSizeHints];
+};
+
+class QQuickFlexboxLayoutItem
+{
+public:
+ explicit QQuickFlexboxLayoutItem(QQuickItem *item);
+ ~QQuickFlexboxLayoutItem();
+
+ void resetDefault();
+ void resetMargins();
+ void resetPaddings();
+ void resetSize();
+
+ QQuickItem *quickItem() const { return m_item; }
+ YGNodeRef yogaItem() const { return m_yogaNode; }
+
+ void insertChild(QQuickFlexboxLayoutItem *item, int index);
+
+ // Adapter APIs to the yoga library
+ void setMinSize(const QSizeF &size);
+ void setSize(const QSizeF &size);
+ void setWidth(const qreal &width);
+ void setHeight(const qreal &height);
+ void setMaxSize(const QSizeF &size);
+ void setFlexBasis(qreal value, bool reset = false);
+ void setFlexDirection(QQuickFlexboxLayout::FlexboxDirection direction);
+ void setFlexWrap(QQuickFlexboxLayout::FlexboxWrap wrap);
+ void setFlexAlignItemsProperty(QQuickFlexboxLayout::FlexboxAlignment align);
+ void setFlexAlignSelfProperty(QQuickFlexboxLayout::FlexboxAlignment align);
+ void setFlexAlignContentProperty(QQuickFlexboxLayout::FlexboxAlignment align);
+ void setFlexJustifyContentProperty(QQuickFlexboxLayout::FlexboxJustify justify);
+ void setItemGrowAlongMainAxis(qreal value);
+ void setItemShrinkAlongMainAxis(qreal value);
+ void setFlexMargin(QQuickFlexboxLayout::FlexboxEdge edge, qreal value);
+ void setFlexPadding(QQuickFlexboxLayout::FlexboxEdge edge, qreal value);
+ void setItemStretchAlongCrossSection();
+ void setFlexGap(QQuickFlexboxLayout::FlexboxGap gap, qreal value);
+ void inheritItemStretchAlongCrossSection();
+
+ bool hasMeasureFunc() const;
+ void resetMeasureFunc();
+ QPoint position() const;
+ QSizeF size() const;
+ bool isFlexBasisUndefined() const;
+ bool isItemStreched() const;
+ QSizeF computedLayoutSize() { return m_computedLayoutSize; }
+ SizeHints &cachedItemSizeHints() const;
+ void computeLayout(const QSizeF &size = QSizeF());
+
+private:
+ QQuickItem *m_item;
+ YGNodeRef m_yogaNode;
+ QSizeF m_computedLayoutSize;
+ mutable SizeHints m_cachedSizeHint;
+};
+
+QT_END_NAMESPACE
+
+#endif // QQUICKFLEXBOXLAYOUTITEM_P_H