diff options
author | Santhosh Kumar <[email protected]> | 2025-03-24 12:44:47 +0100 |
---|---|---|
committer | Jan Arve Sæther <[email protected]> | 2025-05-28 03:29:34 +0200 |
commit | 6617c64b8f00ce99abcf255520456f2bc5867737 (patch) | |
tree | 9dcf99bcf5c908a04ed926f6baea9c8414504c88 /src | |
parent | b1fc47a467562340278b90acb36c3ea12c12bfa9 (diff) |
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')
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 |