From 4eec47344e25b2e2cb2e6dbdb3f989ed5b6e2747 Mon Sep 17 00:00:00 2001 From: "J.T. Yim" Date: Wed, 17 Aug 2022 09:55:15 -0700 Subject: [PATCH] Add flexlayout dirSync to react-native-github Summary: Changelog: [Internal] Include FlexLayout C++ source to ReactCommons Reviewed By: d16r, NickGerleman Differential Revision: D38716145 fbshipit-source-id: 0ca2ab040e72168f2f1b479609b6cda2787eba66 --- ReactCommon/flexlayout/README.MD | 8 + .../flexlayout/flexlayout/Dimension.cpp | 165 ++ ReactCommon/flexlayout/flexlayout/Dimension.h | 67 + .../flexlayout/flexlayout/FlexBoxStyle.cpp | 191 +++ .../flexlayout/flexlayout/FlexBoxStyle.h | 111 ++ .../flexlayout/flexlayout/FlexItem.cpp | 107 ++ ReactCommon/flexlayout/flexlayout/FlexItem.h | 60 + .../flexlayout/flexlayout/FlexItemStyle.cpp | 124 ++ .../flexlayout/flexlayout/FlexItemStyle.h | 285 ++++ .../flexlayout/flexlayout/FlexLayout.cpp | 8 + .../flexlayout/flexlayout/FlexLayout.h | 40 + .../flexlayout/flexlayout/FlexLayoutEnums.h | 67 + .../flexlayout/flexlayout/FlexLayoutMacros.h | 13 + .../flexlayout/flexlayout/FlexLine.cpp | 293 ++++ ReactCommon/flexlayout/flexlayout/FlexLine.h | 47 + .../flexlayout/FlexboxAlgorithm.cpp | 45 + .../flexlayout/flexlayout/FlexboxAlgorithm.h | 1404 +++++++++++++++++ .../flexlayout/flexlayout/LayoutOutput.h | 290 ++++ .../flexlayout/flexlayout/Rounding.cpp | 69 + ReactCommon/flexlayout/flexlayout/Rounding.h | 22 + ReactCommon/flexlayout/flexlayout/Type.h | 18 + ReactCommon/flexlayout/flexlayout/Utils.cpp | 66 + ReactCommon/flexlayout/flexlayout/Utils.h | 204 +++ 23 files changed, 3704 insertions(+) create mode 100644 ReactCommon/flexlayout/README.MD create mode 100644 ReactCommon/flexlayout/flexlayout/Dimension.cpp create mode 100644 ReactCommon/flexlayout/flexlayout/Dimension.h create mode 100644 ReactCommon/flexlayout/flexlayout/FlexBoxStyle.cpp create mode 100644 ReactCommon/flexlayout/flexlayout/FlexBoxStyle.h create mode 100644 ReactCommon/flexlayout/flexlayout/FlexItem.cpp create mode 100644 ReactCommon/flexlayout/flexlayout/FlexItem.h create mode 100644 ReactCommon/flexlayout/flexlayout/FlexItemStyle.cpp create mode 100644 ReactCommon/flexlayout/flexlayout/FlexItemStyle.h create mode 100644 ReactCommon/flexlayout/flexlayout/FlexLayout.cpp create mode 100644 ReactCommon/flexlayout/flexlayout/FlexLayout.h create mode 100644 ReactCommon/flexlayout/flexlayout/FlexLayoutEnums.h create mode 100644 ReactCommon/flexlayout/flexlayout/FlexLayoutMacros.h create mode 100644 ReactCommon/flexlayout/flexlayout/FlexLine.cpp create mode 100644 ReactCommon/flexlayout/flexlayout/FlexLine.h create mode 100644 ReactCommon/flexlayout/flexlayout/FlexboxAlgorithm.cpp create mode 100644 ReactCommon/flexlayout/flexlayout/FlexboxAlgorithm.h create mode 100644 ReactCommon/flexlayout/flexlayout/LayoutOutput.h create mode 100644 ReactCommon/flexlayout/flexlayout/Rounding.cpp create mode 100644 ReactCommon/flexlayout/flexlayout/Rounding.h create mode 100644 ReactCommon/flexlayout/flexlayout/Type.h create mode 100644 ReactCommon/flexlayout/flexlayout/Utils.cpp create mode 100644 ReactCommon/flexlayout/flexlayout/Utils.h diff --git a/ReactCommon/flexlayout/README.MD b/ReactCommon/flexlayout/README.MD new file mode 100644 index 00000000000..e16c69aa313 --- /dev/null +++ b/ReactCommon/flexlayout/README.MD @@ -0,0 +1,8 @@ +# FlexLayout Source Code + +The react-native repo is exposing this subset of the source code for the FlexLayout layout engine for exploratory purposes. We're currently experimenting with this alternative layout engine for Yoga, which we hope might help to address some of the pain points brought by the community over the years. + +This inclusion of the new files in the repository can be safely ignored as they will have no functional impact on end users during this phase, ie. APK size will not be affected, and the layout API and functionality will remain entirely unchanged. + +Please also note that we also will not yet be considering feature or pull requests for the FlexLayout engine at this point in time. Feel free to comment in the community discussions if you have any concerns or questions related to FlexLayout: +https://github.com/react-native-community/discussions-and-proposals/discussions/499 diff --git a/ReactCommon/flexlayout/flexlayout/Dimension.cpp b/ReactCommon/flexlayout/flexlayout/Dimension.cpp new file mode 100644 index 00000000000..628ff55b4ab --- /dev/null +++ b/ReactCommon/flexlayout/flexlayout/Dimension.cpp @@ -0,0 +1,165 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "Dimension.h" +#include "Utils.h" + +#ifdef DEBUG +#include +#endif + +namespace facebook { +namespace flexlayout { + +#ifdef DEBUG +auto operator<<(std::ostream& os, const AlignContent& x) -> std::ostream& { + switch (x) { + case AlignContent::FlexStart: + os << "FlexStart"; + break; + case AlignContent::Center: + os << "Center"; + break; + case AlignContent::FlexEnd: + os << "FlexEnd"; + break; + case AlignContent::Stretch: + os << "Stretch"; + break; + case AlignContent::Baseline: + os << "Baseline"; + break; + case AlignContent::SpaceBetween: + os << "SpaceBetween"; + break; + case AlignContent::SpaceAround: + os << "SpaceAround"; + break; + } + return os; +} + +auto operator<<(std::ostream& os, const AlignItems& x) -> std::ostream& { + switch (x) { + case AlignItems::FlexStart: + os << "FlexStart"; + break; + case AlignItems::Center: + os << "Center"; + break; + case AlignItems::FlexEnd: + os << "FlexEnd"; + break; + case AlignItems::Stretch: + os << "Stretch"; + break; + case AlignItems::Baseline: + os << "Baseline"; + break; + } + return os; +} + +auto operator<<(std::ostream& os, const AlignSelf& x) -> std::ostream& { + switch (x) { + case AlignSelf::Auto: + os << "Auto"; + break; + case AlignSelf::FlexStart: + os << "FlexStart"; + break; + case AlignSelf::Center: + os << "Center"; + break; + case AlignSelf::FlexEnd: + os << "FlexEnd"; + break; + case AlignSelf::Stretch: + os << "Stretch"; + break; + case AlignSelf::Baseline: + os << "Baseline"; + break; + } + return os; +} + +auto operator<<(std::ostream& os, const Edge& x) -> std::ostream& { + switch (x) { + case Edge::Left: + os << "Left"; + break; + case Edge::Top: + os << "Top"; + break; + case Edge::Right: + os << "Right"; + break; + case Edge::Bottom: + os << "Bottom"; + break; + } + return os; +} + +auto operator<<(std::ostream& os, const PositionType& x) -> std::ostream& { + switch (x) { + case PositionType::Relative: + os << "Relative"; + break; + case PositionType::Absolute: + os << "Absolute"; + break; + } + return os; +} + +auto operator<<(std::ostream& os, const Display& x) -> std::ostream& { + switch (x) { + case Display::Flex: + os << "Flex"; + break; + case Display::None: + os << "None"; + break; + } + return os; +} +#endif + +namespace utils { + +auto Dimension::operator==(const Dimension& rhs) const -> bool { + return unit == rhs.unit && FlexLayoutFloatsEqual(value, rhs.value); +} + +auto Dimension::operator!=(const Dimension& rhs) const -> bool { + return !(*this == rhs); +} + +#ifdef DEBUG +auto operator<<(std::ostream& os, const Dimension& x) -> std::ostream& { + switch (x.unit) { + case Unit::Undefined: + os << "-"; + break; + case Unit::Point: + os << x.value << "pt"; + break; + case Unit::Percent: + os << x.value << "%"; + break; + case Unit::Auto: + os << "Auto"; + break; + } + return os; +} +#endif +} // namespace utils +} // namespace flexlayout +} // namespace facebook diff --git a/ReactCommon/flexlayout/flexlayout/Dimension.h b/ReactCommon/flexlayout/flexlayout/Dimension.h new file mode 100644 index 00000000000..5fb310ff457 --- /dev/null +++ b/ReactCommon/flexlayout/flexlayout/Dimension.h @@ -0,0 +1,67 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include "FlexLayoutEnums.h" +#include "FlexLayoutMacros.h" +#include "Type.h" + +#ifdef DEBUG +#include +#endif + +namespace facebook { +namespace flexlayout { + +#ifdef DEBUG +auto operator<<(std::ostream& os, const AlignContent& x) -> std::ostream&; +auto operator<<(std::ostream& os, const AlignItems& x) -> std::ostream&; +auto operator<<(std::ostream& os, const AlignSelf& x) -> std::ostream&; +auto operator<<(std::ostream& os, const Edge& x) -> std::ostream&; +auto operator<<(std::ostream& os, const PositionType& x) -> std::ostream&; +auto operator<<(std::ostream& os, const Display& x) -> std::ostream&; +#endif + +namespace utils { + +class Dimension { + public: + Dimension() { + value = NAN; + unit = Unit::Undefined; + } + + explicit Dimension(const Float value, const Unit unit) + : value(value), unit(unit) {} + + [[nodiscard]] auto resolve(const Float ownerSize) const -> Float { + switch (unit) { + case Unit::Point: + return value; + case Unit::Percent: + return value * ownerSize * 0.01f; + case Unit::Auto: + case Unit::Undefined: + return NAN; + } + } + + FLEX_LAYOUT_EXPORT auto operator==(const Dimension& rhs) const -> bool; + auto operator!=(const Dimension& rhs) const -> bool; + + Float value; + Unit unit; +}; + +#ifdef DEBUG +auto operator<<(std::ostream& os, const Dimension& x) -> std::ostream&; +#endif +} // namespace utils +} // namespace flexlayout +} // namespace facebook diff --git a/ReactCommon/flexlayout/flexlayout/FlexBoxStyle.cpp b/ReactCommon/flexlayout/flexlayout/FlexBoxStyle.cpp new file mode 100644 index 00000000000..f029352723d --- /dev/null +++ b/ReactCommon/flexlayout/flexlayout/FlexBoxStyle.cpp @@ -0,0 +1,191 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "FlexBoxStyle.h" +#include "Utils.h" + +#ifdef DEBUG +#include +#endif + +namespace facebook { +namespace flexlayout { +namespace style { + +auto FlexBoxStyle::getPaddingAndBorder(Edge edge, Float ownerWidth) const + -> Float { + const auto paddingValue = padding[static_cast(edge)].resolve(ownerWidth); + const auto borderValue = border[static_cast(edge)].resolve(ownerWidth); + return (isUndefined(paddingValue) ? 0 : paddingValue) + + (isUndefined(borderValue) ? 0 : borderValue); +} + +#ifdef DEBUG +static auto operator<<(std::ostream& os, const Direction& d) -> std::ostream& { + switch (d) { + case Direction::Inherit: + os << "Inherit"; + break; + case Direction::LTR: + os << "LTR"; + break; + case Direction::RTL: + os << "RTL"; + break; + } + return os; +} + +static auto operator<<(std::ostream& os, const FlexDirection& d) + -> std::ostream& { + switch (d) { + case FlexDirection::Row: + os << "Row"; + break; + case FlexDirection::RowReverse: + os << "RowReverse"; + break; + case FlexDirection::Column: + os << "Column"; + break; + case FlexDirection::ColumnReverse: + os << "ColumnReverse"; + break; + } + return os; +} + +static auto operator<<(std::ostream& os, const JustifyContent& x) + -> std::ostream& { + switch (x) { + case JustifyContent::FlexStart: + os << "FlexStart"; + break; + case JustifyContent::Center: + os << "Center"; + break; + case JustifyContent::FlexEnd: + os << "FlexEnd"; + break; + case JustifyContent::SpaceBetween: + os << "SpaceBetween"; + break; + case JustifyContent::SpaceAround: + os << "SpaceAround"; + break; + case JustifyContent::SpaceEvenly: + os << "SpaceEvenly"; + break; + } + return os; +} + +static auto operator<<(std::ostream& os, const FlexWrap& x) -> std::ostream& { + switch (x) { + case FlexWrap::NoWrap: + os << "NoWrap"; + break; + case FlexWrap::Wrap: + os << "Wrap"; + break; + case FlexWrap::WrapReverse: + os << "WrapReverse"; + break; + } + return os; +} + +static auto operator<<(std::ostream& os, const Overflow& x) -> std::ostream& { + switch (x) { + case Overflow::Visible: + os << "Visible"; + break; + case Overflow::Hidden: + os << "Hidden"; + break; + case Overflow::Scroll: + os << "Scroll"; + break; + } + return os; +} + +auto operator<<(std::ostream& os, const FlexBoxStyle& style) -> std::ostream& { + std::stringstream styleStr; + const auto defaultStyle = FlexBoxStyle{}; + + if (style.direction != defaultStyle.direction) { + styleStr << " direction: " << style.direction << std::endl; + } + + if (style.flexDirection != defaultStyle.flexDirection) { + styleStr << " flexDirection: " << style.flexDirection << std::endl; + } + + if (style.justifyContent != defaultStyle.justifyContent) { + styleStr << " justifyContent: " << style.justifyContent << std::endl; + } + + if (style.alignContent != defaultStyle.alignContent) { + styleStr << " alignContent: " << style.alignContent << std::endl; + } + + if (style.alignItems != defaultStyle.alignItems) { + styleStr << " alignItems: " << style.alignItems << std::endl; + } + + if (style.flexWrap != defaultStyle.flexWrap) { + styleStr << " flexWrap: " << style.flexWrap << std::endl; + } + + if (style.overflow != defaultStyle.overflow) { + styleStr << " overflow: " << style.overflow << std::endl; + } + + if (style.pointScaleFactor != defaultStyle.pointScaleFactor) { + styleStr << " pointScaleFactor: " << style.pointScaleFactor << std::endl; + } + + std::stringstream paddingStr; + for (auto edge : {Edge::Left, Edge::Top, Edge::Right, Edge::Bottom}) { + const auto value = style.getPadding(edge); + if (value.unit != Unit::Undefined) { + paddingStr << " " << edge << ": " << value << std::endl; + } + } + + if (!paddingStr.str().empty()) { + styleStr << " padding: {" << std::endl; + styleStr << paddingStr.str(); + styleStr << " }" << std::endl; + } + + std::stringstream borderStr; + for (auto edge : {Edge::Left, Edge::Top, Edge::Right, Edge::Bottom}) { + const auto value = style.getBorder(edge); + if (value.unit != Unit::Undefined) { + borderStr << " " << edge << ": " << value << std::endl; + } + } + + if (!borderStr.str().empty()) { + styleStr << " border: {" << std::endl; + styleStr << borderStr.str(); + styleStr << " }" << std::endl; + } + + if (!styleStr.str().empty()) { + os << '{' << std::endl; + os << styleStr.str(); + os << '}'; + } + return os; +} +#endif +} // namespace style +} // namespace flexlayout +} // namespace facebook diff --git a/ReactCommon/flexlayout/flexlayout/FlexBoxStyle.h b/ReactCommon/flexlayout/flexlayout/FlexBoxStyle.h new file mode 100644 index 00000000000..b1645e897f2 --- /dev/null +++ b/ReactCommon/flexlayout/flexlayout/FlexBoxStyle.h @@ -0,0 +1,111 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include "Dimension.h" +#include "FlexLayoutEnums.h" +#include "FlexLayoutMacros.h" + +#ifdef DEBUG +#include +#endif + +namespace facebook { +namespace flexlayout { +namespace style { + +using namespace facebook::flexlayout::utils; + +class FLEX_LAYOUT_EXPORT FlexBoxStyle { + private: + std::array padding = {}; + std::array border = {}; + + public: + Direction direction : 2; + FlexDirection flexDirection : 2; + JustifyContent justifyContent : 3; + AlignContent alignContent : 3; + AlignItems alignItems : 3; + FlexWrap flexWrap : 2; + Overflow overflow : 2; + + FlexBoxStyle() { + direction = Direction::Inherit; + justifyContent = JustifyContent::FlexStart; + flexWrap = FlexWrap::NoWrap; + overflow = Overflow::Visible; + alignContent = AlignContent::Stretch; + alignItems = AlignItems::Stretch; + flexDirection = FlexDirection::Row; + } + + // https://www.w3.org/TR/css-flexbox-1/#axis-mapping + auto mainAxis() const -> FlexDirection { + switch (direction) { + case Direction::RTL: + switch (flexDirection) { + case FlexDirection::Row: + return FlexDirection::RowReverse; + case FlexDirection::RowReverse: + return FlexDirection::Row; + case FlexDirection::Column: + case FlexDirection::ColumnReverse: + return flexDirection; + } + case Direction::Inherit: + case Direction::LTR: + return flexDirection; + } + } + + // https://www.w3.org/TR/css-flexbox-1/#axis-mapping + auto crossAxis() const -> FlexDirection { + switch (mainAxis()) { + case FlexDirection::Row: + case FlexDirection::RowReverse: + return FlexDirection::Column; + case FlexDirection::Column: + case FlexDirection::ColumnReverse: + return direction == Direction::RTL ? FlexDirection::RowReverse + : FlexDirection::Row; + } + } + + auto getPadding(Edge edge) const -> Dimension { + return padding[static_cast(edge)]; + } + + void setPadding(Edge edge, Float value) { + padding[static_cast(edge)] = Dimension(value, Unit::Point); + } + + void setPaddingPercent(Edge edge, Float value) { + padding[static_cast(edge)] = Dimension(value, Unit::Percent); + } + + auto getBorder(Edge edge) const -> Dimension { + return border[static_cast(edge)]; + } + + void setBorder(Edge edge, Float value) { + border[static_cast(edge)] = Dimension(value, Unit::Point); + } + + auto getPaddingAndBorder(Edge edge, Float ownerWidth) const -> Float; + + float pointScaleFactor = 1; +}; + +#ifdef DEBUG +auto operator<<(std::ostream& os, const FlexBoxStyle& style) -> std::ostream&; +#endif +} // namespace style +} // namespace flexlayout +} // namespace facebook diff --git a/ReactCommon/flexlayout/flexlayout/FlexItem.cpp b/ReactCommon/flexlayout/flexlayout/FlexItem.cpp new file mode 100644 index 00000000000..f6bda9dccad --- /dev/null +++ b/ReactCommon/flexlayout/flexlayout/FlexItem.cpp @@ -0,0 +1,107 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "FlexItem.h" + +#include + +auto facebook::flexlayout::algo::FlexItem::crossSizeRange( + const bool isMainAxisRow, + const Float availableInnerCrossDim, + const AlignItems align, + const bool isExactCrossDim, + const bool isSingleLineContainer, + const bool flexBasisOverflows, + const FlexDirection crossAxis, + const Float availableInnerWidth) const -> Range { + assert(std::isfinite(targetMainSize)); + + const auto crossSize = isMainAxisRow ? resolvedHeight : resolvedWidth; + const auto resolvedCrossSize = crossSize.resolve(availableInnerCrossDim); + + // Try to determine the exact cross size + const auto exactCrossSize = [&]() { + // Derived from aspect ratio + const auto ratio = flexItemStyle.aspectRatio; + if (ratio > 0) { + return isMainAxisRow ? targetMainSize / ratio : targetMainSize * ratio; + } + + // Cannot resolve percentages if the cross dimension of the container is not + // known + if (crossSize.unit == Unit::Percent && !isExactCrossDim) { + return NAN; + } + + // Exact specified cross size + if (isDefined(resolvedCrossSize)) { + return resolvedCrossSize; + } + + // Derived from align-items: stretch + const auto contentFitsOnOneLine = + isSingleLineContainer || !flexBasisOverflows; + const auto noAutoMarginsOnCrossAxis = + flexItemStyle.getMargin(getLeadingEdge(crossAxis)).unit != Unit::Auto && + flexItemStyle.getMargin(getTrailingEdge(crossAxis)).unit != Unit::Auto; + if (isExactCrossDim && contentFitsOnOneLine && + align == AlignItems::Stretch && noAutoMarginsOnCrossAxis) { + return availableInnerCrossDim - + flexItemStyle.getMarginForAxis(crossAxis, availableInnerWidth); + } + + // If we are here, there is no exact size for this item + return NAN; + }(); + + const auto minCross = + isMainAxisRow ? flexItemStyle.minHeight : flexItemStyle.minWidth; + const auto maxCross = + isMainAxisRow ? flexItemStyle.maxHeight : flexItemStyle.maxWidth; + const auto resolvedMinCross = minCross.resolve(availableInnerCrossDim); + const auto resolvedMaxCross = maxCross.resolve(availableInnerCrossDim); + + // The min / max constraints are applied differently depending on whether the + // cross size is exact or not + if (isDefined(exactCrossSize)) { + // If the cross size is exact, apply min / max constraints... + const auto usedMinCrossSize = + isDefined(resolvedMinCross) ? resolvedMinCross : 0.0f; + const auto usedMaxCrossSize = isDefined(resolvedMaxCross) + ? resolvedMaxCross + : std::numeric_limits::infinity(); + const auto usedCrossSize = + std::max(std::min(exactCrossSize, usedMaxCrossSize), usedMinCrossSize); + + // ...and produce a single value range + return {usedCrossSize, usedCrossSize}; + } + + // From https://www.w3.org/TR/css-flexbox-1/#algo-cross-item: + // "Determine the hypothetical cross size of each item by performing layout + // with the used main size and the available space, treating auto as + // fit-content." + // + // The exact cross size isn't known, measure the item with a + // range from 0 to size available on the cross axis (fit-content in CSS + // terms)... + const auto tentativeMinCrossSize = + isUndefined(availableInnerCrossDim) ? NAN : 0.0f; + const auto tentativeMaxCrossSize = availableInnerCrossDim <= 0 + ? NAN + : availableInnerCrossDim - + flexItemStyle.getMarginForAxis(crossAxis, availableInnerWidth); + + // ...applying both min and max constraints to the min size and only max + // constraint to the max size + const auto usedMinCrossSize = ConstraintMinMax( + tentativeMinCrossSize, resolvedMinCross, resolvedMaxCross); + const auto usedMaxCrossSize = + ConstraintMax(tentativeMaxCrossSize, resolvedMaxCross); + + return {usedMinCrossSize, usedMaxCrossSize}; +} diff --git a/ReactCommon/flexlayout/flexlayout/FlexItem.h b/ReactCommon/flexlayout/flexlayout/FlexItem.h new file mode 100644 index 00000000000..a0ebda571a5 --- /dev/null +++ b/ReactCommon/flexlayout/flexlayout/FlexItem.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include "Dimension.h" +#include "FlexItemStyle.h" + +namespace facebook { +namespace flexlayout { +namespace algo { + +using namespace facebook::flexlayout; +using namespace facebook::flexlayout::utils; + +class FlexItem { + public: + size_t index; + const FlexItemStyleBase& flexItemStyle; + // TODO T68413071 Use Aggregrate Initialization + Dimension resolvedWidth = Dimension(); + Dimension resolvedHeight = Dimension(); + float computedFlexBasis = 0; + Float targetMainSize = NAN; + + explicit FlexItem( + size_t index, + const FlexItemStyleBase& flexItemStyleValue, + Dimension width, + Dimension height) + : index(index), + flexItemStyle(flexItemStyleValue), + resolvedWidth(width), + resolvedHeight(height) {} + + /** + Returns the range of sizes along the cross axis that must be used when + measuring this item. + + Preconditions: + - The used size along the main axis ( \c targetMainSize) is determined + */ + auto crossSizeRange( + bool isMainAxisRow, + Float availableInnerCrossDim, + AlignItems align, + bool isExactCrossDim, + bool isSingleLineContainer, + bool flexBasisOverflows, + FlexDirection crossAxis, + Float availableInnerWidth) const -> Range; +}; + +} // namespace algo +} // namespace flexlayout +} // namespace facebook diff --git a/ReactCommon/flexlayout/flexlayout/FlexItemStyle.cpp b/ReactCommon/flexlayout/flexlayout/FlexItemStyle.cpp new file mode 100644 index 00000000000..21a2be394cd --- /dev/null +++ b/ReactCommon/flexlayout/flexlayout/FlexItemStyle.cpp @@ -0,0 +1,124 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "FlexItemStyle.h" + +#ifdef DEBUG +#include +#endif + +namespace facebook { +namespace flexlayout { +namespace style { + +#ifdef DEBUG +auto operator<<(std::ostream& os, const FlexItemStyleBase& style) + -> std::ostream& { + std::stringstream styleStr; + const auto defaultStyle = FlexItemStyleBase{}; + + if (!FlexLayoutFloatsEqual(style.flex, defaultStyle.flex)) { + styleStr << " flex: " << style.flex << std::endl; + } + + if (!FlexLayoutFloatsEqual(style.flexGrow, defaultStyle.flexGrow)) { + styleStr << " flexGrow: " << style.flexGrow << std::endl; + } + + if (!FlexLayoutFloatsEqual(style.flexShrink, defaultStyle.flexShrink)) { + styleStr << " flexShrink: " << style.flexShrink << std::endl; + } + + if (style.flexBasis != defaultStyle.flexBasis) { + styleStr << " flexBasis: " << style.flexBasis << std::endl; + } + + if (!FlexLayoutFloatsEqual(style.aspectRatio, defaultStyle.aspectRatio)) { + styleStr << " aspectRatio: " << style.aspectRatio << std::endl; + } + + if (style.alignSelf != defaultStyle.alignSelf) { + styleStr << " alignSelf: " << style.alignSelf << std::endl; + } + + if (style.positionType != defaultStyle.positionType) { + styleStr << " positionType: " << style.positionType << std::endl; + } + + if (style.display != defaultStyle.display) { + styleStr << " display: " << style.display << std::endl; + } + + if (style.width != defaultStyle.width) { + styleStr << " width: " << style.width << std::endl; + } + + if (style.minWidth != defaultStyle.minWidth) { + styleStr << " minWidth: " << style.minWidth << std::endl; + } + + if (style.maxWidth != defaultStyle.maxWidth) { + styleStr << " maxWidth: " << style.maxWidth << std::endl; + } + + if (style.height != defaultStyle.height) { + styleStr << " height: " << style.height << std::endl; + } + + if (style.minHeight != defaultStyle.minHeight) { + styleStr << " minHeight: " << style.minHeight << std::endl; + } + + if (style.maxHeight != defaultStyle.maxHeight) { + styleStr << " maxHeight: " << style.maxHeight << std::endl; + } + + std::stringstream marginStr; + for (auto edge : {Edge::Left, Edge::Top, Edge::Right, Edge::Bottom}) { + const auto value = style.getMargin(edge); + if (value.unit != Unit::Undefined) { + marginStr << " " << edge << ": " << value << std::endl; + } + } + + if (!marginStr.str().empty()) { + styleStr << " margin: {" << std::endl; + styleStr << marginStr.str(); + styleStr << " }" << std::endl; + } + + std::stringstream positionStr; + for (auto edge : {Edge::Left, Edge::Top, Edge::Right, Edge::Bottom}) { + const auto value = style.getPosition(edge); + if (value.unit != Unit::Undefined) { + positionStr << " " << edge << ": " << value << std::endl; + } + } + + if (!positionStr.str().empty()) { + styleStr << " position: {" << std::endl; + styleStr << positionStr.str(); + styleStr << " }" << std::endl; + } + + if (style.isReferenceBaseline != defaultStyle.isReferenceBaseline) { + styleStr << " isReferenceBaseline: " << std::boolalpha + << style.isReferenceBaseline << std::endl; + } + + if (!styleStr.str().empty()) { + os << " {" << std::endl; + os << styleStr.str(); + os << " }"; + } + + return os; +} +#endif +} // namespace style +} // namespace flexlayout +} // namespace facebook diff --git a/ReactCommon/flexlayout/flexlayout/FlexItemStyle.h b/ReactCommon/flexlayout/flexlayout/FlexItemStyle.h new file mode 100644 index 00000000000..1627b69bdf7 --- /dev/null +++ b/ReactCommon/flexlayout/flexlayout/FlexItemStyle.h @@ -0,0 +1,285 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include +#include +#include "Dimension.h" +#include "FlexLayoutEnums.h" +#include "Utils.h" + +#ifdef DEBUG +#include +#endif + +namespace facebook { +namespace flexlayout { +namespace style { + +using namespace facebook::flexlayout; +using namespace facebook::flexlayout::utils; + +class FLEX_LAYOUT_EXPORT FlexItemStyleBase { + public: + Float flex = NAN; + Float flexGrow = 0; + Float flexShrink = 1; + // TODO T68413071 Use Aggregate initialization Dimension flexBasis{NAN, + // Unit::Auto} + Dimension flexBasis = Dimension(NAN, Unit::Auto); + Float aspectRatio = NAN; + AlignSelf alignSelf : 3; + PositionType positionType : 2; + Display display : 2; + Dimension width = Dimension(NAN, Unit::Auto); + Dimension minWidth = Dimension(NAN, Unit::Undefined); + Dimension maxWidth = Dimension(NAN, Unit::Undefined); + Dimension height = Dimension(NAN, Unit::Auto); + Dimension minHeight = Dimension(NAN, Unit::Undefined); + Dimension maxHeight = Dimension(NAN, Unit::Undefined); + bool isReferenceBaseline = false; + bool enableTextRounding = false; + + private: + std::array margin = {}; + std::array position = {}; + + public: + FlexItemStyleBase() { + alignSelf = AlignSelf::Auto; + positionType = PositionType::Relative; + display = Display::Flex; + } + + auto getLeadingMargin(const FlexDirection axis, const Float widthSize) const + -> Float { + return ResolveValueMargin(getMargin(getLeadingEdge(axis)), widthSize); + } + + auto getTrailingMargin(const FlexDirection axis, const Float widthSize) const + -> Float { + return ResolveValueMargin(getMargin(getTrailingEdge(axis)), widthSize); + } + + auto getMarginForAxis(const FlexDirection axis, const Float widthSize) const + -> Float { + const auto marginForAxis = + getLeadingMargin(axis, widthSize) + getTrailingMargin(axis, widthSize); + return isUndefined(marginForAxis) ? 0 : marginForAxis; + } + + auto isFlexible() const -> bool { + return ( + (positionType == PositionType::Relative) && + (flexGrow != 0 || flexShrink != 0)); + } + + auto nodeBoundAxis( + const FlexDirection axis, + const float value, + const float axisSize) const -> Float { + if (FlexDirectionIsRow(axis)) { + return ConstraintMinMax( + value, minWidth.resolve(axisSize), maxWidth.resolve(axisSize)); + } + return ConstraintMinMax( + value, minHeight.resolve(axisSize), maxHeight.resolve(axisSize)); + } + + auto marginLeadingValue(FlexDirection axis) const -> Dimension { + return getMargin(getLeadingEdge(axis)); + } + + auto marginTrailingValue(FlexDirection axis) const -> Dimension { + return getMargin(getTrailingEdge(axis)); + } + + auto relativePosition(const FlexDirection axis, const float axisSize) const + -> Float { + const float leadingPosition = + getPosition(getLeadingEdge(axis)).resolve(axisSize); + if (isDefined(leadingPosition)) { + return leadingPosition; + } + + float trailingPosition = + getPosition(getTrailingEdge(axis)).resolve(axisSize); + if (isDefined(trailingPosition)) { + trailingPosition = -1 * trailingPosition; + } + return isUndefined(trailingPosition) ? 0 : trailingPosition; + } + + void setFlexBasis(Float value) { + flexBasis = Dimension(value, Unit::Point); + } + + void setFlexBasisPercent(Float value) { + flexBasis = Dimension(value, Unit::Percent); + } + + void setFlexBasisAuto() { + flexBasis = Dimension(NAN, Unit::Auto); + } + + auto getMargin(Edge edge) const -> Dimension { + return margin[static_cast(edge)]; + } + + void setMargin(Edge edge, Float value) { + margin[static_cast(edge)] = Dimension(value, Unit::Point); + } + + void setMarginPercent(Edge edge, Float value) { + margin[static_cast(edge)] = Dimension(value, Unit::Percent); + } + + void setMarginAuto(Edge edge) { + margin[static_cast(edge)] = Dimension(NAN, Unit::Auto); + } + + auto getPosition(Edge edge) const -> Dimension { + return position[static_cast(edge)]; + } + + void setPosition(Edge edge, Float value) { + position[static_cast(edge)] = Dimension(value, Unit::Point); + } + + void setPositionPercent(Edge edge, Float value) { + position[static_cast(edge)] = Dimension(value, Unit::Percent); + } + + void setWidth(Float value) { + width = Dimension(value, Unit::Point); + } + + void setWidthPercent(Float value) { + width = Dimension(value, Unit::Percent); + } + + void setWidthAuto() { + width = Dimension(NAN, Unit::Auto); + } + + void setMinWidth(Float value) { + minWidth = Dimension(value, Unit::Point); + } + + void setMinWidthPercent(Float value) { + minWidth = Dimension(value, Unit::Percent); + } + + void setMaxWidth(Float value) { + maxWidth = Dimension(value, Unit::Point); + } + + void setMaxWidthPercent(Float value) { + maxWidth = Dimension(value, Unit::Percent); + } + + void setHeight(Float value) { + height = Dimension(value, Unit::Point); + } + + void setHeightPercent(Float value) { + height = Dimension(value, Unit::Percent); + } + + void setHeightAuto() { + height = Dimension(NAN, Unit::Auto); + } + + void setMinHeight(Float value) { + minHeight = Dimension(value, Unit::Point); + } + + void setMinHeightPercent(Float value) { + minHeight = Dimension(value, Unit::Percent); + } + + void setMaxHeight(Float value) { + maxHeight = Dimension(value, Unit::Point); + } + + void setMaxHeightPercent(Float value) { + maxHeight = Dimension(value, Unit::Percent); + } +}; + +template +struct FLEX_LAYOUT_EXPORT FlexItemStyle : public FlexItemStyleBase { + using MeasureFunction = MeasureOutput (*)( + const MeasureData& measureData, + const Float minWidth, + const Float maxWidth, + const Float minHeight, + const Float maxHeight, + const Float ownerWidth, + const Float ownerHeight); + + using BaselineFunction = Float (*)( + const MeasureData& baselineData, + const Float width, + const Float height); + + MeasureFunction measureFunction = {nullptr}; + BaselineFunction baselineFunction = {nullptr}; + // Measure data set by the UI Framework, this is returned back in measure + // function callback. This is required to connect multiple sub trees together. + MeasureData measureData; +}; + +#ifndef __OBJC__ +// Non-ObjC code will recieve a pointer to const in the measure / baseline +// function +template +using PtrToConstIfNotObjC = const MeasureData*; +#else +// Code that uses ObjC types for measure data will receive a non-const pointer +// in the measure / baseline function since const doesn't do much for ObjC types +// (and makes it outright impossible to declare a function that accepts a +// pointer to const id or id) +template +using PtrToConstIfNotObjC = std::conditional_t< + std::is_convertible::value, + MeasureData*, + const MeasureData*>; +#endif + +template +struct FLEX_LAYOUT_EXPORT FlexItemStyle + : public FlexItemStyleBase { + using MeasureFunction = MeasureOutput (*)( + PtrToConstIfNotObjC measureData, + const Float minWidth, + const Float maxWidth, + const Float minHeight, + const Float maxHeight, + const Float ownerWidth, + const Float ownerHeight); + + using BaselineFunction = Float (*)( + PtrToConstIfNotObjC baselineData, + const Float width, + const Float height); + + MeasureFunction measureFunction = nullptr; + BaselineFunction baselineFunction = nullptr; + PtrToConstIfNotObjC measureData = nullptr; +}; + +#ifdef DEBUG +auto operator<<(std::ostream& os, const FlexItemStyleBase& style) + -> std::ostream&; +#endif + +} // namespace style +} // namespace flexlayout +} // namespace facebook diff --git a/ReactCommon/flexlayout/flexlayout/FlexLayout.cpp b/ReactCommon/flexlayout/flexlayout/FlexLayout.cpp new file mode 100644 index 00000000000..3dce1661047 --- /dev/null +++ b/ReactCommon/flexlayout/flexlayout/FlexLayout.cpp @@ -0,0 +1,8 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "FlexLayout.h" diff --git a/ReactCommon/flexlayout/flexlayout/FlexLayout.h b/ReactCommon/flexlayout/flexlayout/FlexLayout.h new file mode 100644 index 00000000000..2ed5decd0a2 --- /dev/null +++ b/ReactCommon/flexlayout/flexlayout/FlexLayout.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include +#include "FlexItemStyle.h" +#include "FlexLayoutMacros.h" +#include "FlexboxAlgorithm.h" +#include "LayoutOutput.h" +#include "Type.h" + +namespace facebook { +namespace flexlayout { +namespace core { + +using namespace facebook::flexlayout::style; +using namespace facebook::flexlayout::layoutoutput; + +template +FLEX_LAYOUT_EXPORT auto calculateLayout( + const FlexBoxStyle& parent, + const std::vector>& children, + const Float minWidth, + const Float maxWidth, + const Float minHeight, + const Float maxHeight, + const Float ownerWidth) -> LayoutOutput { + return algo::calculateLayoutInternal( + parent, children, minWidth, maxWidth, minHeight, maxHeight, ownerWidth); +} + +} // namespace core +} // namespace flexlayout +} // namespace facebook diff --git a/ReactCommon/flexlayout/flexlayout/FlexLayoutEnums.h b/ReactCommon/flexlayout/flexlayout/FlexLayoutEnums.h new file mode 100644 index 00000000000..40469caa32a --- /dev/null +++ b/ReactCommon/flexlayout/flexlayout/FlexLayoutEnums.h @@ -0,0 +1,67 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once +#include + +namespace facebook { +namespace flexlayout { + +enum class Unit : uint8_t { Undefined, Point, Percent, Auto }; + +enum class Direction : uint8_t { Inherit, LTR, RTL }; + +enum class FlexDirection : uint8_t { Row, RowReverse, Column, ColumnReverse }; + +enum class JustifyContent : uint8_t { + FlexStart, + Center, + FlexEnd, + SpaceBetween, + SpaceAround, + SpaceEvenly +}; + +enum class AlignContent : uint8_t { + FlexStart, + Center, + FlexEnd, + Stretch, + Baseline, + SpaceBetween, + SpaceAround +}; + +enum class AlignItems : uint8_t { + FlexStart, + Center, + FlexEnd, + Stretch, + Baseline, +}; + +enum class AlignSelf : uint8_t { + Auto, + FlexStart, + Center, + FlexEnd, + Stretch, + Baseline, +}; + +enum class FlexWrap : uint8_t { NoWrap, Wrap, WrapReverse }; + +enum class Overflow : uint8_t { Visible, Hidden, Scroll }; + +enum class Edge : uint8_t { Left, Top, Right, Bottom }; + +enum class PositionType : uint8_t { Relative, Absolute }; + +enum class Display : uint8_t { Flex, None }; + +} // namespace flexlayout +} // namespace facebook diff --git a/ReactCommon/flexlayout/flexlayout/FlexLayoutMacros.h b/ReactCommon/flexlayout/flexlayout/FlexLayoutMacros.h new file mode 100644 index 00000000000..1e8a4bbf05c --- /dev/null +++ b/ReactCommon/flexlayout/flexlayout/FlexLayoutMacros.h @@ -0,0 +1,13 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once +#include +#include + +#define FLEX_LAYOUT_EXPORT __attribute__((visibility("default"))) +#define UNDEFINED NAN diff --git a/ReactCommon/flexlayout/flexlayout/FlexLine.cpp b/ReactCommon/flexlayout/flexlayout/FlexLine.cpp new file mode 100644 index 00000000000..66955a1336e --- /dev/null +++ b/ReactCommon/flexlayout/flexlayout/FlexLine.cpp @@ -0,0 +1,293 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "FlexLine.h" + +#include +#include +#include + +namespace facebook { +namespace flexlayout { +namespace algo { + +template +auto mapReduce(const Range& range, Result initial, Map map, Reduce reduce) { + return std::inner_product( + std::cbegin(range), + std::cend(range), + std::cbegin(range), + initial, + reduce, + [&](const auto& x, const auto&) { return map(x); }); +} + +template +auto any_of(const Range& range, Predicate predicate) { + return std::any_of(std::cbegin(range), std::cend(range), predicate); +} + +namespace { +// Stores extra bits of information for each flex item used when resolving +// flexible lengths but not used afterwards +struct Item { + enum class Violation { None, Min, Max }; + + FlexItem& flexItem; + bool isFrozen = false; + Violation violation = Violation::None; +}; +} // namespace + +static auto calculateRemainingFreeSpace( + const std::vector& items, + const FlexDirection mainAxis, + const Float availableInnerMainDim, + const Float availableInnerWidth, + const bool sizeBasedOnContent) -> Float { + if (sizeBasedOnContent || isUndefined(availableInnerMainDim)) { + return 0.0f; + } + // Sum the outer sizes of all items on the line... + const auto sumOfOuterSizes = mapReduce( + items, + 0.0f, + [&](const Item& item) { + // For frozen items, use their outer target main size; for other + // items, use their outer flex base size. + const auto innerSize = item.isFrozen ? item.flexItem.targetMainSize + : item.flexItem.computedFlexBasis; + const auto margin = item.flexItem.flexItemStyle.getMarginForAxis( + mainAxis, availableInnerWidth); + return innerSize + margin; + }, + std::plus<>{}); + // ...and subtract this from the flex container’s inner main size. + return availableInnerMainDim - sumOfOuterSizes; +} + +auto FlexLine::resolveFlexibleLengths( + const FlexDirection mainAxis, + const Float availableInnerMainDim, + const Float availableInnerWidth, + const bool sizeBasedOnContent) -> Float { + enum class FlexFactor { Grow, Shrink }; + + // 1. Determine the used flex factor. + const auto usedFlexFactor = [&]() { + if (sizeBasedOnContent || isUndefined(availableInnerMainDim)) { + return FlexFactor::Shrink; + } + + // Sum the outer hypothetical main sizes of all items on the line. + const auto sumOfOuterHypotheticalMainSizes = mapReduce( + flexItems, + 0.0f, + [&](const FlexItem& item) { + const auto hypotheticalMainSize = item.flexItemStyle.nodeBoundAxis( + mainAxis, item.computedFlexBasis, availableInnerMainDim); + const auto margin = item.flexItemStyle.getMarginForAxis( + mainAxis, availableInnerWidth); + return hypotheticalMainSize + margin; + }, + std::plus<>{}); + + // If the sum is less than the flex container’s inner main size, use the + // flex grow factor for the rest of this algorithm; otherwise, use the flex + // shrink factor. + return sumOfOuterHypotheticalMainSizes < availableInnerMainDim + ? FlexFactor::Grow + : FlexFactor::Shrink; + }(); + + auto items = std::vector{}; + for (auto& flexItem : flexItems) { + items.push_back({flexItem}); + } + + // 2. Size inflexible items. Freeze, setting its target main size to its + // hypothetical main size… + for (auto& item : items) { + const auto& style = item.flexItem.flexItemStyle; + const auto flexFactor = [&]() { + switch (usedFlexFactor) { + case FlexFactor::Grow: + return style.flexGrow; + case FlexFactor::Shrink: + return style.flexShrink; + } + }(); + + // any item that has a flex factor of zero + const auto flexFactorIsZero = flexFactor == 0.0f; + // if using the flex grow factor: any item that has a flex base size greater + // than its hypothetical main size + const auto flexBaseSize = item.flexItem.computedFlexBasis; + const auto hypotheticalMainSize = + style.nodeBoundAxis(mainAxis, flexBaseSize, availableInnerMainDim); + const auto usingFlexGrowAndBaseSizeLargerThanHypothetical = + usedFlexFactor == FlexFactor::Grow && + flexBaseSize > hypotheticalMainSize; + // if using the flex shrink factor: any item that has a flex base size + // smaller than its hypothetical main size + const auto usingFlexShrinkAndBaseSizeSmallerThanHypothetical = + usedFlexFactor == FlexFactor::Shrink && + flexBaseSize < hypotheticalMainSize; + + if (flexFactorIsZero || usingFlexGrowAndBaseSizeLargerThanHypothetical || + usingFlexShrinkAndBaseSizeSmallerThanHypothetical) { + item.isFrozen = true; + item.flexItem.targetMainSize = hypotheticalMainSize; + } + } + + // 3. Calculate initial free space. + const auto initialFreeSpace = calculateRemainingFreeSpace( + items, + mainAxis, + availableInnerMainDim, + availableInnerWidth, + sizeBasedOnContent); + + // 4. Loop: + // a. Check for flexible items. If all the flex items on the line are + // frozen, free space has been distributed; exit this loop. + while (any_of(items, [](const Item& item) { return !item.isFrozen; })) { + // Calculate the remaining free space as for initial free space, above. + auto remainingFreeSpace = calculateRemainingFreeSpace( + items, + mainAxis, + availableInnerMainDim, + availableInnerWidth, + sizeBasedOnContent); + + const auto totalFlexFactorOfUnfrozenItems = mapReduce( + items, + 0.0f, + [&](const Item& item) { + if (item.isFrozen) { + return 0.0f; + } + const auto& flexItemStyle = item.flexItem.flexItemStyle; + switch (usedFlexFactor) { + case FlexFactor::Grow: + return flexItemStyle.flexGrow; + case FlexFactor::Shrink: + return flexItemStyle.flexShrink * item.flexItem.computedFlexBasis; + } + }, + std::plus<>{}); + + // If the sum of the unfrozen flex items’ flex factors is less than one + if (totalFlexFactorOfUnfrozenItems < 1) { + // multiply the initial free space by this sum. If the magnitude of this + // value is less than the magnitude of the remaining free space... + if (initialFreeSpace * totalFlexFactorOfUnfrozenItems < + remainingFreeSpace) { + // use this as the remaining free space. + remainingFreeSpace = initialFreeSpace * totalFlexFactorOfUnfrozenItems; + } + } + + auto totalViolation = 0.0f; + for (auto& item : items) { + if (item.isFrozen) { + continue; + } + + const auto& style = item.flexItem.flexItemStyle; + const auto flexBaseSize = item.flexItem.computedFlexBasis; + // c. Distribute free space proportional to the flex factors. + const auto targetMainSize = [&]() { + if (remainingFreeSpace == 0.0f || + totalFlexFactorOfUnfrozenItems == 0.0f) { + return flexBaseSize; + } + + switch (usedFlexFactor) { + // If using the flex grow factor + case FlexFactor::Grow: { + // Find the ratio of the item’s flex grow factor to the sum of the + // flex grow factors of all unfrozen items on the line. + const auto ratio = style.flexGrow / totalFlexFactorOfUnfrozenItems; + // Set the item’s target main size to its flex base size plus a + // fraction of the remaining free space proportional to the ratio. + return flexBaseSize + ratio * remainingFreeSpace; + } + case FlexFactor::Shrink: { + // For every unfrozen item on the line, multiply its flex shrink + // factor by its inner flex base size, and note this as its scaled + // flex shrink factor. + const auto scaledFlexShrinkFactor = style.flexShrink * flexBaseSize; + // Find the ratio of the item’s scaled flex shrink factor to the + // sum of the scaled flex shrink factors of all unfrozen items on + // the line. + const auto ratio = + scaledFlexShrinkFactor / totalFlexFactorOfUnfrozenItems; + // Set the item’s target main size to its flex base size minus a + // fraction of the absolute value of the remaining free space + // proportional to the ratio. + return flexBaseSize - ratio * std::abs(remainingFreeSpace); + } + } + }(); + + // Clamp each non-frozen item’s target main size by its used min and max + // main sizes and floor its content-box size at zero. + const auto clampedTargetMainSize = + style.nodeBoundAxis(mainAxis, targetMainSize, availableInnerMainDim); + + // d. Fix min/max violations. + if (clampedTargetMainSize > targetMainSize) { + // If the item’s target main size was made larger by this, it’s a min + // violation. + item.violation = Item::Violation::Min; + } else if (clampedTargetMainSize < targetMainSize) { + // If the item’s target main size was made smaller by this, it’s a max + // violation. + item.violation = Item::Violation::Max; + } else { + item.violation = Item::Violation::None; + } + + // The total violation is the sum of the adjustments from the previous + // step ∑(clamped size - unclamped size). + totalViolation += clampedTargetMainSize - targetMainSize; + item.flexItem.targetMainSize = clampedTargetMainSize; + } + + // e: Freeze over-flexed items. + const auto totalViolationIsZero = totalViolation == 0.0f; + for (auto& item : items) { + // If the total violation is: + // Zero: Freeze all items. + // Positive: Freeze all the items with min violations. + const auto itemWithMinViolationAndPositiveTotalViolation = + totalViolation > 0.0f && item.violation == Item::Violation::Min; + // Negative: Freeze all the items with max violations. + const auto itemWithMaxViolationAndNegativeTotalViolation = + totalViolation < 0.0f && item.violation == Item::Violation::Max; + + if (totalViolationIsZero || + itemWithMinViolationAndPositiveTotalViolation || + itemWithMaxViolationAndNegativeTotalViolation) { + item.isFrozen = true; + } + } + } + + return calculateRemainingFreeSpace( + items, + mainAxis, + availableInnerMainDim, + availableInnerWidth, + sizeBasedOnContent); +} + +} // namespace algo +} // namespace flexlayout +} // namespace facebook diff --git a/ReactCommon/flexlayout/flexlayout/FlexLine.h b/ReactCommon/flexlayout/flexlayout/FlexLine.h new file mode 100644 index 00000000000..5ec0ec4ea29 --- /dev/null +++ b/ReactCommon/flexlayout/flexlayout/FlexLine.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include +#include "FlexItem.h" + +namespace facebook { +namespace flexlayout { +namespace algo { + +struct FlexLine { + std::vector flexItems; + float crossDim = 0.0f; + float mainDim = 0.0f; + float maxBaseline = 0.0f; + + /** + Resolves flexible lengths on the main axis according to + https://www.w3.org/TR/css-flexbox-1/#resolve-flexible-lengths . + + Preconditions: + - All items have defined flex base size (i.e. \c + FlexItem::computedFlexBasis), see + https://www.w3.org/TR/css-flexbox-1/#flex-base-size + + Postconditions: + - All items have defined used main sizes (i.e. \c FlexItem::targetMainSize) + + \returns the amount of free space available for justification + */ + auto resolveFlexibleLengths( + FlexDirection mainAxis, + Float availableInnerMainDim, + Float availableInnerWidth, + bool sizeBasedOnContent) -> Float; +}; + +} // namespace algo +} // namespace flexlayout +} // namespace facebook diff --git a/ReactCommon/flexlayout/flexlayout/FlexboxAlgorithm.cpp b/ReactCommon/flexlayout/flexlayout/FlexboxAlgorithm.cpp new file mode 100644 index 00000000000..bbfb41d8248 --- /dev/null +++ b/ReactCommon/flexlayout/flexlayout/FlexboxAlgorithm.cpp @@ -0,0 +1,45 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "FlexboxAlgorithm.h" + +namespace facebook { +namespace flexlayout { +namespace algo { + +FLEX_LAYOUT_EXPORT auto IsBaselineNode( + const FlexBoxStyle& node, + const FlexItemStyleBase& flexItemStyle) -> bool { + if (FlexDirectionIsColumn(node.flexDirection)) { + return false; + } + return ResolveAlignment(flexItemStyle.alignSelf, node.alignItems) == + AlignItems::Baseline; +} + +FLEX_LAYOUT_EXPORT auto ResolveAlignment( + AlignSelf alignSelf, + AlignItems alignItems) -> AlignItems { + switch (alignSelf) { + case AlignSelf::Auto: + return alignItems; + case AlignSelf::FlexStart: + return AlignItems::FlexStart; + case AlignSelf::Center: + return AlignItems::Center; + case AlignSelf::FlexEnd: + return AlignItems::FlexEnd; + case AlignSelf::Stretch: + return AlignItems::Stretch; + case AlignSelf::Baseline: + return AlignItems::Baseline; + } +} + +} // namespace algo +} // namespace flexlayout +} // namespace facebook diff --git a/ReactCommon/flexlayout/flexlayout/FlexboxAlgorithm.h b/ReactCommon/flexlayout/flexlayout/FlexboxAlgorithm.h new file mode 100644 index 00000000000..69abd294163 --- /dev/null +++ b/ReactCommon/flexlayout/flexlayout/FlexboxAlgorithm.h @@ -0,0 +1,1404 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include +#include +#include +#include "FlexBoxStyle.h" +#include "FlexItem.h" +#include "FlexItemStyle.h" +#include "FlexLine.h" +#include "LayoutOutput.h" +#include "Rounding.h" +#include "Type.h" + +namespace facebook { +namespace flexlayout { +namespace algo { + +using namespace facebook::flexlayout::style; + +FLEX_LAYOUT_EXPORT auto IsBaselineNode( + const FlexBoxStyle& node, + const FlexItemStyleBase& flexItemStyle) -> bool; + +FLEX_LAYOUT_EXPORT auto ResolveAlignment( + AlignSelf alignSelf, + AlignItems alignItems) -> AlignItems; + +template +FLEX_LAYOUT_EXPORT auto calculateLayoutInternal( + const FlexBoxStyle& node, + const std::vector>& children, + const Float minWidth, + const Float maxWidth, + const Float minHeight, + const Float maxHeight, + const Float ownerWidth) -> layoutoutput::LayoutOutput { + // Step 1 Initialize values required for algorithm + const auto isMainAxisRow = FlexDirectionIsRow(node.flexDirection); + const std::array paddingAndBorder = { + node.getPaddingAndBorder(Edge::Left, ownerWidth), + node.getPaddingAndBorder(Edge::Top, ownerWidth), + node.getPaddingAndBorder(Edge::Right, ownerWidth), + node.getPaddingAndBorder(Edge::Bottom, ownerWidth)}; + const auto paddingAndBorderAxisMain = isMainAxisRow + ? paddingAndBorder[static_cast(Edge::Left)] + + paddingAndBorder[static_cast(Edge::Right)] + : paddingAndBorder[static_cast(Edge::Top)] + + paddingAndBorder[static_cast(Edge::Bottom)]; + const auto paddingAndBorderAxisCross = isMainAxisRow + ? paddingAndBorder[static_cast(Edge::Top)] + + paddingAndBorder[static_cast(Edge::Bottom)] + : paddingAndBorder[static_cast(Edge::Left)] + + paddingAndBorder[static_cast(Edge::Right)]; + + const auto isExactWidth = + isDefined(minWidth) && isDefined(maxWidth) && minWidth == maxWidth; + const auto isExactHeight = + isDefined(minHeight) && isDefined(maxHeight) && minHeight == maxHeight; + + const auto isExactCrossDim = isMainAxisRow ? isExactHeight : isExactWidth; + const auto isExactMainDim = isMainAxisRow ? isExactWidth : isExactHeight; + + auto minMainDim = isMainAxisRow ? minWidth : minHeight; + const auto minCrossDim = isMainAxisRow ? minHeight : minWidth; + + const auto mainAxis = node.mainAxis(); + + auto nodeLayoutOutput = layoutoutput::LayoutOutput{}; + if (children.empty()) { + const auto mainDim = isMainAxisRow ? maxWidth : maxHeight; + const auto crossDim = isMainAxisRow ? maxHeight : maxWidth; + + const auto mainDimFinal = ConstraintMin( + !isExactMainDim ? paddingAndBorderAxisMain : mainDim, minMainDim); + const auto crossDimFinal = ConstraintMin( + !isExactCrossDim ? paddingAndBorderAxisCross : crossDim, minCrossDim); + + nodeLayoutOutput.setSize( + mainAxis, + isUndefined(mainDimFinal) ? 0 : mainDimFinal, + isUndefined(crossDimFinal) ? 0 : crossDimFinal); + + return nodeLayoutOutput; + } + + // Step 2 Calculate available main and cross size + const auto availableInnerWidth = std::max( + maxWidth - paddingAndBorder[static_cast(Edge::Left)] - + paddingAndBorder[static_cast(Edge::Right)], + 0.0f); + const auto availableInnerHeight = std::max( + maxHeight - paddingAndBorder[static_cast(Edge::Top)] - + paddingAndBorder[static_cast(Edge::Bottom)], + 0.0f); + auto availableInnerMainDim = + isMainAxisRow ? availableInnerWidth : availableInnerHeight; + + // We are removing all children with DisplayNone and positionType Absolute as + // they are not needed at all during the flexbox algorithm. DisplayNone will + // not be displayed so their layout values can be default i.e Undefined. + // Absolute children will be measured and layed out at the last step after + // container's size has been determined. + auto flexItems = std::vector{}; + auto singleFlexChildIndex = std::numeric_limits::max(); + auto index = size_t{0}; + auto singleFlexChildPossible = true; + for (const auto& child : children) { + if (child.display != Display::None && + child.positionType != PositionType::Absolute) { + const auto w = + isDefined(child.maxWidth.value) && (child.minWidth == child.maxWidth) + ? child.maxWidth + : child.width; + const auto h = isDefined(child.maxHeight.value) && + (child.minHeight == child.maxHeight) + ? child.maxHeight + : child.height; + flexItems.emplace_back(index, child, w, h); + + if (isExactMainDim && singleFlexChildPossible) { + if (child.isFlexible()) { + if (singleFlexChildIndex != std::numeric_limits::max() || + FlexLayoutFloatsEqual(child.flexGrow, 0.0f) || + FlexLayoutFloatsEqual(child.flexShrink, 0.0f)) { + // There is already a flexible child, or this flexible child doesn't + // have flexGrow and flexShrink, abort + singleFlexChildIndex = std::numeric_limits::max(); + singleFlexChildPossible = false; + } else { + singleFlexChildIndex = index; + } + } + } + } + nodeLayoutOutput.children.push_back( + typename layoutoutput::LayoutOutput::Child()); + nodeLayoutOutput.children[index].enableTextRounding = + child.enableTextRounding; + index++; + } + + // Step 3 Calculate the flex base size and hypothetical main size + auto totalOuterFlexBasis = 0.0; + for (auto& flexItem : flexItems) { + const auto& flexItemStyle = children[flexItem.index]; + if (flexItem.index == singleFlexChildIndex) { + flexItem.computedFlexBasis = 0; + } else { + const auto resolvedFlexBasis = + flexItemStyle.flexBasis.resolve(availableInnerMainDim); + const auto resolvedWidth = + flexItem.resolvedWidth.resolve(availableInnerWidth); + const auto resolvedHeight = + flexItem.resolvedHeight.resolve(availableInnerHeight); + const auto crossSize = isMainAxisRow ? resolvedHeight : resolvedWidth; + if (isDefined(resolvedFlexBasis) && isDefined(availableInnerMainDim)) { + // if flex basis is defined use that + flexItem.computedFlexBasis = resolvedFlexBasis; + } else if (isMainAxisRow && isDefined(resolvedWidth)) { + // if main axis is row and width is defined use width as flexBasis + flexItem.computedFlexBasis = resolvedWidth; + } else if (!isMainAxisRow && isDefined(resolvedHeight)) { + // if main axis is column and height is defined use height as flexBasis + flexItem.computedFlexBasis = resolvedHeight; + } else if ( + isDefined(flexItemStyle.aspectRatio) && isDefined(crossSize) && + flexItemStyle.flexBasis.unit == Unit::Auto) { + // if aspect ratio is defined and cross size is defined then flex basis + // is crossSize * aspectRatio + if (isMainAxisRow) { + flexItem.computedFlexBasis = crossSize * flexItemStyle.aspectRatio; + } else { + flexItem.computedFlexBasis = flexItemStyle.aspectRatio > 0 + ? crossSize / flexItemStyle.aspectRatio + : 0.0f; + } + } else { + // calculate childWidth/childHeight and measure child + // flex basis is child's measured main size + const auto align = + ResolveAlignment(flexItemStyle.alignSelf, node.alignItems); + + float childMinWidth = NAN; + float childMaxWidth = NAN; + + float childMinHeight = NAN; + float childMaxHeight = NAN; + + if (isDefined(resolvedWidth)) { + childMinWidth = resolvedWidth; + childMaxWidth = resolvedWidth; + } + + if (isDefined(resolvedHeight)) { + childMinHeight = resolvedHeight; + childMaxHeight = resolvedHeight; + } + + // The W3C spec doesn't say anything about the 'overflow' property, but + // all major browsers appear to implement the following logic. + if ((!isMainAxisRow && node.overflow == Overflow::Scroll) || + node.overflow != Overflow::Scroll) { + if (isUndefined(resolvedWidth) && isDefined(availableInnerWidth)) { + childMinWidth = 0; + childMaxWidth = availableInnerWidth <= 0 + ? NAN + : FlexLayoutFloatMax( + 0, + availableInnerWidth - + flexItemStyle.getMarginForAxis( + FlexDirection::Row, availableInnerWidth)); + } + } + + if ((isMainAxisRow && node.overflow == Overflow::Scroll) || + node.overflow != Overflow::Scroll) { + if (isUndefined(resolvedHeight) && isDefined(availableInnerHeight)) { + childMinHeight = 0; + childMaxHeight = availableInnerHeight <= 0 + ? NAN + : FlexLayoutFloatMax( + 0, + availableInnerHeight - + flexItemStyle.getMarginForAxis( + FlexDirection::Column, availableInnerWidth)); + } + } + + if (isDefined(flexItemStyle.aspectRatio)) { + if (!isMainAxisRow && isDefined(resolvedWidth)) { + childMinHeight = resolvedWidth / flexItemStyle.aspectRatio; + childMaxHeight = childMinHeight; + } else if (isMainAxisRow && isDefined(resolvedHeight)) { + childMinWidth = resolvedHeight * flexItemStyle.aspectRatio; + childMaxWidth = childMinWidth; + } + } + + // 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 auto childWidthStretch = align == AlignItems::Stretch && + !(isDefined(childMaxWidth) && childMinWidth == childMaxWidth); + if (!isMainAxisRow && isUndefined(resolvedWidth) && isExactWidth && + childWidthStretch) { + childMinWidth = FlexLayoutFloatMax( + 0, + availableInnerWidth - + flexItemStyle.getMarginForAxis( + FlexDirection::Row, availableInnerWidth)); + childMaxWidth = childMinWidth; + if (isDefined(flexItemStyle.aspectRatio)) { + childMinHeight = childMinWidth / flexItemStyle.aspectRatio; + childMaxHeight = childMinHeight; + } + } + + const auto childHeightStretch = align == AlignItems::Stretch && + !(isDefined(childMaxHeight) && childMinHeight == childMaxHeight); + if (isMainAxisRow && isUndefined(resolvedHeight) && isExactHeight && + childHeightStretch) { + childMinHeight = FlexLayoutFloatMax( + 0, + availableInnerHeight - + flexItemStyle.getMarginForAxis( + FlexDirection::Column, availableInnerWidth)); + childMaxHeight = childMinHeight; + if (isDefined(flexItemStyle.aspectRatio)) { + childMinWidth = childMinHeight * flexItemStyle.aspectRatio; + childMaxWidth = childMinWidth; + } + } + + childMinWidth = ConstraintMinMax( + childMinWidth, + flexItemStyle.minWidth.resolve(availableInnerWidth), + flexItemStyle.maxWidth.resolve(availableInnerWidth)); + + childMaxWidth = ConstraintMinMax( + childMaxWidth, + flexItemStyle.minWidth.resolve(availableInnerWidth), + flexItemStyle.maxWidth.resolve(availableInnerWidth)); + + const auto resolvedMinHeight = + flexItemStyle.minHeight.resolve(availableInnerHeight); + const auto usedMinHeight = + isDefined(resolvedMinHeight) ? resolvedMinHeight : 0; + const auto resolvedMaxHeight = + flexItemStyle.maxHeight.resolve(availableInnerHeight); + const auto usedMaxHeight = isDefined(resolvedMaxHeight) + ? resolvedMaxHeight + : std::numeric_limits::infinity(); + if (isUndefined(childMinHeight)) { + childMinHeight = resolvedMinHeight; + } else { + childMinHeight = + std::min(std::max(childMinHeight, usedMinHeight), usedMaxHeight); + } + + if (isUndefined(childMaxHeight)) { + childMaxHeight = resolvedMaxHeight; + } else { + childMaxHeight = + std::min(std::max(childMaxHeight, usedMinHeight), usedMaxHeight); + } + + const auto measureParams = layoutoutput::MeasureParams{ + childMinWidth, childMaxWidth, childMinHeight, childMaxHeight}; + auto measureOutput = flexItemStyle.measureFunction( + flexItemStyle.measureData, + childMinWidth, + childMaxWidth, + childMinHeight, + childMaxHeight, + availableInnerWidth, + availableInnerHeight); + flexItem.computedFlexBasis = + isMainAxisRow ? measureOutput.width : measureOutput.height; + nodeLayoutOutput.children[flexItem.index].setMeasureOutput( + std::move(measureOutput), measureParams); + } + } + + // hypothetical main size is flexBasis with child's min and max constraints + + totalOuterFlexBasis += flexItem.computedFlexBasis + + flexItemStyle.getMarginForAxis(mainAxis, availableInnerWidth); + } + + // totalOuterFlexBasis is mainSize of flexContainer at this point + + const auto maxMainDim = isMainAxisRow ? maxWidth : maxHeight; + const auto maxCrossDim = isMainAxisRow ? maxHeight : maxWidth; + const auto flexBasisOverflows = + isUndefined(minMainDim) && isUndefined(maxMainDim) + ? false + : totalOuterFlexBasis > availableInnerMainDim; + const auto isSingleLineContainer = node.flexWrap == FlexWrap::NoWrap; + const auto isAtMostMainDim = isDefined(maxMainDim) && + (isUndefined(minMainDim) || FlexLayoutFloatsEqual(minMainDim, 0) || + minMainDim != maxMainDim); + + if (!isSingleLineContainer && flexBasisOverflows && isAtMostMainDim) { + minMainDim = maxMainDim; + } + + // Step 4 Collect flex items into flex lines + + // Accumulated cross dimensions of all lines so far. + auto totalLineCrossDim = 0.0f; + + // Max main dimension of all the lines. + auto maxLineMainDim = 0.0f; + + auto flexLines = std::vector{}; + const auto crossAxis = node.crossAxis(); + const auto availableInnerCrossDim = + !isMainAxisRow ? availableInnerWidth : availableInnerHeight; + + for (auto i = std::size_t{0}; i < flexItems.size();) { + auto flexLine = FlexLine{}; + auto mainSizeConsumedOnLine = 0.0f; + + auto innerIndex = i; + for (; innerIndex < flexItems.size(); ++innerIndex) { + const auto& flexItem = flexItems.at(innerIndex); + const auto& flexItemStyle = flexItem.flexItemStyle; + const auto flexBasisWithMinMaxConstraints = flexItemStyle.nodeBoundAxis( + mainAxis, flexItem.computedFlexBasis, availableInnerMainDim); + const auto sizeOfChildIncludingMargin = flexBasisWithMinMaxConstraints + + flexItemStyle.getMarginForAxis(mainAxis, availableInnerWidth); + if (!isSingleLineContainer && + mainSizeConsumedOnLine + sizeOfChildIncludingMargin > + availableInnerMainDim && + !flexLine.flexItems.empty()) { + // If noWrap we dont want to break as we want all children on single + // line if size exceeds then break there should be atleast one item on + // the line + break; + } + + mainSizeConsumedOnLine += sizeOfChildIncludingMargin; + flexLine.flexItems.push_back(flexItem); + } + + i = innerIndex; + + auto sizeBasedOnContent = false; + if (isUndefined(minMainDim) && isUndefined(maxMainDim)) { + // do nothing + } else if ( + (isUndefined(minMainDim) || isUndefined(maxMainDim)) || + (isDefined(maxMainDim) && isDefined(minMainDim) && + maxMainDim != minMainDim)) { + if (mainSizeConsumedOnLine < minMainDim - paddingAndBorderAxisMain) { + availableInnerMainDim = minMainDim - paddingAndBorderAxisMain; + } else if ( + mainSizeConsumedOnLine > maxMainDim - paddingAndBorderAxisMain) { + availableInnerMainDim = maxMainDim - paddingAndBorderAxisMain; + } else { + sizeBasedOnContent = true; + } + } + + // Step 5 Resolve flexible lengths on main axis + // https://www.w3.org/TR/css-flexbox-1/#resolve-flexible-lengths + const auto remainingFreeSpace = flexLine.resolveFlexibleLengths( + mainAxis, + availableInnerMainDim, + availableInnerWidth, + sizeBasedOnContent); + + auto maxBaseline = 0.0f; + const FlexItem* baselineChild = nullptr; + auto isAlignBaseline = false; + auto cumulativeHeight = 0.0f; + + for (const auto& flexItem : flexLine.flexItems) { + const auto& flexItemStyle = children[flexItem.index]; + // Determine the hypothetical cross size of each item by performing layout + // with the used main size and the available space, treating auto as + // fit-content. + const auto align = + ResolveAlignment(flexItemStyle.alignSelf, node.alignItems); + + const auto childCrossSizeRange = flexItem.crossSizeRange( + isMainAxisRow, + availableInnerCrossDim, + align, + isExactCrossDim, + isSingleLineContainer, + flexBasisOverflows, + crossAxis, + availableInnerWidth); + + const auto childMinWidth = + isMainAxisRow ? flexItem.targetMainSize : childCrossSizeRange.min; + const auto childMaxWidth = + isMainAxisRow ? flexItem.targetMainSize : childCrossSizeRange.max; + const auto childMinHeight = + isMainAxisRow ? childCrossSizeRange.min : flexItem.targetMainSize; + const auto childMaxHeight = + isMainAxisRow ? childCrossSizeRange.max : flexItem.targetMainSize; + + const auto measureParams = layoutoutput::MeasureParams{ + childMinWidth, childMaxWidth, childMinHeight, childMaxHeight}; + if (!nodeLayoutOutput.children[flexItem.index].canBeReusedFor( + measureParams)) { + auto measureOutput = flexItemStyle.measureFunction( + flexItemStyle.measureData, + childMinWidth, + childMaxWidth, + childMinHeight, + childMaxHeight, + availableInnerWidth, + availableInnerHeight); + nodeLayoutOutput.children[flexItem.index].setMeasureOutput( + std::move(measureOutput), measureParams); + } + + const auto& childLayout = nodeLayoutOutput.children[flexItem.index]; + const auto width = childLayout.width; + const auto height = childLayout.height; + // Baseline Calculation + const auto margin = flexItem.flexItemStyle.getLeadingMargin( + FlexDirection::Column, availableInnerWidth); + const auto baseline = margin + cumulativeHeight + [&]() { + if (const auto baselineFunc = + children[flexItem.index].baselineFunction) { + return baselineFunc( + children[flexItem.index].measureData, width, height); + } + return isDefined(childLayout.baseline) ? childLayout.baseline : height; + }(); + + nodeLayoutOutput.children[flexItem.index].baseline = baseline; + if (!isMainAxisRow) { + cumulativeHeight += height; + } + + if (align == AlignItems::Baseline || + flexItem.flexItemStyle.isReferenceBaseline) { + maxBaseline = FlexLayoutFloatMax(maxBaseline, baseline); + if (!isAlignBaseline) { + baselineChild = &flexItem; + } + isAlignBaseline = true; + } + + if (baselineChild == nullptr) { + baselineChild = &flexItem; + } + } + flexLine.maxBaseline = maxBaseline; + + if (flexLines.empty()) { + nodeLayoutOutput.baseline = + getLeadingPadding(node, FlexDirection::Column, 0) + + nodeLayoutOutput.children[baselineChild->index].baseline; + } + + // Step 6 Cross size determination + // If there's no flex wrap, the cross dimension is defined by the container. + if (isSingleLineContainer && isExactCrossDim) { + flexLine.crossDim = availableInnerCrossDim; + } else { + for (const auto& flexItem : flexLine.flexItems) { + const auto& flexItemStyle = flexItem.flexItemStyle; + if (IsBaselineNode(node, flexItemStyle)) { + // If the child is baseline aligned then the cross dimension is + // calculated by adding maxAscent and maxDescent from the baseline. + const auto value = flexLine.maxBaseline - + nodeLayoutOutput.children[flexItem.index].baseline + + nodeLayoutOutput.children[flexItem.index].height + + flexItemStyle.getMarginForAxis( + FlexDirection::Column, availableInnerWidth); + flexLine.crossDim = FlexLayoutFloatMax(flexLine.crossDim, value); + } 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 + const auto crossSize = FlexDirectionIsRow(crossAxis) + ? nodeLayoutOutput.children[flexItem.index].width + : nodeLayoutOutput.children[flexItem.index].height; + flexLine.crossDim = FlexLayoutFloatMax( + flexLine.crossDim, + crossSize + + flexItemStyle.getLeadingMargin( + crossAxis, availableInnerWidth) + + flexItemStyle.getTrailingMargin( + crossAxis, availableInnerWidth)); + } + } + } + + const auto containerCrossDim = [&]() { + if (isExactCrossDim) { + return availableInnerCrossDim; + } + + return FlexLayoutFloatMax( + ConstraintMinMax( + flexLine.crossDim + paddingAndBorderAxisCross, + minCrossDim, + maxCrossDim), + paddingAndBorderAxisCross) - + paddingAndBorderAxisCross; + }(); + + for (const auto& flexItem : flexLine.flexItems) { + const auto& flexItemStyle = flexItem.flexItemStyle; + const auto align = + ResolveAlignment(flexItemStyle.alignSelf, node.alignItems); + + const auto crossSize = FlexDirectionIsRow(crossAxis) + ? flexItem.resolvedWidth + : flexItem.resolvedHeight; + + const auto needsStretch = align == AlignItems::Stretch && + flexItemStyle.getMargin(getLeadingEdge(crossAxis)).unit != + Unit::Auto && + flexItemStyle.getMargin(getTrailingEdge(crossAxis)).unit != + Unit::Auto && + isUndefined(crossSize.resolve(availableInnerCrossDim)); + + if (!needsStretch) { + continue; + } + + auto childMainSize = isMainAxisRow + ? nodeLayoutOutput.children[flexItem.index].width + : nodeLayoutOutput.children[flexItem.index].height; + float childCrossSize; + + if (isUndefined(flexItemStyle.aspectRatio)) { + childCrossSize = containerCrossDim - + flexItemStyle.getMarginForAxis(crossAxis, availableInnerWidth); + } else { + childCrossSize = isMainAxisRow + ? childMainSize / flexItemStyle.aspectRatio + : childMainSize * flexItemStyle.aspectRatio; + } + + childMainSize = ConstraintMinMax( + childMainSize, + (isMainAxisRow ? flexItemStyle.minWidth : flexItemStyle.minHeight) + .resolve( + isMainAxisRow ? availableInnerWidth : availableInnerHeight), + (isMainAxisRow ? flexItemStyle.maxWidth : flexItemStyle.maxHeight) + .resolve( + isMainAxisRow ? availableInnerWidth : availableInnerHeight)); + childCrossSize = ConstraintMinMax( + childCrossSize, + (!isMainAxisRow ? flexItemStyle.minWidth : flexItemStyle.minHeight) + .resolve( + !isMainAxisRow ? availableInnerWidth : availableInnerHeight), + (!isMainAxisRow ? flexItemStyle.maxWidth : flexItemStyle.maxHeight) + .resolve( + !isMainAxisRow ? availableInnerWidth : availableInnerHeight)); + + auto childWidth = isMainAxisRow ? childMainSize : childCrossSize; + auto childHeight = !isMainAxisRow ? childMainSize : childCrossSize; + + const auto crossAxisDoesNotGrow = + node.alignContent != AlignContent::Stretch && !isSingleLineContainer; + if (isUndefined(childWidth) || (!isMainAxisRow && crossAxisDoesNotGrow)) { + childWidth = NAN; + } + if (isUndefined(childHeight) || (isMainAxisRow && crossAxisDoesNotGrow)) { + childHeight = NAN; + } + + const auto measureParams = layoutoutput::MeasureParams{ + childWidth, childWidth, childHeight, childHeight}; + if (!nodeLayoutOutput.children[flexItem.index].canBeReusedFor( + measureParams)) { + auto measureOutput = children[flexItem.index].measureFunction( + children[flexItem.index].measureData, + childWidth, + childWidth, + childHeight, + childHeight, + availableInnerWidth, + availableInnerHeight); + nodeLayoutOutput.children[flexItem.index].setMeasureOutput( + std::move(measureOutput), measureParams); + } + } + + // Step 7 Main Axis Alignment + // Cast to Float straight away as it is being used in multiple fp arithmetic + // expressions below + const auto numberOfAutoMarginsOnCurrentLine = static_cast( + std::count_if( + flexLine.flexItems.cbegin(), + flexLine.flexItems.cend(), + [mainAxis](const FlexItem& flexItem) { + return flexItem.flexItemStyle.marginLeadingValue(mainAxis).unit == + Unit::Auto; + }) + + std::count_if( + flexLine.flexItems.cbegin(), + flexLine.flexItems.cend(), + [mainAxis](const FlexItem& flexItem) { + return flexItem.flexItemStyle.marginTrailingValue(mainAxis) + .unit == Unit::Auto; + })); + + const auto leadingPaddingAndBorderMain = + getLeadingPaddingAndBorder(node, mainAxis, ownerWidth); + const auto leadingPaddingAndBorderCross = + getLeadingPaddingAndBorder(node, crossAxis, ownerWidth); + + flexLine.mainDim = leadingPaddingAndBorderMain; + + // 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. + // Cast to Float straight away as it is being used in multiple fp arithmetic + // expressions below + const auto itemsOnLine = static_cast(flexLine.flexItems.size()); + const auto leadingMainDim = [&]() { + if (remainingFreeSpace > 0 && numberOfAutoMarginsOnCurrentLine > 0) { + return 0.0f; + } + + switch (node.justifyContent) { + case JustifyContent::Center: + return remainingFreeSpace / 2; + case JustifyContent::FlexEnd: + return remainingFreeSpace; + case JustifyContent::SpaceEvenly: + // Space is distributed evenly across all elements + return remainingFreeSpace / (itemsOnLine + 1); + case JustifyContent::SpaceAround: + // Space on the edges is half of the space between elements + return remainingFreeSpace / itemsOnLine / 2; + case JustifyContent::FlexStart: + case JustifyContent::SpaceBetween: + return 0.0f; + } + }(); + + const auto betweenMainDim = [&]() { + if (remainingFreeSpace > 0 && numberOfAutoMarginsOnCurrentLine > 0) { + return 0.0f; + } + + switch (node.justifyContent) { + case JustifyContent::SpaceBetween: + return itemsOnLine > 1 + ? FlexLayoutFloatMax(remainingFreeSpace, 0) / (itemsOnLine - 1) + : 0.0f; + case JustifyContent::SpaceEvenly: + // Space is distributed evenly across all elements + return remainingFreeSpace / (itemsOnLine + 1); + case JustifyContent::SpaceAround: + // Space on the edges is half of the space between elements + return remainingFreeSpace / itemsOnLine; + case JustifyContent::FlexStart: + case JustifyContent::Center: + case JustifyContent::FlexEnd: + return 0.0f; + } + }(); + + flexLine.mainDim += leadingMainDim; + + const auto usedSize = std::accumulate( + flexLine.flexItems.cbegin(), + flexLine.flexItems.cend(), + 0.0f, + [&](Float size, const FlexItem& item) { + const auto& child = nodeLayoutOutput.children[item.index]; + const auto innerSize = isMainAxisRow ? child.width : child.height; + const auto margins = item.flexItemStyle.getMarginForAxis( + mainAxis, availableInnerWidth); + return size + innerSize + margins; + }); + + const auto containerMainDim = usedSize + remainingFreeSpace; + + // Step 8A Cross Axis Alignment + for (const auto& flexItem : flexLine.flexItems) { + // this is from previous step done here to prevent one more loop + if (flexItem.flexItemStyle.marginLeadingValue(mainAxis).unit == + Unit::Auto) { + flexLine.mainDim += + remainingFreeSpace / numberOfAutoMarginsOnCurrentLine; + } + + // https://www.w3.org/TR/css-flexbox-1/#justify-content-property + const auto relativePositionMain = flexItem.flexItemStyle.relativePosition( + mainAxis, availableInnerWidth); + + const auto leadingMainMargin = flexItem.flexItemStyle.getLeadingMargin( + mainAxis, availableInnerWidth); + + const auto offsetFromMainStartEdgeOfLine = + (isUndefined(leadingMainMargin) ? 0 : leadingMainMargin) + + relativePositionMain + flexLine.mainDim; + + const auto flowIsReverse = mainAxis == FlexDirection::RowReverse || + mainAxis == FlexDirection::ColumnReverse; + // Reverse flows have their start and end edges reversed i.e. the start + // edge would be at containerMainDim, not zero + const auto mainStartEdgeOfLine = + flowIsReverse ? containerMainDim + paddingAndBorderAxisMain : 0; + // Offsets push items towards zero in reverse flows and away from zero in + // non-reverse flows + const auto directionalOffsetFromMainStartEdgeOfLine = flowIsReverse + ? -offsetFromMainStartEdgeOfLine + : offsetFromMainStartEdgeOfLine; + nodeLayoutOutput.children[flexItem.index].setStartPositionOnAxis( + mainStartEdgeOfLine + directionalOffsetFromMainStartEdgeOfLine, + mainAxis); + + if (flexItem.flexItemStyle.marginTrailingValue(mainAxis).unit == + Unit::Auto) { + flexLine.mainDim += + remainingFreeSpace / numberOfAutoMarginsOnCurrentLine; + } + + const auto mainSize = isMainAxisRow + ? nodeLayoutOutput.children[flexItem.index].width + : nodeLayoutOutput.children[flexItem.index].height; + flexLine.mainDim += betweenMainDim + mainSize + + flexItem.flexItemStyle.getMarginForAxis( + mainAxis, availableInnerWidth); + + const auto alignItem = + ResolveAlignment(flexItem.flexItemStyle.alignSelf, node.alignItems); + + const auto crossSize = isMainAxisRow + ? nodeLayoutOutput.children[flexItem.index].height + : nodeLayoutOutput.children[flexItem.index].width; + + const auto leadingCrossDim = leadingPaddingAndBorderCross + [&]() { + if (alignItem == AlignItems::Stretch && + flexItem.flexItemStyle.marginLeadingValue(crossAxis).unit != + Unit::Auto && + flexItem.flexItemStyle.marginTrailingValue(crossAxis).unit != + Unit::Auto) { + return 0.0f; + } + + const auto remainingCrossDim = containerCrossDim - crossSize - + flexItem.flexItemStyle.getMarginForAxis( + crossAxis, availableInnerWidth); + + if (flexItem.flexItemStyle.marginLeadingValue(crossAxis).unit == + Unit::Auto && + flexItem.flexItemStyle.marginTrailingValue(crossAxis).unit == + Unit::Auto) { + return FlexLayoutFloatMax(0.0f, remainingCrossDim / 2); + } + if (flexItem.flexItemStyle.marginTrailingValue(crossAxis).unit == + Unit::Auto) { + return 0.0f; + } + if (flexItem.flexItemStyle.marginLeadingValue(crossAxis).unit == + Unit::Auto) { + return FlexLayoutFloatMax(0.0f, remainingCrossDim); + } + + switch (alignItem) { + case AlignItems::FlexStart: + case AlignItems::Stretch: + return 0.0f; + case AlignItems::FlexEnd: + return remainingCrossDim; + case AlignItems::Center: + return remainingCrossDim / 2; + case AlignItems::Baseline: + return isMainAxisRow ? flexLine.maxBaseline - + nodeLayoutOutput.children[flexItem.index].baseline + : 0.0f; + } + }(); + + // https://www.w3.org/TR/css-flexbox-1/#align-items-property + const auto relativePositionCross = + flexItem.flexItemStyle.relativePosition( + crossAxis, availableInnerWidth); + const auto leadingCrossMargin = flexItem.flexItemStyle.getLeadingMargin( + crossAxis, availableInnerWidth); + + const auto offsetFromCrossStartEdgeOfLine = + (isUndefined(leadingCrossMargin) ? 0 : leadingCrossMargin) + + relativePositionCross + leadingCrossDim; + const auto offsetFromCrossStartEdgeOfContainer = + totalLineCrossDim + offsetFromCrossStartEdgeOfLine; + + const auto crossStartEdgeOfContainer = + crossAxis == FlexDirection::RowReverse + ? containerCrossDim + paddingAndBorderAxisCross + : 0; + const auto directionalOffsetFromCrossStartEdgeOfContainer = + crossAxis == FlexDirection::RowReverse + ? -offsetFromCrossStartEdgeOfContainer + : offsetFromCrossStartEdgeOfContainer; + + nodeLayoutOutput.children[flexItem.index].setStartPositionOnAxis( + crossStartEdgeOfContainer + + directionalOffsetFromCrossStartEdgeOfContainer, + crossAxis); + } + + flexLine.mainDim += getTrailingPaddingAndBorder(node, mainAxis, ownerWidth); + + totalLineCrossDim += flexLine.crossDim; + maxLineMainDim = FlexLayoutFloatMax(maxLineMainDim, flexLine.mainDim); + + flexLines.push_back(flexLine); + } + + // Step 8B Multi line content alignment + auto newAvailableInnerCrossDim = + isExactCrossDim ? availableInnerCrossDim : totalLineCrossDim; + + if (node.flexWrap != FlexWrap::NoWrap) { + const auto remainingAlignContentDim = + newAvailableInnerCrossDim - totalLineCrossDim; + // Cast to Float straight away as it is being used in multiple fp arithmetic + // expressions below + const auto lineCount = static_cast(flexLines.size()); + const auto crossDimLead = [&]() { + switch (node.alignContent) { + case AlignContent::SpaceBetween: + if (newAvailableInnerCrossDim > totalLineCrossDim && lineCount > 1) { + return remainingAlignContentDim / (lineCount - 1); + } + return 0.0f; + case AlignContent::SpaceAround: + if (newAvailableInnerCrossDim > totalLineCrossDim && lineCount > 1) { + return remainingAlignContentDim / lineCount; + } + return 0.0f; + case AlignContent::Stretch: + if (newAvailableInnerCrossDim > totalLineCrossDim) { + return remainingAlignContentDim / lineCount; + } + return 0.0f; + case AlignContent::FlexStart: + case AlignContent::Baseline: + case AlignContent::FlexEnd: + case AlignContent::Center: + return 0.0f; + } + }(); + + auto currentLead = getLeadingPaddingAndBorder(node, crossAxis, ownerWidth); + switch (node.alignContent) { + case AlignContent::FlexEnd: + currentLead += remainingAlignContentDim; + break; + case AlignContent::Center: + currentLead += remainingAlignContentDim / 2; + break; + case AlignContent::SpaceAround: + if (newAvailableInnerCrossDim > totalLineCrossDim) { + currentLead += remainingAlignContentDim / (2 * lineCount); + } else { + currentLead += remainingAlignContentDim / 2; + } + break; + case AlignContent::SpaceBetween: + case AlignContent::Stretch: + case AlignContent::FlexStart: + case AlignContent::Baseline: + break; + } + + for (const auto& flexLine : flexLines) { + const auto lineHeight = flexLine.crossDim + crossDimLead; + for (const auto& flexItem : flexLine.flexItems) { + auto crossAxisStart = 0.0f; + + auto crossSize = isMainAxisRow + ? nodeLayoutOutput.children[flexItem.index].height + : nodeLayoutOutput.children[flexItem.index].width; + + const auto alignItem = + ResolveAlignment(flexItem.flexItemStyle.alignSelf, node.alignItems); + + switch (alignItem) { + case AlignItems::FlexStart: + crossAxisStart = currentLead + + flexItem.flexItemStyle.getLeadingMargin( + crossAxis, availableInnerWidth); + break; + case AlignItems::FlexEnd: + crossAxisStart = currentLead + lineHeight - + flexItem.flexItemStyle.getTrailingMargin( + crossAxis, availableInnerWidth) - + crossSize; + break; + case AlignItems::Center: + crossAxisStart = currentLead + (lineHeight - crossSize) / 2; + break; + case AlignItems::Stretch: { + crossAxisStart = currentLead + + flexItem.flexItemStyle.getLeadingMargin( + crossAxis, availableInnerWidth); + + const auto definedCrossSize = + (isMainAxisRow ? flexItem.resolvedHeight + : flexItem.resolvedWidth) + .resolve( + isMainAxisRow ? availableInnerHeight + : availableInnerWidth); + + if (isUndefined(definedCrossSize)) { + const auto mainSize = isMainAxisRow + ? nodeLayoutOutput.children[flexItem.index].width + : nodeLayoutOutput.children[flexItem.index].height; + const auto margin = flexItem.flexItemStyle.getMarginForAxis( + crossAxis, availableInnerWidth); + float width = isMainAxisRow ? mainSize : lineHeight - margin; + float height = isMainAxisRow ? lineHeight - margin : mainSize; + + width = ConstraintMinMax( + width, + flexItem.flexItemStyle.minWidth.resolve(availableInnerWidth), + flexItem.flexItemStyle.maxWidth.resolve(availableInnerWidth)); + height = ConstraintMinMax( + height, + flexItem.flexItemStyle.minHeight.resolve( + availableInnerHeight), + flexItem.flexItemStyle.maxHeight.resolve( + availableInnerHeight)); + + if (!(FlexLayoutFloatsEqual( + isMainAxisRow ? width : height, mainSize) && + FlexLayoutFloatsEqual( + isMainAxisRow ? height : width, crossSize))) { + const auto measureParams = + layoutoutput::MeasureParams{width, width, height, height}; + if (!nodeLayoutOutput.children[flexItem.index].canBeReusedFor( + measureParams)) { + auto measureOutput = children[flexItem.index].measureFunction( + children[flexItem.index].measureData, + width, + width, + height, + height, + availableInnerWidth, + availableInnerHeight); + + nodeLayoutOutput.children[flexItem.index].setMeasureOutput( + std::move(measureOutput), measureParams); + } + + crossSize = isMainAxisRow + ? nodeLayoutOutput.children[flexItem.index].height + : nodeLayoutOutput.children[flexItem.index].width; + } + } + } break; + case AlignItems::Baseline: + break; + } + + if (alignItem != AlignItems::Baseline) { + if (isMainAxisRow) { + nodeLayoutOutput.children[flexItem.index].top = crossAxisStart; + } else { + const auto crossStartEdgeOfContainer = + crossAxis == FlexDirection::RowReverse + ? newAvailableInnerCrossDim + : 0; + const auto directionalOffsetFromCrossStartEdgeOfContainer = + crossAxis == FlexDirection::RowReverse ? -crossAxisStart + : crossAxisStart; + nodeLayoutOutput.children[flexItem.index].setStartPositionOnAxis( + crossStartEdgeOfContainer + + directionalOffsetFromCrossStartEdgeOfContainer, + crossAxis); + } + } + + if (node.flexWrap == FlexWrap::WrapReverse) { + if (isMainAxisRow) { + const auto topPosition = + nodeLayoutOutput.children[flexItem.index].top; + nodeLayoutOutput.children[flexItem.index].top = + newAvailableInnerCrossDim + paddingAndBorderAxisCross - + topPosition - (crossAxisStart + crossSize - topPosition); + } else { + const auto crossEndEdgeOfContainer = + crossAxis == FlexDirection::RowReverse + ? 0 + : newAvailableInnerCrossDim; + const auto directionalOffsetFromCrossEndEdgeOfContainer = + crossAxis == FlexDirection::RowReverse ? crossAxisStart + : -crossAxisStart; + nodeLayoutOutput.children[flexItem.index].setEndPositionOnAxis( + crossEndEdgeOfContainer + + directionalOffsetFromCrossEndEdgeOfContainer, + crossAxis); + } + } + } + currentLead += lineHeight; + } + } + + // Step 9 Computing final Dimensions + auto newAvailableInnerMainDim = + availableInnerMainDim + paddingAndBorderAxisMain; + newAvailableInnerCrossDim = + availableInnerCrossDim + paddingAndBorderAxisCross; + + if (isUndefined(maxMainDim) || + (node.overflow != Overflow::Scroll && isAtMostMainDim)) { + // Clamp the size to the min/max size, if specified, and make sure it + // doesn't go below the padding and border amount. + newAvailableInnerMainDim = + ConstraintMinMax(maxLineMainDim, minMainDim, maxMainDim); + } else if (isAtMostMainDim && node.overflow == Overflow::Scroll) { + newAvailableInnerMainDim = FlexLayoutFloatMax( + FlexLayoutFloatMin( + availableInnerMainDim + paddingAndBorderAxisMain, + ConstraintMinMax(maxLineMainDim, minMainDim, maxMainDim)), + paddingAndBorderAxisMain); + } + + const auto isAtMostCrossDim = isDefined(maxCrossDim) && + (isUndefined(minCrossDim) || FlexLayoutFloatsEqual(minCrossDim, 0) || + minCrossDim != maxCrossDim); + + if (isUndefined(maxCrossDim) || + (node.overflow != Overflow::Scroll && isAtMostCrossDim)) { + // Clamp the size to the min/max size, if specified, and make sure it + // doesn't go below the padding and border amount. + newAvailableInnerCrossDim = ConstraintMinMax( + totalLineCrossDim + paddingAndBorderAxisCross, + minCrossDim, + maxCrossDim); + } else if (isAtMostCrossDim && node.overflow == Overflow::Scroll) { + newAvailableInnerCrossDim = FlexLayoutFloatMax( + FlexLayoutFloatMin( + availableInnerCrossDim + paddingAndBorderAxisCross, + ConstraintMinMax( + totalLineCrossDim + paddingAndBorderAxisCross, + minCrossDim, + maxCrossDim)), + paddingAndBorderAxisCross); + } + + nodeLayoutOutput.setSize( + mainAxis, newAvailableInnerMainDim, newAvailableInnerCrossDim); + + nodeLayoutOutput.roundToPixelGrid(node.pointScaleFactor); + + // Step 10 Sizing and positioning absolute children + for (auto i = std::size_t{0}; i < children.size(); ++i) { + const auto& child = children[i]; + if (child.positionType != PositionType::Absolute || + child.display == Display::None) { + continue; + } + + const auto nodeWidth = + isMainAxisRow ? newAvailableInnerMainDim : newAvailableInnerCrossDim; + const auto nodeHeight = + isMainAxisRow ? newAvailableInnerCrossDim : newAvailableInnerMainDim; + + const auto w = + isDefined(child.maxWidth.value) && (child.minWidth == child.maxWidth) + ? child.maxWidth + : child.width; + const auto h = + isDefined(child.maxHeight.value) && (child.minHeight == child.maxWidth) + ? child.maxHeight + : child.height; + + const auto childWidth = w.resolve(nodeWidth); + const auto childHeight = h.resolve(nodeHeight); + + const auto left = child.getPosition(Edge::Left).resolve(nodeWidth); + const auto right = child.getPosition(Edge::Right).resolve(nodeWidth); + const auto top = child.getPosition(Edge::Top).resolve(nodeHeight); + const auto bottom = child.getPosition(Edge::Bottom).resolve(nodeHeight); + + const auto startMain = [&]() { + switch (mainAxis) { + case FlexDirection::Row: + return left; + case FlexDirection::RowReverse: + return right; + case FlexDirection::Column: + return top; + case FlexDirection::ColumnReverse: + return bottom; + } + }(); + const auto startCross = [&]() { + switch (crossAxis) { + case FlexDirection::Row: + return left; + case FlexDirection::RowReverse: + return right; + case FlexDirection::Column: + case FlexDirection::ColumnReverse: + return top; + } + }(); + const auto endMain = [&]() { + switch (mainAxis) { + case FlexDirection::Row: + return right; + case FlexDirection::RowReverse: + return left; + case FlexDirection::Column: + return bottom; + case FlexDirection::ColumnReverse: + return top; + } + }(); + const auto endCross = [&]() { + switch (crossAxis) { + case FlexDirection::Row: + return right; + case FlexDirection::RowReverse: + return left; + case FlexDirection::Column: + case FlexDirection::ColumnReverse: + return bottom; + } + }(); + + auto borderLeft = node.getBorder(Edge::Left).resolve(nodeWidth); + borderLeft = isUndefined(borderLeft) ? 0 : borderLeft; + auto borderTop = node.getBorder(Edge::Top).resolve(nodeWidth); + borderTop = isUndefined(borderTop) ? 0 : borderTop; + + auto borderRight = node.getBorder(Edge::Right).resolve(nodeWidth); + borderRight = isUndefined(borderRight) ? 0 : borderRight; + auto borderBottom = node.getBorder(Edge::Bottom).resolve(nodeWidth); + borderBottom = isUndefined(borderBottom) ? 0 : borderBottom; + + const auto borderStartMain = isMainAxisRow ? borderLeft : borderTop; + const auto borderStartCross = isMainAxisRow ? borderTop : borderLeft; + const auto borderEndMain = isMainAxisRow ? borderRight : borderBottom; + const auto borderEndCross = isMainAxisRow ? borderBottom : borderRight; + + auto width = isDefined(childWidth) + ? childWidth + : ((isDefined(left) && isDefined(right)) + ? nodeWidth - borderLeft - borderRight - left - right + : NAN); + auto height = isDefined(childHeight) + ? childHeight + : ((isDefined(top) && isDefined(bottom)) + ? nodeHeight - borderTop - borderBottom - top - bottom + : NAN); + + if (isUndefined(width) ^ isUndefined(height)) { + if (isDefined(child.aspectRatio)) { + if (isUndefined(width)) { + width = height * child.aspectRatio; + } else if (isUndefined(height)) { + height = width / child.aspectRatio; + } + } + } + + auto absMinWidth = width; + auto absMaxWidth = width; + + if (!isMainAxisRow && isUndefined(width) && isDefined(nodeWidth) && + nodeWidth > 0) { + absMinWidth = 0; + absMaxWidth = nodeWidth; + } + + const auto measureParams = layoutoutput::MeasureParams{ + absMinWidth, + absMaxWidth, + height, + height, + }; + auto measureOutput = child.measureFunction( + child.measureData, + absMinWidth, + absMaxWidth, + height, + height, + availableInnerWidth, + availableInnerHeight); + const auto measuredMainSize = + isMainAxisRow ? measureOutput.width : measureOutput.height; + const auto measuredCrossSize = + isMainAxisRow ? measureOutput.height : measureOutput.width; + nodeLayoutOutput.children[i].setMeasureOutput( + std::move(measureOutput), measureParams); + + const auto marginLeft = + ResolveValueMargin(child.getMargin(Edge::Left), nodeWidth); + const auto marginTop = + ResolveValueMargin(child.getMargin(Edge::Top), nodeWidth); + const auto marginRight = + ResolveValueMargin(child.getMargin(Edge::Right), nodeWidth); + const auto marginBottom = + ResolveValueMargin(child.getMargin(Edge::Bottom), nodeWidth); + + const auto marginStartMain = [&]() { + switch (mainAxis) { + case FlexDirection::Row: + return marginLeft; + case FlexDirection::RowReverse: + return marginRight; + case FlexDirection::Column: + return marginTop; + case FlexDirection::ColumnReverse: + return marginBottom; + } + }(); + const auto marginEndMain = [&]() { + switch (mainAxis) { + case FlexDirection::Row: + return marginRight; + case FlexDirection::RowReverse: + return marginLeft; + case FlexDirection::Column: + return marginBottom; + case FlexDirection::ColumnReverse: + return marginTop; + } + }(); + const auto marginStartCross = [&]() { + switch (crossAxis) { + case FlexDirection::Row: + return marginLeft; + case FlexDirection::RowReverse: + return marginRight; + case FlexDirection::Column: + case FlexDirection::ColumnReverse: + return marginTop; + } + }(); + const auto marginEndCross = [&]() { + switch (crossAxis) { + case FlexDirection::Row: + return marginRight; + case FlexDirection::RowReverse: + return marginLeft; + case FlexDirection::Column: + case FlexDirection::ColumnReverse: + return marginBottom; + } + }(); + + const auto freeMainSpace = + (isMainAxisRow ? nodeWidth : nodeHeight) - measuredMainSize; + const auto freeCrossSpace = + (isMainAxisRow ? nodeHeight : nodeWidth) - measuredCrossSize; + // do main axis alignment on absolute child + // do cross axis alignment on absolute child + + const auto offsetMain = [&]() { + if (isDefined(startMain)) { + return startMain + borderStartMain + marginStartMain; + } + if (isDefined(endMain)) { + return freeMainSpace - endMain - borderEndMain - marginEndMain; + } + switch (node.justifyContent) { + case JustifyContent::FlexStart: + case JustifyContent::SpaceBetween: + return marginStartMain + + (isMainAxisRow + ? paddingAndBorder[static_cast(Edge::Left)] + : paddingAndBorder[static_cast(Edge::Top)]); + case JustifyContent::FlexEnd: + return freeMainSpace - + (isMainAxisRow + ? paddingAndBorder[static_cast(Edge::Right)] + : paddingAndBorder[static_cast(Edge::Bottom)]); + case JustifyContent::SpaceEvenly: + case JustifyContent::SpaceAround: + case JustifyContent::Center: + return freeMainSpace / 2; + } + }(); + + const auto mainStartEdgeOfContainer = + mainAxis == FlexDirection::RowReverse ? nodeWidth : 0; + const auto directionalOffsetFromMainStartEdgeOfContainer = + mainAxis == FlexDirection::RowReverse ? -offsetMain : offsetMain; + nodeLayoutOutput.children[i].setStartPositionOnAxis( + mainStartEdgeOfContainer + + directionalOffsetFromMainStartEdgeOfContainer, + mainAxis); + + const auto isWrapReverse = node.flexWrap == FlexWrap::WrapReverse; + const auto offsetCross = [&]() { + if (isDefined(startCross)) { + return startCross + borderStartCross + marginStartCross; + } + if (isDefined(endCross)) { + return freeCrossSpace - endCross - borderEndCross - marginEndCross; + } + + const auto alignItem = ResolveAlignment(child.alignSelf, node.alignItems); + switch (alignItem) { + // Absolutely-positioned children are considered to be “fixed-size”, + // so a value of 'stretch' is treated the same as 'flex-start'. See + // https://www.w3.org/TR/css-flexbox-1/#abspos-items + case AlignItems::FlexStart: + case AlignItems::Stretch: + // https://www.w3.org/TR/CSS2/visudet.html#abs-non-replaced-width + return marginStartCross + + (isWrapReverse ? freeCrossSpace - + getTrailingPaddingAndBorder( + node, crossAxis, availableInnerWidth) + : getLeadingPaddingAndBorder( + node, crossAxis, availableInnerWidth)); + case AlignItems::FlexEnd: + return isWrapReverse + ? getLeadingPaddingAndBorder(node, crossAxis, availableInnerWidth) + : freeCrossSpace - + getTrailingPaddingAndBorder( + node, crossAxis, availableInnerWidth); + case AlignItems::Center: + case AlignItems::Baseline: + return freeCrossSpace / 2; + } + }(); + + const auto crossStartEdgeOfContainer = + crossAxis == FlexDirection::RowReverse ? nodeWidth : 0; + const auto directionalOffsetFromCrossStartEdgeOfContainer = + crossAxis == FlexDirection::RowReverse ? -offsetCross : offsetCross; + nodeLayoutOutput.children[i].setStartPositionOnAxis( + crossStartEdgeOfContainer + + directionalOffsetFromCrossStartEdgeOfContainer, + crossAxis); + } + + for (auto i = std::size_t{0}; i < children.size(); ++i) { + // Because we never invoke the measure function for children with + // Display::None their size needs to be set to zero explicitly. + if (children[i].display == Display::None) { + nodeLayoutOutput.children[i].width = 0; + nodeLayoutOutput.children[i].height = 0; + } + } + + return nodeLayoutOutput; +} +} // namespace algo +} // namespace flexlayout +} // namespace facebook diff --git a/ReactCommon/flexlayout/flexlayout/LayoutOutput.h b/ReactCommon/flexlayout/flexlayout/LayoutOutput.h new file mode 100644 index 00000000000..669367c721b --- /dev/null +++ b/ReactCommon/flexlayout/flexlayout/LayoutOutput.h @@ -0,0 +1,290 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include +#include "Dimension.h" +#include "FlexLayoutEnums.h" +#include "FlexLayoutMacros.h" +#include "Rounding.h" +#include "Utils.h" + +namespace facebook { +namespace flexlayout { +namespace layoutoutput { + +struct None {}; +constexpr auto none = None{}; + +struct MeasureParams { + Float minWidth = -1; + Float maxWidth = -1; + Float minHeight = -1; + Float maxHeight = -1; + + auto haveFixedWidth() const -> bool { + return minWidth == maxWidth; + } + + auto haveFixedHeight() const -> bool { + return minHeight == maxHeight; + } + + auto operator==(const MeasureParams& other) const -> bool { + return minWidth == other.minWidth && maxWidth == other.maxWidth && + minHeight == other.minHeight && maxHeight == other.maxHeight; + } +}; + +struct LayoutOutputBase { + struct Child { + Float left; + Float top; + // Width and height need to have default values not equal to zero otherwise + // we won't be able to discern the case when the child was never measured at + // all. In canBeReusedFor(), if width and height were to have default values + // of zero and measureParams are {0, 0, 0, 0} (i.e. have fixed width and + // height equal to 0) fixedWidthMatchesMeasured[Width|Height] would be true + // even though the child was never measured in the first place. This means + // we would never invoke the measure function at all which is incorrect. + Float width = UNDEFINED; + Float height = UNDEFINED; + bool enableTextRounding = false; + Float baseline = UNDEFINED; + MeasureParams lastMeasureParams; + + auto canBeReusedFor(const MeasureParams& measureParams) const -> bool { + const auto sameWidthRange = + utils::FlexLayoutFloatsEqual( + measureParams.minWidth, lastMeasureParams.minWidth) && + utils::FlexLayoutFloatsEqual( + measureParams.maxWidth, lastMeasureParams.maxWidth); + + const auto fixedWidthMatchesMeasuredWidth = + measureParams.haveFixedWidth() && measureParams.minWidth == width; + + const auto measuredWidthMatchesStricterRange = + !measureParams.haveFixedWidth() && + !lastMeasureParams.haveFixedWidth() && + measureParams.maxWidth <= lastMeasureParams.maxWidth && + width <= measureParams.maxWidth; + + const auto widthIsCompatible = sameWidthRange || + fixedWidthMatchesMeasuredWidth || measuredWidthMatchesStricterRange; + + const auto sameHeightRange = + utils::FlexLayoutFloatsEqual( + measureParams.minHeight, lastMeasureParams.minHeight) && + utils::FlexLayoutFloatsEqual( + measureParams.maxHeight, lastMeasureParams.maxHeight); + + const auto fixedHeightMatchesMeasuredHeight = + measureParams.haveFixedHeight() && measureParams.minHeight == height; + + const auto measuredHeightMatchesStricterRange = + !measureParams.haveFixedHeight() && + !lastMeasureParams.haveFixedHeight() && + measureParams.maxHeight <= lastMeasureParams.maxHeight && + height <= measureParams.maxHeight; + + const auto heightIsCompatible = sameHeightRange || + fixedHeightMatchesMeasuredHeight || + measuredHeightMatchesStricterRange; + + return measureParams == lastMeasureParams || + (widthIsCompatible && heightIsCompatible); + } + + void setStartPositionOnAxis( + const Float position, + const FlexDirection axis) { + // https://www.w3.org/TR/css-flexbox-1/#axis-mapping + const auto startEdge = [&]() { + switch (axis) { + case FlexDirection::Row: + return Edge::Left; + case FlexDirection::RowReverse: + return Edge::Right; + case FlexDirection::Column: + return Edge::Top; + case FlexDirection::ColumnReverse: + return Edge::Bottom; + } + }(); + setPositionForEdge(position, startEdge); + } + + void setEndPositionOnAxis(const Float position, const FlexDirection axis) { + // https://www.w3.org/TR/css-flexbox-1/#axis-mapping + const auto endEdge = [&]() { + switch (axis) { + case FlexDirection::Row: + return Edge::Right; + case FlexDirection::RowReverse: + return Edge::Left; + case FlexDirection::Column: + return Edge::Bottom; + case FlexDirection::ColumnReverse: + return Edge::Top; + } + }(); + setPositionForEdge(position, endEdge); + } + + void roundToPixelGrid(const double pointScaleFactor) { + // Convert from float to double as it matters for rounding precision + const double leftAsDouble = left; + const double topAsDouble = top; + + // This used to depend on NodeType (Default or Text), not sure if we need + // this now + const bool textRounding = enableTextRounding; + + left = algo::RoundValueToPixelGrid( + leftAsDouble, pointScaleFactor, false, textRounding); + top = algo::RoundValueToPixelGrid( + topAsDouble, pointScaleFactor, false, textRounding); + + // 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 = + !utils::FlexLayoutDoubleEqual( + fmod((double)width * pointScaleFactor, 1.0), 0) && + !utils::FlexLayoutDoubleEqual( + fmod((double)width * pointScaleFactor, 1.0), 1.0); + const bool hasFractionalHeight = + !utils::FlexLayoutDoubleEqual( + fmod((double)height * pointScaleFactor, 1.0), 0) && + !utils::FlexLayoutDoubleEqual( + fmod((double)height * pointScaleFactor, 1.0), 1.0); + + width = algo::RoundValueToPixelGrid( + leftAsDouble + width, + pointScaleFactor, + (textRounding && hasFractionalWidth), + (textRounding && !hasFractionalWidth)) - + algo::RoundValueToPixelGrid( + leftAsDouble, pointScaleFactor, false, textRounding); + + height = algo::RoundValueToPixelGrid( + topAsDouble + height, + pointScaleFactor, + (textRounding && hasFractionalHeight), + (textRounding && !hasFractionalHeight)) - + algo::RoundValueToPixelGrid( + topAsDouble, pointScaleFactor, false, textRounding); + } + + private: + void setPositionForEdge(const Float position, const Edge edge) { + switch (edge) { + case Edge::Left: + left = position; + break; + case Edge::Top: + top = position; + break; + case Edge::Right: + left = position - width; + break; + case Edge::Bottom: + top = position - height; + break; + } + } + }; + + Float width; + Float height; + Float baseline = UNDEFINED; + + void + setSize(const FlexDirection axis, const Float mainDim, const Float crossDim) { + switch (axis) { + case FlexDirection::Row: + case FlexDirection::RowReverse: + width = mainDim; + height = crossDim; + break; + case FlexDirection::Column: + case FlexDirection::ColumnReverse: + width = crossDim; + height = mainDim; + break; + } + } + + void roundToPixelGrid(const double pointScaleFactor) { + // This used to depend on NodeType (Default or Text), not sure if we need + // this now + const bool textRounding = false; + + // 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 = + !utils::FlexLayoutDoubleEqual( + fmod((double)width * pointScaleFactor, 1.0), 0) && + !utils::FlexLayoutDoubleEqual( + fmod((double)width * pointScaleFactor, 1.0), 1.0); + const bool hasFractionalHeight = + !utils::FlexLayoutDoubleEqual( + fmod((double)height * pointScaleFactor, 1.0), 0) && + !utils::FlexLayoutDoubleEqual( + fmod((double)height * pointScaleFactor, 1.0), 1.0); + + width = algo::RoundValueToPixelGrid( + width, + pointScaleFactor, + (textRounding && hasFractionalWidth), + (textRounding && !hasFractionalWidth)); + + height = algo::RoundValueToPixelGrid( + height, + pointScaleFactor, + (textRounding && hasFractionalHeight), + (textRounding && !hasFractionalHeight)); + } +}; + +template +class FLEX_LAYOUT_EXPORT LayoutOutput : public LayoutOutputBase { + public: + struct Child : LayoutOutputBase::Child { + MeasureResult measureResult; + + void setMeasureOutput( + MeasureOutput mo, + const MeasureParams& params) { + width = mo.width; + height = mo.height; + baseline = mo.baseline; + measureResult = std::move(mo.result); + lastMeasureParams = params; + } + }; + + void roundToPixelGrid(const double pointScaleFactor) { + if (pointScaleFactor == 0.0f) { + return; + } + + LayoutOutputBase::roundToPixelGrid(pointScaleFactor); + for (auto& child : children) { + child.roundToPixelGrid(pointScaleFactor); + } + } + + std::vector children = {}; +}; + +} // namespace layoutoutput +} // namespace flexlayout +} // namespace facebook diff --git a/ReactCommon/flexlayout/flexlayout/Rounding.cpp b/ReactCommon/flexlayout/flexlayout/Rounding.cpp new file mode 100644 index 00000000000..d4a07ec5ff2 --- /dev/null +++ b/ReactCommon/flexlayout/flexlayout/Rounding.cpp @@ -0,0 +1,69 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "Rounding.h" +#include +#include "Utils.h" + +namespace facebook { +namespace flexlayout { +namespace algo { + +using namespace facebook::flexlayout::utils; + +auto RoundValueToPixelGrid( + const double value, + const double pointScaleFactor, + const bool forceCeil, + const bool forceFloor) -> float { + double scaledValue = value * pointScaleFactor; + + if (isUndefined(scaledValue) || isUndefined(pointScaleFactor)) { + return NAN; + } + + // We want to calculate `fractial` such that `floor(scaledValue) = scaledValue + // - fractial`. + double fractial = fmod(scaledValue, 1.0f); + 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; + } + + // Check if the value is already rounded or we force-round down or up + if (FlexLayoutDoubleEqual(fractial, 0) || forceFloor) { + scaledValue = scaledValue - fractial; + } else if (FlexLayoutDoubleEqual(fractial, 1.0f) || forceCeil) { + scaledValue = scaledValue - fractial + 1.0f; + } else { + // Finally we just round the value + scaledValue = scaledValue - fractial + + (isDefined(fractial) && + (fractial > 0.5f || FlexLayoutDoubleEqual(fractial, 0.5f)) + ? 1.0f + : 0.0f); + } + return static_cast(scaledValue / pointScaleFactor); +} + +} // namespace algo +} // namespace flexlayout +} // namespace facebook diff --git a/ReactCommon/flexlayout/flexlayout/Rounding.h b/ReactCommon/flexlayout/flexlayout/Rounding.h new file mode 100644 index 00000000000..ce71985d65f --- /dev/null +++ b/ReactCommon/flexlayout/flexlayout/Rounding.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "FlexLayoutMacros.h" + +namespace facebook { +namespace flexlayout { +namespace algo { + +FLEX_LAYOUT_EXPORT auto RoundValueToPixelGrid( + double value, + double pointScaleFactor, + bool forceCeil, + bool forceFloor) -> float; + +} // namespace algo +} // namespace flexlayout +} // namespace facebook diff --git a/ReactCommon/flexlayout/flexlayout/Type.h b/ReactCommon/flexlayout/flexlayout/Type.h new file mode 100644 index 00000000000..1f1b815eae2 --- /dev/null +++ b/ReactCommon/flexlayout/flexlayout/Type.h @@ -0,0 +1,18 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once +#include + +namespace facebook { +namespace flexlayout { + +using Float = float; +#define EPSILON FLT_EPSILON; + +} // namespace flexlayout +} // namespace facebook diff --git a/ReactCommon/flexlayout/flexlayout/Utils.cpp b/ReactCommon/flexlayout/flexlayout/Utils.cpp new file mode 100644 index 00000000000..312d1723d62 --- /dev/null +++ b/ReactCommon/flexlayout/flexlayout/Utils.cpp @@ -0,0 +1,66 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "Utils.h" +#include +#include "Type.h" + +namespace facebook { +namespace flexlayout { +namespace utils { + +using namespace std; +using namespace facebook::flexlayout; + +auto isUndefined(float value) -> bool { + return std::isnan(value); +} + +auto isUndefined(double value) -> bool { + return std::isnan(value); +} + +auto isDefined(float value) -> bool { + return !isUndefined(value); +} + +auto isDefined(double value) -> bool { + return !isUndefined(value); +} + +auto FlexLayoutFloatsEqual(const Float a, const Float b) -> bool { + if (isDefined(a) && isDefined(b)) { + return fabs(a - b) < EPSILON; + } + return isUndefined(a) && isUndefined(b); +} + +auto FlexLayoutDoubleEqual(const double a, const double b) -> bool { + if (isDefined(a) && isDefined(b)) { + return fabs(a - b) < EPSILON; + } + return isUndefined(a) && isUndefined(b); +} + +auto FlexLayoutFloatMax(const Float a, const Float b) -> float { + if (isDefined(a) && isDefined(b)) { + return fmax(a, b); + } + return isUndefined(a) ? b : a; +} + +auto FlexLayoutFloatMin(const Float a, const Float b) -> float { + if (isDefined(a) && isDefined(b)) { + return fmin(a, b); + } + + return isUndefined(a) ? b : a; +} + +} // namespace utils +} // namespace flexlayout +} // namespace facebook diff --git a/ReactCommon/flexlayout/flexlayout/Utils.h b/ReactCommon/flexlayout/flexlayout/Utils.h new file mode 100644 index 00000000000..ae1fc4ce55f --- /dev/null +++ b/ReactCommon/flexlayout/flexlayout/Utils.h @@ -0,0 +1,204 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once +#include +#include +#include "Dimension.h" +#include "FlexBoxStyle.h" +#include "FlexLayoutEnums.h" +#include "FlexLayoutMacros.h" + +namespace facebook { +namespace flexlayout { + +template +struct MeasureOutput { + Float width; + Float height; + Float baseline; + Result result; + + auto getWidth() const -> Float { + return width; + } + + auto getHeight() const -> Float { + return height; + } + + template + MeasureOutput(Float aWidth, Float aHeight, T&& result) + : MeasureOutput(aWidth, aHeight, UNDEFINED, std::forward(result)) {} + template + MeasureOutput(Float width, Float height, Float baseline, T&& result) + : width{width}, + height{height}, + baseline{baseline}, + result{std::forward(result)} {} +}; + +struct Range { + Float min; + Float max; +}; + +namespace utils { + +using namespace facebook::flexlayout::style; + +inline auto FlexDirectionIsRow(const FlexDirection flexDirection) -> bool { + return flexDirection == FlexDirection::Row || + flexDirection == FlexDirection::RowReverse; +} + +inline auto FlexDirectionIsColumn(const FlexDirection flexDirection) -> bool { + return flexDirection == FlexDirection::Column || + flexDirection == FlexDirection::ColumnReverse; +} + +FLEX_LAYOUT_EXPORT auto isUndefined(float value) -> bool; + +FLEX_LAYOUT_EXPORT auto isUndefined(double value) -> bool; + +FLEX_LAYOUT_EXPORT auto isDefined(float value) -> bool; + +FLEX_LAYOUT_EXPORT auto isDefined(double value) -> bool; + +FLEX_LAYOUT_EXPORT auto FlexLayoutFloatsEqual(float a, float b) -> bool; + +FLEX_LAYOUT_EXPORT auto FlexLayoutDoubleEqual(double a, double b) -> bool; + +FLEX_LAYOUT_EXPORT auto FlexLayoutFloatMax(float a, float b) -> float; + +FLEX_LAYOUT_EXPORT auto FlexLayoutFloatMin(float a, float b) -> float; + +inline auto getLeadingEdge(const FlexDirection axis) -> Edge { + switch (axis) { + case FlexDirection::Row: + return Edge::Left; + case FlexDirection::RowReverse: + return Edge::Right; + case FlexDirection::Column: + return Edge::Top; + case FlexDirection::ColumnReverse: + return Edge::Bottom; + default: + return Edge::Left; + } +} + +inline auto getTrailingEdge(const FlexDirection axis) -> Edge { + switch (axis) { + case FlexDirection::Row: + return Edge::Right; + case FlexDirection::RowReverse: + return Edge::Left; + case FlexDirection::Column: + return Edge::Bottom; + case FlexDirection::ColumnReverse: + return Edge::Top; + default: + return Edge::Right; + } +} + +inline auto ResolveValueMargin(const Dimension value, const float ownerSize) + -> float { + return value.unit == Unit::Auto + ? 0 + : (isUndefined(value.resolve(ownerSize)) ? 0 : value.resolve(ownerSize)); +} + +inline auto ConstraintMinMax( + const float value, + const float minValue, + const float maxValue) -> float { + if (isUndefined(value) && isUndefined(minValue) && isUndefined(maxValue)) { + return value; + } + if (isUndefined(value) && isDefined(maxValue)) { + return maxValue; + } + return FlexLayoutFloatMin( + FlexLayoutFloatMax( + isUndefined(value) ? 0 : value, isUndefined(minValue) ? 0 : minValue), + isUndefined(maxValue) ? std::numeric_limits::max() : maxValue); +} + +inline auto ConstraintMin(const float value, const float minValue) -> float { + if (isUndefined(value) && isUndefined(minValue)) { + return value; + } + return FlexLayoutFloatMax( + isUndefined(value) ? 0 : value, isUndefined(minValue) ? 0 : minValue); +} + +inline auto ConstraintMax(const float value, const float maxValue) -> float { + if (isUndefined(value) && isUndefined(maxValue)) { + return value; + } + return FlexLayoutFloatMin( + isUndefined(value) ? 0 : value, + isUndefined(maxValue) ? std::numeric_limits::max() : maxValue); +} + +inline auto getLeadingBorder( + const FlexBoxStyle& node, + const FlexDirection axis, + const float widthSize) -> float { + const float border = node.getBorder(getLeadingEdge(axis)).resolve(widthSize); + return isUndefined(border) ? 0 : border; +} + +inline auto getTrailingBorder( + const FlexBoxStyle& node, + const FlexDirection axis, + const float widthSize) -> float { + const float border = node.getBorder(getTrailingEdge(axis)).resolve(widthSize); + return isUndefined(border) ? 0 : border; +} + +inline auto getLeadingPadding( + const FlexBoxStyle& node, + const FlexDirection axis, + const float widthSize) -> float { + const float padding = + node.getPadding(getLeadingEdge(axis)).resolve(widthSize); + return isUndefined(padding) ? 0 : padding; +} + +inline auto getTrailingPadding( + const FlexBoxStyle& node, + const FlexDirection axis, + const float widthSize) -> float { + const float padding = + node.getPadding(getTrailingEdge(axis)).resolve(widthSize); + return isUndefined(padding) ? 0 : padding; +} + +inline auto getLeadingPaddingAndBorder( + const FlexBoxStyle& node, + const FlexDirection axis, + const float widthSize) -> float { + const float leadingPaddingAndBorder = + getLeadingPadding(node, axis, widthSize) + + getLeadingBorder(node, axis, widthSize); + return isUndefined(leadingPaddingAndBorder) ? 0 : leadingPaddingAndBorder; +} + +inline auto getTrailingPaddingAndBorder( + const FlexBoxStyle& node, + const FlexDirection axis, + const float widthSize) -> float { + return getTrailingPadding(node, axis, widthSize) + + getTrailingBorder(node, axis, widthSize); +} + +} // namespace utils +} // namespace flexlayout +} // namespace facebook