From b3ebeebc310c1c6deaafef45d6f3e3400151dd17 Mon Sep 17 00:00:00 2001 From: Mihir Iyer Date: Tue, 19 Jun 2018 10:28:31 -0700 Subject: [PATCH] Bug 1398483 - Implement column and row gap for flexbox. r=dholbert MozReview-Commit-ID: 2EmaG3G0HRI --HG-- extra : rebase_source : ce90f9a38c0f3a6634a95b0f4f97a2a3fbd48a57 --- layout/generic/nsFlexContainerFrame.cpp | 158 +++++++++++++++--- layout/generic/nsFlexContainerFrame.h | 5 +- .../flexbox-column-row-gap-001-ref.html | 97 +++++++++++ .../flexbox/flexbox-column-row-gap-001.html | 65 +++++++ .../flexbox-column-row-gap-002-ref.html | 67 ++++++++ .../flexbox/flexbox-column-row-gap-002.html | 62 +++++++ .../w3c-css/submitted/flexbox/reftest.list | 4 + 7 files changed, 433 insertions(+), 25 deletions(-) create mode 100644 layout/reftests/w3c-css/submitted/flexbox/flexbox-column-row-gap-001-ref.html create mode 100644 layout/reftests/w3c-css/submitted/flexbox/flexbox-column-row-gap-001.html create mode 100644 layout/reftests/w3c-css/submitted/flexbox/flexbox-column-row-gap-002-ref.html create mode 100644 layout/reftests/w3c-css/submitted/flexbox/flexbox-column-row-gap-002.html diff --git a/layout/generic/nsFlexContainerFrame.cpp b/layout/generic/nsFlexContainerFrame.cpp index d07fab1d7304..c2fcb961b535 100644 --- a/layout/generic/nsFlexContainerFrame.cpp +++ b/layout/generic/nsFlexContainerFrame.cpp @@ -887,17 +887,23 @@ protected: class nsFlexContainerFrame::FlexLine : public LinkedListElement { public: - FlexLine() + explicit FlexLine(nscoord aMainGapSize) : mNumItems(0), mNumFrozenItems(0), mTotalItemMBP(0), mTotalOuterHypotheticalMainSize(0), mLineCrossSize(0), mFirstBaselineOffset(nscoord_MIN), - mLastBaselineOffset(nscoord_MIN) + mLastBaselineOffset(nscoord_MIN), + mMainGapSize(aMainGapSize) {} - // Returns the sum of our FlexItems' outer hypothetical main sizes. + nscoord GetSumOfGaps() const { + return mNumItems > 0 ? (mNumItems - 1) * mMainGapSize : 0; + } + + // Returns the sum of our FlexItems' outer hypothetical main sizes plus the + // sum of main axis {row,column}-gaps between items. // ("outer" = margin-box, and "hypothetical" = before flexing) nscoord GetTotalOuterHypotheticalMainSize() const { return mTotalOuterHypotheticalMainSize; @@ -983,7 +989,15 @@ public: mTotalItemMBP = AddChecked(mTotalItemMBP, itemMBP); mTotalOuterHypotheticalMainSize = - AddChecked(mTotalOuterHypotheticalMainSize, aItemOuterHypotheticalMainSize); + AddChecked(mTotalOuterHypotheticalMainSize, + aItemOuterHypotheticalMainSize); + + // If the item added was not the first item in the line, we add in any gap + // space as needed. + if (mNumItems >= 2) { + mTotalOuterHypotheticalMainSize = + AddChecked(mTotalOuterHypotheticalMainSize, mMainGapSize); + } } // Computes the cross-size and baseline position of this FlexLine, based on @@ -1027,6 +1041,22 @@ public: return mLastBaselineOffset; } + /** + * Returns the number of items held in this line. Used for total gap + * calculations. + */ + uint32_t GetNumItems() const { + return mNumItems; + } + + /** + * Returns the gap size in the main axis for this line. Used for gap + * calculations. + */ + nscoord GetMainGapSize() const { + return mMainGapSize; + } + // Runs the "Resolving Flexible Lengths" algorithm from section 9.7 of the // CSS flexbox spec to distribute aFlexContainerMainSize among our flex items. void ResolveFlexibleLengths(nscoord aFlexContainerMainSize, @@ -1064,14 +1094,18 @@ private: // Sum of margin/border/padding for the FlexItems in this FlexLine. nscoord mTotalItemMBP; - // Sum of FlexItems' outer hypothetical main sizes. + // Sum of FlexItems' outer hypothetical main sizes and all main-axis + // {row,columnm}-gaps between items. // (i.e. their flex base sizes, clamped via their min/max-size properties, - // plus their main-axis margin/border/padding.) + // plus their main-axis margin/border/padding, plus the sum of the gaps.) nscoord mTotalOuterHypotheticalMainSize; nscoord mLineCrossSize; nscoord mFirstBaselineOffset; nscoord mLastBaselineOffset; + + // Maintain size of each {row,column}-gap in the main axis + const nscoord mMainGapSize; }; // Information about a strut left behind by a FlexItem that's been collapsed @@ -2232,6 +2266,9 @@ public: "miscounted the number of auto margins"); } + // Advances past the gap space (if any) between two flex items + void TraverseGap(nscoord aGapSize) { mPosition += aGapSize; } + // Advances past the packing space (if any) between two flex items void TraversePackingSpace(); @@ -2257,7 +2294,11 @@ public: const ReflowInput& aReflowInput, nscoord aContentBoxCrossSize, bool aIsCrossSizeDefinite, - const FlexboxAxisTracker& aAxisTracker); + const FlexboxAxisTracker& aAxisTracker, + const nscoord aCrossGapSize); + + // Advances past the gap (if any) between two flex lines + void TraverseGap() { mPosition += mCrossGapSize; } // Advances past the packing space (if any) between two flex lines void TraversePackingSpace(); @@ -2279,6 +2320,8 @@ private: uint32_t mNumPackingSpacesRemaining; // XXX this should be uint16_t when we add explicit fallback handling uint8_t mAlignContent; + + nscoord mCrossGapSize = 0; }; // Utility class for managing our position along the cross axis, *within* a @@ -2559,11 +2602,11 @@ FlexLine::ResolveFlexibleLengths(nscoord aFlexContainerMainSize, MOZ_ASSERT(!IsEmpty() || aLineInfo, "empty lines should take the early-return above"); - // Subtract space occupied by our items' margins/borders/padding, so we can - // just be dealing with the space available for our flex items' content + // Subtract space occupied by our items' margins/borders/padding/gaps, so + // we can just be dealing with the space available for our flex items' content // boxes. nscoord spaceAvailableForFlexItemsContentBoxes = - aFlexContainerMainSize - mTotalItemMBP; + aFlexContainerMainSize - (mTotalItemMBP + GetSumOfGaps()); nscoord origAvailableFreeSpace; bool isOrigAvailFreeSpaceInitialized = false; @@ -2900,6 +2943,9 @@ MainAxisPositionTracker:: mNumAutoMarginsInMainAxis += item->GetNumAutoMarginsInAxis(mAxis); } + // Subtract space required for row/col gap from the remaining packing space + mPackingSpaceRemaining -= aLine->GetSumOfGaps(); + if (mPackingSpaceRemaining <= 0) { // No available packing space to use for resolving auto margins. mNumAutoMarginsInMainAxis = 0; @@ -3047,12 +3093,14 @@ CrossAxisPositionTracker:: const ReflowInput& aReflowInput, nscoord aContentBoxCrossSize, bool aIsCrossSizeDefinite, - const FlexboxAxisTracker& aAxisTracker) + const FlexboxAxisTracker& aAxisTracker, + const nscoord aCrossGapSize) : PositionTracker(aAxisTracker.GetCrossAxis(), aAxisTracker.IsCrossAxisReversed()), mPackingSpaceRemaining(0), mNumPackingSpacesRemaining(0), - mAlignContent(aReflowInput.mStylePosition->mAlignContent) + mAlignContent(aReflowInput.mStylePosition->mAlignContent), + mCrossGapSize(aCrossGapSize) { MOZ_ASSERT(aFirstLine, "null first line pointer"); @@ -3105,6 +3153,11 @@ CrossAxisPositionTracker:: numLines++; } + // Subtract space required for row/col gap from the remaining packing space + MOZ_ASSERT(numLines >= 1, + "GenerateFlexLines should've produced at least 1 line"); + mPackingSpaceRemaining -= aCrossGapSize * (numLines - 1); + // If packing space is negative, 'space-between' and 'stretch' behave like // 'flex-start', and 'space-around' and 'space-evenly' behave like 'center'. // In those cases, it's simplest to just pretend we have a different @@ -3709,9 +3762,10 @@ FlexboxAxisTracker::InitAxesFromModernProps( // back depending on aShouldInsertAtFront), and returns a pointer to it. static FlexLine* AddNewFlexLineToList(LinkedList& aLines, - bool aShouldInsertAtFront) + bool aShouldInsertAtFront, + nscoord aMainGapSize) { - FlexLine* newLine = new FlexLine(); + FlexLine* newLine = new FlexLine(aMainGapSize); if (aShouldInsertAtFront) { aLines.insertFront(newLine); } else { @@ -3761,6 +3815,7 @@ nsFlexContainerFrame::GenerateFlexLines( nscoord aAvailableBSizeForContent, const nsTArray& aStruts, const FlexboxAxisTracker& aAxisTracker, + nscoord aMainGapSize, nsTArray& aPlaceholders, /* out */ LinkedList& aLines /* out */) { @@ -3779,7 +3834,8 @@ nsFlexContainerFrame::GenerateFlexLines( // We have at least one FlexLine. Even an empty flex container has a single // (empty) flex line. - FlexLine* curLine = AddNewFlexLineToList(aLines, shouldInsertAtFront); + FlexLine* curLine = AddNewFlexLineToList(aLines, shouldInsertAtFront, + aMainGapSize); nscoord wrapThreshold; if (isSingleLine) { @@ -3843,7 +3899,7 @@ nsFlexContainerFrame::GenerateFlexLines( // Honor "page-break-before", if we're multi-line and this line isn't empty: if (!isSingleLine && !curLine->IsEmpty() && childFrame->StyleDisplay()->mBreakBefore) { - curLine = AddNewFlexLineToList(aLines, shouldInsertAtFront); + curLine = AddNewFlexLineToList(aLines, shouldInsertAtFront, aMainGapSize); } UniquePtr item; @@ -3883,21 +3939,25 @@ nsFlexContainerFrame::GenerateFlexLines( nscoord newOuterSize = AddChecked(curLine->GetTotalOuterHypotheticalMainSize(), itemOuterHypotheticalMainSize); + // Account for gap between this line's previous item and this item + newOuterSize = AddChecked(newOuterSize, aMainGapSize); if (newOuterSize == nscoord_MAX || newOuterSize > wrapThreshold) { - curLine = AddNewFlexLineToList(aLines, shouldInsertAtFront); + curLine = AddNewFlexLineToList(aLines, shouldInsertAtFront, + aMainGapSize); } } // Add item to current flex line (and update the line's bookkeeping about // how large its items collectively are). - curLine->AddItem(item.release(), shouldInsertAtFront, + curLine->AddItem(item.release(), + shouldInsertAtFront, itemInnerHypotheticalMainSize, itemOuterHypotheticalMainSize); // Honor "page-break-after", if we're multi-line and have more children: if (!isSingleLine && childFrame->GetNextSibling() && childFrame->StyleDisplay()->mBreakAfter) { - curLine = AddNewFlexLineToList(aLines, shouldInsertAtFront); + curLine = AddNewFlexLineToList(aLines, shouldInsertAtFront, aMainGapSize); } itemIdxInContainer++; } @@ -4089,6 +4149,9 @@ FlexLine::PositionItemsInMainAxis(uint8_t aJustifyContent, mainAxisPosnTracker.ExitChildFrame(itemMainBorderBoxSize); mainAxisPosnTracker.ExitMargin(item->GetMargin()); mainAxisPosnTracker.TraversePackingSpace(); + if (item->getNext()) { + mainAxisPosnTracker.TraverseGap(mMainGapSize); + } } } @@ -4270,18 +4333,38 @@ nsFlexContainerFrame::Reflow(nsPresContext* aPresContext, nscoord contentBoxMainSize = GetMainSizeFromReflowInput(aReflowInput, axisTracker); + // Calculate gap size for main and cross axis + nscoord mainGapSize; + nscoord crossGapSize; + if (axisTracker.IsRowOriented()) { + mainGapSize = nsLayoutUtils::ResolveGapToLength(stylePos->mColumnGap, + contentBoxMainSize); + crossGapSize = + nsLayoutUtils::ResolveGapToLength(stylePos->mRowGap, + GetEffectiveComputedBSize(aReflowInput)); + } else { + mainGapSize = nsLayoutUtils::ResolveGapToLength(stylePos->mRowGap, + contentBoxMainSize); + NS_WARNING_ASSERTION(aReflowInput.ComputedISize() != NS_UNCONSTRAINEDSIZE, + "Unconstrained inline size; this should only result " + "from huge sizes (not intrinsic sizing w/ orthogonal " + "flows)"); + crossGapSize = + nsLayoutUtils::ResolveGapToLength(stylePos->mColumnGap, + aReflowInput.ComputedISize()); + } AutoTArray struts; DoFlexLayout(aPresContext, aDesiredSize, aReflowInput, aStatus, contentBoxMainSize, availableBSizeForContent, - struts, axisTracker); + struts, axisTracker, mainGapSize, crossGapSize); if (!struts.IsEmpty()) { // We're restarting flex layout, with new knowledge of collapsed items. aStatus.Reset(); DoFlexLayout(aPresContext, aDesiredSize, aReflowInput, aStatus, contentBoxMainSize, availableBSizeForContent, - struts, axisTracker); + struts, axisTracker, mainGapSize, crossGapSize); } } @@ -4515,7 +4598,9 @@ nsFlexContainerFrame::DoFlexLayout(nsPresContext* aPresContext, nscoord aContentBoxMainSize, nscoord aAvailableBSizeForContent, nsTArray& aStruts, - const FlexboxAxisTracker& aAxisTracker) + const FlexboxAxisTracker& aAxisTracker, + nscoord aMainGapSize, + nscoord aCrossGapSize) { MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!"); @@ -4527,6 +4612,7 @@ nsFlexContainerFrame::DoFlexLayout(nsPresContext* aPresContext, aContentBoxMainSize, aAvailableBSizeForContent, aStruts, aAxisTracker, + aMainGapSize, placeholderKids, lines); if (lines.getFirst()->IsEmpty() && @@ -4679,7 +4765,8 @@ nsFlexContainerFrame::DoFlexLayout(nsPresContext* aPresContext, CrossAxisPositionTracker crossAxisPosnTracker(lines.getFirst(), aReflowInput, contentBoxCrossSize, - isCrossSizeDefinite, aAxisTracker); + isCrossSizeDefinite, aAxisTracker, + aCrossGapSize); // Now that we know the cross size of each line (including // "align-content:stretch" adjustments, from the CrossAxisPositionTracker @@ -4717,6 +4804,7 @@ nsFlexContainerFrame::DoFlexLayout(nsPresContext* aPresContext, ConvertLegacyStyleToJustifyContent(StyleXUL()) : aReflowInput.mStylePosition->mJustifyContent; + lineIndex = 0; for (FlexLine* line = lines.getFirst(); line; line = line->getNext(), ++lineIndex) { @@ -4738,6 +4826,10 @@ nsFlexContainerFrame::DoFlexLayout(nsPresContext* aPresContext, aAxisTracker); crossAxisPosnTracker.TraverseLine(*line); crossAxisPosnTracker.TraversePackingSpace(); + + if (line->getNext()) { + crossAxisPosnTracker.TraverseGap(); + } } // If the container should derive its baseline from the last FlexLine, @@ -5145,9 +5237,22 @@ nsFlexContainerFrame::IntrinsicISize(gfxContext* aRenderingContext, const nsStylePosition* stylePos = StylePosition(); const FlexboxAxisTracker axisTracker(this, GetWritingMode()); + nscoord mainGapSize; + if (axisTracker.IsRowOriented()) { + mainGapSize = nsLayoutUtils::ResolveGapToLength(stylePos->mColumnGap, + NS_UNCONSTRAINEDSIZE); + } else { + mainGapSize = nsLayoutUtils::ResolveGapToLength(stylePos->mRowGap, + NS_UNCONSTRAINEDSIZE); + } + const bool useMozBoxCollapseBehavior = ShouldUseMozBoxCollapseBehavior(StyleDisplay()); + // The loop below sets aside space for a gap before each item besides the + // first. This bool helps us handle that special-case. + bool onFirstChild = true; + for (nsIFrame* childFrame : mFrames) { // If we're using legacy "visibility:collapse" behavior, then we don't // care about the sizes of any collapsed children. @@ -5158,7 +5263,8 @@ nsFlexContainerFrame::IntrinsicISize(gfxContext* aRenderingContext, nsLayoutUtils::IntrinsicForContainer(aRenderingContext, childFrame, aType); // * For a row-oriented single-line flex container, the intrinsic - // {min/pref}-isize is the sum of its items' {min/pref}-isizes. + // {min/pref}-isize is the sum of its items' {min/pref}-isizes and + // (n-1) column gaps. // * For a column-oriented flex container, the intrinsic min isize // is the max of its items' min isizes. // * For a row-oriented multi-line flex container, the intrinsic @@ -5167,6 +5273,10 @@ nsFlexContainerFrame::IntrinsicISize(gfxContext* aRenderingContext, if (axisTracker.IsRowOriented() && (isSingleLine || aType == nsLayoutUtils::PREF_ISIZE)) { containerISize += childISize; + if (!onFirstChild) { + containerISize += mainGapSize; + } + onFirstChild = false; } else { // (col-oriented, or MIN_ISIZE for multi-line row flex container) containerISize = std::max(containerISize, childISize); } diff --git a/layout/generic/nsFlexContainerFrame.h b/layout/generic/nsFlexContainerFrame.h index 289829d123e2..693cf60523af 100644 --- a/layout/generic/nsFlexContainerFrame.h +++ b/layout/generic/nsFlexContainerFrame.h @@ -258,7 +258,9 @@ protected: nscoord aContentBoxMainSize, nscoord aAvailableBSizeForContent, nsTArray& aStruts, - const FlexboxAxisTracker& aAxisTracker); + const FlexboxAxisTracker& aAxisTracker, + nscoord aMainGapSize, + nscoord aCrossGapSize); /** * Checks whether our child-frame list "mFrames" is sorted, using the given @@ -353,6 +355,7 @@ protected: nscoord aAvailableBSizeForContent, const nsTArray& aStruts, const FlexboxAxisTracker& aAxisTracker, + nscoord aMainGapSize, nsTArray& aPlaceholders, mozilla::LinkedList& aLines); diff --git a/layout/reftests/w3c-css/submitted/flexbox/flexbox-column-row-gap-001-ref.html b/layout/reftests/w3c-css/submitted/flexbox/flexbox-column-row-gap-001-ref.html new file mode 100644 index 000000000000..0606afe53987 --- /dev/null +++ b/layout/reftests/w3c-css/submitted/flexbox/flexbox-column-row-gap-001-ref.html @@ -0,0 +1,97 @@ + + + + + + Reference: Testing row and column gaps in horizontal and vertical multi-line flex containers with the space-around property and auto margins + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + diff --git a/layout/reftests/w3c-css/submitted/flexbox/flexbox-column-row-gap-001.html b/layout/reftests/w3c-css/submitted/flexbox/flexbox-column-row-gap-001.html new file mode 100644 index 000000000000..25b28e809475 --- /dev/null +++ b/layout/reftests/w3c-css/submitted/flexbox/flexbox-column-row-gap-001.html @@ -0,0 +1,65 @@ + + + + + + CSS Test: Testing row and column gaps in horizontal and vertical multi-line flex containers with the space-around property and auto margins + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + diff --git a/layout/reftests/w3c-css/submitted/flexbox/flexbox-column-row-gap-002-ref.html b/layout/reftests/w3c-css/submitted/flexbox/flexbox-column-row-gap-002-ref.html new file mode 100644 index 000000000000..e23ddfe26151 --- /dev/null +++ b/layout/reftests/w3c-css/submitted/flexbox/flexbox-column-row-gap-002-ref.html @@ -0,0 +1,67 @@ + + + + + + Reference: Testing row and column gaps in vertical writing mode multi-line flex containers + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + diff --git a/layout/reftests/w3c-css/submitted/flexbox/flexbox-column-row-gap-002.html b/layout/reftests/w3c-css/submitted/flexbox/flexbox-column-row-gap-002.html new file mode 100644 index 000000000000..fcd19cf8d483 --- /dev/null +++ b/layout/reftests/w3c-css/submitted/flexbox/flexbox-column-row-gap-002.html @@ -0,0 +1,62 @@ + + + + + + CSS Test: Testing row and column gaps in vertical writing mode multi-line flex containers + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + diff --git a/layout/reftests/w3c-css/submitted/flexbox/reftest.list b/layout/reftests/w3c-css/submitted/flexbox/reftest.list index 4121cfc26166..b822d4a77dab 100644 --- a/layout/reftests/w3c-css/submitted/flexbox/reftest.list +++ b/layout/reftests/w3c-css/submitted/flexbox/reftest.list @@ -98,6 +98,10 @@ fuzzy-if(Android,158,32) == flexbox-align-self-vert-rtl-001.xhtml flexbox-align == flexbox-collapsed-item-horiz-002.html flexbox-collapsed-item-horiz-002-ref.html == flexbox-collapsed-item-horiz-003.html flexbox-collapsed-item-horiz-003-ref.html +# Tests for "row gap" and "column gap" +== flexbox-column-row-gap-001.html flexbox-column-row-gap-001-ref.html +== flexbox-column-row-gap-002.html flexbox-column-row-gap-002-ref.html + # Tests for "flex-basis: content" == flexbox-flex-basis-content-001a.html flexbox-flex-basis-content-001-ref.html == flexbox-flex-basis-content-001b.html flexbox-flex-basis-content-001-ref.html