From 974eec826464aaa6341c4696dfdc0fe352ec97cb Mon Sep 17 00:00:00 2001 From: Andy Street Date: Fri, 11 Nov 2016 10:50:09 -0800 Subject: [PATCH] Add CSSLayout to ReactCommon Summary: First step to sharing CSSLayout code between iOS and Android. Reviewed By: emilsjolander Differential Revision: D4160286 fbshipit-source-id: 976f5820b19a7011e0a14317c858465f932e1f59 --- .../first-party/csslayoutjni/jni/CSSJNI.cpp | 296 ++ ReactCommon/CSSLayout/CSSLayout.c | 2510 +++++++++++++++++ ReactCommon/CSSLayout/CSSLayout.h | 241 ++ ReactCommon/CSSLayout/CSSMacros.h | 42 + ReactCommon/CSSLayout/CSSNodeList.c | 100 + ReactCommon/CSSLayout/CSSNodeList.h | 33 + 6 files changed, 3222 insertions(+) create mode 100644 ReactAndroid/src/main/jni/first-party/csslayoutjni/jni/CSSJNI.cpp create mode 100644 ReactCommon/CSSLayout/CSSLayout.c create mode 100644 ReactCommon/CSSLayout/CSSLayout.h create mode 100644 ReactCommon/CSSLayout/CSSMacros.h create mode 100644 ReactCommon/CSSLayout/CSSNodeList.c create mode 100644 ReactCommon/CSSLayout/CSSNodeList.h diff --git a/ReactAndroid/src/main/jni/first-party/csslayoutjni/jni/CSSJNI.cpp b/ReactAndroid/src/main/jni/first-party/csslayoutjni/jni/CSSJNI.cpp new file mode 100644 index 000000000..1b5d1de95 --- /dev/null +++ b/ReactAndroid/src/main/jni/first-party/csslayoutjni/jni/CSSJNI.cpp @@ -0,0 +1,296 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#include +#include +#include + +using namespace facebook::jni; +using namespace std; + +static void _jniTransferLayoutDirection(CSSNodeRef node, alias_ref javaNode) { + static auto layoutDirectionField = javaNode->getClass()->getField("mLayoutDirection"); + javaNode->setFieldValue(layoutDirectionField, static_cast(CSSNodeLayoutGetDirection(node))); +} + +static void _jniTransferLayoutOutputsRecursive(CSSNodeRef root) { + auto javaNode = adopt_local( + Environment::current()->NewLocalRef(reinterpret_cast(CSSNodeGetContext(root)))); + + static auto widthField = javaNode->getClass()->getField("mWidth"); + static auto heightField = javaNode->getClass()->getField("mHeight"); + static auto leftField = javaNode->getClass()->getField("mLeft"); + static auto topField = javaNode->getClass()->getField("mTop"); + + javaNode->setFieldValue(widthField, CSSNodeLayoutGetWidth(root)); + javaNode->setFieldValue(heightField, CSSNodeLayoutGetHeight(root)); + javaNode->setFieldValue(leftField, CSSNodeLayoutGetLeft(root)); + javaNode->setFieldValue(topField, CSSNodeLayoutGetTop(root)); + _jniTransferLayoutDirection(root, javaNode); + + for (uint32_t i = 0; i < CSSNodeChildCount(root); i++) { + _jniTransferLayoutOutputsRecursive(CSSNodeGetChild(root, i)); + } +} + +static void _jniPrint(CSSNodeRef node) { + auto obj = adopt_local( + Environment::current()->NewLocalRef(reinterpret_cast(CSSNodeGetContext(node)))); + cout << obj->toString() << endl; +} + +static CSSSize _jniMeasureFunc(CSSNodeRef node, + float width, + CSSMeasureMode widthMode, + float height, + CSSMeasureMode heightMode) { + auto obj = adopt_local( + Environment::current()->NewLocalRef(reinterpret_cast(CSSNodeGetContext(node)))); + + static auto measureFunc = findClassLocal("com/facebook/csslayout/CSSNode") + ->getMethod("measure"); + + _jniTransferLayoutDirection(node, obj); + const auto measureResult = measureFunc(obj, width, widthMode, height, heightMode); + + static_assert(sizeof(measureResult) == 8, + "Expected measureResult to be 8 bytes, or two 32 bit ints"); + + const float measuredWidth = static_cast(0xFFFFFFFF & (measureResult >> 32)); + const float measuredHeight = static_cast(0xFFFFFFFF & measureResult); + + return CSSSize{measuredWidth, measuredHeight}; +} + +static global_ref *jLogger; +static int _jniLog(CSSLogLevel level, const char *format, va_list args) { + char buffer[256]; + int result = vsnprintf(buffer, sizeof(buffer), format, args); + + static auto logFunc = + findClassLocal("com/facebook/csslayout/CSSLogger")->getMethod("log"); + logFunc(jLogger->get(), static_cast(level), Environment::current()->NewStringUTF(buffer)); + + return result; +} + +static inline CSSNodeRef _jlong2CSSNodeRef(jlong addr) { + return reinterpret_cast(static_cast(addr)); +} + +void jni_CSSLayoutSetLogger(alias_ref clazz, alias_ref logger) { + if (jLogger) { + jLogger->releaseAlias(); + delete jLogger; + } + + if (logger) { + jLogger = new global_ref(make_global(logger)); + CSSLayoutSetLogger(_jniLog); + } else { + jLogger = NULL; + CSSLayoutSetLogger(NULL); + } +} + +void jni_CSSLog(alias_ref clazz, jint level, jstring message) { + const char *nMessage = Environment::current()->GetStringUTFChars(message, 0); + CSSLog(static_cast(level), "%s", nMessage); + Environment::current()->ReleaseStringUTFChars(message, nMessage); +} + +jint jni_CSSNodeGetInstanceCount(alias_ref clazz) { + return CSSNodeGetInstanceCount(); +} + +jlong jni_CSSNodeNew(alias_ref thiz) { + const CSSNodeRef node = CSSNodeNew(); + CSSNodeSetContext(node, Environment::current()->NewWeakGlobalRef(thiz.get())); + CSSNodeSetPrintFunc(node, _jniPrint); + return reinterpret_cast(node); +} + +void jni_CSSNodeFree(alias_ref thiz, jlong nativePointer) { + Environment::current()->DeleteWeakGlobalRef( + reinterpret_cast(CSSNodeGetContext(_jlong2CSSNodeRef(nativePointer)))); + CSSNodeFree(_jlong2CSSNodeRef(nativePointer)); +} + +void jni_CSSNodeReset(alias_ref thiz, jlong nativePointer) { + const CSSNodeRef node = _jlong2CSSNodeRef(nativePointer); + void *context = CSSNodeGetContext(node); + CSSNodeReset(node); + CSSNodeSetContext(node, context); + CSSNodeSetPrintFunc(node, _jniPrint); +} + +void jni_CSSNodeInsertChild(alias_ref, + jlong nativePointer, + jlong childPointer, + jint index) { + CSSNodeInsertChild(_jlong2CSSNodeRef(nativePointer), _jlong2CSSNodeRef(childPointer), index); +} + +void jni_CSSNodeRemoveChild(alias_ref, jlong nativePointer, jlong childPointer) { + CSSNodeRemoveChild(_jlong2CSSNodeRef(nativePointer), _jlong2CSSNodeRef(childPointer)); +} + +void jni_CSSNodeCalculateLayout(alias_ref, jlong nativePointer) { + const CSSNodeRef root = _jlong2CSSNodeRef(nativePointer); + CSSNodeCalculateLayout(root, + CSSUndefined, + CSSUndefined, + CSSNodeStyleGetDirection(_jlong2CSSNodeRef(nativePointer))); + _jniTransferLayoutOutputsRecursive(root); +} + +void jni_CSSNodeMarkDirty(alias_ref, jlong nativePointer) { + CSSNodeMarkDirty(_jlong2CSSNodeRef(nativePointer)); +} + +jboolean jni_CSSNodeIsDirty(alias_ref, jlong nativePointer) { + return (jboolean) CSSNodeIsDirty(_jlong2CSSNodeRef(nativePointer)); +} + +void jni_CSSNodeSetHasMeasureFunc(alias_ref, + jlong nativePointer, + jboolean hasMeasureFunc) { + CSSNodeSetMeasureFunc(_jlong2CSSNodeRef(nativePointer), hasMeasureFunc ? _jniMeasureFunc : NULL); +} + +jboolean jni_CSSNodeHasNewLayout(alias_ref, jlong nativePointer) { + return (jboolean) CSSNodeGetHasNewLayout(_jlong2CSSNodeRef(nativePointer)); +} + +void jni_CSSNodeMarkLayoutSeen(alias_ref, jlong nativePointer) { + CSSNodeSetHasNewLayout(_jlong2CSSNodeRef(nativePointer), false); +} + +#define CSS_NODE_JNI_STYLE_PROP(javatype, type, name) \ + javatype jni_CSSNodeStyleGet##name(alias_ref, jlong nativePointer) { \ + return (javatype) CSSNodeStyleGet##name(_jlong2CSSNodeRef(nativePointer)); \ + } \ + \ + void jni_CSSNodeStyleSet##name(alias_ref, jlong nativePointer, javatype value) { \ + CSSNodeStyleSet##name(_jlong2CSSNodeRef(nativePointer), static_cast(value)); \ + } + +#define CSS_NODE_JNI_STYLE_EDGE_PROP(javatype, type, name) \ + javatype jni_CSSNodeStyleGet##name(alias_ref, jlong nativePointer, jint edge) { \ + return (javatype) CSSNodeStyleGet##name(_jlong2CSSNodeRef(nativePointer), \ + static_cast(edge)); \ + } \ + \ + void jni_CSSNodeStyleSet##name(alias_ref, \ + jlong nativePointer, \ + jint edge, \ + javatype value) { \ + CSSNodeStyleSet##name(_jlong2CSSNodeRef(nativePointer), \ + static_cast(edge), \ + static_cast(value)); \ + } + +CSS_NODE_JNI_STYLE_PROP(jint, CSSDirection, Direction); +CSS_NODE_JNI_STYLE_PROP(jint, CSSFlexDirection, FlexDirection); +CSS_NODE_JNI_STYLE_PROP(jint, CSSJustify, JustifyContent); +CSS_NODE_JNI_STYLE_PROP(jint, CSSAlign, AlignItems); +CSS_NODE_JNI_STYLE_PROP(jint, CSSAlign, AlignSelf); +CSS_NODE_JNI_STYLE_PROP(jint, CSSAlign, AlignContent); +CSS_NODE_JNI_STYLE_PROP(jint, CSSPositionType, PositionType); +CSS_NODE_JNI_STYLE_PROP(jint, CSSWrapType, FlexWrap); +CSS_NODE_JNI_STYLE_PROP(jint, CSSOverflow, Overflow); + +void jni_CSSNodeStyleSetFlex(alias_ref, jlong nativePointer, jfloat value) { + CSSNodeStyleSetFlex(_jlong2CSSNodeRef(nativePointer), static_cast(value)); +} +CSS_NODE_JNI_STYLE_PROP(jfloat, float, FlexGrow); +CSS_NODE_JNI_STYLE_PROP(jfloat, float, FlexShrink); +CSS_NODE_JNI_STYLE_PROP(jfloat, float, FlexBasis); + +CSS_NODE_JNI_STYLE_EDGE_PROP(jfloat, float, Position); +CSS_NODE_JNI_STYLE_EDGE_PROP(jfloat, float, Margin); +CSS_NODE_JNI_STYLE_EDGE_PROP(jfloat, float, Padding); +CSS_NODE_JNI_STYLE_EDGE_PROP(jfloat, float, Border); + +CSS_NODE_JNI_STYLE_PROP(jfloat, float, Width); +CSS_NODE_JNI_STYLE_PROP(jfloat, float, MinWidth); +CSS_NODE_JNI_STYLE_PROP(jfloat, float, MaxWidth); +CSS_NODE_JNI_STYLE_PROP(jfloat, float, Height); +CSS_NODE_JNI_STYLE_PROP(jfloat, float, MinHeight); +CSS_NODE_JNI_STYLE_PROP(jfloat, float, MaxHeight); + +#define CSSMakeNativeMethod(name) makeNativeMethod(#name, name) + +jint JNI_OnLoad(JavaVM *vm, void *) { + return initialize(vm, [] { + registerNatives("com/facebook/csslayout/CSSNode", + { + CSSMakeNativeMethod(jni_CSSNodeNew), + CSSMakeNativeMethod(jni_CSSNodeFree), + CSSMakeNativeMethod(jni_CSSNodeReset), + CSSMakeNativeMethod(jni_CSSNodeInsertChild), + CSSMakeNativeMethod(jni_CSSNodeRemoveChild), + CSSMakeNativeMethod(jni_CSSNodeCalculateLayout), + CSSMakeNativeMethod(jni_CSSNodeHasNewLayout), + CSSMakeNativeMethod(jni_CSSNodeMarkDirty), + CSSMakeNativeMethod(jni_CSSNodeIsDirty), + CSSMakeNativeMethod(jni_CSSNodeMarkLayoutSeen), + CSSMakeNativeMethod(jni_CSSNodeSetHasMeasureFunc), + + CSSMakeNativeMethod(jni_CSSNodeStyleGetDirection), + CSSMakeNativeMethod(jni_CSSNodeStyleSetDirection), + CSSMakeNativeMethod(jni_CSSNodeStyleGetFlexDirection), + CSSMakeNativeMethod(jni_CSSNodeStyleSetFlexDirection), + CSSMakeNativeMethod(jni_CSSNodeStyleGetJustifyContent), + CSSMakeNativeMethod(jni_CSSNodeStyleSetJustifyContent), + CSSMakeNativeMethod(jni_CSSNodeStyleGetAlignItems), + CSSMakeNativeMethod(jni_CSSNodeStyleSetAlignItems), + CSSMakeNativeMethod(jni_CSSNodeStyleGetAlignSelf), + CSSMakeNativeMethod(jni_CSSNodeStyleSetAlignSelf), + CSSMakeNativeMethod(jni_CSSNodeStyleGetAlignContent), + CSSMakeNativeMethod(jni_CSSNodeStyleSetAlignContent), + CSSMakeNativeMethod(jni_CSSNodeStyleGetPositionType), + CSSMakeNativeMethod(jni_CSSNodeStyleSetPositionType), + CSSMakeNativeMethod(jni_CSSNodeStyleSetFlexWrap), + CSSMakeNativeMethod(jni_CSSNodeStyleGetOverflow), + CSSMakeNativeMethod(jni_CSSNodeStyleSetOverflow), + CSSMakeNativeMethod(jni_CSSNodeStyleSetFlex), + CSSMakeNativeMethod(jni_CSSNodeStyleGetFlexGrow), + CSSMakeNativeMethod(jni_CSSNodeStyleSetFlexGrow), + CSSMakeNativeMethod(jni_CSSNodeStyleGetFlexShrink), + CSSMakeNativeMethod(jni_CSSNodeStyleSetFlexShrink), + CSSMakeNativeMethod(jni_CSSNodeStyleGetFlexBasis), + CSSMakeNativeMethod(jni_CSSNodeStyleSetFlexBasis), + CSSMakeNativeMethod(jni_CSSNodeStyleGetMargin), + CSSMakeNativeMethod(jni_CSSNodeStyleSetMargin), + CSSMakeNativeMethod(jni_CSSNodeStyleGetPadding), + CSSMakeNativeMethod(jni_CSSNodeStyleSetPadding), + CSSMakeNativeMethod(jni_CSSNodeStyleGetBorder), + CSSMakeNativeMethod(jni_CSSNodeStyleSetBorder), + CSSMakeNativeMethod(jni_CSSNodeStyleGetPosition), + CSSMakeNativeMethod(jni_CSSNodeStyleSetPosition), + CSSMakeNativeMethod(jni_CSSNodeStyleGetWidth), + CSSMakeNativeMethod(jni_CSSNodeStyleSetWidth), + CSSMakeNativeMethod(jni_CSSNodeStyleGetHeight), + CSSMakeNativeMethod(jni_CSSNodeStyleSetHeight), + CSSMakeNativeMethod(jni_CSSNodeStyleGetMinWidth), + CSSMakeNativeMethod(jni_CSSNodeStyleSetMinWidth), + CSSMakeNativeMethod(jni_CSSNodeStyleGetMinHeight), + CSSMakeNativeMethod(jni_CSSNodeStyleSetMinHeight), + CSSMakeNativeMethod(jni_CSSNodeStyleGetMaxWidth), + CSSMakeNativeMethod(jni_CSSNodeStyleSetMaxWidth), + CSSMakeNativeMethod(jni_CSSNodeStyleGetMaxHeight), + CSSMakeNativeMethod(jni_CSSNodeStyleSetMaxHeight), + + CSSMakeNativeMethod(jni_CSSNodeGetInstanceCount), + CSSMakeNativeMethod(jni_CSSLayoutSetLogger), + CSSMakeNativeMethod(jni_CSSLog), + }); + }); +} diff --git a/ReactCommon/CSSLayout/CSSLayout.c b/ReactCommon/CSSLayout/CSSLayout.c new file mode 100644 index 000000000..f2d884b70 --- /dev/null +++ b/ReactCommon/CSSLayout/CSSLayout.c @@ -0,0 +1,2510 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#include + +#include "CSSLayout.h" +#include "CSSNodeList.h" + +#ifdef _MSC_VER +#include +#define isnan _isnan + +#ifndef __cplusplus +#define inline __inline +#endif + +/* define fmaxf if < VC12 */ +#if _MSC_VER < 1800 +__forceinline const float fmaxf(const float a, const float b) { + return (a > b) ? a : b; +} +#endif +#endif + +typedef struct CSSCachedMeasurement { + float availableWidth; + float availableHeight; + CSSMeasureMode widthMeasureMode; + CSSMeasureMode heightMeasureMode; + + float computedWidth; + float computedHeight; +} CSSCachedMeasurement; + +// This value was chosen based on empiracle data. Even the most complicated +// layouts should not require more than 16 entries to fit within the cache. +enum { CSS_MAX_CACHED_RESULT_COUNT = 16 }; + +typedef struct CSSLayout { + float position[4]; + float dimensions[2]; + CSSDirection direction; + + float computedFlexBasis; + + // Instead of recomputing the entire layout every single time, we + // cache some information to break early when nothing changed + uint32_t generationCount; + CSSDirection lastParentDirection; + + uint32_t nextCachedMeasurementsIndex; + CSSCachedMeasurement cachedMeasurements[CSS_MAX_CACHED_RESULT_COUNT]; + float measuredDimensions[2]; + + CSSCachedMeasurement cachedLayout; +} CSSLayout; + +typedef struct CSSStyle { + CSSDirection direction; + CSSFlexDirection flexDirection; + CSSJustify justifyContent; + CSSAlign alignContent; + CSSAlign alignItems; + CSSAlign alignSelf; + CSSPositionType positionType; + CSSWrapType flexWrap; + CSSOverflow overflow; + float flex; + float flexGrow; + float flexShrink; + float flexBasis; + float margin[CSSEdgeCount]; + float position[CSSEdgeCount]; + float padding[CSSEdgeCount]; + float border[CSSEdgeCount]; + float dimensions[2]; + float minDimensions[2]; + float maxDimensions[2]; +} CSSStyle; + +typedef struct CSSNode { + CSSStyle style; + CSSLayout layout; + uint32_t lineIndex; + bool hasNewLayout; + CSSNodeRef parent; + CSSNodeListRef children; + bool isDirty; + + struct CSSNode *nextChild; + + CSSMeasureFunc measure; + CSSPrintFunc print; + void *context; +} CSSNode; + +static void _CSSNodeMarkDirty(const CSSNodeRef node); + +#ifdef ANDROID +#include +static int _csslayoutAndroidLog(CSSLogLevel level, const char *format, va_list args) { + int androidLevel = CSSLogLevelDebug; + switch (level) { + case CSSLogLevelError: + androidLevel = ANDROID_LOG_ERROR; + break; + case CSSLogLevelWarn: + androidLevel = ANDROID_LOG_WARN; + break; + case CSSLogLevelInfo: + androidLevel = ANDROID_LOG_INFO; + break; + case CSSLogLevelDebug: + androidLevel = ANDROID_LOG_DEBUG; + break; + case CSSLogLevelVerbose: + androidLevel = ANDROID_LOG_VERBOSE; + break; + } + const int result = __android_log_vprint(androidLevel, "css-layout", format, args); + return result; +} +static CSSLogger gLogger = &_csslayoutAndroidLog; +#else +static int _csslayoutDefaultLog(CSSLogLevel level, const char *format, va_list args) { + switch (level) { + case CSSLogLevelError: + return vfprintf(stderr, format, args); + case CSSLogLevelWarn: + case CSSLogLevelInfo: + case CSSLogLevelDebug: + case CSSLogLevelVerbose: + default: + return vprintf(format, args); + } +} +static CSSLogger gLogger = &_csslayoutDefaultLog; +#endif + +static inline float computedEdgeValue(const float edges[CSSEdgeCount], + const CSSEdge edge, + const float defaultValue) { + CSS_ASSERT(edge <= CSSEdgeEnd, "Cannot get computed value of multi-edge shorthands"); + + if (!CSSValueIsUndefined(edges[edge])) { + return edges[edge]; + } + + if ((edge == CSSEdgeTop || edge == CSSEdgeBottom) && + !CSSValueIsUndefined(edges[CSSEdgeVertical])) { + return edges[CSSEdgeVertical]; + } + + if ((edge == CSSEdgeLeft || edge == CSSEdgeRight || edge == CSSEdgeStart || edge == CSSEdgeEnd) && + !CSSValueIsUndefined(edges[CSSEdgeHorizontal])) { + return edges[CSSEdgeHorizontal]; + } + + if (!CSSValueIsUndefined(edges[CSSEdgeAll])) { + return edges[CSSEdgeAll]; + } + + if (edge == CSSEdgeStart || edge == CSSEdgeEnd) { + return CSSUndefined; + } + + return defaultValue; +} + +static int32_t gNodeInstanceCount = 0; + +CSSNodeRef CSSNodeNew(void) { + const CSSNodeRef node = calloc(1, sizeof(CSSNode)); + CSS_ASSERT(node, "Could not allocate memory for node"); + gNodeInstanceCount++; + + CSSNodeInit(node); + return node; +} + +void CSSNodeFree(const CSSNodeRef node) { + if (node->parent) { + CSSNodeListDelete(node->parent->children, node); + node->parent = NULL; + } + + const uint32_t childCount = CSSNodeChildCount(node); + for (uint32_t i = 0; i < childCount; i++) { + const CSSNodeRef child = CSSNodeGetChild(node, i); + child->parent = NULL; + } + + CSSNodeListFree(node->children); + free(node); + gNodeInstanceCount--; +} + +void CSSNodeFreeRecursive(const CSSNodeRef root) { + while (CSSNodeChildCount(root) > 0) { + const CSSNodeRef child = CSSNodeGetChild(root, 0); + CSSNodeRemoveChild(root, child); + CSSNodeFreeRecursive(child); + } + CSSNodeFree(root); +} + +void CSSNodeReset(const CSSNodeRef node) { + CSS_ASSERT(CSSNodeChildCount(node) == 0, "Cannot reset a node which still has children attached"); + CSS_ASSERT(node->parent == NULL, "Cannot reset a node still attached to a parent"); + + CSSNodeListFree(node->children); + memset(node, 0, sizeof(CSSNode)); + CSSNodeInit(node); +} + +int32_t CSSNodeGetInstanceCount(void) { + return gNodeInstanceCount; +} + +void CSSNodeInit(const CSSNodeRef node) { + node->parent = NULL; + node->children = NULL; + node->hasNewLayout = true; + node->isDirty = false; + + node->style.flex = CSSUndefined; + node->style.flexGrow = CSSUndefined; + node->style.flexShrink = CSSUndefined; + node->style.flexBasis = CSSUndefined; + + node->style.alignItems = CSSAlignStretch; + node->style.alignContent = CSSAlignFlexStart; + + node->style.direction = CSSDirectionInherit; + node->style.flexDirection = CSSFlexDirectionColumn; + + node->style.overflow = CSSOverflowVisible; + + // Some of the fields default to undefined and not 0 + node->style.dimensions[CSSDimensionWidth] = CSSUndefined; + node->style.dimensions[CSSDimensionHeight] = CSSUndefined; + + node->style.minDimensions[CSSDimensionWidth] = CSSUndefined; + node->style.minDimensions[CSSDimensionHeight] = CSSUndefined; + + node->style.maxDimensions[CSSDimensionWidth] = CSSUndefined; + node->style.maxDimensions[CSSDimensionHeight] = CSSUndefined; + + for (CSSEdge edge = CSSEdgeLeft; edge < CSSEdgeCount; edge++) { + node->style.position[edge] = CSSUndefined; + node->style.margin[edge] = CSSUndefined; + node->style.padding[edge] = CSSUndefined; + node->style.border[edge] = CSSUndefined; + } + + node->layout.dimensions[CSSDimensionWidth] = CSSUndefined; + node->layout.dimensions[CSSDimensionHeight] = CSSUndefined; + + // Such that the comparison is always going to be false + node->layout.lastParentDirection = (CSSDirection) -1; + node->layout.nextCachedMeasurementsIndex = 0; + node->layout.computedFlexBasis = CSSUndefined; + + node->layout.measuredDimensions[CSSDimensionWidth] = CSSUndefined; + node->layout.measuredDimensions[CSSDimensionHeight] = CSSUndefined; + node->layout.cachedLayout.widthMeasureMode = (CSSMeasureMode) -1; + node->layout.cachedLayout.heightMeasureMode = (CSSMeasureMode) -1; + node->layout.cachedLayout.computedWidth = -1; + node->layout.cachedLayout.computedHeight = -1; +} + +static void _CSSNodeMarkDirty(const CSSNodeRef node) { + if (!node->isDirty) { + node->isDirty = true; + node->layout.computedFlexBasis = CSSUndefined; + if (node->parent) { + _CSSNodeMarkDirty(node->parent); + } + } +} + +void CSSNodeSetMeasureFunc(const CSSNodeRef node, CSSMeasureFunc measureFunc) { + if (measureFunc == NULL) { + node->measure = NULL; + } else { + CSS_ASSERT(CSSNodeChildCount(node) == 0, + "Cannot set measure function: Nodes with measure functions cannot have children."); + node->measure = measureFunc; + } +} + +CSSMeasureFunc CSSNodeGetMeasureFunc(const CSSNodeRef node) { + return node->measure; +} + +void CSSNodeInsertChild(const CSSNodeRef node, const CSSNodeRef child, const uint32_t index) { + CSS_ASSERT(child->parent == NULL, "Child already has a parent, it must be removed first."); + CSS_ASSERT(node->measure == NULL, + "Cannot add child: Nodes with measure functions cannot have children."); + CSSNodeListInsert(&node->children, child, index); + child->parent = node; + _CSSNodeMarkDirty(node); +} + +void CSSNodeRemoveChild(const CSSNodeRef node, const CSSNodeRef child) { + if (CSSNodeListDelete(node->children, child) != NULL) { + child->parent = NULL; + _CSSNodeMarkDirty(node); + } +} + +CSSNodeRef CSSNodeGetChild(const CSSNodeRef node, const uint32_t index) { + return CSSNodeListGet(node->children, index); +} + +inline uint32_t CSSNodeChildCount(const CSSNodeRef node) { + return CSSNodeListCount(node->children); +} + +void CSSNodeMarkDirty(const CSSNodeRef node) { + CSS_ASSERT(node->measure != NULL, + "Only leaf nodes with custom measure functions" + "should manually mark themselves as dirty"); + _CSSNodeMarkDirty(node); +} + +bool CSSNodeIsDirty(const CSSNodeRef node) { + return node->isDirty; +} + +inline float CSSNodeStyleGetFlexGrow(CSSNodeRef node) { + if (!CSSValueIsUndefined(node->style.flexGrow)) { + return node->style.flexGrow; + } + if (!CSSValueIsUndefined(node->style.flex) && node->style.flex > 0) { + return node->style.flex; + } + return 0; +} + +inline float CSSNodeStyleGetFlexShrink(CSSNodeRef node) { + if (!CSSValueIsUndefined(node->style.flexShrink)) { + return node->style.flexShrink; + } + if (!CSSValueIsUndefined(node->style.flex) && node->style.flex < 0) { + return -node->style.flex; + } + return 0; +} + +inline float CSSNodeStyleGetFlexBasis(CSSNodeRef node) { + if (!CSSValueIsUndefined(node->style.flexBasis)) { + return node->style.flexBasis; + } + if (!CSSValueIsUndefined(node->style.flex)) { + return node->style.flex > 0 ? 0 : CSSUndefined; + } + return CSSUndefined; +} + +void CSSNodeStyleSetFlex(const CSSNodeRef node, const float flex) { + if (node->style.flex != flex) { + node->style.flex = flex; + _CSSNodeMarkDirty(node); + } +} + +#define CSS_NODE_PROPERTY_IMPL(type, name, paramName, instanceName) \ + void CSSNodeSet##name(const CSSNodeRef node, type paramName) { \ + node->instanceName = paramName; \ + } \ + \ + type CSSNodeGet##name(const CSSNodeRef node) { \ + return node->instanceName; \ + } + +#define CSS_NODE_STYLE_PROPERTY_SETTER_IMPL(type, name, paramName, instanceName) \ + void CSSNodeStyleSet##name(const CSSNodeRef node, const type paramName) { \ + if (node->style.instanceName != paramName) { \ + node->style.instanceName = paramName; \ + _CSSNodeMarkDirty(node); \ + } \ + } + +#define CSS_NODE_STYLE_PROPERTY_IMPL(type, name, paramName, instanceName) \ + CSS_NODE_STYLE_PROPERTY_SETTER_IMPL(type, name, paramName, instanceName) \ + \ + type CSSNodeStyleGet##name(const CSSNodeRef node) { \ + return node->style.instanceName; \ + } + +#define CSS_NODE_STYLE_EDGE_PROPERTY_IMPL(type, name, paramName, instanceName, defaultValue) \ + void CSSNodeStyleSet##name(const CSSNodeRef node, const CSSEdge edge, const type paramName) { \ + if (node->style.instanceName[edge] != paramName) { \ + node->style.instanceName[edge] = paramName; \ + _CSSNodeMarkDirty(node); \ + } \ + } \ + \ + type CSSNodeStyleGet##name(const CSSNodeRef node, const CSSEdge edge) { \ + return computedEdgeValue(node->style.instanceName, edge, defaultValue); \ + } + +#define CSS_NODE_LAYOUT_PROPERTY_IMPL(type, name, instanceName) \ + type CSSNodeLayoutGet##name(const CSSNodeRef node) { \ + return node->layout.instanceName; \ + } + +CSS_NODE_PROPERTY_IMPL(void *, Context, context, context); +CSS_NODE_PROPERTY_IMPL(CSSPrintFunc, PrintFunc, printFunc, print); +CSS_NODE_PROPERTY_IMPL(bool, HasNewLayout, hasNewLayout, hasNewLayout); + +CSS_NODE_STYLE_PROPERTY_IMPL(CSSDirection, Direction, direction, direction); +CSS_NODE_STYLE_PROPERTY_IMPL(CSSFlexDirection, FlexDirection, flexDirection, flexDirection); +CSS_NODE_STYLE_PROPERTY_IMPL(CSSJustify, JustifyContent, justifyContent, justifyContent); +CSS_NODE_STYLE_PROPERTY_IMPL(CSSAlign, AlignContent, alignContent, alignContent); +CSS_NODE_STYLE_PROPERTY_IMPL(CSSAlign, AlignItems, alignItems, alignItems); +CSS_NODE_STYLE_PROPERTY_IMPL(CSSAlign, AlignSelf, alignSelf, alignSelf); +CSS_NODE_STYLE_PROPERTY_IMPL(CSSPositionType, PositionType, positionType, positionType); +CSS_NODE_STYLE_PROPERTY_IMPL(CSSWrapType, FlexWrap, flexWrap, flexWrap); +CSS_NODE_STYLE_PROPERTY_IMPL(CSSOverflow, Overflow, overflow, overflow); + +CSS_NODE_STYLE_PROPERTY_SETTER_IMPL(float, FlexGrow, flexGrow, flexGrow); +CSS_NODE_STYLE_PROPERTY_SETTER_IMPL(float, FlexShrink, flexShrink, flexShrink); +CSS_NODE_STYLE_PROPERTY_SETTER_IMPL(float, FlexBasis, flexBasis, flexBasis); + +CSS_NODE_STYLE_EDGE_PROPERTY_IMPL(float, Position, position, position, CSSUndefined); +CSS_NODE_STYLE_EDGE_PROPERTY_IMPL(float, Margin, margin, margin, 0); +CSS_NODE_STYLE_EDGE_PROPERTY_IMPL(float, Padding, padding, padding, 0); +CSS_NODE_STYLE_EDGE_PROPERTY_IMPL(float, Border, border, border, 0); + +CSS_NODE_STYLE_PROPERTY_IMPL(float, Width, width, dimensions[CSSDimensionWidth]); +CSS_NODE_STYLE_PROPERTY_IMPL(float, Height, height, dimensions[CSSDimensionHeight]); +CSS_NODE_STYLE_PROPERTY_IMPL(float, MinWidth, minWidth, minDimensions[CSSDimensionWidth]); +CSS_NODE_STYLE_PROPERTY_IMPL(float, MinHeight, minHeight, minDimensions[CSSDimensionHeight]); +CSS_NODE_STYLE_PROPERTY_IMPL(float, MaxWidth, maxWidth, maxDimensions[CSSDimensionWidth]); +CSS_NODE_STYLE_PROPERTY_IMPL(float, MaxHeight, maxHeight, maxDimensions[CSSDimensionHeight]); + +CSS_NODE_LAYOUT_PROPERTY_IMPL(float, Left, position[CSSEdgeLeft]); +CSS_NODE_LAYOUT_PROPERTY_IMPL(float, Top, position[CSSEdgeTop]); +CSS_NODE_LAYOUT_PROPERTY_IMPL(float, Right, position[CSSEdgeRight]); +CSS_NODE_LAYOUT_PROPERTY_IMPL(float, Bottom, position[CSSEdgeBottom]); +CSS_NODE_LAYOUT_PROPERTY_IMPL(float, Width, dimensions[CSSDimensionWidth]); +CSS_NODE_LAYOUT_PROPERTY_IMPL(float, Height, dimensions[CSSDimensionHeight]); +CSS_NODE_LAYOUT_PROPERTY_IMPL(CSSDirection, Direction, direction); + +uint32_t gCurrentGenerationCount = 0; + +bool layoutNodeInternal(const CSSNodeRef node, + const float availableWidth, + const float availableHeight, + const CSSDirection parentDirection, + const CSSMeasureMode widthMeasureMode, + const CSSMeasureMode heightMeasureMode, + const bool performLayout, + const char *reason); + +inline bool CSSValueIsUndefined(const float value) { + return isnan(value); +} + +static inline bool eq(const float a, const float b) { + if (CSSValueIsUndefined(a)) { + return CSSValueIsUndefined(b); + } + return fabs(a - b) < 0.0001; +} + +static void indent(const uint32_t n) { + for (uint32_t i = 0; i < n; i++) { + CSSLog(CSSLogLevelDebug, " "); + } +} + +static void printNumberIfNotZero(const char *str, const float number) { + if (!eq(number, 0)) { + CSSLog(CSSLogLevelDebug, "%s: %g, ", str, number); + } +} + +static void printNumberIfNotUndefined(const char *str, const float number) { + if (!CSSValueIsUndefined(number)) { + CSSLog(CSSLogLevelDebug, "%s: %g, ", str, number); + } +} + +static bool eqFour(const float four[4]) { + return eq(four[0], four[1]) && eq(four[0], four[2]) && eq(four[0], four[3]); +} + +static void _CSSNodePrint(const CSSNodeRef node, + const CSSPrintOptions options, + const uint32_t level) { + indent(level); + CSSLog(CSSLogLevelDebug, "{"); + + if (node->print) { + node->print(node); + } + + if (options & CSSPrintOptionsLayout) { + CSSLog(CSSLogLevelDebug, "layout: {"); + CSSLog(CSSLogLevelDebug, "width: %g, ", node->layout.dimensions[CSSDimensionWidth]); + CSSLog(CSSLogLevelDebug, "height: %g, ", node->layout.dimensions[CSSDimensionHeight]); + CSSLog(CSSLogLevelDebug, "top: %g, ", node->layout.position[CSSEdgeTop]); + CSSLog(CSSLogLevelDebug, "left: %g", node->layout.position[CSSEdgeLeft]); + CSSLog(CSSLogLevelDebug, "}, "); + } + + if (options & CSSPrintOptionsStyle) { + if (node->style.flexDirection == CSSFlexDirectionColumn) { + CSSLog(CSSLogLevelDebug, "flexDirection: 'column', "); + } else if (node->style.flexDirection == CSSFlexDirectionColumnReverse) { + CSSLog(CSSLogLevelDebug, "flexDirection: 'column-reverse', "); + } else if (node->style.flexDirection == CSSFlexDirectionRow) { + CSSLog(CSSLogLevelDebug, "flexDirection: 'row', "); + } else if (node->style.flexDirection == CSSFlexDirectionRowReverse) { + CSSLog(CSSLogLevelDebug, "flexDirection: 'row-reverse', "); + } + + if (node->style.justifyContent == CSSJustifyCenter) { + CSSLog(CSSLogLevelDebug, "justifyContent: 'center', "); + } else if (node->style.justifyContent == CSSJustifyFlexEnd) { + CSSLog(CSSLogLevelDebug, "justifyContent: 'flex-end', "); + } else if (node->style.justifyContent == CSSJustifySpaceAround) { + CSSLog(CSSLogLevelDebug, "justifyContent: 'space-around', "); + } else if (node->style.justifyContent == CSSJustifySpaceBetween) { + CSSLog(CSSLogLevelDebug, "justifyContent: 'space-between', "); + } + + if (node->style.alignItems == CSSAlignCenter) { + CSSLog(CSSLogLevelDebug, "alignItems: 'center', "); + } else if (node->style.alignItems == CSSAlignFlexEnd) { + CSSLog(CSSLogLevelDebug, "alignItems: 'flex-end', "); + } else if (node->style.alignItems == CSSAlignStretch) { + CSSLog(CSSLogLevelDebug, "alignItems: 'stretch', "); + } + + if (node->style.alignContent == CSSAlignCenter) { + CSSLog(CSSLogLevelDebug, "alignContent: 'center', "); + } else if (node->style.alignContent == CSSAlignFlexEnd) { + CSSLog(CSSLogLevelDebug, "alignContent: 'flex-end', "); + } else if (node->style.alignContent == CSSAlignStretch) { + CSSLog(CSSLogLevelDebug, "alignContent: 'stretch', "); + } + + if (node->style.alignSelf == CSSAlignFlexStart) { + CSSLog(CSSLogLevelDebug, "alignSelf: 'flex-start', "); + } else if (node->style.alignSelf == CSSAlignCenter) { + CSSLog(CSSLogLevelDebug, "alignSelf: 'center', "); + } else if (node->style.alignSelf == CSSAlignFlexEnd) { + CSSLog(CSSLogLevelDebug, "alignSelf: 'flex-end', "); + } else if (node->style.alignSelf == CSSAlignStretch) { + CSSLog(CSSLogLevelDebug, "alignSelf: 'stretch', "); + } + + printNumberIfNotUndefined("flexGrow", CSSNodeStyleGetFlexGrow(node)); + printNumberIfNotUndefined("flexShrink", CSSNodeStyleGetFlexShrink(node)); + printNumberIfNotUndefined("flexBasis", CSSNodeStyleGetFlexBasis(node)); + + if (node->style.overflow == CSSOverflowHidden) { + CSSLog(CSSLogLevelDebug, "overflow: 'hidden', "); + } else if (node->style.overflow == CSSOverflowVisible) { + CSSLog(CSSLogLevelDebug, "overflow: 'visible', "); + } else if (node->style.overflow == CSSOverflowScroll) { + CSSLog(CSSLogLevelDebug, "overflow: 'scroll', "); + } + + if (eqFour(node->style.margin)) { + printNumberIfNotZero("margin", computedEdgeValue(node->style.margin, CSSEdgeLeft, 0)); + } else { + printNumberIfNotZero("marginLeft", computedEdgeValue(node->style.margin, CSSEdgeLeft, 0)); + printNumberIfNotZero("marginRight", computedEdgeValue(node->style.margin, CSSEdgeRight, 0)); + printNumberIfNotZero("marginTop", computedEdgeValue(node->style.margin, CSSEdgeTop, 0)); + printNumberIfNotZero("marginBottom", computedEdgeValue(node->style.margin, CSSEdgeBottom, 0)); + printNumberIfNotZero("marginStart", computedEdgeValue(node->style.margin, CSSEdgeStart, 0)); + printNumberIfNotZero("marginEnd", computedEdgeValue(node->style.margin, CSSEdgeEnd, 0)); + } + + if (eqFour(node->style.padding)) { + printNumberIfNotZero("padding", computedEdgeValue(node->style.padding, CSSEdgeLeft, 0)); + } else { + printNumberIfNotZero("paddingLeft", computedEdgeValue(node->style.padding, CSSEdgeLeft, 0)); + printNumberIfNotZero("paddingRight", computedEdgeValue(node->style.padding, CSSEdgeRight, 0)); + printNumberIfNotZero("paddingTop", computedEdgeValue(node->style.padding, CSSEdgeTop, 0)); + printNumberIfNotZero("paddingBottom", + computedEdgeValue(node->style.padding, CSSEdgeBottom, 0)); + printNumberIfNotZero("paddingStart", computedEdgeValue(node->style.padding, CSSEdgeStart, 0)); + printNumberIfNotZero("paddingEnd", computedEdgeValue(node->style.padding, CSSEdgeEnd, 0)); + } + + if (eqFour(node->style.border)) { + printNumberIfNotZero("borderWidth", computedEdgeValue(node->style.border, CSSEdgeLeft, 0)); + } else { + printNumberIfNotZero("borderLeftWidth", + computedEdgeValue(node->style.border, CSSEdgeLeft, 0)); + printNumberIfNotZero("borderRightWidth", + computedEdgeValue(node->style.border, CSSEdgeRight, 0)); + printNumberIfNotZero("borderTopWidth", computedEdgeValue(node->style.border, CSSEdgeTop, 0)); + printNumberIfNotZero("borderBottomWidth", + computedEdgeValue(node->style.border, CSSEdgeBottom, 0)); + printNumberIfNotZero("borderStartWidth", + computedEdgeValue(node->style.border, CSSEdgeStart, 0)); + printNumberIfNotZero("borderEndWidth", computedEdgeValue(node->style.border, CSSEdgeEnd, 0)); + } + + printNumberIfNotUndefined("width", node->style.dimensions[CSSDimensionWidth]); + printNumberIfNotUndefined("height", node->style.dimensions[CSSDimensionHeight]); + printNumberIfNotUndefined("maxWidth", node->style.maxDimensions[CSSDimensionWidth]); + printNumberIfNotUndefined("maxHeight", node->style.maxDimensions[CSSDimensionHeight]); + printNumberIfNotUndefined("minWidth", node->style.minDimensions[CSSDimensionWidth]); + printNumberIfNotUndefined("minHeight", node->style.minDimensions[CSSDimensionHeight]); + + if (node->style.positionType == CSSPositionTypeAbsolute) { + CSSLog(CSSLogLevelDebug, "position: 'absolute', "); + } + + printNumberIfNotUndefined("left", + computedEdgeValue(node->style.position, CSSEdgeLeft, CSSUndefined)); + printNumberIfNotUndefined("right", + computedEdgeValue(node->style.position, CSSEdgeRight, CSSUndefined)); + printNumberIfNotUndefined("top", + computedEdgeValue(node->style.position, CSSEdgeTop, CSSUndefined)); + printNumberIfNotUndefined("bottom", + computedEdgeValue(node->style.position, CSSEdgeBottom, CSSUndefined)); + } + + const uint32_t childCount = CSSNodeListCount(node->children); + if (options & CSSPrintOptionsChildren && childCount > 0) { + CSSLog(CSSLogLevelDebug, "children: [\n"); + for (uint32_t i = 0; i < childCount; i++) { + _CSSNodePrint(CSSNodeGetChild(node, i), options, level + 1); + } + indent(level); + CSSLog(CSSLogLevelDebug, "]},\n"); + } else { + CSSLog(CSSLogLevelDebug, "},\n"); + } +} + +void CSSNodePrint(const CSSNodeRef node, const CSSPrintOptions options) { + _CSSNodePrint(node, options, 0); +} + +static const CSSEdge leading[4] = { + [CSSFlexDirectionColumn] = CSSEdgeTop, + [CSSFlexDirectionColumnReverse] = CSSEdgeBottom, + [CSSFlexDirectionRow] = CSSEdgeLeft, + [CSSFlexDirectionRowReverse] = CSSEdgeRight, +}; +static const CSSEdge trailing[4] = { + [CSSFlexDirectionColumn] = CSSEdgeBottom, + [CSSFlexDirectionColumnReverse] = CSSEdgeTop, + [CSSFlexDirectionRow] = CSSEdgeRight, + [CSSFlexDirectionRowReverse] = CSSEdgeLeft, +}; +static const CSSEdge pos[4] = { + [CSSFlexDirectionColumn] = CSSEdgeTop, + [CSSFlexDirectionColumnReverse] = CSSEdgeBottom, + [CSSFlexDirectionRow] = CSSEdgeLeft, + [CSSFlexDirectionRowReverse] = CSSEdgeRight, +}; +static const CSSDimension dim[4] = { + [CSSFlexDirectionColumn] = CSSDimensionHeight, + [CSSFlexDirectionColumnReverse] = CSSDimensionHeight, + [CSSFlexDirectionRow] = CSSDimensionWidth, + [CSSFlexDirectionRowReverse] = CSSDimensionWidth, +}; + +static inline bool isRowDirection(const CSSFlexDirection flexDirection) { + return flexDirection == CSSFlexDirectionRow || flexDirection == CSSFlexDirectionRowReverse; +} + +static inline bool isColumnDirection(const CSSFlexDirection flexDirection) { + return flexDirection == CSSFlexDirectionColumn || flexDirection == CSSFlexDirectionColumnReverse; +} + +static inline float getLeadingMargin(const CSSNodeRef node, const CSSFlexDirection axis) { + if (isRowDirection(axis) && !CSSValueIsUndefined(node->style.margin[CSSEdgeStart])) { + return node->style.margin[CSSEdgeStart]; + } + + return computedEdgeValue(node->style.margin, leading[axis], 0); +} + +static float getTrailingMargin(const CSSNodeRef node, const CSSFlexDirection axis) { + if (isRowDirection(axis) && !CSSValueIsUndefined(node->style.margin[CSSEdgeEnd])) { + return node->style.margin[CSSEdgeEnd]; + } + + return computedEdgeValue(node->style.margin, trailing[axis], 0); +} + +static float getLeadingPadding(const CSSNodeRef node, const CSSFlexDirection axis) { + if (isRowDirection(axis) && !CSSValueIsUndefined(node->style.padding[CSSEdgeStart]) && + node->style.padding[CSSEdgeStart] >= 0) { + return node->style.padding[CSSEdgeStart]; + } + + return fmaxf(computedEdgeValue(node->style.padding, leading[axis], 0), 0); +} + +static float getTrailingPadding(const CSSNodeRef node, const CSSFlexDirection axis) { + if (isRowDirection(axis) && !CSSValueIsUndefined(node->style.padding[CSSEdgeEnd]) && + node->style.padding[CSSEdgeEnd] >= 0) { + return node->style.padding[CSSEdgeEnd]; + } + + return fmaxf(computedEdgeValue(node->style.padding, trailing[axis], 0), 0); +} + +static float getLeadingBorder(const CSSNodeRef node, const CSSFlexDirection axis) { + if (isRowDirection(axis) && !CSSValueIsUndefined(node->style.border[CSSEdgeStart]) && + node->style.border[CSSEdgeStart] >= 0) { + return node->style.border[CSSEdgeStart]; + } + + return fmaxf(computedEdgeValue(node->style.border, leading[axis], 0), 0); +} + +static float getTrailingBorder(const CSSNodeRef node, const CSSFlexDirection axis) { + if (isRowDirection(axis) && !CSSValueIsUndefined(node->style.border[CSSEdgeEnd]) && + node->style.border[CSSEdgeEnd] >= 0) { + return node->style.border[CSSEdgeEnd]; + } + + return fmaxf(computedEdgeValue(node->style.border, trailing[axis], 0), 0); +} + +static inline float getLeadingPaddingAndBorder(const CSSNodeRef node, const CSSFlexDirection axis) { + return getLeadingPadding(node, axis) + getLeadingBorder(node, axis); +} + +static inline float getTrailingPaddingAndBorder(const CSSNodeRef node, + const CSSFlexDirection axis) { + return getTrailingPadding(node, axis) + getTrailingBorder(node, axis); +} + +static inline float getMarginAxis(const CSSNodeRef node, const CSSFlexDirection axis) { + return getLeadingMargin(node, axis) + getTrailingMargin(node, axis); +} + +static inline float getPaddingAndBorderAxis(const CSSNodeRef node, const CSSFlexDirection axis) { + return getLeadingPaddingAndBorder(node, axis) + getTrailingPaddingAndBorder(node, axis); +} + +static inline CSSAlign getAlignItem(const CSSNodeRef node, const CSSNodeRef child) { + return child->style.alignSelf == CSSAlignAuto ? node->style.alignItems : child->style.alignSelf; +} + +static inline CSSDirection resolveDirection(const CSSNodeRef node, + const CSSDirection parentDirection) { + if (node->style.direction == CSSDirectionInherit) { + return parentDirection > CSSDirectionInherit ? parentDirection : CSSDirectionLTR; + } else { + return node->style.direction; + } +} + +static inline CSSFlexDirection resolveAxis(const CSSFlexDirection flexDirection, + const CSSDirection direction) { + if (direction == CSSDirectionRTL) { + if (flexDirection == CSSFlexDirectionRow) { + return CSSFlexDirectionRowReverse; + } else if (flexDirection == CSSFlexDirectionRowReverse) { + return CSSFlexDirectionRow; + } + } + + return flexDirection; +} + +static CSSFlexDirection getCrossFlexDirection(const CSSFlexDirection flexDirection, + const CSSDirection direction) { + return isColumnDirection(flexDirection) ? resolveAxis(CSSFlexDirectionRow, direction) + : CSSFlexDirectionColumn; +} + +static inline bool isFlex(const CSSNodeRef node) { + return (node->style.positionType == CSSPositionTypeRelative && + (node->style.flexGrow != 0 || node->style.flexShrink != 0 || node->style.flex != 0)); +} + +static inline float getDimWithMargin(const CSSNodeRef node, const CSSFlexDirection axis) { + return node->layout.measuredDimensions[dim[axis]] + getLeadingMargin(node, axis) + + getTrailingMargin(node, axis); +} + +static inline bool isStyleDimDefined(const CSSNodeRef node, const CSSFlexDirection axis) { + const float value = node->style.dimensions[dim[axis]]; + return !CSSValueIsUndefined(value) && value >= 0.0; +} + +static inline bool isLayoutDimDefined(const CSSNodeRef node, const CSSFlexDirection axis) { + const float value = node->layout.measuredDimensions[dim[axis]]; + return !CSSValueIsUndefined(value) && value >= 0.0; +} + +static inline bool isLeadingPosDefined(const CSSNodeRef node, const CSSFlexDirection axis) { + return (isRowDirection(axis) && + !CSSValueIsUndefined( + computedEdgeValue(node->style.position, CSSEdgeStart, CSSUndefined))) || + !CSSValueIsUndefined(computedEdgeValue(node->style.position, leading[axis], CSSUndefined)); +} + +static inline bool isTrailingPosDefined(const CSSNodeRef node, const CSSFlexDirection axis) { + return (isRowDirection(axis) && + !CSSValueIsUndefined( + computedEdgeValue(node->style.position, CSSEdgeEnd, CSSUndefined))) || + !CSSValueIsUndefined( + computedEdgeValue(node->style.position, trailing[axis], CSSUndefined)); +} + +static float getLeadingPosition(const CSSNodeRef node, const CSSFlexDirection axis) { + if (isRowDirection(axis)) { + const float leadingPosition = + computedEdgeValue(node->style.position, CSSEdgeStart, CSSUndefined); + if (!CSSValueIsUndefined(leadingPosition)) { + return leadingPosition; + } + } + + const float leadingPosition = + computedEdgeValue(node->style.position, leading[axis], CSSUndefined); + + return CSSValueIsUndefined(leadingPosition) ? 0 : leadingPosition; +} + +static float getTrailingPosition(const CSSNodeRef node, const CSSFlexDirection axis) { + if (isRowDirection(axis)) { + const float trailingPosition = + computedEdgeValue(node->style.position, CSSEdgeEnd, CSSUndefined); + if (!CSSValueIsUndefined(trailingPosition)) { + return trailingPosition; + } + } + + const float trailingPosition = + computedEdgeValue(node->style.position, trailing[axis], CSSUndefined); + + return CSSValueIsUndefined(trailingPosition) ? 0 : trailingPosition; +} + +static float boundAxisWithinMinAndMax(const CSSNodeRef node, + const CSSFlexDirection axis, + const float value) { + float min = CSSUndefined; + float max = CSSUndefined; + + if (isColumnDirection(axis)) { + min = node->style.minDimensions[CSSDimensionHeight]; + max = node->style.maxDimensions[CSSDimensionHeight]; + } else if (isRowDirection(axis)) { + min = node->style.minDimensions[CSSDimensionWidth]; + max = node->style.maxDimensions[CSSDimensionWidth]; + } + + float boundValue = value; + + if (!CSSValueIsUndefined(max) && max >= 0.0 && boundValue > max) { + boundValue = max; + } + + if (!CSSValueIsUndefined(min) && min >= 0.0 && boundValue < min) { + boundValue = min; + } + + return boundValue; +} + +// Like boundAxisWithinMinAndMax but also ensures that the value doesn't go +// below the +// padding and border amount. +static inline float boundAxis(const CSSNodeRef node, + const CSSFlexDirection axis, + const float value) { + return fmaxf(boundAxisWithinMinAndMax(node, axis, value), getPaddingAndBorderAxis(node, axis)); +} + +static void setTrailingPosition(const CSSNodeRef node, + const CSSNodeRef child, + const CSSFlexDirection axis) { + const float size = child->layout.measuredDimensions[dim[axis]]; + child->layout.position[trailing[axis]] = + node->layout.measuredDimensions[dim[axis]] - size - child->layout.position[pos[axis]]; +} + +// If both left and right are defined, then use left. Otherwise return +// +left or -right depending on which is defined. +static float getRelativePosition(const CSSNodeRef node, const CSSFlexDirection axis) { + return isLeadingPosDefined(node, axis) ? getLeadingPosition(node, axis) + : -getTrailingPosition(node, axis); +} + +static void constrainMaxSizeForMode(const float maxSize, CSSMeasureMode *mode, float *size) { + switch (*mode) { + case CSSMeasureModeExactly: + case CSSMeasureModeAtMost: + *size = (CSSValueIsUndefined(maxSize) || *size < maxSize) ? *size : maxSize; + break; + case CSSMeasureModeUndefined: + if (!CSSValueIsUndefined(maxSize)) { + *mode = CSSMeasureModeAtMost; + *size = maxSize; + } + break; + case CSSMeasureModeCount: + break; + } +} + +static void setPosition(const CSSNodeRef node, const CSSDirection direction) { + const CSSFlexDirection mainAxis = resolveAxis(node->style.flexDirection, direction); + const CSSFlexDirection crossAxis = getCrossFlexDirection(mainAxis, direction); + const float relativePositionMain = getRelativePosition(node, mainAxis); + const float relativePositionCross = getRelativePosition(node, crossAxis); + + node->layout.position[leading[mainAxis]] = + getLeadingMargin(node, mainAxis) + relativePositionMain; + node->layout.position[trailing[mainAxis]] = + getTrailingMargin(node, mainAxis) + relativePositionMain; + node->layout.position[leading[crossAxis]] = + getLeadingMargin(node, crossAxis) + relativePositionCross; + node->layout.position[trailing[crossAxis]] = + getTrailingMargin(node, crossAxis) + relativePositionCross; +} + +static void computeChildFlexBasis(const CSSNodeRef node, + const CSSNodeRef child, + const float width, + const CSSMeasureMode widthMode, + const float height, + const CSSMeasureMode heightMode, + const CSSDirection direction) { + const CSSFlexDirection mainAxis = resolveAxis(node->style.flexDirection, direction); + const bool isMainAxisRow = isRowDirection(mainAxis); + + float childWidth; + float childHeight; + CSSMeasureMode childWidthMeasureMode; + CSSMeasureMode childHeightMeasureMode; + + const bool isRowStyleDimDefined = isStyleDimDefined(child, CSSFlexDirectionRow); + const bool isColumnStyleDimDefined = isStyleDimDefined(child, CSSFlexDirectionColumn); + + if (!CSSValueIsUndefined(CSSNodeStyleGetFlexBasis(child)) && + !CSSValueIsUndefined(isMainAxisRow ? width : height)) { + if (CSSValueIsUndefined(child->layout.computedFlexBasis)) { + child->layout.computedFlexBasis = + fmaxf(CSSNodeStyleGetFlexBasis(child), getPaddingAndBorderAxis(child, mainAxis)); + } + } else if (isMainAxisRow && isRowStyleDimDefined) { + // The width is definite, so use that as the flex basis. + child->layout.computedFlexBasis = fmaxf(child->style.dimensions[CSSDimensionWidth], + getPaddingAndBorderAxis(child, CSSFlexDirectionRow)); + } else if (!isMainAxisRow && isColumnStyleDimDefined) { + // The height is definite, so use that as the flex basis. + child->layout.computedFlexBasis = fmaxf(child->style.dimensions[CSSDimensionHeight], + getPaddingAndBorderAxis(child, CSSFlexDirectionColumn)); + } else { + // Compute the flex basis and hypothetical main size (i.e. the clamped + // flex basis). + childWidth = CSSUndefined; + childHeight = CSSUndefined; + childWidthMeasureMode = CSSMeasureModeUndefined; + childHeightMeasureMode = CSSMeasureModeUndefined; + + if (isRowStyleDimDefined) { + childWidth = + child->style.dimensions[CSSDimensionWidth] + getMarginAxis(child, CSSFlexDirectionRow); + childWidthMeasureMode = CSSMeasureModeExactly; + } + if (isColumnStyleDimDefined) { + childHeight = child->style.dimensions[CSSDimensionHeight] + + getMarginAxis(child, CSSFlexDirectionColumn); + childHeightMeasureMode = CSSMeasureModeExactly; + } + + // The W3C spec doesn't say anything about the 'overflow' property, + // but all major browsers appear to implement the following logic. + if ((!isMainAxisRow && node->style.overflow == CSSOverflowScroll) || + node->style.overflow != CSSOverflowScroll) { + if (CSSValueIsUndefined(childWidth) && !CSSValueIsUndefined(width)) { + childWidth = width; + childWidthMeasureMode = CSSMeasureModeAtMost; + } + } + + if ((isMainAxisRow && node->style.overflow == CSSOverflowScroll) || + node->style.overflow != CSSOverflowScroll) { + if (CSSValueIsUndefined(childHeight) && !CSSValueIsUndefined(height)) { + childHeight = height; + childHeightMeasureMode = CSSMeasureModeAtMost; + } + } + + // 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 + if (!isMainAxisRow && !CSSValueIsUndefined(width) && !isRowStyleDimDefined && + widthMode == CSSMeasureModeExactly && getAlignItem(node, child) == CSSAlignStretch) { + childWidth = width; + childWidthMeasureMode = CSSMeasureModeExactly; + } + if (isMainAxisRow && !CSSValueIsUndefined(height) && !isColumnStyleDimDefined && + heightMode == CSSMeasureModeExactly && getAlignItem(node, child) == CSSAlignStretch) { + childHeight = height; + childHeightMeasureMode = CSSMeasureModeExactly; + } + + constrainMaxSizeForMode(child->style.maxDimensions[CSSDimensionWidth], + &childWidthMeasureMode, + &childWidth); + constrainMaxSizeForMode(child->style.maxDimensions[CSSDimensionHeight], + &childHeightMeasureMode, + &childHeight); + + // Measure the child + layoutNodeInternal(child, + childWidth, + childHeight, + direction, + childWidthMeasureMode, + childHeightMeasureMode, + false, + "measure"); + + child->layout.computedFlexBasis = + fmaxf(isMainAxisRow ? child->layout.measuredDimensions[CSSDimensionWidth] + : child->layout.measuredDimensions[CSSDimensionHeight], + getPaddingAndBorderAxis(child, mainAxis)); + } +} + +static void absoluteLayoutChild(const CSSNodeRef node, + const CSSNodeRef child, + const float width, + const CSSMeasureMode widthMode, + const CSSDirection direction) { + const CSSFlexDirection mainAxis = resolveAxis(node->style.flexDirection, direction); + const CSSFlexDirection crossAxis = getCrossFlexDirection(mainAxis, direction); + const bool isMainAxisRow = isRowDirection(mainAxis); + + float childWidth = CSSUndefined; + float childHeight = CSSUndefined; + CSSMeasureMode childWidthMeasureMode = CSSMeasureModeUndefined; + CSSMeasureMode childHeightMeasureMode = CSSMeasureModeUndefined; + + if (isStyleDimDefined(child, CSSFlexDirectionRow)) { + childWidth = + child->style.dimensions[CSSDimensionWidth] + getMarginAxis(child, CSSFlexDirectionRow); + } else { + // If the child doesn't have a specified width, compute the width based + // on the left/right + // offsets if they're defined. + if (isLeadingPosDefined(child, CSSFlexDirectionRow) && + isTrailingPosDefined(child, CSSFlexDirectionRow)) { + childWidth = node->layout.measuredDimensions[CSSDimensionWidth] - + (getLeadingBorder(node, CSSFlexDirectionRow) + + getTrailingBorder(node, CSSFlexDirectionRow)) - + (getLeadingPosition(child, CSSFlexDirectionRow) + + getTrailingPosition(child, CSSFlexDirectionRow)); + childWidth = boundAxis(child, CSSFlexDirectionRow, childWidth); + } + } + + if (isStyleDimDefined(child, CSSFlexDirectionColumn)) { + childHeight = + child->style.dimensions[CSSDimensionHeight] + getMarginAxis(child, CSSFlexDirectionColumn); + } else { + // If the child doesn't have a specified height, compute the height + // based on the top/bottom + // offsets if they're defined. + if (isLeadingPosDefined(child, CSSFlexDirectionColumn) && + isTrailingPosDefined(child, CSSFlexDirectionColumn)) { + childHeight = node->layout.measuredDimensions[CSSDimensionHeight] - + (getLeadingBorder(node, CSSFlexDirectionColumn) + + getTrailingBorder(node, CSSFlexDirectionColumn)) - + (getLeadingPosition(child, CSSFlexDirectionColumn) + + getTrailingPosition(child, CSSFlexDirectionColumn)); + childHeight = boundAxis(child, CSSFlexDirectionColumn, childHeight); + } + } + + // If we're still missing one or the other dimension, measure the content. + if (CSSValueIsUndefined(childWidth) || CSSValueIsUndefined(childHeight)) { + childWidthMeasureMode = + CSSValueIsUndefined(childWidth) ? CSSMeasureModeUndefined : CSSMeasureModeExactly; + childHeightMeasureMode = + CSSValueIsUndefined(childHeight) ? CSSMeasureModeUndefined : CSSMeasureModeExactly; + + // According to the spec, if the main size is not definite and the + // child's inline axis is parallel to the main axis (i.e. it's + // horizontal), the child should be sized using "UNDEFINED" in + // the main size. Otherwise use "AT_MOST" in the cross axis. + if (!isMainAxisRow && CSSValueIsUndefined(childWidth) && widthMode != CSSMeasureModeUndefined) { + childWidth = width; + childWidthMeasureMode = CSSMeasureModeAtMost; + } + + layoutNodeInternal(child, + childWidth, + childHeight, + direction, + childWidthMeasureMode, + childHeightMeasureMode, + false, + "abs-measure"); + childWidth = child->layout.measuredDimensions[CSSDimensionWidth] + + getMarginAxis(child, CSSFlexDirectionRow); + childHeight = child->layout.measuredDimensions[CSSDimensionHeight] + + getMarginAxis(child, CSSFlexDirectionColumn); + } + + layoutNodeInternal(child, + childWidth, + childHeight, + direction, + CSSMeasureModeExactly, + CSSMeasureModeExactly, + true, + "abs-layout"); + + if (isTrailingPosDefined(child, mainAxis) && !isLeadingPosDefined(child, mainAxis)) { + child->layout.position[leading[mainAxis]] = node->layout.measuredDimensions[dim[mainAxis]] - + child->layout.measuredDimensions[dim[mainAxis]] - + getTrailingBorder(node, mainAxis) - + getTrailingPosition(child, mainAxis); + } + + if (isTrailingPosDefined(child, crossAxis) && !isLeadingPosDefined(child, crossAxis)) { + child->layout.position[leading[crossAxis]] = node->layout.measuredDimensions[dim[crossAxis]] - + child->layout.measuredDimensions[dim[crossAxis]] - + getTrailingBorder(node, crossAxis) - + getTrailingPosition(child, crossAxis); + } +} + +// +// This is the main routine that implements a subset of the flexbox layout +// algorithm +// described in the W3C CSS documentation: https://www.w3.org/TR/css3-flexbox/. +// +// Limitations of this algorithm, compared to the full standard: +// * Display property is always assumed to be 'flex' except for Text nodes, +// which +// are assumed to be 'inline-flex'. +// * The 'zIndex' property (or any form of z ordering) is not supported. Nodes +// are +// stacked in document order. +// * The 'order' property is not supported. The order of flex items is always +// defined +// by document order. +// * The 'visibility' property is always assumed to be 'visible'. Values of +// 'collapse' +// and 'hidden' are not supported. +// * The 'wrap' property supports only 'nowrap' (which is the default) or +// 'wrap'. The +// rarely-used 'wrap-reverse' is not supported. +// * Rather than allowing arbitrary combinations of flexGrow, flexShrink and +// flexBasis, this algorithm supports only the three most common +// combinations: +// flex: 0 is equiavlent to flex: 0 0 auto +// flex: n (where n is a positive value) is equivalent to flex: n 1 auto +// If POSITIVE_FLEX_IS_AUTO is 0, then it is equivalent to flex: n 0 0 +// This is faster because the content doesn't need to be measured, but +// it's +// less flexible because the basis is always 0 and can't be overriden +// with +// the width/height attributes. +// flex: -1 (or any negative value) is equivalent to flex: 0 1 auto +// * Margins cannot be specified as 'auto'. They must be specified in terms of +// pixel +// values, and the default value is 0. +// * The 'baseline' value is not supported for alignItems and alignSelf +// properties. +// * Values of width, maxWidth, minWidth, height, maxHeight and minHeight must +// be +// specified as pixel values, not as percentages. +// * There is no support for calculation of dimensions based on intrinsic +// aspect ratios +// (e.g. images). +// * There is no support for forced breaks. +// * It does not support vertical inline directions (top-to-bottom or +// bottom-to-top text). +// +// Deviations from standard: +// * Section 4.5 of the spec indicates that all flex items have a default +// minimum +// main size. For text blocks, for example, this is the width of the widest +// word. +// Calculating the minimum width is expensive, so we forego it and assume a +// default +// minimum main size of 0. +// * Min/Max sizes in the main axis are not honored when resolving flexible +// lengths. +// * The spec indicates that the default value for 'flexDirection' is 'row', +// but +// the algorithm below assumes a default of 'column'. +// +// Input parameters: +// - node: current node to be sized and layed out +// - availableWidth & availableHeight: available size to be used for sizing +// the node +// or CSSUndefined if the size is not available; interpretation depends on +// layout +// flags +// - parentDirection: the inline (text) direction within the parent +// (left-to-right or +// right-to-left) +// - widthMeasureMode: indicates the sizing rules for the width (see below +// for explanation) +// - heightMeasureMode: indicates the sizing rules for the height (see below +// for explanation) +// - performLayout: specifies whether the caller is interested in just the +// dimensions +// of the node or it requires the entire node and its subtree to be layed +// out +// (with final positions) +// +// Details: +// This routine is called recursively to lay out subtrees of flexbox +// elements. It uses the +// information in node.style, which is treated as a read-only input. It is +// responsible for +// setting the layout.direction and layout.measuredDimensions fields for the +// input node as well +// as the layout.position and layout.lineIndex fields for its child nodes. +// The +// layout.measuredDimensions field includes any border or padding for the +// node but does +// not include margins. +// +// The spec describes four different layout modes: "fill available", "max +// content", "min +// content", +// and "fit content". Of these, we don't use "min content" because we don't +// support default +// minimum main sizes (see above for details). Each of our measure modes maps +// to a layout mode +// from the spec (https://www.w3.org/TR/css3-sizing/#terms): +// - CSSMeasureModeUndefined: max content +// - CSSMeasureModeExactly: fill available +// - CSSMeasureModeAtMost: fit content +// +// When calling layoutNodeImpl and layoutNodeInternal, if the caller passes +// an available size of +// undefined then it must also pass a measure mode of CSSMeasureModeUndefined +// in that dimension. +// +static void layoutNodeImpl(const CSSNodeRef node, + const float availableWidth, + const float availableHeight, + const CSSDirection parentDirection, + const CSSMeasureMode widthMeasureMode, + const CSSMeasureMode heightMeasureMode, + const bool performLayout) { + CSS_ASSERT(CSSValueIsUndefined(availableWidth) ? widthMeasureMode == CSSMeasureModeUndefined + : true, + "availableWidth is indefinite so widthMeasureMode must be " + "CSSMeasureModeUndefined"); + CSS_ASSERT(CSSValueIsUndefined(availableHeight) ? heightMeasureMode == CSSMeasureModeUndefined + : true, + "availableHeight is indefinite so heightMeasureMode must be " + "CSSMeasureModeUndefined"); + + const float paddingAndBorderAxisRow = getPaddingAndBorderAxis(node, CSSFlexDirectionRow); + const float paddingAndBorderAxisColumn = getPaddingAndBorderAxis(node, CSSFlexDirectionColumn); + const float marginAxisRow = getMarginAxis(node, CSSFlexDirectionRow); + const float marginAxisColumn = getMarginAxis(node, CSSFlexDirectionColumn); + + // Set the resolved resolution in the node's layout. + const CSSDirection direction = resolveDirection(node, parentDirection); + node->layout.direction = direction; + + // For content (text) nodes, determine the dimensions based on the text + // contents. + if (node->measure) { + const float innerWidth = availableWidth - marginAxisRow - paddingAndBorderAxisRow; + const float innerHeight = availableHeight - marginAxisColumn - paddingAndBorderAxisColumn; + + if (widthMeasureMode == CSSMeasureModeExactly && heightMeasureMode == CSSMeasureModeExactly) { + // Don't bother sizing the text if both dimensions are already defined. + node->layout.measuredDimensions[CSSDimensionWidth] = + boundAxis(node, CSSFlexDirectionRow, availableWidth - marginAxisRow); + node->layout.measuredDimensions[CSSDimensionHeight] = + boundAxis(node, CSSFlexDirectionColumn, availableHeight - marginAxisColumn); + } else if (innerWidth <= 0 || innerHeight <= 0) { + // Don't bother sizing the text if there's no horizontal or vertical + // space. + node->layout.measuredDimensions[CSSDimensionWidth] = boundAxis(node, CSSFlexDirectionRow, 0); + node->layout.measuredDimensions[CSSDimensionHeight] = + boundAxis(node, CSSFlexDirectionColumn, 0); + } else { + // Measure the text under the current constraints. + const CSSSize measuredSize = + node->measure(node, innerWidth, widthMeasureMode, innerHeight, heightMeasureMode); + + node->layout.measuredDimensions[CSSDimensionWidth] = + boundAxis(node, + CSSFlexDirectionRow, + (widthMeasureMode == CSSMeasureModeUndefined || + widthMeasureMode == CSSMeasureModeAtMost) + ? measuredSize.width + paddingAndBorderAxisRow + : availableWidth - marginAxisRow); + node->layout.measuredDimensions[CSSDimensionHeight] = + boundAxis(node, + CSSFlexDirectionColumn, + (heightMeasureMode == CSSMeasureModeUndefined || + heightMeasureMode == CSSMeasureModeAtMost) + ? measuredSize.height + paddingAndBorderAxisColumn + : availableHeight - marginAxisColumn); + } + + return; + } + + // For nodes with no children, use the available values if they were provided, + // or + // the minimum size as indicated by the padding and border sizes. + const uint32_t childCount = CSSNodeListCount(node->children); + if (childCount == 0) { + node->layout.measuredDimensions[CSSDimensionWidth] = + boundAxis(node, + CSSFlexDirectionRow, + (widthMeasureMode == CSSMeasureModeUndefined || + widthMeasureMode == CSSMeasureModeAtMost) + ? paddingAndBorderAxisRow + : availableWidth - marginAxisRow); + node->layout.measuredDimensions[CSSDimensionHeight] = + boundAxis(node, + CSSFlexDirectionColumn, + (heightMeasureMode == CSSMeasureModeUndefined || + heightMeasureMode == CSSMeasureModeAtMost) + ? paddingAndBorderAxisColumn + : availableHeight - marginAxisColumn); + return; + } + + // If we're not being asked to perform a full layout, we can handle a number + // of common + // cases here without incurring the cost of the remaining function. + if (!performLayout) { + // If we're being asked to size the content with an at most constraint but + // there is no available + // width, + // the measurement will always be zero. + if (widthMeasureMode == CSSMeasureModeAtMost && availableWidth <= 0 && + heightMeasureMode == CSSMeasureModeAtMost && availableHeight <= 0) { + node->layout.measuredDimensions[CSSDimensionWidth] = boundAxis(node, CSSFlexDirectionRow, 0); + node->layout.measuredDimensions[CSSDimensionHeight] = + boundAxis(node, CSSFlexDirectionColumn, 0); + return; + } + + if (widthMeasureMode == CSSMeasureModeAtMost && availableWidth <= 0) { + node->layout.measuredDimensions[CSSDimensionWidth] = boundAxis(node, CSSFlexDirectionRow, 0); + node->layout.measuredDimensions[CSSDimensionHeight] = + boundAxis(node, + CSSFlexDirectionColumn, + CSSValueIsUndefined(availableHeight) ? 0 + : (availableHeight - marginAxisColumn)); + return; + } + + if (heightMeasureMode == CSSMeasureModeAtMost && availableHeight <= 0) { + node->layout.measuredDimensions[CSSDimensionWidth] = + boundAxis(node, + CSSFlexDirectionRow, + CSSValueIsUndefined(availableWidth) ? 0 : (availableWidth - marginAxisRow)); + node->layout.measuredDimensions[CSSDimensionHeight] = + boundAxis(node, CSSFlexDirectionColumn, 0); + return; + } + + // If we're being asked to use an exact width/height, there's no need to + // measure the children. + if (widthMeasureMode == CSSMeasureModeExactly && heightMeasureMode == CSSMeasureModeExactly) { + node->layout.measuredDimensions[CSSDimensionWidth] = + boundAxis(node, CSSFlexDirectionRow, availableWidth - marginAxisRow); + node->layout.measuredDimensions[CSSDimensionHeight] = + boundAxis(node, CSSFlexDirectionColumn, availableHeight - marginAxisColumn); + return; + } + } + + // STEP 1: CALCULATE VALUES FOR REMAINDER OF ALGORITHM + const CSSFlexDirection mainAxis = resolveAxis(node->style.flexDirection, direction); + const CSSFlexDirection crossAxis = getCrossFlexDirection(mainAxis, direction); + const bool isMainAxisRow = isRowDirection(mainAxis); + const CSSJustify justifyContent = node->style.justifyContent; + const bool isNodeFlexWrap = node->style.flexWrap == CSSWrapTypeWrap; + + CSSNodeRef firstAbsoluteChild = NULL; + CSSNodeRef currentAbsoluteChild = NULL; + + const float leadingPaddingAndBorderMain = getLeadingPaddingAndBorder(node, mainAxis); + const float trailingPaddingAndBorderMain = getTrailingPaddingAndBorder(node, mainAxis); + const float leadingPaddingAndBorderCross = getLeadingPaddingAndBorder(node, crossAxis); + const float paddingAndBorderAxisMain = getPaddingAndBorderAxis(node, mainAxis); + const float paddingAndBorderAxisCross = getPaddingAndBorderAxis(node, crossAxis); + + const CSSMeasureMode measureModeMainDim = isMainAxisRow ? widthMeasureMode : heightMeasureMode; + const CSSMeasureMode measureModeCrossDim = isMainAxisRow ? heightMeasureMode : widthMeasureMode; + + // STEP 2: DETERMINE AVAILABLE SIZE IN MAIN AND CROSS DIRECTIONS + const float availableInnerWidth = availableWidth - marginAxisRow - paddingAndBorderAxisRow; + const float availableInnerHeight = + availableHeight - marginAxisColumn - paddingAndBorderAxisColumn; + const float availableInnerMainDim = isMainAxisRow ? availableInnerWidth : availableInnerHeight; + const float availableInnerCrossDim = isMainAxisRow ? availableInnerHeight : availableInnerWidth; + + // If there is only one child with flexGrow + flexShrink it means we can set the + // computedFlexBasis to 0 instead of measuring and shrinking / flexing the child to exactly + // match the remaining space + CSSNodeRef singleFlexChild = NULL; + if ((isMainAxisRow && widthMeasureMode == CSSMeasureModeExactly) || + (!isMainAxisRow && heightMeasureMode == CSSMeasureModeExactly)) { + for (uint32_t i = 0; i < childCount; i++) { + const CSSNodeRef child = CSSNodeGetChild(node, i); + if (singleFlexChild) { + if (isFlex(child)) { + // There is already a flexible child, abort. + singleFlexChild = NULL; + break; + } + } else if (CSSNodeStyleGetFlexGrow(child) > 0 && CSSNodeStyleGetFlexShrink(child) > 0) { + singleFlexChild = child; + } + } + } + + // STEP 3: DETERMINE FLEX BASIS FOR EACH ITEM + for (uint32_t i = 0; i < childCount; i++) { + const CSSNodeRef child = CSSNodeListGet(node->children, i); + + if (performLayout) { + // Set the initial position (relative to the parent). + const CSSDirection childDirection = resolveDirection(child, direction); + setPosition(child, childDirection); + } + + // Absolute-positioned children don't participate in flex layout. Add them + // to a list that we can process later. + if (child->style.positionType == CSSPositionTypeAbsolute) { + // Store a private linked list of absolutely positioned children + // so that we can efficiently traverse them later. + if (firstAbsoluteChild == NULL) { + firstAbsoluteChild = child; + } + if (currentAbsoluteChild != NULL) { + currentAbsoluteChild->nextChild = child; + } + currentAbsoluteChild = child; + child->nextChild = NULL; + } else { + if (child == singleFlexChild) { + child->layout.computedFlexBasis = 0; + } else { + computeChildFlexBasis(node, + child, + availableInnerWidth, + widthMeasureMode, + availableInnerHeight, + heightMeasureMode, + direction); + } + } + } + + // STEP 4: COLLECT FLEX ITEMS INTO FLEX LINES + + // Indexes of children that represent the first and last items in the line. + uint32_t startOfLineIndex = 0; + uint32_t endOfLineIndex = 0; + + // Number of lines. + uint32_t lineCount = 0; + + // Accumulated cross dimensions of all lines so far. + float totalLineCrossDim = 0; + + // Max main dimension of all the lines. + float maxLineMainDim = 0; + + for (; endOfLineIndex < childCount; lineCount++, startOfLineIndex = endOfLineIndex) { + // Number of items on the currently line. May be different than the + // difference + // between start and end indicates because we skip over absolute-positioned + // items. + uint32_t itemsOnLine = 0; + + // sizeConsumedOnCurrentLine is accumulation of the dimensions and margin + // of all the children on the current line. This will be used in order to + // either set the dimensions of the node if none already exist or to compute + // the remaining space left for the flexible children. + float sizeConsumedOnCurrentLine = 0; + + float totalFlexGrowFactors = 0; + float totalFlexShrinkScaledFactors = 0; + + // Maintain a linked list of the child nodes that can shrink and/or grow. + CSSNodeRef firstRelativeChild = NULL; + CSSNodeRef currentRelativeChild = NULL; + + // Add items to the current line until it's full or we run out of items. + for (uint32_t i = startOfLineIndex; i < childCount; i++, endOfLineIndex++) { + const CSSNodeRef child = CSSNodeListGet(node->children, i); + child->lineIndex = lineCount; + + if (child->style.positionType != CSSPositionTypeAbsolute) { + const float outerFlexBasis = + child->layout.computedFlexBasis + getMarginAxis(child, mainAxis); + + // If this is a multi-line flow and this item pushes us over the + // available size, we've + // hit the end of the current line. Break out of the loop and lay out + // the current line. + if (sizeConsumedOnCurrentLine + outerFlexBasis > availableInnerMainDim && isNodeFlexWrap && + itemsOnLine > 0) { + break; + } + + sizeConsumedOnCurrentLine += outerFlexBasis; + itemsOnLine++; + + if (isFlex(child)) { + totalFlexGrowFactors += CSSNodeStyleGetFlexGrow(child); + + // Unlike the grow factor, the shrink factor is scaled relative to the + // child + // dimension. + totalFlexShrinkScaledFactors += + -CSSNodeStyleGetFlexShrink(child) * child->layout.computedFlexBasis; + } + + // Store a private linked list of children that need to be layed out. + if (firstRelativeChild == NULL) { + firstRelativeChild = child; + } + if (currentRelativeChild != NULL) { + currentRelativeChild->nextChild = child; + } + currentRelativeChild = child; + child->nextChild = NULL; + } + } + + // If we don't need to measure the cross axis, we can skip the entire flex + // step. + const bool canSkipFlex = !performLayout && measureModeCrossDim == CSSMeasureModeExactly; + + // In order to position the elements in the main axis, we have two + // controls. The space between the beginning and the first element + // and the space between each two elements. + float leadingMainDim = 0; + float betweenMainDim = 0; + + // STEP 5: RESOLVING FLEXIBLE LENGTHS ON MAIN AXIS + // Calculate the remaining available space that needs to be allocated. + // If the main dimension size isn't known, it is computed based on + // the line length, so there's no more space left to distribute. + float remainingFreeSpace = 0; + if (!CSSValueIsUndefined(availableInnerMainDim)) { + remainingFreeSpace = availableInnerMainDim - sizeConsumedOnCurrentLine; + } else if (sizeConsumedOnCurrentLine < 0) { + // availableInnerMainDim is indefinite which means the node is being sized + // based on its + // content. + // sizeConsumedOnCurrentLine is negative which means the node will + // allocate 0 pixels for + // its content. Consequently, remainingFreeSpace is 0 - + // sizeConsumedOnCurrentLine. + remainingFreeSpace = -sizeConsumedOnCurrentLine; + } + + const float originalRemainingFreeSpace = remainingFreeSpace; + float deltaFreeSpace = 0; + + if (!canSkipFlex) { + float childFlexBasis; + float flexShrinkScaledFactor; + float flexGrowFactor; + float baseMainSize; + float boundMainSize; + + // Do two passes over the flex items to figure out how to distribute the + // remaining space. + // The first pass finds the items whose min/max constraints trigger, + // freezes them at those + // sizes, and excludes those sizes from the remaining space. The second + // pass sets the size + // of each flexible item. It distributes the remaining space amongst the + // items whose min/max + // constraints didn't trigger in pass 1. For the other items, it sets + // their sizes by forcing + // their min/max constraints to trigger again. + // + // This two pass approach for resolving min/max constraints deviates from + // the spec. The + // spec (https://www.w3.org/TR/css-flexbox-1/#resolve-flexible-lengths) + // describes a process + // that needs to be repeated a variable number of times. The algorithm + // implemented here + // won't handle all cases but it was simpler to implement and it mitigates + // performance + // concerns because we know exactly how many passes it'll do. + + // First pass: detect the flex items whose min/max constraints trigger + float deltaFlexShrinkScaledFactors = 0; + float deltaFlexGrowFactors = 0; + currentRelativeChild = firstRelativeChild; + while (currentRelativeChild != NULL) { + childFlexBasis = currentRelativeChild->layout.computedFlexBasis; + + if (remainingFreeSpace < 0) { + flexShrinkScaledFactor = + -CSSNodeStyleGetFlexShrink(currentRelativeChild) * childFlexBasis; + + // Is this child able to shrink? + if (flexShrinkScaledFactor != 0) { + baseMainSize = + childFlexBasis + + remainingFreeSpace / totalFlexShrinkScaledFactors * flexShrinkScaledFactor; + boundMainSize = boundAxis(currentRelativeChild, mainAxis, baseMainSize); + if (baseMainSize != boundMainSize) { + // By excluding this item's size and flex factor from remaining, + // this item's + // min/max constraints should also trigger in the second pass + // resulting in the + // item's size calculation being identical in the first and second + // passes. + deltaFreeSpace -= boundMainSize - childFlexBasis; + deltaFlexShrinkScaledFactors -= flexShrinkScaledFactor; + } + } + } else if (remainingFreeSpace > 0) { + flexGrowFactor = CSSNodeStyleGetFlexGrow(currentRelativeChild); + + // Is this child able to grow? + if (flexGrowFactor != 0) { + baseMainSize = + childFlexBasis + remainingFreeSpace / totalFlexGrowFactors * flexGrowFactor; + boundMainSize = boundAxis(currentRelativeChild, mainAxis, baseMainSize); + if (baseMainSize != boundMainSize) { + // By excluding this item's size and flex factor from remaining, + // this item's + // min/max constraints should also trigger in the second pass + // resulting in the + // item's size calculation being identical in the first and second + // passes. + deltaFreeSpace -= boundMainSize - childFlexBasis; + deltaFlexGrowFactors -= flexGrowFactor; + } + } + } + + currentRelativeChild = currentRelativeChild->nextChild; + } + + totalFlexShrinkScaledFactors += deltaFlexShrinkScaledFactors; + totalFlexGrowFactors += deltaFlexGrowFactors; + remainingFreeSpace += deltaFreeSpace; + + // Second pass: resolve the sizes of the flexible items + deltaFreeSpace = 0; + currentRelativeChild = firstRelativeChild; + while (currentRelativeChild != NULL) { + childFlexBasis = currentRelativeChild->layout.computedFlexBasis; + float updatedMainSize = childFlexBasis; + + if (remainingFreeSpace < 0) { + flexShrinkScaledFactor = + -CSSNodeStyleGetFlexShrink(currentRelativeChild) * childFlexBasis; + // Is this child able to shrink? + if (flexShrinkScaledFactor != 0) { + float childSize; + + if (totalFlexShrinkScaledFactors == 0) { + childSize = childFlexBasis + flexShrinkScaledFactor; + } else { + childSize = + childFlexBasis + + (remainingFreeSpace / totalFlexShrinkScaledFactors) * flexShrinkScaledFactor; + } + + updatedMainSize = boundAxis(currentRelativeChild, mainAxis, childSize); + } + } else if (remainingFreeSpace > 0) { + flexGrowFactor = CSSNodeStyleGetFlexGrow(currentRelativeChild); + + // Is this child able to grow? + if (flexGrowFactor != 0) { + updatedMainSize = + boundAxis(currentRelativeChild, + mainAxis, + childFlexBasis + + remainingFreeSpace / totalFlexGrowFactors * flexGrowFactor); + } + } + + deltaFreeSpace -= updatedMainSize - childFlexBasis; + + float childWidth; + float childHeight; + CSSMeasureMode childWidthMeasureMode; + CSSMeasureMode childHeightMeasureMode; + + if (isMainAxisRow) { + childWidth = updatedMainSize + getMarginAxis(currentRelativeChild, CSSFlexDirectionRow); + childWidthMeasureMode = CSSMeasureModeExactly; + + if (!CSSValueIsUndefined(availableInnerCrossDim) && + !isStyleDimDefined(currentRelativeChild, CSSFlexDirectionColumn) && + heightMeasureMode == CSSMeasureModeExactly && + getAlignItem(node, currentRelativeChild) == CSSAlignStretch) { + childHeight = availableInnerCrossDim; + childHeightMeasureMode = CSSMeasureModeExactly; + } else if (!isStyleDimDefined(currentRelativeChild, CSSFlexDirectionColumn)) { + childHeight = availableInnerCrossDim; + childHeightMeasureMode = + CSSValueIsUndefined(childHeight) ? CSSMeasureModeUndefined : CSSMeasureModeAtMost; + } else { + childHeight = currentRelativeChild->style.dimensions[CSSDimensionHeight] + + getMarginAxis(currentRelativeChild, CSSFlexDirectionColumn); + childHeightMeasureMode = CSSMeasureModeExactly; + } + } else { + childHeight = + updatedMainSize + getMarginAxis(currentRelativeChild, CSSFlexDirectionColumn); + childHeightMeasureMode = CSSMeasureModeExactly; + + if (!CSSValueIsUndefined(availableInnerCrossDim) && + !isStyleDimDefined(currentRelativeChild, CSSFlexDirectionRow) && + widthMeasureMode == CSSMeasureModeExactly && + getAlignItem(node, currentRelativeChild) == CSSAlignStretch) { + childWidth = availableInnerCrossDim; + childWidthMeasureMode = CSSMeasureModeExactly; + } else if (!isStyleDimDefined(currentRelativeChild, CSSFlexDirectionRow)) { + childWidth = availableInnerCrossDim; + childWidthMeasureMode = + CSSValueIsUndefined(childWidth) ? CSSMeasureModeUndefined : CSSMeasureModeAtMost; + } else { + childWidth = currentRelativeChild->style.dimensions[CSSDimensionWidth] + + getMarginAxis(currentRelativeChild, CSSFlexDirectionRow); + childWidthMeasureMode = CSSMeasureModeExactly; + } + } + + constrainMaxSizeForMode(currentRelativeChild->style.maxDimensions[CSSDimensionWidth], + &childWidthMeasureMode, + &childWidth); + constrainMaxSizeForMode(currentRelativeChild->style.maxDimensions[CSSDimensionHeight], + &childHeightMeasureMode, + &childHeight); + + const bool requiresStretchLayout = + !isStyleDimDefined(currentRelativeChild, crossAxis) && + getAlignItem(node, currentRelativeChild) == CSSAlignStretch; + + // Recursively call the layout algorithm for this child with the updated + // main size. + layoutNodeInternal(currentRelativeChild, + childWidth, + childHeight, + direction, + childWidthMeasureMode, + childHeightMeasureMode, + performLayout && !requiresStretchLayout, + "flex"); + + currentRelativeChild = currentRelativeChild->nextChild; + } + } + + remainingFreeSpace = originalRemainingFreeSpace + deltaFreeSpace; + + // STEP 6: MAIN-AXIS JUSTIFICATION & CROSS-AXIS SIZE DETERMINATION + + // At this point, all the children have their dimensions set in the main + // axis. + // Their dimensions are also set in the cross axis with the exception of + // items + // that are aligned "stretch". We need to compute these stretch values and + // set the final positions. + + // If we are using "at most" rules in the main axis. Calculate the remaining space when + // constraint by the min size defined for the main axis. + + if (measureModeMainDim == CSSMeasureModeAtMost && remainingFreeSpace > 0) { + if (!CSSValueIsUndefined(node->style.minDimensions[dim[mainAxis]]) && + node->style.minDimensions[dim[mainAxis]] >= 0) { + remainingFreeSpace = fmax(0, + node->style.minDimensions[dim[mainAxis]] - + (availableInnerMainDim - remainingFreeSpace)); + } else { + remainingFreeSpace = 0; + } + } + + switch (justifyContent) { + case CSSJustifyCenter: + leadingMainDim = remainingFreeSpace / 2; + break; + case CSSJustifyFlexEnd: + leadingMainDim = remainingFreeSpace; + break; + case CSSJustifySpaceBetween: + if (itemsOnLine > 1) { + betweenMainDim = fmaxf(remainingFreeSpace, 0) / (itemsOnLine - 1); + } else { + betweenMainDim = 0; + } + break; + case CSSJustifySpaceAround: + // Space on the edges is half of the space between elements + betweenMainDim = remainingFreeSpace / itemsOnLine; + leadingMainDim = betweenMainDim / 2; + break; + case CSSJustifyFlexStart: + break; + } + + float mainDim = leadingPaddingAndBorderMain + leadingMainDim; + float crossDim = 0; + + for (uint32_t i = startOfLineIndex; i < endOfLineIndex; i++) { + const CSSNodeRef child = CSSNodeListGet(node->children, i); + + if (child->style.positionType == CSSPositionTypeAbsolute && + isLeadingPosDefined(child, mainAxis)) { + if (performLayout) { + // In case the child is position absolute and has left/top being + // defined, we override the position to whatever the user said + // (and margin/border). + child->layout.position[pos[mainAxis]] = getLeadingPosition(child, mainAxis) + + getLeadingBorder(node, mainAxis) + + getLeadingMargin(child, mainAxis); + } + } else { + // Now that we placed the element, we need to update the variables. + // We need to do that only for relative elements. Absolute elements + // do not take part in that phase. + if (child->style.positionType == CSSPositionTypeRelative) { + if (performLayout) { + child->layout.position[pos[mainAxis]] += mainDim; + } + + if (canSkipFlex) { + // If we skipped the flex step, then we can't rely on the + // measuredDims because + // they weren't computed. This means we can't call getDimWithMargin. + mainDim += + betweenMainDim + getMarginAxis(child, mainAxis) + child->layout.computedFlexBasis; + crossDim = availableInnerCrossDim; + } else { + // The main dimension is the sum of all the elements dimension plus + // the spacing. + mainDim += betweenMainDim + getDimWithMargin(child, mainAxis); + + // The cross dimension is the max of the elements dimension since + // there + // can only be one element in that cross dimension. + crossDim = fmaxf(crossDim, getDimWithMargin(child, crossAxis)); + } + } else if (performLayout) { + child->layout.position[pos[mainAxis]] += + getLeadingBorder(node, mainAxis) + leadingMainDim; + } + } + } + + mainDim += trailingPaddingAndBorderMain; + + float containerCrossAxis = availableInnerCrossDim; + if (measureModeCrossDim == CSSMeasureModeUndefined || + measureModeCrossDim == CSSMeasureModeAtMost) { + // Compute the cross axis from the max cross dimension of the children. + containerCrossAxis = boundAxis(node, crossAxis, crossDim + paddingAndBorderAxisCross) - + paddingAndBorderAxisCross; + + if (measureModeCrossDim == CSSMeasureModeAtMost) { + containerCrossAxis = fminf(containerCrossAxis, availableInnerCrossDim); + } + } + + // If there's no flex wrap, the cross dimension is defined by the container. + if (!isNodeFlexWrap && measureModeCrossDim == CSSMeasureModeExactly) { + crossDim = availableInnerCrossDim; + } + + // Clamp to the min/max size specified on the container. + crossDim = boundAxis(node, crossAxis, crossDim + paddingAndBorderAxisCross) - + paddingAndBorderAxisCross; + + // STEP 7: CROSS-AXIS ALIGNMENT + // We can skip child alignment if we're just measuring the container. + if (performLayout) { + for (uint32_t i = startOfLineIndex; i < endOfLineIndex; i++) { + const CSSNodeRef child = CSSNodeListGet(node->children, i); + + if (child->style.positionType == CSSPositionTypeAbsolute) { + // If the child is absolutely positioned and has a + // top/left/bottom/right + // set, override all the previously computed positions to set it + // correctly. + if (isLeadingPosDefined(child, crossAxis)) { + child->layout.position[pos[crossAxis]] = getLeadingPosition(child, crossAxis) + + getLeadingBorder(node, crossAxis) + + getLeadingMargin(child, crossAxis); + } else { + child->layout.position[pos[crossAxis]] = + getLeadingBorder(node, crossAxis) + getLeadingMargin(child, crossAxis); + } + } else { + float leadingCrossDim = leadingPaddingAndBorderCross; + + // For a relative children, we're either using alignItems (parent) or + // alignSelf (child) in order to determine the position in the cross + // axis + const CSSAlign alignItem = getAlignItem(node, child); + + // If the child uses align stretch, we need to lay it out one more + // time, this time + // forcing the cross-axis size to be the computed cross size for the + // current line. + if (alignItem == CSSAlignStretch) { + const bool isCrossSizeDefinite = + (isMainAxisRow && isStyleDimDefined(child, CSSFlexDirectionColumn)) || + (!isMainAxisRow && isStyleDimDefined(child, CSSFlexDirectionRow)); + + float childWidth; + float childHeight; + CSSMeasureMode childWidthMeasureMode = CSSMeasureModeExactly; + CSSMeasureMode childHeightMeasureMode = CSSMeasureModeExactly; + + if (isMainAxisRow) { + childHeight = crossDim; + childWidth = child->layout.measuredDimensions[CSSDimensionWidth] + + getMarginAxis(child, CSSFlexDirectionRow); + } else { + childWidth = crossDim; + childHeight = child->layout.measuredDimensions[CSSDimensionHeight] + + getMarginAxis(child, CSSFlexDirectionColumn); + } + + constrainMaxSizeForMode(child->style.maxDimensions[CSSDimensionWidth], + &childWidthMeasureMode, + &childWidth); + constrainMaxSizeForMode(child->style.maxDimensions[CSSDimensionHeight], + &childHeightMeasureMode, + &childHeight); + + // If the child defines a definite size for its cross axis, there's + // no need to stretch. + if (!isCrossSizeDefinite) { + childWidthMeasureMode = + CSSValueIsUndefined(childWidth) ? CSSMeasureModeUndefined : CSSMeasureModeExactly; + childHeightMeasureMode = CSSValueIsUndefined(childHeight) ? CSSMeasureModeUndefined + : CSSMeasureModeExactly; + + layoutNodeInternal(child, + childWidth, + childHeight, + direction, + childWidthMeasureMode, + childHeightMeasureMode, + true, + "stretch"); + } + } else if (alignItem != CSSAlignFlexStart) { + const float remainingCrossDim = containerCrossAxis - getDimWithMargin(child, crossAxis); + + if (alignItem == CSSAlignCenter) { + leadingCrossDim += remainingCrossDim / 2; + } else { // CSSAlignFlexEnd + leadingCrossDim += remainingCrossDim; + } + } + + // And we apply the position + child->layout.position[pos[crossAxis]] += totalLineCrossDim + leadingCrossDim; + } + } + } + + totalLineCrossDim += crossDim; + maxLineMainDim = fmaxf(maxLineMainDim, mainDim); + } + + // STEP 8: MULTI-LINE CONTENT ALIGNMENT + if (lineCount > 1 && performLayout && !CSSValueIsUndefined(availableInnerCrossDim)) { + const float remainingAlignContentDim = availableInnerCrossDim - totalLineCrossDim; + + float crossDimLead = 0; + float currentLead = leadingPaddingAndBorderCross; + + switch (node->style.alignContent) { + case CSSAlignFlexEnd: + currentLead += remainingAlignContentDim; + break; + case CSSAlignCenter: + currentLead += remainingAlignContentDim / 2; + break; + case CSSAlignStretch: + if (availableInnerCrossDim > totalLineCrossDim) { + crossDimLead = (remainingAlignContentDim / lineCount); + } + break; + case CSSAlignAuto: + case CSSAlignFlexStart: + break; + } + + uint32_t endIndex = 0; + for (uint32_t i = 0; i < lineCount; i++) { + uint32_t startIndex = endIndex; + uint32_t ii; + + // compute the line's height and find the endIndex + float lineHeight = 0; + for (ii = startIndex; ii < childCount; ii++) { + const CSSNodeRef child = CSSNodeListGet(node->children, ii); + + if (child->style.positionType == CSSPositionTypeRelative) { + if (child->lineIndex != i) { + break; + } + + if (isLayoutDimDefined(child, crossAxis)) { + lineHeight = fmaxf(lineHeight, + child->layout.measuredDimensions[dim[crossAxis]] + + getMarginAxis(child, crossAxis)); + } + } + } + endIndex = ii; + lineHeight += crossDimLead; + + if (performLayout) { + for (ii = startIndex; ii < endIndex; ii++) { + const CSSNodeRef child = CSSNodeListGet(node->children, ii); + + if (child->style.positionType == CSSPositionTypeRelative) { + switch (getAlignItem(node, child)) { + case CSSAlignFlexStart: { + child->layout.position[pos[crossAxis]] = + currentLead + getLeadingMargin(child, crossAxis); + break; + } + case CSSAlignFlexEnd: { + child->layout.position[pos[crossAxis]] = + currentLead + lineHeight - getTrailingMargin(child, crossAxis) - + child->layout.measuredDimensions[dim[crossAxis]]; + break; + } + case CSSAlignCenter: { + float childHeight = child->layout.measuredDimensions[dim[crossAxis]]; + child->layout.position[pos[crossAxis]] = + currentLead + (lineHeight - childHeight) / 2; + break; + } + case CSSAlignStretch: { + child->layout.position[pos[crossAxis]] = + currentLead + getLeadingMargin(child, crossAxis); + // TODO(prenaux): Correctly set the height of items with indefinite + // (auto) crossAxis dimension. + break; + } + case CSSAlignAuto: + break; + } + } + } + } + + currentLead += lineHeight; + } + } + + // STEP 9: COMPUTING FINAL DIMENSIONS + node->layout.measuredDimensions[CSSDimensionWidth] = + boundAxis(node, CSSFlexDirectionRow, availableWidth - marginAxisRow); + node->layout.measuredDimensions[CSSDimensionHeight] = + boundAxis(node, CSSFlexDirectionColumn, availableHeight - marginAxisColumn); + + // If the user didn't specify a width or height for the node, set the + // dimensions based on the children. + if (measureModeMainDim == CSSMeasureModeUndefined) { + // Clamp the size to the min/max size, if specified, and make sure it + // doesn't go below the padding and border amount. + node->layout.measuredDimensions[dim[mainAxis]] = boundAxis(node, mainAxis, maxLineMainDim); + } else if (measureModeMainDim == CSSMeasureModeAtMost) { + node->layout.measuredDimensions[dim[mainAxis]] = + fmaxf(fminf(availableInnerMainDim + paddingAndBorderAxisMain, + boundAxisWithinMinAndMax(node, mainAxis, maxLineMainDim)), + paddingAndBorderAxisMain); + } + + if (measureModeCrossDim == CSSMeasureModeUndefined) { + // Clamp the size to the min/max size, if specified, and make sure it + // doesn't go below the padding and border amount. + node->layout.measuredDimensions[dim[crossAxis]] = + boundAxis(node, crossAxis, totalLineCrossDim + paddingAndBorderAxisCross); + } else if (measureModeCrossDim == CSSMeasureModeAtMost) { + node->layout.measuredDimensions[dim[crossAxis]] = + fmaxf(fminf(availableInnerCrossDim + paddingAndBorderAxisCross, + boundAxisWithinMinAndMax(node, + crossAxis, + totalLineCrossDim + paddingAndBorderAxisCross)), + paddingAndBorderAxisCross); + } + + if (performLayout) { + // STEP 10: SIZING AND POSITIONING ABSOLUTE CHILDREN + for (currentAbsoluteChild = firstAbsoluteChild; currentAbsoluteChild != NULL; + currentAbsoluteChild = currentAbsoluteChild->nextChild) { + absoluteLayoutChild( + node, currentAbsoluteChild, availableInnerWidth, widthMeasureMode, direction); + } + + // STEP 11: SETTING TRAILING POSITIONS FOR CHILDREN + const bool needsMainTrailingPos = + mainAxis == CSSFlexDirectionRowReverse || mainAxis == CSSFlexDirectionColumnReverse; + const bool needsCrossTrailingPos = + CSSFlexDirectionRowReverse || crossAxis == CSSFlexDirectionColumnReverse; + + // Set trailing position if necessary. + if (needsMainTrailingPos || needsCrossTrailingPos) { + for (uint32_t i = 0; i < childCount; i++) { + const CSSNodeRef child = CSSNodeListGet(node->children, i); + + if (needsMainTrailingPos) { + setTrailingPosition(node, child, mainAxis); + } + + if (needsCrossTrailingPos) { + setTrailingPosition(node, child, crossAxis); + } + } + } + } +} + +uint32_t gDepth = 0; +bool gPrintTree = false; +bool gPrintChanges = false; +bool gPrintSkips = false; + +static const char *spacer = " "; + +static const char *getSpacer(const unsigned long level) { + const unsigned long spacerLen = strlen(spacer); + if (level > spacerLen) { + return &spacer[0]; + } else { + return &spacer[spacerLen - level]; + } +} + +static const char *getModeName(const CSSMeasureMode mode, const bool performLayout) { + const char *kMeasureModeNames[CSSMeasureModeCount] = {"UNDEFINED", "EXACTLY", "AT_MOST"}; + const char *kLayoutModeNames[CSSMeasureModeCount] = {"LAY_UNDEFINED", + "LAY_EXACTLY", + "LAY_AT_" + "MOST"}; + + if (mode >= CSSMeasureModeCount) { + return ""; + } + + return performLayout ? kLayoutModeNames[mode] : kMeasureModeNames[mode]; +} + +static inline bool newSizeIsExactAndMatchesOldMeasuredSize(CSSMeasureMode sizeMode, + float size, + float lastComputedSize) { + return sizeMode == CSSMeasureModeExactly && eq(size, lastComputedSize); +} + +static inline bool oldSizeIsUnspecifiedAndStillFits(CSSMeasureMode sizeMode, + float size, + CSSMeasureMode lastSizeMode, + float lastComputedSize) { + return sizeMode == CSSMeasureModeAtMost && lastSizeMode == CSSMeasureModeUndefined && + size >= lastComputedSize; +} + +static inline bool newMeasureSizeIsStricterAndStillValid(CSSMeasureMode sizeMode, + float size, + CSSMeasureMode lastSizeMode, + float lastSize, + float lastComputedSize) { + return lastSizeMode == CSSMeasureModeAtMost && sizeMode == CSSMeasureModeAtMost && + lastSize > size && lastComputedSize <= size; +} + +bool CSSNodeCanUseCachedMeasurement(const CSSMeasureMode widthMode, + const float width, + const CSSMeasureMode heightMode, + const float height, + const CSSMeasureMode lastWidthMode, + const float lastWidth, + const CSSMeasureMode lastHeightMode, + const float lastHeight, + const float lastComputedWidth, + const float lastComputedHeight, + const float marginRow, + const float marginColumn) { + if (lastComputedHeight < 0 || lastComputedWidth < 0) { + return false; + } + + const bool hasSameWidthSpec = lastWidthMode == widthMode && eq(lastWidth, width); + const bool hasSameHeightSpec = lastHeightMode == heightMode && eq(lastHeight, height); + + const bool widthIsCompatible = + hasSameWidthSpec || + newSizeIsExactAndMatchesOldMeasuredSize(widthMode, width - marginRow, lastComputedWidth) || + oldSizeIsUnspecifiedAndStillFits(widthMode, + width - marginRow, + lastWidthMode, + lastComputedWidth) || + newMeasureSizeIsStricterAndStillValid( + widthMode, width - marginRow, lastWidthMode, lastWidth, lastComputedWidth); + + const bool heightIsCompatible = + hasSameHeightSpec || newSizeIsExactAndMatchesOldMeasuredSize(heightMode, + height - marginColumn, + lastComputedHeight) || + oldSizeIsUnspecifiedAndStillFits(heightMode, + height - marginColumn, + lastHeightMode, + lastComputedHeight) || + newMeasureSizeIsStricterAndStillValid( + heightMode, height - marginColumn, lastHeightMode, lastHeight, lastComputedHeight); + + return widthIsCompatible && heightIsCompatible; +} + +// +// This is a wrapper around the layoutNodeImpl function. It determines +// whether the layout request is redundant and can be skipped. +// +// Parameters: +// Input parameters are the same as layoutNodeImpl (see above) +// Return parameter is true if layout was performed, false if skipped +// +bool layoutNodeInternal(const CSSNodeRef node, + const float availableWidth, + const float availableHeight, + const CSSDirection parentDirection, + const CSSMeasureMode widthMeasureMode, + const CSSMeasureMode heightMeasureMode, + const bool performLayout, + const char *reason) { + CSSLayout *layout = &node->layout; + + gDepth++; + + const bool needToVisitNode = + (node->isDirty && layout->generationCount != gCurrentGenerationCount) || + layout->lastParentDirection != parentDirection; + + if (needToVisitNode) { + // Invalidate the cached results. + layout->nextCachedMeasurementsIndex = 0; + layout->cachedLayout.widthMeasureMode = (CSSMeasureMode) -1; + layout->cachedLayout.heightMeasureMode = (CSSMeasureMode) -1; + layout->cachedLayout.computedWidth = -1; + layout->cachedLayout.computedHeight = -1; + } + + CSSCachedMeasurement *cachedResults = NULL; + + // Determine whether the results are already cached. We maintain a separate + // cache for layouts and measurements. A layout operation modifies the + // positions + // and dimensions for nodes in the subtree. The algorithm assumes that each + // node + // gets layed out a maximum of one time per tree layout, but multiple + // measurements + // may be required to resolve all of the flex dimensions. + // We handle nodes with measure functions specially here because they are the + // most + // expensive to measure, so it's worth avoiding redundant measurements if at + // all possible. + if (node->measure) { + const float marginAxisRow = getMarginAxis(node, CSSFlexDirectionRow); + const float marginAxisColumn = getMarginAxis(node, CSSFlexDirectionColumn); + + // First, try to use the layout cache. + if (CSSNodeCanUseCachedMeasurement(widthMeasureMode, + availableWidth, + heightMeasureMode, + availableHeight, + layout->cachedLayout.widthMeasureMode, + layout->cachedLayout.availableWidth, + layout->cachedLayout.heightMeasureMode, + layout->cachedLayout.availableHeight, + layout->cachedLayout.computedWidth, + layout->cachedLayout.computedHeight, + marginAxisRow, + marginAxisColumn)) { + cachedResults = &layout->cachedLayout; + } else { + // Try to use the measurement cache. + for (uint32_t i = 0; i < layout->nextCachedMeasurementsIndex; i++) { + if (CSSNodeCanUseCachedMeasurement(widthMeasureMode, + availableWidth, + heightMeasureMode, + availableHeight, + layout->cachedMeasurements[i].widthMeasureMode, + layout->cachedMeasurements[i].availableWidth, + layout->cachedMeasurements[i].heightMeasureMode, + layout->cachedMeasurements[i].availableHeight, + layout->cachedMeasurements[i].computedWidth, + layout->cachedMeasurements[i].computedHeight, + marginAxisRow, + marginAxisColumn)) { + cachedResults = &layout->cachedMeasurements[i]; + break; + } + } + } + } else if (performLayout) { + if (eq(layout->cachedLayout.availableWidth, availableWidth) && + eq(layout->cachedLayout.availableHeight, availableHeight) && + layout->cachedLayout.widthMeasureMode == widthMeasureMode && + layout->cachedLayout.heightMeasureMode == heightMeasureMode) { + cachedResults = &layout->cachedLayout; + } + } else { + for (uint32_t i = 0; i < layout->nextCachedMeasurementsIndex; i++) { + if (eq(layout->cachedMeasurements[i].availableWidth, availableWidth) && + eq(layout->cachedMeasurements[i].availableHeight, availableHeight) && + layout->cachedMeasurements[i].widthMeasureMode == widthMeasureMode && + layout->cachedMeasurements[i].heightMeasureMode == heightMeasureMode) { + cachedResults = &layout->cachedMeasurements[i]; + break; + } + } + } + + if (!needToVisitNode && cachedResults != NULL) { + layout->measuredDimensions[CSSDimensionWidth] = cachedResults->computedWidth; + layout->measuredDimensions[CSSDimensionHeight] = cachedResults->computedHeight; + + if (gPrintChanges && gPrintSkips) { + printf("%s%d.{[skipped] ", getSpacer(gDepth), gDepth); + if (node->print) { + node->print(node); + } + printf("wm: %s, hm: %s, aw: %f ah: %f => d: (%f, %f) %s\n", + getModeName(widthMeasureMode, performLayout), + getModeName(heightMeasureMode, performLayout), + availableWidth, + availableHeight, + cachedResults->computedWidth, + cachedResults->computedHeight, + reason); + } + } else { + if (gPrintChanges) { + printf("%s%d.{%s", getSpacer(gDepth), gDepth, needToVisitNode ? "*" : ""); + if (node->print) { + node->print(node); + } + printf("wm: %s, hm: %s, aw: %f ah: %f %s\n", + getModeName(widthMeasureMode, performLayout), + getModeName(heightMeasureMode, performLayout), + availableWidth, + availableHeight, + reason); + } + + layoutNodeImpl(node, + availableWidth, + availableHeight, + parentDirection, + widthMeasureMode, + heightMeasureMode, + performLayout); + + if (gPrintChanges) { + printf("%s%d.}%s", getSpacer(gDepth), gDepth, needToVisitNode ? "*" : ""); + if (node->print) { + node->print(node); + } + printf("wm: %s, hm: %s, d: (%f, %f) %s\n", + getModeName(widthMeasureMode, performLayout), + getModeName(heightMeasureMode, performLayout), + layout->measuredDimensions[CSSDimensionWidth], + layout->measuredDimensions[CSSDimensionHeight], + reason); + } + + layout->lastParentDirection = parentDirection; + + if (cachedResults == NULL) { + if (layout->nextCachedMeasurementsIndex == CSS_MAX_CACHED_RESULT_COUNT) { + if (gPrintChanges) { + printf("Out of cache entries!\n"); + } + layout->nextCachedMeasurementsIndex = 0; + } + + CSSCachedMeasurement *newCacheEntry; + if (performLayout) { + // Use the single layout cache entry. + newCacheEntry = &layout->cachedLayout; + } else { + // Allocate a new measurement cache entry. + newCacheEntry = &layout->cachedMeasurements[layout->nextCachedMeasurementsIndex]; + layout->nextCachedMeasurementsIndex++; + } + + newCacheEntry->availableWidth = availableWidth; + newCacheEntry->availableHeight = availableHeight; + newCacheEntry->widthMeasureMode = widthMeasureMode; + newCacheEntry->heightMeasureMode = heightMeasureMode; + newCacheEntry->computedWidth = layout->measuredDimensions[CSSDimensionWidth]; + newCacheEntry->computedHeight = layout->measuredDimensions[CSSDimensionHeight]; + } + } + + if (performLayout) { + node->layout.dimensions[CSSDimensionWidth] = node->layout.measuredDimensions[CSSDimensionWidth]; + node->layout.dimensions[CSSDimensionHeight] = + node->layout.measuredDimensions[CSSDimensionHeight]; + node->hasNewLayout = true; + node->isDirty = false; + } + + gDepth--; + layout->generationCount = gCurrentGenerationCount; + return (needToVisitNode || cachedResults == NULL); +} + +void CSSNodeCalculateLayout(const CSSNodeRef node, + const float availableWidth, + const float availableHeight, + const CSSDirection parentDirection) { + // Increment the generation count. This will force the recursive routine to + // visit + // all dirty nodes at least once. Subsequent visits will be skipped if the + // input + // parameters don't change. + gCurrentGenerationCount++; + + float width = availableWidth; + float height = availableHeight; + CSSMeasureMode widthMeasureMode = CSSMeasureModeUndefined; + CSSMeasureMode heightMeasureMode = CSSMeasureModeUndefined; + + if (!CSSValueIsUndefined(width)) { + widthMeasureMode = CSSMeasureModeExactly; + } else if (isStyleDimDefined(node, CSSFlexDirectionRow)) { + width = + node->style.dimensions[dim[CSSFlexDirectionRow]] + getMarginAxis(node, CSSFlexDirectionRow); + widthMeasureMode = CSSMeasureModeExactly; + } else if (node->style.maxDimensions[CSSDimensionWidth] >= 0.0) { + width = node->style.maxDimensions[CSSDimensionWidth]; + widthMeasureMode = CSSMeasureModeAtMost; + } + + if (!CSSValueIsUndefined(height)) { + heightMeasureMode = CSSMeasureModeExactly; + } else if (isStyleDimDefined(node, CSSFlexDirectionColumn)) { + height = node->style.dimensions[dim[CSSFlexDirectionColumn]] + + getMarginAxis(node, CSSFlexDirectionColumn); + heightMeasureMode = CSSMeasureModeExactly; + } else if (node->style.maxDimensions[CSSDimensionHeight] >= 0.0) { + height = node->style.maxDimensions[CSSDimensionHeight]; + heightMeasureMode = CSSMeasureModeAtMost; + } + + if (layoutNodeInternal(node, + width, + height, + parentDirection, + widthMeasureMode, + heightMeasureMode, + true, + "initia" + "l")) { + setPosition(node, node->layout.direction); + + if (gPrintTree) { + CSSNodePrint(node, CSSPrintOptionsLayout | CSSPrintOptionsChildren | CSSPrintOptionsStyle); + } + } +} + +void CSSLayoutSetLogger(CSSLogger logger) { + gLogger = logger; +} + +void CSSLog(CSSLogLevel level, const char *format, ...) { + va_list args; + va_start(args, format); + gLogger(level, format, args); + va_end(args); +} diff --git a/ReactCommon/CSSLayout/CSSLayout.h b/ReactCommon/CSSLayout/CSSLayout.h new file mode 100644 index 000000000..fb06d6d45 --- /dev/null +++ b/ReactCommon/CSSLayout/CSSLayout.h @@ -0,0 +1,241 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +#ifndef __cplusplus +#include +#endif + +// Not defined in MSVC++ +#ifndef NAN +static const unsigned long __nan[2] = {0xffffffff, 0x7fffffff}; +#define NAN (*(const float *) __nan) +#endif + +#define CSSUndefined NAN + +#include "CSSMacros.h" + +CSS_EXTERN_C_BEGIN + +typedef enum CSSDirection { + CSSDirectionInherit, + CSSDirectionLTR, + CSSDirectionRTL, +} CSSDirection; + +typedef enum CSSFlexDirection { + CSSFlexDirectionColumn, + CSSFlexDirectionColumnReverse, + CSSFlexDirectionRow, + CSSFlexDirectionRowReverse, +} CSSFlexDirection; + +typedef enum CSSJustify { + CSSJustifyFlexStart, + CSSJustifyCenter, + CSSJustifyFlexEnd, + CSSJustifySpaceBetween, + CSSJustifySpaceAround, +} CSSJustify; + +typedef enum CSSOverflow { + CSSOverflowVisible, + CSSOverflowHidden, + CSSOverflowScroll, +} CSSOverflow; + +// Note: auto is only a valid value for alignSelf. It is NOT a valid value for +// alignItems. +typedef enum CSSAlign { + CSSAlignAuto, + CSSAlignFlexStart, + CSSAlignCenter, + CSSAlignFlexEnd, + CSSAlignStretch, +} CSSAlign; + +typedef enum CSSPositionType { + CSSPositionTypeRelative, + CSSPositionTypeAbsolute, +} CSSPositionType; + +typedef enum CSSWrapType { + CSSWrapTypeNoWrap, + CSSWrapTypeWrap, +} CSSWrapType; + +typedef enum CSSMeasureMode { + CSSMeasureModeUndefined, + CSSMeasureModeExactly, + CSSMeasureModeAtMost, + CSSMeasureModeCount, +} CSSMeasureMode; + +typedef enum CSSDimension { + CSSDimensionWidth, + CSSDimensionHeight, +} CSSDimension; + +typedef enum CSSEdge { + CSSEdgeLeft, + CSSEdgeTop, + CSSEdgeRight, + CSSEdgeBottom, + CSSEdgeStart, + CSSEdgeEnd, + CSSEdgeHorizontal, + CSSEdgeVertical, + CSSEdgeAll, + CSSEdgeCount, +} CSSEdge; + +typedef enum CSSPrintOptions { + CSSPrintOptionsLayout = 1, + CSSPrintOptionsStyle = 2, + CSSPrintOptionsChildren = 4, +} CSSPrintOptions; + +typedef struct CSSSize { + float width; + float height; +} CSSSize; + +typedef enum CSSLogLevel { + CSSLogLevelError, + CSSLogLevelWarn, + CSSLogLevelInfo, + CSSLogLevelDebug, + CSSLogLevelVerbose, +} CSSLogLevel; + +typedef struct CSSNode *CSSNodeRef; +typedef CSSSize (*CSSMeasureFunc)(CSSNodeRef node, + float width, + CSSMeasureMode widthMode, + float height, + CSSMeasureMode heightMode); +typedef void (*CSSPrintFunc)(CSSNodeRef node); +typedef int (*CSSLogger)(CSSLogLevel level, const char *format, va_list args); + +// CSSNode +WIN_EXPORT CSSNodeRef CSSNodeNew(void); +WIN_EXPORT void CSSNodeInit(const CSSNodeRef node); +WIN_EXPORT void CSSNodeFree(const CSSNodeRef node); +WIN_EXPORT void CSSNodeFreeRecursive(const CSSNodeRef node); +WIN_EXPORT void CSSNodeReset(const CSSNodeRef node); +WIN_EXPORT int32_t CSSNodeGetInstanceCount(void); + +WIN_EXPORT void CSSNodeInsertChild(const CSSNodeRef node, + const CSSNodeRef child, + const uint32_t index); +WIN_EXPORT void CSSNodeRemoveChild(const CSSNodeRef node, const CSSNodeRef child); +WIN_EXPORT CSSNodeRef CSSNodeGetChild(const CSSNodeRef node, const uint32_t index); +WIN_EXPORT uint32_t CSSNodeChildCount(const CSSNodeRef node); + +WIN_EXPORT void CSSNodeCalculateLayout(const CSSNodeRef node, + const float availableWidth, + const float availableHeight, + const CSSDirection parentDirection); + +// Mark a node as dirty. Only valid for nodes with a custom measure function +// set. +// CSSLayout knows when to mark all other nodes as dirty but because nodes with +// measure functions +// depends on information not known to CSSLayout they must perform this dirty +// marking manually. +WIN_EXPORT void CSSNodeMarkDirty(const CSSNodeRef node); +WIN_EXPORT bool CSSNodeIsDirty(const CSSNodeRef node); + +WIN_EXPORT void CSSNodePrint(const CSSNodeRef node, const CSSPrintOptions options); + +WIN_EXPORT bool CSSValueIsUndefined(const float value); + +WIN_EXPORT bool CSSNodeCanUseCachedMeasurement(const CSSMeasureMode widthMode, + const float width, + const CSSMeasureMode heightMode, + const float height, + const CSSMeasureMode lastWidthMode, + const float lastWidth, + const CSSMeasureMode lastHeightMode, + const float lastHeight, + const float lastComputedWidth, + const float lastComputedHeight, + const float marginRow, + const float marginColumn); + +#define CSS_NODE_PROPERTY(type, name, paramName) \ + WIN_EXPORT void CSSNodeSet##name(const CSSNodeRef node, type paramName); \ + WIN_EXPORT type CSSNodeGet##name(const CSSNodeRef node); + +#define CSS_NODE_STYLE_PROPERTY(type, name, paramName) \ + WIN_EXPORT void CSSNodeStyleSet##name(const CSSNodeRef node, const type paramName); \ + WIN_EXPORT type CSSNodeStyleGet##name(const CSSNodeRef node); + +#define CSS_NODE_STYLE_EDGE_PROPERTY(type, name, paramName) \ + WIN_EXPORT void CSSNodeStyleSet##name(const CSSNodeRef node, \ + const CSSEdge edge, \ + const type paramName); \ + WIN_EXPORT type CSSNodeStyleGet##name(const CSSNodeRef node, const CSSEdge edge); + +#define CSS_NODE_LAYOUT_PROPERTY(type, name) \ + WIN_EXPORT type CSSNodeLayoutGet##name(const CSSNodeRef node); + +CSS_NODE_PROPERTY(void *, Context, context); +CSS_NODE_PROPERTY(CSSMeasureFunc, MeasureFunc, measureFunc); +CSS_NODE_PROPERTY(CSSPrintFunc, PrintFunc, printFunc); +CSS_NODE_PROPERTY(bool, HasNewLayout, hasNewLayout); + +CSS_NODE_STYLE_PROPERTY(CSSDirection, Direction, direction); +CSS_NODE_STYLE_PROPERTY(CSSFlexDirection, FlexDirection, flexDirection); +CSS_NODE_STYLE_PROPERTY(CSSJustify, JustifyContent, justifyContent); +CSS_NODE_STYLE_PROPERTY(CSSAlign, AlignContent, alignContent); +CSS_NODE_STYLE_PROPERTY(CSSAlign, AlignItems, alignItems); +CSS_NODE_STYLE_PROPERTY(CSSAlign, AlignSelf, alignSelf); +CSS_NODE_STYLE_PROPERTY(CSSPositionType, PositionType, positionType); +CSS_NODE_STYLE_PROPERTY(CSSWrapType, FlexWrap, flexWrap); +CSS_NODE_STYLE_PROPERTY(CSSOverflow, Overflow, overflow); + +WIN_EXPORT void CSSNodeStyleSetFlex(const CSSNodeRef node, const float flex); +CSS_NODE_STYLE_PROPERTY(float, FlexGrow, flexGrow); +CSS_NODE_STYLE_PROPERTY(float, FlexShrink, flexShrink); +CSS_NODE_STYLE_PROPERTY(float, FlexBasis, flexBasis); + +CSS_NODE_STYLE_EDGE_PROPERTY(float, Position, position); +CSS_NODE_STYLE_EDGE_PROPERTY(float, Margin, margin); +CSS_NODE_STYLE_EDGE_PROPERTY(float, Padding, padding); +CSS_NODE_STYLE_EDGE_PROPERTY(float, Border, border); + +CSS_NODE_STYLE_PROPERTY(float, Width, width); +CSS_NODE_STYLE_PROPERTY(float, Height, height); +CSS_NODE_STYLE_PROPERTY(float, MinWidth, minWidth); +CSS_NODE_STYLE_PROPERTY(float, MinHeight, minHeight); +CSS_NODE_STYLE_PROPERTY(float, MaxWidth, maxWidth); +CSS_NODE_STYLE_PROPERTY(float, MaxHeight, maxHeight); + +CSS_NODE_LAYOUT_PROPERTY(float, Left); +CSS_NODE_LAYOUT_PROPERTY(float, Top); +CSS_NODE_LAYOUT_PROPERTY(float, Right); +CSS_NODE_LAYOUT_PROPERTY(float, Bottom); +CSS_NODE_LAYOUT_PROPERTY(float, Width); +CSS_NODE_LAYOUT_PROPERTY(float, Height); +CSS_NODE_LAYOUT_PROPERTY(CSSDirection, Direction); + +WIN_EXPORT void CSSLayoutSetLogger(CSSLogger logger); +WIN_EXPORT void CSSLog(CSSLogLevel level, const char *message, ...); + +CSS_EXTERN_C_END diff --git a/ReactCommon/CSSLayout/CSSMacros.h b/ReactCommon/CSSLayout/CSSMacros.h new file mode 100644 index 000000000..8d2375e57 --- /dev/null +++ b/ReactCommon/CSSLayout/CSSMacros.h @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#pragma once + +#ifdef __cplusplus +#define CSS_EXTERN_C_BEGIN extern "C" { +#define CSS_EXTERN_C_END } +#else +#define CSS_EXTERN_C_BEGIN +#define CSS_EXTERN_C_END +#endif + +#ifdef _WINDLL +#define WIN_EXPORT __declspec(dllexport) +#else +#define WIN_EXPORT +#endif + +#ifndef FB_ASSERTIONS_ENABLED +#define FB_ASSERTIONS_ENABLED 1 +#endif + +#if FB_ASSERTIONS_ENABLED +#define CSS_ABORT() abort() +#else +#define CSS_ABORT() +#endif + +#ifndef CSS_ASSERT +#define CSS_ASSERT(X, message) \ + if (!(X)) { \ + CSSLog(CSSLogLevelError, "%s", message); \ + CSS_ABORT(); \ + } +#endif diff --git a/ReactCommon/CSSLayout/CSSNodeList.c b/ReactCommon/CSSLayout/CSSNodeList.c new file mode 100644 index 000000000..d48cf4b35 --- /dev/null +++ b/ReactCommon/CSSLayout/CSSNodeList.c @@ -0,0 +1,100 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#include "CSSNodeList.h" + +struct CSSNodeList { + uint32_t capacity; + uint32_t count; + CSSNodeRef *items; +}; + +CSSNodeListRef CSSNodeListNew(const uint32_t initialCapacity) { + const CSSNodeListRef list = malloc(sizeof(struct CSSNodeList)); + CSS_ASSERT(list != NULL, "Could not allocate memory for list"); + + list->capacity = initialCapacity; + list->count = 0; + list->items = malloc(sizeof(CSSNodeRef) * list->capacity); + CSS_ASSERT(list->items != NULL, "Could not allocate memory for items"); + + return list; +} + +void CSSNodeListFree(const CSSNodeListRef list) { + if (list) { + free(list->items); + free(list); + } +} + +uint32_t CSSNodeListCount(const CSSNodeListRef list) { + if (list) { + return list->count; + } + return 0; +} + +void CSSNodeListAdd(CSSNodeListRef *listp, const CSSNodeRef node) { + if (!*listp) { + *listp = CSSNodeListNew(4); + } + CSSNodeListInsert(listp, node, (*listp)->count); +} + +void CSSNodeListInsert(CSSNodeListRef *listp, const CSSNodeRef node, const uint32_t index) { + if (!*listp) { + *listp = CSSNodeListNew(4); + } + CSSNodeListRef list = *listp; + + if (list->count == list->capacity) { + list->capacity *= 2; + list->items = realloc(list->items, sizeof(CSSNodeRef) * list->capacity); + CSS_ASSERT(list->items != NULL, "Could not extend allocation for items"); + } + + for (uint32_t i = list->count; i > index; i--) { + list->items[i] = list->items[i - 1]; + } + + list->count++; + list->items[index] = node; +} + +CSSNodeRef CSSNodeListRemove(const CSSNodeListRef list, const uint32_t index) { + const CSSNodeRef removed = list->items[index]; + list->items[index] = NULL; + + for (uint32_t i = index; i < list->count - 1; i++) { + list->items[i] = list->items[i + 1]; + list->items[i + 1] = NULL; + } + + list->count--; + return removed; +} + +CSSNodeRef CSSNodeListDelete(const CSSNodeListRef list, const CSSNodeRef node) { + for (uint32_t i = 0; i < list->count; i++) { + if (list->items[i] == node) { + return CSSNodeListRemove(list, i); + } + } + + return NULL; +} + +CSSNodeRef CSSNodeListGet(const CSSNodeListRef list, const uint32_t index) { + if (CSSNodeListCount(list) > 0) { + return list->items[index]; + } + + return NULL; +} diff --git a/ReactCommon/CSSLayout/CSSNodeList.h b/ReactCommon/CSSLayout/CSSNodeList.h new file mode 100644 index 000000000..155fcf6e5 --- /dev/null +++ b/ReactCommon/CSSLayout/CSSNodeList.h @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#pragma once + +#include +#include +#include +#include + +#include "CSSLayout.h" +#include "CSSMacros.h" + +CSS_EXTERN_C_BEGIN + +typedef struct CSSNodeList *CSSNodeListRef; + +CSSNodeListRef CSSNodeListNew(const uint32_t initialCapacity); +void CSSNodeListFree(const CSSNodeListRef list); +uint32_t CSSNodeListCount(const CSSNodeListRef list); +void CSSNodeListAdd(CSSNodeListRef *listp, const CSSNodeRef node); +void CSSNodeListInsert(CSSNodeListRef *listp, const CSSNodeRef node, const uint32_t index); +CSSNodeRef CSSNodeListRemove(const CSSNodeListRef list, const uint32_t index); +CSSNodeRef CSSNodeListDelete(const CSSNodeListRef list, const CSSNodeRef node); +CSSNodeRef CSSNodeListGet(const CSSNodeListRef list, const uint32_t index); + +CSS_EXTERN_C_END