diff --git a/External/css-layout b/External/css-layout index 99c7ce2..e6d3aea 160000 --- a/External/css-layout +++ b/External/css-layout @@ -1 +1 @@ -Subproject commit 99c7ce24b1bacee8ddf6224d840a664f39dd2088 +Subproject commit e6d3aea73deebb971540b5c7bc1d808815f527da diff --git a/Generated/Layout.c b/Generated/Layout.c index 97fc5fe..c4da1d7 100644 --- a/Generated/Layout.c +++ b/Generated/Layout.c @@ -11,9 +11,23 @@ #include #include #include -#include +// in concatenated header, don't include Layout.h it's already at the top +#ifndef CSS_LAYOUT_IMPLEMENTATION #include "Layout.h" +#endif + +#ifdef _MSC_VER +#include +#define isnan _isnan + +/* 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 bool isUndefined(float value) { return isnan(value); @@ -28,16 +42,33 @@ static bool eq(float a, float b) { void init_css_node(css_node_t *node) { node->style.align_items = CSS_ALIGN_STRETCH; + node->style.align_content = CSS_ALIGN_FLEX_START; + + node->style.direction = CSS_DIRECTION_INHERIT; + node->style.flex_direction = CSS_FLEX_DIRECTION_COLUMN; // Some of the fields default to undefined and not 0 node->style.dimensions[CSS_WIDTH] = CSS_UNDEFINED; node->style.dimensions[CSS_HEIGHT] = CSS_UNDEFINED; + node->style.minDimensions[CSS_WIDTH] = CSS_UNDEFINED; + node->style.minDimensions[CSS_HEIGHT] = CSS_UNDEFINED; + + node->style.maxDimensions[CSS_WIDTH] = CSS_UNDEFINED; + node->style.maxDimensions[CSS_HEIGHT] = CSS_UNDEFINED; + node->style.position[CSS_LEFT] = CSS_UNDEFINED; node->style.position[CSS_TOP] = CSS_UNDEFINED; node->style.position[CSS_RIGHT] = CSS_UNDEFINED; node->style.position[CSS_BOTTOM] = CSS_UNDEFINED; + node->style.margin[CSS_START] = CSS_UNDEFINED; + node->style.margin[CSS_END] = CSS_UNDEFINED; + node->style.padding[CSS_START] = CSS_UNDEFINED; + node->style.padding[CSS_END] = CSS_UNDEFINED; + node->style.border[CSS_START] = CSS_UNDEFINED; + node->style.border[CSS_END] = CSS_UNDEFINED; + node->layout.dimensions[CSS_WIDTH] = CSS_UNDEFINED; node->layout.dimensions[CSS_HEIGHT] = CSS_UNDEFINED; @@ -45,11 +76,12 @@ void init_css_node(css_node_t *node) { node->layout.last_requested_dimensions[CSS_WIDTH] = -1; node->layout.last_requested_dimensions[CSS_HEIGHT] = -1; node->layout.last_parent_max_width = -1; + node->layout.last_direction = (css_direction_t)-1; node->layout.should_update = true; } css_node_t *new_css_node() { - css_node_t *node = calloc(1, sizeof(*node)); + css_node_t *node = (css_node_t *)calloc(1, sizeof(*node)); init_css_node(node); return node; } @@ -106,8 +138,14 @@ static void print_css_node_rec( } if (options & CSS_PRINT_STYLE) { - if (node->style.flex_direction == CSS_FLEX_DIRECTION_ROW) { + if (node->style.flex_direction == CSS_FLEX_DIRECTION_COLUMN) { + printf("flexDirection: 'column', "); + } else if (node->style.flex_direction == CSS_FLEX_DIRECTION_COLUMN_REVERSE) { + printf("flexDirection: 'columnReverse', "); + } else if (node->style.flex_direction == CSS_FLEX_DIRECTION_ROW) { printf("flexDirection: 'row', "); + } else if (node->style.flex_direction == CSS_FLEX_DIRECTION_ROW_REVERSE) { + printf("flexDirection: 'rowReverse', "); } if (node->style.justify_content == CSS_JUSTIFY_CENTER) { @@ -128,6 +166,14 @@ static void print_css_node_rec( printf("alignItems: 'stretch', "); } + if (node->style.align_content == CSS_ALIGN_CENTER) { + printf("alignContent: 'center', "); + } else if (node->style.align_content == CSS_ALIGN_FLEX_END) { + printf("alignContent: 'flex-end', "); + } else if (node->style.align_content == CSS_ALIGN_STRETCH) { + printf("alignContent: 'stretch', "); + } + if (node->style.align_self == CSS_ALIGN_FLEX_START) { printf("alignSelf: 'flex-start', "); } else if (node->style.align_self == CSS_ALIGN_CENTER) { @@ -147,6 +193,8 @@ static void print_css_node_rec( print_number_0("marginRight", node->style.margin[CSS_RIGHT]); print_number_0("marginTop", node->style.margin[CSS_TOP]); print_number_0("marginBottom", node->style.margin[CSS_BOTTOM]); + print_number_0("marginStart", node->style.margin[CSS_START]); + print_number_0("marginEnd", node->style.margin[CSS_END]); } if (four_equal(node->style.padding)) { @@ -156,6 +204,8 @@ static void print_css_node_rec( print_number_0("paddingRight", node->style.padding[CSS_RIGHT]); print_number_0("paddingTop", node->style.padding[CSS_TOP]); print_number_0("paddingBottom", node->style.padding[CSS_BOTTOM]); + print_number_0("paddingStart", node->style.padding[CSS_START]); + print_number_0("paddingEnd", node->style.padding[CSS_END]); } if (four_equal(node->style.border)) { @@ -165,6 +215,8 @@ static void print_css_node_rec( print_number_0("borderRightWidth", node->style.border[CSS_RIGHT]); print_number_0("borderTopWidth", node->style.border[CSS_TOP]); print_number_0("borderBottomWidth", node->style.border[CSS_BOTTOM]); + print_number_0("borderStartWidth", node->style.border[CSS_START]); + print_number_0("borderEndWidth", node->style.border[CSS_END]); } print_number_nan("width", node->style.dimensions[CSS_WIDTH]); @@ -197,53 +249,131 @@ void print_css_node(css_node_t *node, css_print_options_t options) { } -static css_position_t leading[2] = { +static css_position_t leading[4] = { /* CSS_FLEX_DIRECTION_COLUMN = */ CSS_TOP, - /* CSS_FLEX_DIRECTION_ROW = */ CSS_LEFT + /* CSS_FLEX_DIRECTION_COLUMN_REVERSE = */ CSS_BOTTOM, + /* CSS_FLEX_DIRECTION_ROW = */ CSS_LEFT, + /* CSS_FLEX_DIRECTION_ROW_REVERSE = */ CSS_RIGHT }; -static css_position_t trailing[2] = { +static css_position_t trailing[4] = { /* CSS_FLEX_DIRECTION_COLUMN = */ CSS_BOTTOM, - /* CSS_FLEX_DIRECTION_ROW = */ CSS_RIGHT + /* CSS_FLEX_DIRECTION_COLUMN_REVERSE = */ CSS_TOP, + /* CSS_FLEX_DIRECTION_ROW = */ CSS_RIGHT, + /* CSS_FLEX_DIRECTION_ROW_REVERSE = */ CSS_LEFT }; -static css_position_t pos[2] = { +static css_position_t pos[4] = { /* CSS_FLEX_DIRECTION_COLUMN = */ CSS_TOP, - /* CSS_FLEX_DIRECTION_ROW = */ CSS_LEFT + /* CSS_FLEX_DIRECTION_COLUMN_REVERSE = */ CSS_BOTTOM, + /* CSS_FLEX_DIRECTION_ROW = */ CSS_LEFT, + /* CSS_FLEX_DIRECTION_ROW_REVERSE = */ CSS_RIGHT }; -static css_dimension_t dim[2] = { +static css_dimension_t dim[4] = { /* CSS_FLEX_DIRECTION_COLUMN = */ CSS_HEIGHT, - /* CSS_FLEX_DIRECTION_ROW = */ CSS_WIDTH + /* CSS_FLEX_DIRECTION_COLUMN_REVERSE = */ CSS_HEIGHT, + /* CSS_FLEX_DIRECTION_ROW = */ CSS_WIDTH, + /* CSS_FLEX_DIRECTION_ROW_REVERSE = */ CSS_WIDTH }; +static bool isRowDirection(css_flex_direction_t flex_direction) { + return flex_direction == CSS_FLEX_DIRECTION_ROW || + flex_direction == CSS_FLEX_DIRECTION_ROW_REVERSE; +} + +static bool isColumnDirection(css_flex_direction_t flex_direction) { + return flex_direction == CSS_FLEX_DIRECTION_COLUMN || + flex_direction == CSS_FLEX_DIRECTION_COLUMN_REVERSE; +} +static float getLeadingMargin(css_node_t *node, css_flex_direction_t axis) { + if (isRowDirection(axis) && !isUndefined(node->style.margin[CSS_START])) { + return node->style.margin[CSS_START]; + } -static float getMargin(css_node_t *node, int location) { - return node->style.margin[location]; + return node->style.margin[leading[axis]]; } -static float getPadding(css_node_t *node, int location) { - if (node->style.padding[location] >= 0) { - return node->style.padding[location]; +static float getTrailingMargin(css_node_t *node, css_flex_direction_t axis) { + if (isRowDirection(axis) && !isUndefined(node->style.margin[CSS_END])) { + return node->style.margin[CSS_END]; } + + return node->style.margin[trailing[axis]]; +} + +static float getLeadingPadding(css_node_t *node, css_flex_direction_t axis) { + if (isRowDirection(axis) && + !isUndefined(node->style.padding[CSS_START]) && + node->style.padding[CSS_START] >= 0) { + return node->style.padding[CSS_START]; + } + + if (node->style.padding[leading[axis]] >= 0) { + return node->style.padding[leading[axis]]; + } + return 0; } -static float getBorder(css_node_t *node, int location) { - if (node->style.border[location] >= 0) { - return node->style.border[location]; +static float getTrailingPadding(css_node_t *node, css_flex_direction_t axis) { + if (isRowDirection(axis) && + !isUndefined(node->style.padding[CSS_END]) && + node->style.padding[CSS_END] >= 0) { + return node->style.padding[CSS_END]; } + + if (node->style.padding[trailing[axis]] >= 0) { + return node->style.padding[trailing[axis]]; + } + return 0; } -static float getPaddingAndBorder(css_node_t *node, int location) { - return getPadding(node, location) + getBorder(node, location); +static float getLeadingBorder(css_node_t *node, css_flex_direction_t axis) { + if (isRowDirection(axis) && + !isUndefined(node->style.border[CSS_START]) && + node->style.border[CSS_START] >= 0) { + return node->style.border[CSS_START]; + } + + if (node->style.border[leading[axis]] >= 0) { + return node->style.border[leading[axis]]; + } + + return 0; +} + +static float getTrailingBorder(css_node_t *node, css_flex_direction_t axis) { + if (isRowDirection(axis) && + !isUndefined(node->style.border[CSS_END]) && + node->style.border[CSS_END] >= 0) { + return node->style.border[CSS_END]; + } + + if (node->style.border[trailing[axis]] >= 0) { + return node->style.border[trailing[axis]]; + } + + return 0; +} + +static float getLeadingPaddingAndBorder(css_node_t *node, css_flex_direction_t axis) { + return getLeadingPadding(node, axis) + getLeadingBorder(node, axis); +} + +static float getTrailingPaddingAndBorder(css_node_t *node, css_flex_direction_t axis) { + return getTrailingPadding(node, axis) + getTrailingBorder(node, axis); +} + +static float getBorderAxis(css_node_t *node, css_flex_direction_t axis) { + return getLeadingBorder(node, axis) + getTrailingBorder(node, axis); } static float getMarginAxis(css_node_t *node, css_flex_direction_t axis) { - return getMargin(node, leading[axis]) + getMargin(node, trailing[axis]); + return getLeadingMargin(node, axis) + getTrailingMargin(node, axis); } static float getPaddingAndBorderAxis(css_node_t *node, css_flex_direction_t axis) { - return getPaddingAndBorder(node, leading[axis]) + getPaddingAndBorder(node, trailing[axis]); + return getLeadingPaddingAndBorder(node, axis) + getTrailingPaddingAndBorder(node, axis); } static css_position_type_t getPositionType(css_node_t *node) { @@ -254,6 +384,10 @@ static css_justify_t getJustifyContent(css_node_t *node) { return node->style.justify_content; } +static css_align_t getAlignContent(css_node_t *node) { + return node->style.align_content; +} + static css_align_t getAlignItem(css_node_t *node, css_node_t *child) { if (child->style.align_self != CSS_ALIGN_AUTO) { return child->style.align_self; @@ -261,10 +395,40 @@ static css_align_t getAlignItem(css_node_t *node, css_node_t *child) { return node->style.align_items; } +static css_direction_t resolveDirection(css_node_t *node, css_direction_t parentDirection) { + css_direction_t direction = node->style.direction; + + if (direction == CSS_DIRECTION_INHERIT) { + direction = parentDirection > CSS_DIRECTION_INHERIT ? parentDirection : CSS_DIRECTION_LTR; + } + + return direction; +} + static css_flex_direction_t getFlexDirection(css_node_t *node) { return node->style.flex_direction; } +static css_flex_direction_t resolveAxis(css_flex_direction_t flex_direction, css_direction_t direction) { + if (direction == CSS_DIRECTION_RTL) { + if (flex_direction == CSS_FLEX_DIRECTION_ROW) { + return CSS_FLEX_DIRECTION_ROW_REVERSE; + } else if (flex_direction == CSS_FLEX_DIRECTION_ROW_REVERSE) { + return CSS_FLEX_DIRECTION_ROW; + } + } + + return flex_direction; +} + +static css_flex_direction_t getCrossFlexDirection(css_flex_direction_t flex_direction, css_direction_t direction) { + if (isColumnDirection(flex_direction)) { + return resolveAxis(CSS_FLEX_DIRECTION_ROW, direction); + } else { + return CSS_FLEX_DIRECTION_COLUMN; + } +} + static float getFlex(css_node_t *node) { return node->style.flex; } @@ -282,12 +446,13 @@ static bool isFlexWrap(css_node_t *node) { static float getDimWithMargin(css_node_t *node, css_flex_direction_t axis) { return node->layout.dimensions[dim[axis]] + - getMargin(node, leading[axis]) + - getMargin(node, trailing[axis]); + getLeadingMargin(node, axis) + + getTrailingMargin(node, axis); } static bool isDimDefined(css_node_t *node, css_flex_direction_t axis) { - return !isUndefined(node->style.dimensions[dim[axis]]); + float value = node->style.dimensions[dim[axis]]; + return !isUndefined(value) && value > 0.0; } static bool isPosDefined(css_node_t *node, css_position_t position) { @@ -306,6 +471,30 @@ static float getPosition(css_node_t *node, css_position_t position) { return 0; } +static float boundAxis(css_node_t *node, css_flex_direction_t axis, float value) { + float min = CSS_UNDEFINED; + float max = CSS_UNDEFINED; + + if (isColumnDirection(axis)) { + min = node->style.minDimensions[CSS_HEIGHT]; + max = node->style.maxDimensions[CSS_HEIGHT]; + } else if (isRowDirection(axis)) { + min = node->style.minDimensions[CSS_WIDTH]; + max = node->style.maxDimensions[CSS_WIDTH]; + } + + float boundValue = value; + + if (!isUndefined(max) && max >= 0.0 && boundValue > max) { + boundValue = max; + } + if (!isUndefined(min) && min >= 0.0 && boundValue < min) { + boundValue = min; + } + + return boundValue; +} + // When the user specifically sets a value for width or height static void setDimensionFromStyle(css_node_t *node, css_flex_direction_t axis) { // The parent already computed us a width or height. We just skip it @@ -319,11 +508,16 @@ static void setDimensionFromStyle(css_node_t *node, css_flex_direction_t axis) { // The dimensions can never be smaller than the padding and border node->layout.dimensions[dim[axis]] = fmaxf( - node->style.dimensions[dim[axis]], + boundAxis(node, axis, node->style.dimensions[dim[axis]]), getPaddingAndBorderAxis(node, axis) ); } +static void setTrailingPosition(css_node_t *node, css_node_t *child, css_flex_direction_t axis) { + child->layout.position[trailing[axis]] = node->layout.dimensions[dim[axis]] - + child->layout.dimensions[dim[axis]] - 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(css_node_t *node, css_flex_direction_t axis) { @@ -334,65 +528,80 @@ static float getRelativePosition(css_node_t *node, css_flex_direction_t axis) { return -getPosition(node, trailing[axis]); } -static void layoutNodeImpl(css_node_t *node, float parentMaxWidth) { +static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction_t parentDirection) { /** START_GENERATED **/ - css_flex_direction_t mainAxis = getFlexDirection(node); - css_flex_direction_t crossAxis = mainAxis == CSS_FLEX_DIRECTION_ROW ? - CSS_FLEX_DIRECTION_COLUMN : - CSS_FLEX_DIRECTION_ROW; + css_direction_t direction = resolveDirection(node, parentDirection); + css_flex_direction_t mainAxis = resolveAxis(getFlexDirection(node), direction); + css_flex_direction_t crossAxis = getCrossFlexDirection(mainAxis, direction); + css_flex_direction_t resolvedRowAxis = resolveAxis(CSS_FLEX_DIRECTION_ROW, direction); // Handle width and height style attributes setDimensionFromStyle(node, mainAxis); setDimensionFromStyle(node, crossAxis); + // Set the resolved resolution in the node's layout + node->layout.direction = direction; + // The position is set by the parent, but we need to complete it with a // delta composed of the margin and left/top/right/bottom - node->layout.position[leading[mainAxis]] += getMargin(node, leading[mainAxis]) + + node->layout.position[leading[mainAxis]] += getLeadingMargin(node, mainAxis) + getRelativePosition(node, mainAxis); - node->layout.position[leading[crossAxis]] += getMargin(node, leading[crossAxis]) + + node->layout.position[trailing[mainAxis]] += getTrailingMargin(node, mainAxis) + + getRelativePosition(node, mainAxis); + node->layout.position[leading[crossAxis]] += getLeadingMargin(node, crossAxis) + + getRelativePosition(node, crossAxis); + node->layout.position[trailing[crossAxis]] += getTrailingMargin(node, crossAxis) + getRelativePosition(node, crossAxis); if (isMeasureDefined(node)) { float width = CSS_UNDEFINED; - if (isDimDefined(node, CSS_FLEX_DIRECTION_ROW)) { + if (isDimDefined(node, resolvedRowAxis)) { width = node->style.dimensions[CSS_WIDTH]; - } else if (!isUndefined(node->layout.dimensions[dim[CSS_FLEX_DIRECTION_ROW]])) { - width = node->layout.dimensions[dim[CSS_FLEX_DIRECTION_ROW]]; + } else if (!isUndefined(node->layout.dimensions[dim[resolvedRowAxis]])) { + width = node->layout.dimensions[dim[resolvedRowAxis]]; } else { width = parentMaxWidth - - getMarginAxis(node, CSS_FLEX_DIRECTION_ROW); + getMarginAxis(node, resolvedRowAxis); } - width -= getPaddingAndBorderAxis(node, CSS_FLEX_DIRECTION_ROW); + width -= getPaddingAndBorderAxis(node, resolvedRowAxis); // We only need to give a dimension for the text if we haven't got any // for it computed yet. It can either be from the style attribute or because // the element is flexible. - bool isRowUndefined = !isDimDefined(node, CSS_FLEX_DIRECTION_ROW) && - isUndefined(node->layout.dimensions[dim[CSS_FLEX_DIRECTION_ROW]]); + bool isRowUndefined = !isDimDefined(node, resolvedRowAxis) && + isUndefined(node->layout.dimensions[dim[resolvedRowAxis]]); bool isColumnUndefined = !isDimDefined(node, CSS_FLEX_DIRECTION_COLUMN) && isUndefined(node->layout.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]]); // Let's not measure the text if we already know both dimensions if (isRowUndefined || isColumnUndefined) { - css_dim_t measure_dim = node->measure( + css_dim_t measureDim = node->measure( node->context, + width ); if (isRowUndefined) { - node->layout.dimensions[CSS_WIDTH] = measure_dim.dimensions[CSS_WIDTH] + - getPaddingAndBorderAxis(node, CSS_FLEX_DIRECTION_ROW); + node->layout.dimensions[CSS_WIDTH] = measureDim.dimensions[CSS_WIDTH] + + getPaddingAndBorderAxis(node, resolvedRowAxis); } if (isColumnUndefined) { - node->layout.dimensions[CSS_HEIGHT] = measure_dim.dimensions[CSS_HEIGHT] + + node->layout.dimensions[CSS_HEIGHT] = measureDim.dimensions[CSS_HEIGHT] + getPaddingAndBorderAxis(node, CSS_FLEX_DIRECTION_COLUMN); } } - return; + if (node->children_count == 0) { + return; + } } + int i; + int ii; + css_node_t* child; + css_flex_direction_t axis; + // Pre-fill some dimensions straight from the parent - for (int i = 0; i < node->children_count; ++i) { - css_node_t* child = node->get_child(node->context, i); + for (i = 0; i < node->children_count; ++i) { + child = node->get_child(node->context, i); // Pre-fill cross axis dimensions when the child is using stretch before // we call the recursive layout pass if (getAlignItem(node, child) == CSS_ALIGN_STRETCH && @@ -400,27 +609,27 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth) { !isUndefined(node->layout.dimensions[dim[crossAxis]]) && !isDimDefined(child, crossAxis)) { child->layout.dimensions[dim[crossAxis]] = fmaxf( - node->layout.dimensions[dim[crossAxis]] - + boundAxis(child, crossAxis, node->layout.dimensions[dim[crossAxis]] - getPaddingAndBorderAxis(node, crossAxis) - - getMarginAxis(child, crossAxis), + getMarginAxis(child, crossAxis)), // You never want to go smaller than padding getPaddingAndBorderAxis(child, crossAxis) ); } else if (getPositionType(child) == CSS_POSITION_ABSOLUTE) { // Pre-fill dimensions when using absolute position and both offsets for the axis are defined (either both // left and right or top and bottom). - for (int ii = 0; ii < 2; ii++) { - css_flex_direction_t axis = (ii != 0) ? CSS_FLEX_DIRECTION_ROW : CSS_FLEX_DIRECTION_COLUMN; + for (ii = 0; ii < 2; ii++) { + axis = (ii != 0) ? CSS_FLEX_DIRECTION_ROW : CSS_FLEX_DIRECTION_COLUMN; if (!isUndefined(node->layout.dimensions[dim[axis]]) && !isDimDefined(child, axis) && isPosDefined(child, leading[axis]) && isPosDefined(child, trailing[axis])) { child->layout.dimensions[dim[axis]] = fmaxf( - node->layout.dimensions[dim[axis]] - - getPaddingAndBorderAxis(node, axis) - - getMarginAxis(child, axis) - - getPosition(child, leading[axis]) - - getPosition(child, trailing[axis]), + boundAxis(child, axis, node->layout.dimensions[dim[axis]] - + getPaddingAndBorderAxis(node, axis) - + getMarginAxis(child, axis) - + getPosition(child, leading[axis]) - + getPosition(child, trailing[axis])), // You never want to go smaller than padding getPaddingAndBorderAxis(child, axis) ); @@ -438,11 +647,12 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth) { // We want to execute the next two loops one per line with flex-wrap int startLine = 0; int endLine = 0; - int nextOffset = 0; + // int nextOffset = 0; int alreadyComputedNextLayout = 0; // We aggregate the total dimensions of the container in those two variables float linesCrossDim = 0; float linesMainDim = 0; + int linesCount = 0; while (endLine < node->children_count) { // Layout non flexible children and count children by type @@ -457,8 +667,10 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth) { int flexibleChildrenCount = 0; float totalFlexible = 0; int nonFlexibleChildrenCount = 0; - for (int i = startLine; i < node->children_count; ++i) { - css_node_t* child = node->get_child(node->context, i); + + float maxWidth; + for (i = startLine; i < node->children_count; ++i) { + child = node->get_child(node->context, i); float nextContentDim = 0; // It only makes sense to consider a child flexible if we have a computed @@ -468,27 +680,28 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth) { totalFlexible += getFlex(child); // Even if we don't know its exact size yet, we already know the padding, - // border and margin. We'll use this partial information to compute the - // remaining space. + // border and margin. We'll use this partial information, which represents + // the smallest possible size for the child, to compute the remaining + // available space. nextContentDim = getPaddingAndBorderAxis(child, mainAxis) + getMarginAxis(child, mainAxis); } else { - float maxWidth = CSS_UNDEFINED; - if (mainAxis == CSS_FLEX_DIRECTION_ROW) { - // do nothing - } else if (isDimDefined(node, CSS_FLEX_DIRECTION_ROW)) { - maxWidth = node->layout.dimensions[dim[CSS_FLEX_DIRECTION_ROW]] - - getPaddingAndBorderAxis(node, CSS_FLEX_DIRECTION_ROW); - } else { + maxWidth = CSS_UNDEFINED; + if (!isRowDirection(mainAxis)) { maxWidth = parentMaxWidth - - getMarginAxis(node, CSS_FLEX_DIRECTION_ROW) - - getPaddingAndBorderAxis(node, CSS_FLEX_DIRECTION_ROW); + getMarginAxis(node, resolvedRowAxis) - + getPaddingAndBorderAxis(node, resolvedRowAxis); + + if (isDimDefined(node, resolvedRowAxis)) { + maxWidth = node->layout.dimensions[dim[resolvedRowAxis]] - + getPaddingAndBorderAxis(node, resolvedRowAxis); + } } // This is the main recursive call. We layout non flexible children. if (alreadyComputedNextLayout == 0) { - layoutNode(child, maxWidth); + layoutNode(child, maxWidth, direction); } // Absolute positioned elements do not take part of the layout, so we @@ -507,6 +720,7 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth) { // If there's only one element, then it's bigger than the content // and needs its own line i != startLine) { + nonFlexibleChildrenCount--; alreadyComputedNextLayout = 1; break; } @@ -535,6 +749,26 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth) { // remaining space if (flexibleChildrenCount != 0) { float flexibleMainDim = remainingMainDim / totalFlexible; + float baseMainDim; + float boundMainDim; + + // Iterate over every child in the axis. If the flex share of remaining + // space doesn't meet min/max bounds, remove this child from flex + // calculations. + for (i = startLine; i < endLine; ++i) { + child = node->get_child(node->context, i); + if (isFlex(child)) { + baseMainDim = flexibleMainDim * getFlex(child) + + getPaddingAndBorderAxis(child, mainAxis); + boundMainDim = boundAxis(child, mainAxis, baseMainDim); + + if (baseMainDim != boundMainDim) { + remainingMainDim -= boundMainDim; + totalFlexible -= getFlex(child); + } + } + } + flexibleMainDim = remainingMainDim / totalFlexible; // The non flexible children can overflow the container, in this case // we should just assume that there is no space available. @@ -544,28 +778,27 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth) { // We iterate over the full array and only apply the action on flexible // children. This is faster than actually allocating a new array that // contains only flexible children. - for (int i = startLine; i < endLine; ++i) { - css_node_t* child = node->get_child(node->context, i); + for (i = startLine; i < endLine; ++i) { + child = node->get_child(node->context, i); if (isFlex(child)) { // At this point we know the final size of the element in the main // dimension - child->layout.dimensions[dim[mainAxis]] = flexibleMainDim * getFlex(child) + - getPaddingAndBorderAxis(child, mainAxis); - - float maxWidth = CSS_UNDEFINED; - if (mainAxis == CSS_FLEX_DIRECTION_ROW) { - // do nothing - } else if (isDimDefined(node, CSS_FLEX_DIRECTION_ROW)) { - maxWidth = node->layout.dimensions[dim[CSS_FLEX_DIRECTION_ROW]] - - getPaddingAndBorderAxis(node, CSS_FLEX_DIRECTION_ROW); - } else { + child->layout.dimensions[dim[mainAxis]] = boundAxis(child, mainAxis, + flexibleMainDim * getFlex(child) + getPaddingAndBorderAxis(child, mainAxis) + ); + + maxWidth = CSS_UNDEFINED; + if (isDimDefined(node, resolvedRowAxis)) { + maxWidth = node->layout.dimensions[dim[resolvedRowAxis]] - + getPaddingAndBorderAxis(node, resolvedRowAxis); + } else if (!isRowDirection(mainAxis)) { maxWidth = parentMaxWidth - - getMarginAxis(node, CSS_FLEX_DIRECTION_ROW) - - getPaddingAndBorderAxis(node, CSS_FLEX_DIRECTION_ROW); + getMarginAxis(node, resolvedRowAxis) - + getPaddingAndBorderAxis(node, resolvedRowAxis); } // And we recursively call the layout algorithm for this child - layoutNode(child, maxWidth); + layoutNode(child, maxWidth, direction); } } @@ -573,9 +806,7 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth) { // space available } else { css_justify_t justifyContent = getJustifyContent(node); - if (justifyContent == CSS_JUSTIFY_FLEX_START) { - // Do nothing - } else if (justifyContent == CSS_JUSTIFY_CENTER) { + if (justifyContent == CSS_JUSTIFY_CENTER) { leadingMainDim = remainingMainDim / 2; } else if (justifyContent == CSS_JUSTIFY_FLEX_END) { leadingMainDim = remainingMainDim; @@ -603,10 +834,11 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth) { // container! float crossDim = 0; float mainDim = leadingMainDim + - getPaddingAndBorder(node, leading[mainAxis]); + getLeadingPaddingAndBorder(node, mainAxis); - for (int i = startLine; i < endLine; ++i) { - css_node_t* child = node->get_child(node->context, i); + for (i = startLine; i < endLine; ++i) { + child = node->get_child(node->context, i); + child->line_index = linesCount; if (getPositionType(child) == CSS_POSITION_ABSOLUTE && isPosDefined(child, leading[mainAxis])) { @@ -614,12 +846,17 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth) { // defined, we override the position to whatever the user said // (and margin/border). child->layout.position[pos[mainAxis]] = getPosition(child, leading[mainAxis]) + - getBorder(node, leading[mainAxis]) + - getMargin(child, leading[mainAxis]); + getLeadingBorder(node, mainAxis) + + getLeadingMargin(child, mainAxis); } else { // If the child is position absolute (without top/left) or relative, // we put it at the current accumulated offset. child->layout.position[pos[mainAxis]] += mainDim; + + // Define the trailing position accordingly. + if (!isUndefined(node->layout.dimensions[dim[mainAxis]])) { + setTrailingPosition(node, child, mainAxis); + } } // Now that we placed the element, we need to update the variables @@ -631,38 +868,24 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth) { 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)); + crossDim = fmaxf(crossDim, boundAxis(child, crossAxis, getDimWithMargin(child, crossAxis))); } } - float containerMainAxis = node->layout.dimensions[dim[mainAxis]]; - // If the user didn't specify a width or height, and it has not been set - // by the container, then we set it via the children. - if (isUndefined(node->layout.dimensions[dim[mainAxis]])) { - containerMainAxis = fmaxf( - // We're missing the last padding at this point to get the final - // dimension - mainDim + getPaddingAndBorder(node, trailing[mainAxis]), - // We can never assign a width smaller than the padding and borders - getPaddingAndBorderAxis(node, mainAxis) - ); - } - float containerCrossAxis = node->layout.dimensions[dim[crossAxis]]; if (isUndefined(node->layout.dimensions[dim[crossAxis]])) { containerCrossAxis = fmaxf( // For the cross dim, we add both sides at the end because the value // is aggregate via a max function. Intermediate negative values // can mess this computation otherwise - crossDim + getPaddingAndBorderAxis(node, crossAxis), + boundAxis(node, crossAxis, crossDim + getPaddingAndBorderAxis(node, crossAxis)), getPaddingAndBorderAxis(node, crossAxis) ); } // Position elements in the cross axis - - for (int i = startLine; i < endLine; ++i) { - css_node_t* child = node->get_child(node->context, i); + for (i = startLine; i < endLine; ++i) { + child = node->get_child(node->context, i); if (getPositionType(child) == CSS_POSITION_ABSOLUTE && isPosDefined(child, leading[crossAxis])) { @@ -670,31 +893,29 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth) { // top/left/bottom/right being set, we override all the previously // computed positions to set it correctly. child->layout.position[pos[crossAxis]] = getPosition(child, leading[crossAxis]) + - getBorder(node, leading[crossAxis]) + - getMargin(child, leading[crossAxis]); + getLeadingBorder(node, crossAxis) + + getLeadingMargin(child, crossAxis); } else { - float leadingCrossDim = getPaddingAndBorder(node, leading[crossAxis]); + float leadingCrossDim = getLeadingPaddingAndBorder(node, crossAxis); // For a relative children, we're either using alignItems (parent) or // alignSelf (child) in order to determine the position in the cross axis if (getPositionType(child) == CSS_POSITION_RELATIVE) { css_align_t alignItem = getAlignItem(node, child); - if (alignItem == CSS_ALIGN_FLEX_START) { - // Do nothing - } else if (alignItem == CSS_ALIGN_STRETCH) { + if (alignItem == CSS_ALIGN_STRETCH) { // You can only stretch if the dimension has not already been set // previously. if (!isDimDefined(child, crossAxis)) { child->layout.dimensions[dim[crossAxis]] = fmaxf( - containerCrossAxis - + boundAxis(child, crossAxis, containerCrossAxis - getPaddingAndBorderAxis(node, crossAxis) - - getMarginAxis(child, crossAxis), + getMarginAxis(child, crossAxis)), // You never want to go smaller than padding getPaddingAndBorderAxis(child, crossAxis) ); } - } else { + } else if (alignItem != CSS_ALIGN_FLEX_START) { // The remaining space between the parent dimensions+padding and child // dimensions+margin. float remainingCrossDim = containerCrossAxis - @@ -711,24 +932,117 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth) { // And we apply the position child->layout.position[pos[crossAxis]] += linesCrossDim + leadingCrossDim; + + // Define the trailing position accordingly. + if (!isUndefined(node->layout.dimensions[dim[crossAxis]])) { + setTrailingPosition(node, child, crossAxis); + } } } linesCrossDim += crossDim; linesMainDim = fmaxf(linesMainDim, mainDim); + linesCount += 1; startLine = endLine; } + // + // + // Note(prenaux): More than one line, we need to layout the crossAxis + // according to alignContent. + // + // Note that we could probably remove and handle the one line case + // here too, but for the moment this is safer since it won't interfere with + // previously working code. + // + // See specs: + // http://www.w3.org/TR/2012/CR-css3-flexbox-20120918/#layout-algorithm + // section 9.4 + // + if (linesCount > 1 && + !isUndefined(node->layout.dimensions[dim[crossAxis]])) { + float nodeCrossAxisInnerSize = node->layout.dimensions[dim[crossAxis]] - + getPaddingAndBorderAxis(node, crossAxis); + float remainingAlignContentDim = nodeCrossAxisInnerSize - linesCrossDim; + + float crossDimLead = 0; + float currentLead = getLeadingPaddingAndBorder(node, crossAxis); + + css_align_t alignContent = getAlignContent(node); + if (alignContent == CSS_ALIGN_FLEX_END) { + currentLead += remainingAlignContentDim; + } else if (alignContent == CSS_ALIGN_CENTER) { + currentLead += remainingAlignContentDim / 2; + } else if (alignContent == CSS_ALIGN_STRETCH) { + if (nodeCrossAxisInnerSize > linesCrossDim) { + crossDimLead = (remainingAlignContentDim / linesCount); + } + } + + int endIndex = 0; + for (i = 0; i < linesCount; ++i) { + int startIndex = endIndex; + + // compute the line's height and find the endIndex + float lineHeight = 0; + for (ii = startIndex; ii < node->children_count; ++ii) { + child = node->get_child(node->context, ii); + if (getPositionType(child) != CSS_POSITION_RELATIVE) { + continue; + } + if (child->line_index != i) { + break; + } + if (!isUndefined(child->layout.dimensions[dim[crossAxis]])) { + lineHeight = fmaxf( + lineHeight, + child->layout.dimensions[dim[crossAxis]] + getMarginAxis(child, crossAxis) + ); + } + } + endIndex = ii; + lineHeight += crossDimLead; + + for (ii = startIndex; ii < endIndex; ++ii) { + child = node->get_child(node->context, ii); + if (getPositionType(child) != CSS_POSITION_RELATIVE) { + continue; + } + + css_align_t alignContentAlignItem = getAlignItem(node, child); + if (alignContentAlignItem == CSS_ALIGN_FLEX_START) { + child->layout.position[pos[crossAxis]] = currentLead + getLeadingMargin(child, crossAxis); + } else if (alignContentAlignItem == CSS_ALIGN_FLEX_END) { + child->layout.position[pos[crossAxis]] = currentLead + lineHeight - getTrailingMargin(child, crossAxis) - child->layout.dimensions[dim[crossAxis]]; + } else if (alignContentAlignItem == CSS_ALIGN_CENTER) { + float childHeight = child->layout.dimensions[dim[crossAxis]]; + child->layout.position[pos[crossAxis]] = currentLead + (lineHeight - childHeight) / 2; + } else if (alignContentAlignItem == CSS_ALIGN_STRETCH) { + child->layout.position[pos[crossAxis]] = currentLead + getLeadingMargin(child, crossAxis); + // TODO(prenaux): Correctly set the height of items with undefined + // (auto) crossAxis dimension. + } + } + + currentLead += lineHeight; + } + } + + bool needsMainTrailingPos = false; + bool needsCrossTrailingPos = false; + // If the user didn't specify a width or height, and it has not been set // by the container, then we set it via the children. if (isUndefined(node->layout.dimensions[dim[mainAxis]])) { node->layout.dimensions[dim[mainAxis]] = fmaxf( // We're missing the last padding at this point to get the final // dimension - linesMainDim + getPaddingAndBorder(node, trailing[mainAxis]), + boundAxis(node, mainAxis, linesMainDim + getTrailingPaddingAndBorder(node, mainAxis)), // We can never assign a width smaller than the padding and borders getPaddingAndBorderAxis(node, mainAxis) ); + + needsMainTrailingPos = true; } if (isUndefined(node->layout.dimensions[dim[crossAxis]])) { @@ -736,37 +1050,54 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth) { // For the cross dim, we add both sides at the end because the value // is aggregate via a max function. Intermediate negative values // can mess this computation otherwise - linesCrossDim + getPaddingAndBorderAxis(node, crossAxis), + boundAxis(node, crossAxis, linesCrossDim + getPaddingAndBorderAxis(node, crossAxis)), getPaddingAndBorderAxis(node, crossAxis) ); + + needsCrossTrailingPos = true; } - // Calculate dimensions for absolutely positioned elements + // Set trailing position if necessary + if (needsMainTrailingPos || needsCrossTrailingPos) { + for (i = 0; i < node->children_count; ++i) { + child = node->get_child(node->context, i); - for (int i = 0; i < node->children_count; ++i) { - css_node_t* child = node->get_child(node->context, i); + if (needsMainTrailingPos) { + setTrailingPosition(node, child, mainAxis); + } + + if (needsCrossTrailingPos) { + setTrailingPosition(node, child, crossAxis); + } + } + } + + // Calculate dimensions for absolutely positioned elements + for (i = 0; i < node->children_count; ++i) { + child = node->get_child(node->context, i); if (getPositionType(child) == CSS_POSITION_ABSOLUTE) { // Pre-fill dimensions when using absolute position and both offsets for the axis are defined (either both // left and right or top and bottom). - for (int ii = 0; ii < 2; ii++) { - css_flex_direction_t axis = (ii != 0) ? CSS_FLEX_DIRECTION_ROW : CSS_FLEX_DIRECTION_COLUMN; + for (ii = 0; ii < 2; ii++) { + axis = (ii != 0) ? CSS_FLEX_DIRECTION_ROW : CSS_FLEX_DIRECTION_COLUMN; if (!isUndefined(node->layout.dimensions[dim[axis]]) && !isDimDefined(child, axis) && isPosDefined(child, leading[axis]) && isPosDefined(child, trailing[axis])) { child->layout.dimensions[dim[axis]] = fmaxf( - node->layout.dimensions[dim[axis]] - - getPaddingAndBorderAxis(node, axis) - - getMarginAxis(child, axis) - - getPosition(child, leading[axis]) - - getPosition(child, trailing[axis]), + boundAxis(child, axis, node->layout.dimensions[dim[axis]] - + getBorderAxis(node, axis) - + getMarginAxis(child, axis) - + getPosition(child, leading[axis]) - + getPosition(child, trailing[axis]) + ), // You never want to go smaller than padding getPaddingAndBorderAxis(child, axis) ); } } - for (int ii = 0; ii < 2; ii++) { - css_flex_direction_t axis = (ii != 0) ? CSS_FLEX_DIRECTION_ROW : CSS_FLEX_DIRECTION_COLUMN; + for (ii = 0; ii < 2; ii++) { + axis = (ii != 0) ? CSS_FLEX_DIRECTION_ROW : CSS_FLEX_DIRECTION_COLUMN; if (isPosDefined(child, trailing[axis]) && !isPosDefined(child, leading[axis])) { child->layout.position[leading[axis]] = @@ -780,8 +1111,9 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth) { /** END_GENERATED **/ } -void layoutNode(css_node_t *node, float parentMaxWidth) { +void layoutNode(css_node_t *node, float parentMaxWidth, css_direction_t parentDirection) { css_layout_t *layout = &node->layout; + css_direction_t direction = node->style.direction; layout->should_update = true; bool skipLayout = @@ -789,6 +1121,7 @@ void layoutNode(css_node_t *node, float parentMaxWidth) { eq(layout->last_requested_dimensions[CSS_WIDTH], layout->dimensions[CSS_WIDTH]) && eq(layout->last_requested_dimensions[CSS_HEIGHT], layout->dimensions[CSS_HEIGHT]) && eq(layout->last_parent_max_width, parentMaxWidth); + eq(layout->last_direction, direction); if (skipLayout) { layout->dimensions[CSS_WIDTH] = layout->last_dimensions[CSS_WIDTH]; @@ -799,8 +1132,9 @@ void layoutNode(css_node_t *node, float parentMaxWidth) { layout->last_requested_dimensions[CSS_WIDTH] = layout->dimensions[CSS_WIDTH]; layout->last_requested_dimensions[CSS_HEIGHT] = layout->dimensions[CSS_HEIGHT]; layout->last_parent_max_width = parentMaxWidth; + layout->last_direction = direction; - layoutNodeImpl(node, parentMaxWidth); + layoutNodeImpl(node, parentMaxWidth, parentDirection); layout->last_dimensions[CSS_WIDTH] = layout->dimensions[CSS_WIDTH]; layout->last_dimensions[CSS_HEIGHT] = layout->dimensions[CSS_HEIGHT]; diff --git a/Generated/Layout.h b/Generated/Layout.h index d5a815c..54d3158 100644 --- a/Generated/Layout.h +++ b/Generated/Layout.h @@ -11,12 +11,29 @@ #define __LAYOUT_H #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 CSS_UNDEFINED NAN +typedef enum { + CSS_DIRECTION_INHERIT = 0, + CSS_DIRECTION_LTR, + CSS_DIRECTION_RTL +} css_direction_t; + typedef enum { CSS_FLEX_DIRECTION_COLUMN = 0, - CSS_FLEX_DIRECTION_ROW + CSS_FLEX_DIRECTION_COLUMN_REVERSE, + CSS_FLEX_DIRECTION_ROW, + CSS_FLEX_DIRECTION_ROW_REVERSE } css_flex_direction_t; typedef enum { @@ -54,6 +71,8 @@ typedef enum { CSS_TOP, CSS_RIGHT, CSS_BOTTOM, + CSS_START, + CSS_END, CSS_POSITION_COUNT } css_position_t; @@ -63,8 +82,9 @@ typedef enum { } css_dimension_t; typedef struct { - float position[2]; + float position[4]; float dimensions[2]; + css_direction_t direction; // Instead of recomputing the entire layout every single time, we // cache some information to break early when nothing changed @@ -73,6 +93,7 @@ typedef struct { float last_parent_max_width; float last_dimensions[2]; float last_position[2]; + css_direction_t last_direction; } css_layout_t; typedef struct { @@ -80,14 +101,16 @@ typedef struct { } css_dim_t; typedef struct { + css_direction_t direction; css_flex_direction_t flex_direction; css_justify_t justify_content; + css_align_t align_content; css_align_t align_items; css_align_t align_self; css_position_type_t position_type; css_wrap_type_t flex_wrap; float flex; - float margin[4]; + float margin[6]; float position[4]; /** * You should skip all the rules that contain negative values for the @@ -99,15 +122,18 @@ typedef struct { * {left: -5 ...} * {left: 0 ...} */ - float padding[4]; - float border[4]; + float padding[6]; + float border[6]; float dimensions[2]; + float minDimensions[2]; + float maxDimensions[2]; } css_style_t; typedef struct css_node { css_style_t style; css_layout_t layout; int children_count; + int line_index; css_dim_t (*measure)(void *context, float width); void (*print)(void *context); @@ -131,7 +157,7 @@ typedef enum { void print_css_node(css_node_t *node, css_print_options_t options); // Function that computes the layout! -void layoutNode(css_node_t *node, float maxWidth); +void layoutNode(css_node_t *node, float maxWidth, css_direction_t parentDirection); bool isUndefined(float value); #endif diff --git a/SwiftBox/Layout+View.swift b/SwiftBox/Layout+View.swift index d93f484..88b0add 100644 --- a/SwiftBox/Layout+View.swift +++ b/SwiftBox/Layout+View.swift @@ -21,7 +21,7 @@ extension Layout { public func apply(view: ViewType) { view.frame = CGRectIntegral(frame) - for (s, layout) in Zip2(view.subviews, children) { + for (s, layout) in zip(view.subviews, children) { let subview = s as ViewType layout.apply(subview) } diff --git a/SwiftBox/Layout.swift b/SwiftBox/Layout.swift index 672332d..6176302 100644 --- a/SwiftBox/Layout.swift +++ b/SwiftBox/Layout.swift @@ -31,7 +31,7 @@ extension Layout: CustomStringConvertible { let selfDescription = "{origin={\(frame.origin.x), \(frame.origin.y)}, size={\(frame.size.width), \(frame.size.height)}}" if children.count > 0 { let indentation = (0...depth).reduce("\n") { accum, _ in accum + "\t" } - let childrenDescription = indentation.join(children.map { $0.descriptionForDepth(depth + 1) }) + let childrenDescription = children.map { $0.descriptionForDepth(depth + 1) }.joinWithSeparator(indentation) return "\(selfDescription)\(indentation)\(childrenDescription)" } else { return selfDescription diff --git a/SwiftBox/Node.swift b/SwiftBox/Node.swift index e0883e6..72ae0c1 100644 --- a/SwiftBox/Node.swift +++ b/SwiftBox/Node.swift @@ -13,16 +13,24 @@ public struct Edges { public let right: CGFloat public let bottom: CGFloat public let top: CGFloat + public let start: CGFloat + public let end: CGFloat - private var asTuple: (Float, Float, Float, Float) { + private var asTuple: (Float, Float, Float, Float, Float, Float) { + return (Float(left), Float(top), Float(right), Float(bottom), Float(start), Float(end)) + } + + private var asCompactTuple: (Float, Float, Float, Float) { return (Float(left), Float(top), Float(right), Float(bottom)) } - public init(left: CGFloat = 0, right: CGFloat = 0, bottom: CGFloat = 0, top: CGFloat = 0) { + public init(left: CGFloat = 0, right: CGFloat = 0, bottom: CGFloat = 0, top: CGFloat = 0, start: CGFloat = 0, end: CGFloat = 0) { self.left = left self.right = right self.bottom = bottom self.top = top + self.start = start + self.end = end } public init(uniform: CGFloat) { @@ -30,12 +38,16 @@ public struct Edges { self.right = uniform self.bottom = uniform self.top = uniform + self.start = uniform + self.end = uniform } } -public enum Direction: UInt32 { +public enum FlexDirection: UInt32 { case Column = 0 - case Row = 1 + case ColumnReverse = 1 + case Row = 2 + case RowReverse = 3 } public enum Justification: UInt32 { @@ -61,6 +73,17 @@ public enum SelfAlignment: UInt32 { case Stretch = 4 } +public enum Direction: UInt32 { + case Inherit = 0 + case LeftToRight = 1 + case RightToLeft = 2 +} + +public enum PositionType: UInt32 { + case Relative = 0 + case Absolute = 1 +} + /// A node in a layout hierarchy. public struct Node { /// Indicates that the value is undefined, for the flexbox algorithm to @@ -68,42 +91,63 @@ public struct Node { public static let Undefined: CGFloat = nan("SwiftBox.Node.Undefined") public let size: CGSize + public let minSize: CGSize + public let maxSize: CGSize public let children: [Node] + public let flexDirection: FlexDirection public let direction: Direction public let margin: Edges public let padding: Edges + public let border: Edges + public let position: Edges public let wrap: Bool public let justification: Justification public let selfAlignment: SelfAlignment public let childAlignment: ChildAlignment + public let contentAlignment: ChildAlignment public let flex: CGFloat + public let positionType: PositionType public let measure: (CGFloat -> CGSize)? - public init(size: CGSize = CGSize(width: Undefined, height: Undefined), children: [Node] = [], direction: Direction = .Column, margin: Edges = Edges(), padding: Edges = Edges(), wrap: Bool = false, justification: Justification = .FlexStart, selfAlignment: SelfAlignment = .Auto, childAlignment: ChildAlignment = .Stretch, flex: CGFloat = 0, measure: (CGFloat -> CGSize)? = nil) { + public init(size: CGSize = CGSize(width: Undefined, height: Undefined), minSize: CGSize = CGSize(width: 0, height: 0), maxSize: CGSize = CGSize(width: Undefined, height: Undefined), children: [Node] = [], flexDirection: FlexDirection = .Column, direction: Direction = .Inherit, margin: Edges = Edges(), padding: Edges = Edges(), border: Edges = Edges(), position: Edges = Edges(), wrap: Bool = false, justification: Justification = .FlexStart, selfAlignment: SelfAlignment = .Auto, childAlignment: ChildAlignment = .Stretch, contentAlignment: ChildAlignment = .Stretch, flex: CGFloat = 0, positionType: PositionType = .Relative, measure: (CGFloat -> CGSize)? = nil) { self.size = size + self.minSize = minSize + self.maxSize = maxSize self.children = children + self.flexDirection = flexDirection self.direction = direction self.margin = margin self.padding = padding + self.border = border + self.position = position self.wrap = wrap self.justification = justification self.selfAlignment = selfAlignment self.childAlignment = childAlignment + self.contentAlignment = contentAlignment self.flex = flex + self.positionType = positionType self.measure = measure } private func createUnderlyingNode() -> NodeImpl { let node = NodeImpl() node.node.memory.style.dimensions = (Float(size.width), Float(size.height)) + node.node.memory.style.minDimensions = (Float(minSize.width), Float(minSize.height)) + node.node.memory.style.maxDimensions = (Float(maxSize.width), Float(maxSize.height)) node.node.memory.style.margin = margin.asTuple node.node.memory.style.padding = padding.asTuple + node.node.memory.style.border = border.asTuple + node.node.memory.style.position = position.asCompactTuple + node.node.memory.style.position_type = css_position_type_t(positionType.rawValue) node.node.memory.style.flex = Float(flex) - node.node.memory.style.flex_direction = css_flex_direction_t(direction.rawValue) + node.node.memory.style.flex_direction = css_flex_direction_t(flexDirection.rawValue) + node.node.memory.style.direction = css_direction_t(direction.rawValue) node.node.memory.style.flex_wrap = css_wrap_type_t(wrap ? 1 : 0) node.node.memory.style.justify_content = css_justify_t(justification.rawValue) node.node.memory.style.align_self = css_align_t(selfAlignment.rawValue) node.node.memory.style.align_items = css_align_t(childAlignment.rawValue) + node.node.memory.style.align_content = css_align_t(contentAlignment.rawValue) if let measure = measure { node.measure = measure } @@ -114,8 +158,9 @@ public struct Node { /// Lay out the receiver and all its children with an optional max width. public func layout(maxWidth: CGFloat? = nil) -> Layout { let node = createUnderlyingNode() + if let maxWidth = maxWidth { - node.layoutWithMaxWidth(maxWidth) + node.layoutWithMaxWidth(maxWidth, parentDirection: node.node.memory.style.direction) } else { node.layout() } diff --git a/SwiftBox/NodeImpl.h b/SwiftBox/NodeImpl.h index 19fd881..5e1b22e 100644 --- a/SwiftBox/NodeImpl.h +++ b/SwiftBox/NodeImpl.h @@ -26,6 +26,6 @@ - (void)layout; -- (void)layoutWithMaxWidth:(CGFloat)maxWidth; +- (void)layoutWithMaxWidth:(CGFloat)maxWidth parentDirection:(css_direction_t)parentDirection; @end diff --git a/SwiftBox/NodeImpl.m b/SwiftBox/NodeImpl.m index 3160710..1ed227f 100644 --- a/SwiftBox/NodeImpl.m +++ b/SwiftBox/NodeImpl.m @@ -62,13 +62,13 @@ - (void)prepareForLayout { } - (void)layout { - [self layoutWithMaxWidth:CSS_UNDEFINED]; + [self layoutWithMaxWidth:CSS_UNDEFINED parentDirection:CSS_DIRECTION_INHERIT]; } -- (void)layoutWithMaxWidth:(CGFloat)maxWidth { +- (void)layoutWithMaxWidth:(CGFloat)maxWidth parentDirection:(css_direction_t)parentDirection { [self prepareForLayout]; - layoutNode(self.node, maxWidth); + layoutNode(self.node, maxWidth, parentDirection); } - (CGRect)frame { diff --git a/SwiftBoxTests/SwiftBoxTests.swift b/SwiftBoxTests/SwiftBoxTests.swift index 32af61e..1fca489 100644 --- a/SwiftBoxTests/SwiftBoxTests.swift +++ b/SwiftBoxTests/SwiftBoxTests.swift @@ -14,7 +14,7 @@ class SwiftBoxTests: XCTestCase { func testDescription() { let parent = Node(size: CGSize(width: 300, height: 300), childAlignment: .Center, - direction: .Row, + flexDirection: .Row, children: [ Node(flex: 75, margin: Edges(left: 10, right: 10), @@ -64,21 +64,21 @@ class SwiftBoxTests: XCTestCase { } func testUndefinedPoint() { - XCTAssertFalse(CGPoint.zeroPoint.isUndefined, "ordinary point is not undefined") + XCTAssertFalse(CGPoint.zero.isUndefined, "ordinary point is not undefined") XCTAssert(CGPoint(x: 0, y: Node.Undefined).isUndefined, "detects undefined point.y") XCTAssert(CGPoint(x: Node.Undefined, y: 0).isUndefined, "detects undefined point.x") } func testUndefinedSize() { - XCTAssertFalse(CGSize.zeroSize.isUndefined, "ordinary size is not undefined") + XCTAssertFalse(CGSize.zero.isUndefined, "ordinary size is not undefined") XCTAssert(CGSize(width: 0, height: Node.Undefined).isUndefined, "detects undefined size.height") XCTAssert(CGSize(width: Node.Undefined, height: 0).isUndefined, "detects undefined size.width") } func testUndefinedRect() { - XCTAssertFalse(CGRect.infiniteRect.isUndefined, "infinite rect is not undefined") - XCTAssertFalse(CGRect.nullRect.isUndefined, "null rect is not undefined") - XCTAssertFalse(CGRect.zeroRect.isUndefined, "zero rect is not undefined") + XCTAssertFalse(CGRect.infinite.isUndefined, "infinite rect is not undefined") + XCTAssertFalse(CGRect.null.isUndefined, "null rect is not undefined") + XCTAssertFalse(CGRect.zero.isUndefined, "zero rect is not undefined") XCTAssert(CGRect(x: 0, y: 0, width: Node.Undefined, height: 0).isUndefined, "detects undefined size in rect") XCTAssert(CGRect(x: Node.Undefined, y: 0, width: 1, height: 1).isUndefined, "detects undefined origin in rect") }