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
This commit is contained in:
J.T. Yim
2022-08-17 09:55:15 -07:00
committed by Facebook GitHub Bot
parent 30e54adce2
commit 4eec47344e
23 changed files with 3704 additions and 0 deletions
+8
View File
@@ -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
@@ -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 <ostream>
#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
@@ -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 <cmath>
#include "FlexLayoutEnums.h"
#include "FlexLayoutMacros.h"
#include "Type.h"
#ifdef DEBUG
#include <iosfwd>
#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
@@ -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 <sstream>
#endif
namespace facebook {
namespace flexlayout {
namespace style {
auto FlexBoxStyle::getPaddingAndBorder(Edge edge, Float ownerWidth) const
-> Float {
const auto paddingValue = padding[static_cast<int>(edge)].resolve(ownerWidth);
const auto borderValue = border[static_cast<int>(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
@@ -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 <array>
#include "Dimension.h"
#include "FlexLayoutEnums.h"
#include "FlexLayoutMacros.h"
#ifdef DEBUG
#include <iosfwd>
#endif
namespace facebook {
namespace flexlayout {
namespace style {
using namespace facebook::flexlayout::utils;
class FLEX_LAYOUT_EXPORT FlexBoxStyle {
private:
std::array<Dimension, 4> padding = {};
std::array<Dimension, 4> 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<size_t>(edge)];
}
void setPadding(Edge edge, Float value) {
padding[static_cast<size_t>(edge)] = Dimension(value, Unit::Point);
}
void setPaddingPercent(Edge edge, Float value) {
padding[static_cast<size_t>(edge)] = Dimension(value, Unit::Percent);
}
auto getBorder(Edge edge) const -> Dimension {
return border[static_cast<size_t>(edge)];
}
void setBorder(Edge edge, Float value) {
border[static_cast<size_t>(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
@@ -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 <cassert>
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<Float>::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};
}
@@ -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
@@ -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 <sstream>
#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
@@ -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 <array>
#include <cmath>
#include <type_traits>
#include "Dimension.h"
#include "FlexLayoutEnums.h"
#include "Utils.h"
#ifdef DEBUG
#include <iosfwd>
#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<Dimension, 4> margin = {};
std::array<Dimension, 4> 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<size_t>(edge)];
}
void setMargin(Edge edge, Float value) {
margin[static_cast<size_t>(edge)] = Dimension(value, Unit::Point);
}
void setMarginPercent(Edge edge, Float value) {
margin[static_cast<size_t>(edge)] = Dimension(value, Unit::Percent);
}
void setMarginAuto(Edge edge) {
margin[static_cast<size_t>(edge)] = Dimension(NAN, Unit::Auto);
}
auto getPosition(Edge edge) const -> Dimension {
return position[static_cast<size_t>(edge)];
}
void setPosition(Edge edge, Float value) {
position[static_cast<size_t>(edge)] = Dimension(value, Unit::Point);
}
void setPositionPercent(Edge edge, Float value) {
position[static_cast<size_t>(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 <typename MeasureData, typename Result>
struct FLEX_LAYOUT_EXPORT FlexItemStyle : public FlexItemStyleBase {
using MeasureFunction = MeasureOutput<Result> (*)(
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 <typename MeasureData>
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<Protocol>)
template <typename MeasureData>
using PtrToConstIfNotObjC = std::conditional_t<
std::is_convertible<MeasureData*, id>::value,
MeasureData*,
const MeasureData*>;
#endif
template <typename MeasureData, typename Result>
struct FLEX_LAYOUT_EXPORT FlexItemStyle<MeasureData*, Result>
: public FlexItemStyleBase {
using MeasureFunction = MeasureOutput<Result> (*)(
PtrToConstIfNotObjC<MeasureData> measureData,
const Float minWidth,
const Float maxWidth,
const Float minHeight,
const Float maxHeight,
const Float ownerWidth,
const Float ownerHeight);
using BaselineFunction = Float (*)(
PtrToConstIfNotObjC<MeasureData> baselineData,
const Float width,
const Float height);
MeasureFunction measureFunction = nullptr;
BaselineFunction baselineFunction = nullptr;
PtrToConstIfNotObjC<MeasureData> measureData = nullptr;
};
#ifdef DEBUG
auto operator<<(std::ostream& os, const FlexItemStyleBase& style)
-> std::ostream&;
#endif
} // namespace style
} // namespace flexlayout
} // namespace facebook
@@ -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"
@@ -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 <cstdint>
#include <vector>
#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 <typename MeasureData, typename Result>
FLEX_LAYOUT_EXPORT auto calculateLayout(
const FlexBoxStyle& parent,
const std::vector<FlexItemStyle<MeasureData, Result>>& children,
const Float minWidth,
const Float maxWidth,
const Float minHeight,
const Float maxHeight,
const Float ownerWidth) -> LayoutOutput<Result> {
return algo::calculateLayoutInternal(
parent, children, minWidth, maxWidth, minHeight, maxHeight, ownerWidth);
}
} // namespace core
} // namespace flexlayout
} // namespace facebook
@@ -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 <cstdint>
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
@@ -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 <cfloat>
#include <cmath>
#define FLEX_LAYOUT_EXPORT __attribute__((visibility("default")))
#define UNDEFINED NAN
@@ -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 <algorithm>
#include <functional>
#include <numeric>
namespace facebook {
namespace flexlayout {
namespace algo {
template <typename Range, typename Result, typename Map, typename Reduce>
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 <typename Range, typename Predicate>
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<Item>& 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 containers 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 containers 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<Item>{};
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 items 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 items 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 items 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 items 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 items 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 items target main size was made larger by this, its a min
// violation.
item.violation = Item::Violation::Min;
} else if (clampedTargetMainSize < targetMainSize) {
// If the items target main size was made smaller by this, its 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
@@ -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 <cstdint>
#include <vector>
#include "FlexItem.h"
namespace facebook {
namespace flexlayout {
namespace algo {
struct FlexLine {
std::vector<FlexItem> 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
@@ -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
File diff suppressed because it is too large Load Diff
@@ -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 <array>
#include <vector>
#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 <typename MeasureResult>
class FLEX_LAYOUT_EXPORT LayoutOutput : public LayoutOutputBase {
public:
struct Child : LayoutOutputBase::Child {
MeasureResult measureResult;
void setMeasureOutput(
MeasureOutput<MeasureResult> 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<Child> children = {};
};
} // namespace layoutoutput
} // namespace flexlayout
} // namespace facebook
@@ -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 <cmath>
#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<Float>(scaledValue / pointScaleFactor);
}
} // namespace algo
} // namespace flexlayout
} // namespace facebook
@@ -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
+18
View File
@@ -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 <cfloat>
namespace facebook {
namespace flexlayout {
using Float = float;
#define EPSILON FLT_EPSILON;
} // namespace flexlayout
} // namespace facebook
@@ -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 <cmath>
#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
+204
View File
@@ -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 <array>
#include <limits>
#include "Dimension.h"
#include "FlexBoxStyle.h"
#include "FlexLayoutEnums.h"
#include "FlexLayoutMacros.h"
namespace facebook {
namespace flexlayout {
template <typename Result>
struct MeasureOutput {
Float width;
Float height;
Float baseline;
Result result;
auto getWidth() const -> Float {
return width;
}
auto getHeight() const -> Float {
return height;
}
template <typename T>
MeasureOutput(Float aWidth, Float aHeight, T&& result)
: MeasureOutput(aWidth, aHeight, UNDEFINED, std::forward<T>(result)) {}
template <typename T>
MeasureOutput(Float width, Float height, Float baseline, T&& result)
: width{width},
height{height},
baseline{baseline},
result{std::forward<T>(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<float>::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<float>::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