| /* |
| * This file is part of the render object implementation for KHTML. |
| * |
| * Copyright (C) 1999 Lars Knoll (knoll@kde.org) |
| * (C) 1999 Antti Koivisto (koivisto@kde.org) |
| * Copyright (C) 2003 Apple Computer, Inc. |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public License |
| * along with this library; see the file COPYING.LIB. If not, write to |
| * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| * |
| */ |
| |
| #include "config.h" |
| #include "RenderFlexibleBox.h" |
| |
| #include "CharacterNames.h" |
| #include "RenderLayer.h" |
| #include "RenderView.h" |
| #include <wtf/StdLibExtras.h> |
| |
| using namespace std; |
| |
| namespace WebCore { |
| |
| class FlexBoxIterator { |
| public: |
| FlexBoxIterator(RenderFlexibleBox* parent) |
| { |
| box = parent; |
| if (box->style()->boxOrient() == HORIZONTAL && box->style()->direction() == RTL) |
| forward = box->style()->boxDirection() != BNORMAL; |
| else |
| forward = box->style()->boxDirection() == BNORMAL; |
| lastOrdinal = 1; |
| if (!forward) { |
| // No choice, since we're going backwards, we have to find out the highest ordinal up front. |
| RenderBox* child = box->firstChildBox(); |
| while (child) { |
| if (child->style()->boxOrdinalGroup() > lastOrdinal) |
| lastOrdinal = child->style()->boxOrdinalGroup(); |
| child = child->nextSiblingBox(); |
| } |
| } |
| |
| reset(); |
| } |
| |
| void reset() |
| { |
| current = 0; |
| currentOrdinal = forward ? 0 : lastOrdinal+1; |
| } |
| |
| RenderBox* first() |
| { |
| reset(); |
| return next(); |
| } |
| |
| RenderBox* next() |
| { |
| do { |
| if (!current) { |
| if (forward) { |
| currentOrdinal++; |
| if (currentOrdinal > lastOrdinal) |
| return 0; |
| current = box->firstChildBox(); |
| } else { |
| currentOrdinal--; |
| if (currentOrdinal == 0) |
| return 0; |
| current = box->lastChildBox(); |
| } |
| } |
| else |
| current = forward ? current->nextSiblingBox() : current->previousSiblingBox(); |
| if (current && current->style()->boxOrdinalGroup() > lastOrdinal) |
| lastOrdinal = current->style()->boxOrdinalGroup(); |
| } while (!current || current->style()->boxOrdinalGroup() != currentOrdinal || |
| current->style()->visibility() == COLLAPSE); |
| return current; |
| } |
| |
| private: |
| RenderFlexibleBox* box; |
| RenderBox* current; |
| bool forward; |
| unsigned int currentOrdinal; |
| unsigned int lastOrdinal; |
| }; |
| |
| RenderFlexibleBox::RenderFlexibleBox(Node* node) |
| :RenderBlock(node) |
| { |
| setChildrenInline(false); // All of our children must be block-level |
| m_flexingChildren = m_stretchingChildren = false; |
| } |
| |
| RenderFlexibleBox::~RenderFlexibleBox() |
| { |
| } |
| |
| void RenderFlexibleBox::calcHorizontalPrefWidths() |
| { |
| for (RenderBox* child = firstChildBox(); child; child = child->nextSiblingBox()) { |
| // positioned children don't affect the minmaxwidth |
| if (child->isPositioned() || child->style()->visibility() == COLLAPSE) |
| continue; |
| |
| // A margin basically has three types: fixed, percentage, and auto (variable). |
| // Auto and percentage margins simply become 0 when computing min/max width. |
| // Fixed margins can be added in as is. |
| Length ml = child->style()->marginLeft(); |
| Length mr = child->style()->marginRight(); |
| int margin = 0, marginLeft = 0, marginRight = 0; |
| if (ml.isFixed()) |
| marginLeft += ml.value(); |
| if (mr.isFixed()) |
| marginRight += mr.value(); |
| margin = marginLeft + marginRight; |
| |
| m_minPrefWidth += child->minPrefWidth() + margin; |
| m_maxPrefWidth += child->maxPrefWidth() + margin; |
| } |
| } |
| |
| void RenderFlexibleBox::calcVerticalPrefWidths() |
| { |
| for (RenderBox* child = firstChildBox(); child; child = child->nextSiblingBox()) { |
| // Positioned children and collapsed children don't affect the min/max width |
| if (child->isPositioned() || child->style()->visibility() == COLLAPSE) |
| continue; |
| |
| // A margin basically has three types: fixed, percentage, and auto (variable). |
| // Auto/percentage margins simply become 0 when computing min/max width. |
| // Fixed margins can be added in as is. |
| Length ml = child->style()->marginLeft(); |
| Length mr = child->style()->marginRight(); |
| int margin = 0; |
| if (ml.isFixed()) |
| margin += ml.value(); |
| if (mr.isFixed()) |
| margin += mr.value(); |
| |
| int w = child->minPrefWidth() + margin; |
| m_minPrefWidth = max(w, m_minPrefWidth); |
| |
| w = child->maxPrefWidth() + margin; |
| m_maxPrefWidth = max(w, m_maxPrefWidth); |
| } |
| } |
| |
| void RenderFlexibleBox::calcPrefWidths() |
| { |
| ASSERT(prefWidthsDirty()); |
| |
| if (style()->width().isFixed() && style()->width().value() > 0) |
| m_minPrefWidth = m_maxPrefWidth = calcContentBoxWidth(style()->width().value()); |
| else { |
| m_minPrefWidth = m_maxPrefWidth = 0; |
| |
| if (hasMultipleLines() || isVertical()) |
| calcVerticalPrefWidths(); |
| else |
| calcHorizontalPrefWidths(); |
| |
| m_maxPrefWidth = max(m_minPrefWidth, m_maxPrefWidth); |
| } |
| |
| if (style()->minWidth().isFixed() && style()->minWidth().value() > 0) { |
| m_maxPrefWidth = max(m_maxPrefWidth, calcContentBoxWidth(style()->minWidth().value())); |
| m_minPrefWidth = max(m_minPrefWidth, calcContentBoxWidth(style()->minWidth().value())); |
| } |
| |
| if (style()->maxWidth().isFixed() && style()->maxWidth().value() != undefinedLength) { |
| m_maxPrefWidth = min(m_maxPrefWidth, calcContentBoxWidth(style()->maxWidth().value())); |
| m_minPrefWidth = min(m_minPrefWidth, calcContentBoxWidth(style()->maxWidth().value())); |
| } |
| |
| int toAdd = borderLeft() + borderRight() + paddingLeft() + paddingRight(); |
| m_minPrefWidth += toAdd; |
| m_maxPrefWidth += toAdd; |
| |
| setPrefWidthsDirty(false); |
| } |
| |
| void RenderFlexibleBox::layoutBlock(bool relayoutChildren) |
| { |
| ASSERT(needsLayout()); |
| |
| if (!relayoutChildren && layoutOnlyPositionedObjects()) |
| return; |
| |
| LayoutRepainter repainter(*this, checkForRepaintDuringLayout()); |
| LayoutStateMaintainer statePusher(view(), this, IntSize(x(), y()), hasTransform() || hasReflection()); |
| |
| int previousWidth = width(); |
| int previousHeight = height(); |
| |
| calcWidth(); |
| calcHeight(); |
| m_overflowWidth = width(); |
| |
| if (previousWidth != width() || previousHeight != height() || |
| (parent()->isFlexibleBox() && parent()->style()->boxOrient() == HORIZONTAL && |
| parent()->style()->boxAlign() == BSTRETCH)) |
| relayoutChildren = true; |
| |
| setHeight(0); |
| m_overflowHeight = 0; |
| m_flexingChildren = m_stretchingChildren = false; |
| |
| initMaxMarginValues(); |
| |
| // For overflow:scroll blocks, ensure we have both scrollbars in place always. |
| if (scrollsOverflow()) { |
| if (style()->overflowX() == OSCROLL) |
| layer()->setHasHorizontalScrollbar(true); |
| if (style()->overflowY() == OSCROLL) |
| layer()->setHasVerticalScrollbar(true); |
| } |
| |
| if (isHorizontal()) |
| layoutHorizontalBox(relayoutChildren); |
| else |
| layoutVerticalBox(relayoutChildren); |
| |
| int oldHeight = height(); |
| calcHeight(); |
| if (oldHeight != height()) { |
| // If the block got expanded in size, then increase our overflowheight to match. |
| if (m_overflowHeight > height()) |
| m_overflowHeight -= (borderBottom() + paddingBottom() + horizontalScrollbarHeight()); |
| if (m_overflowHeight < height()) |
| m_overflowHeight = height(); |
| } |
| if (previousHeight != height()) |
| relayoutChildren = true; |
| |
| layoutPositionedObjects(relayoutChildren || isRoot()); |
| |
| if (!isFloatingOrPositioned() && height() == 0) { |
| // We are a block with no border and padding and a computed height |
| // of 0. The CSS spec states that zero-height blocks collapse their margins |
| // together. |
| // When blocks are self-collapsing, we just use the top margin values and set the |
| // bottom margin max values to 0. This way we don't factor in the values |
| // twice when we collapse with our previous vertically adjacent and |
| // following vertically adjacent blocks. |
| int pos = maxTopPosMargin(); |
| int neg = maxTopNegMargin(); |
| if (maxBottomPosMargin() > pos) |
| pos = maxBottomPosMargin(); |
| if (maxBottomNegMargin() > neg) |
| neg = maxBottomNegMargin(); |
| setMaxTopMargins(pos, neg); |
| setMaxBottomMargins(0, 0); |
| } |
| |
| // Always ensure our overflow width is at least as large as our width. |
| if (m_overflowWidth < width()) |
| m_overflowWidth = width(); |
| |
| if (!hasOverflowClip()) { |
| for (ShadowData* boxShadow = style()->boxShadow(); boxShadow; boxShadow = boxShadow->next) { |
| m_overflowLeft = min(m_overflowLeft, boxShadow->x - boxShadow->blur); |
| m_overflowWidth = max(m_overflowWidth, width() + boxShadow->x + boxShadow->blur); |
| m_overflowTop = min(m_overflowTop, boxShadow->y - boxShadow->blur); |
| m_overflowHeight = max(m_overflowHeight, height() + boxShadow->y + boxShadow->blur); |
| } |
| |
| if (hasReflection()) { |
| IntRect reflection(reflectionBox()); |
| m_overflowTop = min(m_overflowTop, reflection.y()); |
| m_overflowHeight = max(m_overflowHeight, reflection.bottom()); |
| m_overflowLeft = min(m_overflowLeft, reflection.x()); |
| m_overflowHeight = max(m_overflowWidth, reflection.right()); |
| } |
| } |
| |
| statePusher.pop(); |
| |
| // Update our scrollbars if we're overflow:auto/scroll/hidden now that we know if |
| // we overflow or not. |
| if (hasOverflowClip()) |
| layer()->updateScrollInfoAfterLayout(); |
| |
| // Repaint with our new bounds if they are different from our old bounds. |
| repainter.repaintAfterLayout(); |
| |
| setNeedsLayout(false); |
| } |
| |
| void RenderFlexibleBox::layoutHorizontalBox(bool relayoutChildren) |
| { |
| int toAdd = borderBottom() + paddingBottom() + horizontalScrollbarHeight(); |
| int yPos = borderTop() + paddingTop(); |
| int xPos = borderLeft() + paddingLeft(); |
| bool heightSpecified = false; |
| int oldHeight = 0; |
| |
| unsigned int highestFlexGroup = 0; |
| unsigned int lowestFlexGroup = 0; |
| bool haveFlex = false; |
| int remainingSpace = 0; |
| m_overflowHeight = height(); |
| |
| // The first walk over our kids is to find out if we have any flexible children. |
| FlexBoxIterator iterator(this); |
| RenderBox* child = iterator.next(); |
| while (child) { |
| // Check to see if this child flexes. |
| if (!child->isPositioned() && child->style()->boxFlex() > 0.0f) { |
| // We always have to lay out flexible objects again, since the flex distribution |
| // may have changed, and we need to reallocate space. |
| child->setOverrideSize(-1); |
| if (!relayoutChildren) |
| child->setChildNeedsLayout(true, false); |
| haveFlex = true; |
| unsigned int flexGroup = child->style()->boxFlexGroup(); |
| if (lowestFlexGroup == 0) |
| lowestFlexGroup = flexGroup; |
| if (flexGroup < lowestFlexGroup) |
| lowestFlexGroup = flexGroup; |
| if (flexGroup > highestFlexGroup) |
| highestFlexGroup = flexGroup; |
| } |
| child = iterator.next(); |
| } |
| |
| RenderBlock::startDelayUpdateScrollInfo(); |
| |
| // We do 2 passes. The first pass is simply to lay everyone out at |
| // their preferred widths. The second pass handles flexing the children. |
| do { |
| // Reset our height. |
| setHeight(yPos); |
| m_overflowHeight = height(); |
| xPos = borderLeft() + paddingLeft(); |
| |
| // Our first pass is done without flexing. We simply lay the children |
| // out within the box. We have to do a layout first in order to determine |
| // our box's intrinsic height. |
| int maxAscent = 0, maxDescent = 0; |
| child = iterator.first(); |
| while (child) { |
| // make sure we relayout children if we need it. |
| if (relayoutChildren || (child->isReplaced() && (child->style()->width().isPercent() || child->style()->height().isPercent()))) |
| child->setChildNeedsLayout(true, false); |
| |
| if (child->isPositioned()) { |
| child = iterator.next(); |
| continue; |
| } |
| |
| // Compute the child's vertical margins. |
| child->calcVerticalMargins(); |
| |
| // Now do the layout. |
| child->layoutIfNeeded(); |
| |
| // Update our height and overflow height. |
| if (style()->boxAlign() == BBASELINE) { |
| int ascent = child->firstLineBoxBaseline(); |
| if (ascent == -1) |
| ascent = child->height() + child->marginBottom(); |
| ascent += child->marginTop(); |
| int descent = (child->marginTop() + child->height() + child->marginBottom()) - ascent; |
| |
| // Update our maximum ascent. |
| maxAscent = max(maxAscent, ascent); |
| |
| // Update our maximum descent. |
| maxDescent = max(maxDescent, descent); |
| |
| // Now update our height. |
| setHeight(max(yPos + maxAscent + maxDescent, height())); |
| } |
| else |
| setHeight(max(height(), yPos + child->marginTop() + child->height() + child->marginBottom())); |
| |
| child = iterator.next(); |
| } |
| |
| if (!iterator.first() && hasLineIfEmpty()) |
| setHeight(height() + lineHeight(true, true)); |
| |
| setHeight(height() + toAdd); |
| |
| // Always make sure our overflowheight is at least our height. |
| if (m_overflowHeight < height()) |
| m_overflowHeight = height(); |
| |
| oldHeight = height(); |
| calcHeight(); |
| |
| relayoutChildren = false; |
| if (oldHeight != height()) |
| heightSpecified = true; |
| |
| // Now that our height is actually known, we can place our boxes. |
| m_stretchingChildren = (style()->boxAlign() == BSTRETCH); |
| child = iterator.first(); |
| while (child) { |
| if (child->isPositioned()) { |
| child->containingBlock()->insertPositionedObject(child); |
| if (child->style()->hasStaticX()) { |
| if (style()->direction() == LTR) |
| child->layer()->setStaticX(xPos); |
| else child->layer()->setStaticX(width() - xPos); |
| } |
| if (child->style()->hasStaticY()) |
| child->layer()->setStaticY(yPos); |
| child = iterator.next(); |
| continue; |
| } |
| |
| // We need to see if this child's height has changed, since we make block elements |
| // fill the height of a containing box by default. |
| // Now do a layout. |
| int oldChildHeight = child->height(); |
| child->calcHeight(); |
| if (oldChildHeight != child->height()) |
| child->setChildNeedsLayout(true, false); |
| child->layoutIfNeeded(); |
| |
| // We can place the child now, using our value of box-align. |
| xPos += child->marginLeft(); |
| int childY = yPos; |
| switch (style()->boxAlign()) { |
| case BCENTER: |
| childY += child->marginTop() + max(0, (contentHeight() - (child->height() + child->marginTop() + child->marginBottom()))/2); |
| break; |
| case BBASELINE: { |
| int ascent = child->firstLineBoxBaseline(); |
| if (ascent == -1) |
| ascent = child->height() + child->marginBottom(); |
| ascent += child->marginTop(); |
| childY += child->marginTop() + (maxAscent - ascent); |
| break; |
| } |
| case BEND: |
| childY += contentHeight() - child->marginBottom() - child->height(); |
| break; |
| default: // BSTART |
| childY += child->marginTop(); |
| break; |
| } |
| |
| placeChild(child, xPos, childY); |
| |
| if (child->isRenderBlock()) |
| toRenderBlock(child)->addVisualOverflow(toRenderBlock(child)->floatRect()); |
| |
| m_overflowHeight = max(m_overflowHeight, childY + child->overflowHeight(false)); |
| m_overflowTop = min(m_overflowTop, child->y() + child->overflowTop(false)); |
| |
| xPos += child->width() + child->marginRight(); |
| |
| child = iterator.next(); |
| } |
| |
| remainingSpace = borderLeft() + paddingLeft() + contentWidth() - xPos; |
| |
| m_stretchingChildren = false; |
| if (m_flexingChildren) |
| haveFlex = false; // We're done. |
| else if (haveFlex) { |
| // We have some flexible objects. See if we need to grow/shrink them at all. |
| if (!remainingSpace) |
| break; |
| |
| // Allocate the remaining space among the flexible objects. If we are trying to |
| // grow, then we go from the lowest flex group to the highest flex group. For shrinking, |
| // we go from the highest flex group to the lowest group. |
| bool expanding = remainingSpace > 0; |
| unsigned int start = expanding ? lowestFlexGroup : highestFlexGroup; |
| unsigned int end = expanding? highestFlexGroup : lowestFlexGroup; |
| for (unsigned int i = start; i <= end && remainingSpace; i++) { |
| // Always start off by assuming the group can get all the remaining space. |
| int groupRemainingSpace = remainingSpace; |
| do { |
| // Flexing consists of multiple passes, since we have to change ratios every time an object hits its max/min-width |
| // For a given pass, we always start off by computing the totalFlex of all objects that can grow/shrink at all, and |
| // computing the allowed growth before an object hits its min/max width (and thus |
| // forces a totalFlex recomputation). |
| int groupRemainingSpaceAtBeginning = groupRemainingSpace; |
| float totalFlex = 0.0f; |
| child = iterator.first(); |
| while (child) { |
| if (allowedChildFlex(child, expanding, i)) |
| totalFlex += child->style()->boxFlex(); |
| child = iterator.next(); |
| } |
| child = iterator.first(); |
| int spaceAvailableThisPass = groupRemainingSpace; |
| while (child) { |
| int allowedFlex = allowedChildFlex(child, expanding, i); |
| if (allowedFlex) { |
| int projectedFlex = (allowedFlex == INT_MAX) ? allowedFlex : (int)(allowedFlex * (totalFlex / child->style()->boxFlex())); |
| spaceAvailableThisPass = expanding ? min(spaceAvailableThisPass, projectedFlex) : max(spaceAvailableThisPass, projectedFlex); |
| } |
| child = iterator.next(); |
| } |
| |
| // The flex groups may not have any flexible objects this time around. |
| if (!spaceAvailableThisPass || totalFlex == 0.0f) { |
| // If we just couldn't grow/shrink any more, then it's time to transition to the next flex group. |
| groupRemainingSpace = 0; |
| continue; |
| } |
| |
| // Now distribute the space to objects. |
| child = iterator.first(); |
| while (child && spaceAvailableThisPass && totalFlex) { |
| if (allowedChildFlex(child, expanding, i)) { |
| int spaceAdd = (int)(spaceAvailableThisPass * (child->style()->boxFlex()/totalFlex)); |
| if (spaceAdd) { |
| child->setOverrideSize(child->overrideWidth() + spaceAdd); |
| m_flexingChildren = true; |
| relayoutChildren = true; |
| } |
| |
| spaceAvailableThisPass -= spaceAdd; |
| remainingSpace -= spaceAdd; |
| groupRemainingSpace -= spaceAdd; |
| |
| totalFlex -= child->style()->boxFlex(); |
| } |
| child = iterator.next(); |
| } |
| if (groupRemainingSpace == groupRemainingSpaceAtBeginning) { |
| // this is not advancing, avoid getting stuck by distributing the remaining pixels |
| child = iterator.first(); |
| int spaceAdd = groupRemainingSpace > 0 ? 1 : -1; |
| while (child && groupRemainingSpace) { |
| if (allowedChildFlex(child, expanding, i)) { |
| child->setOverrideSize(child->overrideWidth() + spaceAdd); |
| m_flexingChildren = true; |
| relayoutChildren = true; |
| remainingSpace -= spaceAdd; |
| groupRemainingSpace -= spaceAdd; |
| } |
| child = iterator.next(); |
| } |
| } |
| } while (groupRemainingSpace); |
| } |
| |
| // We didn't find any children that could grow. |
| if (haveFlex && !m_flexingChildren) |
| haveFlex = false; |
| } |
| } while (haveFlex); |
| |
| m_flexingChildren = false; |
| |
| RenderBlock::finishDelayUpdateScrollInfo(); |
| |
| if (remainingSpace > 0 && ((style()->direction() == LTR && style()->boxPack() != BSTART) || |
| (style()->direction() == RTL && style()->boxPack() != BEND))) { |
| // Children must be repositioned. |
| int offset = 0; |
| if (style()->boxPack() == BJUSTIFY) { |
| // Determine the total number of children. |
| int totalChildren = 0; |
| child = iterator.first(); |
| while (child) { |
| if (child->isPositioned()) { |
| child = iterator.next(); |
| continue; |
| } |
| totalChildren++; |
| child = iterator.next(); |
| } |
| |
| // Iterate over the children and space them out according to the |
| // justification level. |
| if (totalChildren > 1) { |
| totalChildren--; |
| bool firstChild = true; |
| child = iterator.first(); |
| while (child) { |
| if (child->isPositioned()) { |
| child = iterator.next(); |
| continue; |
| } |
| |
| if (firstChild) { |
| firstChild = false; |
| child = iterator.next(); |
| continue; |
| } |
| |
| offset += remainingSpace/totalChildren; |
| remainingSpace -= (remainingSpace/totalChildren); |
| totalChildren--; |
| |
| placeChild(child, child->x()+offset, child->y()); |
| child = iterator.next(); |
| } |
| } |
| } else { |
| if (style()->boxPack() == BCENTER) |
| offset += remainingSpace/2; |
| else // END for LTR, START for RTL |
| offset += remainingSpace; |
| child = iterator.first(); |
| while (child) { |
| if (child->isPositioned()) { |
| child = iterator.next(); |
| continue; |
| } |
| placeChild(child, child->x()+offset, child->y()); |
| child = iterator.next(); |
| } |
| } |
| } |
| |
| child = iterator.first(); |
| while (child && child->isPositioned()) { |
| child = iterator.next(); |
| } |
| |
| if (child) { |
| m_overflowLeft = min(child->x() + child->overflowLeft(false), m_overflowLeft); |
| |
| RenderBox* lastChild = child; |
| while ((child = iterator.next())) { |
| if (!child->isPositioned()) |
| lastChild = child; |
| } |
| m_overflowWidth = max(lastChild->x() + lastChild->overflowWidth(false), m_overflowWidth); |
| } |
| |
| // So that the calcHeight in layoutBlock() knows to relayout positioned objects because of |
| // a height change, we revert our height back to the intrinsic height before returning. |
| if (heightSpecified) |
| setHeight(oldHeight); |
| } |
| |
| void RenderFlexibleBox::layoutVerticalBox(bool relayoutChildren) |
| { |
| int xPos = borderLeft() + paddingLeft(); |
| int yPos = borderTop() + paddingTop(); |
| if (style()->direction() == RTL) |
| xPos = width() - paddingRight() - borderRight(); |
| int toAdd = borderBottom() + paddingBottom() + horizontalScrollbarHeight(); |
| bool heightSpecified = false; |
| int oldHeight = 0; |
| |
| unsigned int highestFlexGroup = 0; |
| unsigned int lowestFlexGroup = 0; |
| bool haveFlex = false; |
| int remainingSpace = 0; |
| |
| // The first walk over our kids is to find out if we have any flexible children. |
| FlexBoxIterator iterator(this); |
| RenderBox* child = iterator.next(); |
| while (child) { |
| // Check to see if this child flexes. |
| if (!child->isPositioned() && child->style()->boxFlex() > 0.0f) { |
| // We always have to lay out flexible objects again, since the flex distribution |
| // may have changed, and we need to reallocate space. |
| child->setOverrideSize(-1); |
| if (!relayoutChildren) |
| child->setChildNeedsLayout(true, false); |
| haveFlex = true; |
| unsigned int flexGroup = child->style()->boxFlexGroup(); |
| if (lowestFlexGroup == 0) |
| lowestFlexGroup = flexGroup; |
| if (flexGroup < lowestFlexGroup) |
| lowestFlexGroup = flexGroup; |
| if (flexGroup > highestFlexGroup) |
| highestFlexGroup = flexGroup; |
| } |
| child = iterator.next(); |
| } |
| |
| // We confine the line clamp ugliness to vertical flexible boxes (thus keeping it out of |
| // mainstream block layout); this is not really part of the XUL box model. |
| bool haveLineClamp = style()->lineClamp() >= 0 && style()->lineClamp() <= 100; |
| if (haveLineClamp) { |
| int maxLineCount = 0; |
| child = iterator.first(); |
| while (child) { |
| if (!child->isPositioned()) { |
| if (relayoutChildren || (child->isReplaced() && (child->style()->width().isPercent() || child->style()->height().isPercent())) || |
| (child->style()->height().isAuto() && child->isBlockFlow() && !child->needsLayout())) { |
| child->setChildNeedsLayout(true, false); |
| |
| // Dirty all the positioned objects. |
| if (child->isRenderBlock()) { |
| toRenderBlock(child)->markPositionedObjectsForLayout(); |
| toRenderBlock(child)->clearTruncation(); |
| } |
| } |
| child->layoutIfNeeded(); |
| if (child->style()->height().isAuto() && child->isBlockFlow()) |
| maxLineCount = max(maxLineCount, toRenderBlock(child)->lineCount()); |
| } |
| child = iterator.next(); |
| } |
| |
| // Get the # of lines and then alter all block flow children with auto height to use the |
| // specified height. We always try to leave room for at least one line. |
| int numVisibleLines = max(1, static_cast<int>((maxLineCount + 1) * style()->lineClamp() / 100.0)); |
| if (numVisibleLines < maxLineCount) { |
| for (child = iterator.first(); child; child = iterator.next()) { |
| if (child->isPositioned() || !child->style()->height().isAuto() || !child->isBlockFlow()) |
| continue; |
| |
| RenderBlock* blockChild = toRenderBlock(child); |
| int lineCount = blockChild->lineCount(); |
| if (lineCount <= numVisibleLines) |
| continue; |
| |
| int newHeight = blockChild->heightForLineCount(numVisibleLines); |
| if (newHeight == child->height()) |
| continue; |
| |
| child->setChildNeedsLayout(true, false); |
| child->setOverrideSize(newHeight); |
| m_flexingChildren = true; |
| child->layoutIfNeeded(); |
| m_flexingChildren = false; |
| child->setOverrideSize(-1); |
| |
| // FIXME: For now don't support RTL. |
| if (style()->direction() != LTR) |
| continue; |
| |
| // Get the last line |
| RootInlineBox* lastLine = blockChild->lineAtIndex(lineCount-1); |
| if (!lastLine) |
| continue; |
| |
| // See if the last item is an anchor |
| InlineBox* anchorBox = lastLine->lastChild(); |
| if (!anchorBox) |
| continue; |
| if (!anchorBox->renderer()->node()) |
| continue; |
| if (!anchorBox->renderer()->node()->isLink()) |
| continue; |
| |
| RootInlineBox* lastVisibleLine = blockChild->lineAtIndex(numVisibleLines-1); |
| if (!lastVisibleLine) |
| continue; |
| |
| const UChar ellipsisAndSpace[2] = { horizontalEllipsis, ' ' }; |
| DEFINE_STATIC_LOCAL(AtomicString, ellipsisAndSpaceStr, (ellipsisAndSpace, 2)); |
| |
| const Font& font = style(numVisibleLines == 1)->font(); |
| int ellipsisAndSpaceWidth = font.width(TextRun(ellipsisAndSpace, 2)); |
| |
| // Get ellipsis width + " " + anchor width |
| int totalWidth = ellipsisAndSpaceWidth + anchorBox->width(); |
| |
| // See if this width can be accommodated on the last visible line |
| RenderBlock* destBlock = toRenderBlock(lastVisibleLine->renderer()); |
| RenderBlock* srcBlock = toRenderBlock(lastLine->renderer()); |
| |
| // FIXME: Directions of src/destBlock could be different from our direction and from one another. |
| if (srcBlock->style()->direction() != LTR) |
| continue; |
| if (destBlock->style()->direction() != LTR) |
| continue; |
| int ltr = true; |
| |
| int blockRightEdge = destBlock->rightOffset(lastVisibleLine->y(), false); |
| int blockLeftEdge = destBlock->leftOffset(lastVisibleLine->y(), false); |
| |
| int blockEdge = ltr ? blockRightEdge : blockLeftEdge; |
| if (!lastVisibleLine->canAccommodateEllipsis(ltr, blockEdge, |
| lastVisibleLine->x() + lastVisibleLine->width(), |
| totalWidth)) |
| continue; |
| |
| // Let the truncation code kick in. |
| lastVisibleLine->placeEllipsis(ellipsisAndSpaceStr, ltr, blockLeftEdge, blockRightEdge, totalWidth, anchorBox); |
| destBlock->setHasMarkupTruncation(true); |
| } |
| } |
| } |
| |
| RenderBlock::startDelayUpdateScrollInfo(); |
| |
| // We do 2 passes. The first pass is simply to lay everyone out at |
| // their preferred widths. The second pass handles flexing the children. |
| // Our first pass is done without flexing. We simply lay the children |
| // out within the box. |
| do { |
| setHeight(borderTop() + paddingTop()); |
| int minHeight = height() + toAdd; |
| m_overflowHeight = height(); |
| |
| child = iterator.first(); |
| while (child) { |
| // make sure we relayout children if we need it. |
| if (!haveLineClamp && (relayoutChildren || (child->isReplaced() && (child->style()->width().isPercent() || child->style()->height().isPercent())))) |
| child->setChildNeedsLayout(true, false); |
| |
| if (child->isPositioned()) |
| { |
| child->containingBlock()->insertPositionedObject(child); |
| if (child->style()->hasStaticX()) { |
| if (style()->direction() == LTR) |
| child->layer()->setStaticX(borderLeft()+paddingLeft()); |
| else |
| child->layer()->setStaticX(borderRight()+paddingRight()); |
| } |
| if (child->style()->hasStaticY()) |
| child->layer()->setStaticY(height()); |
| child = iterator.next(); |
| continue; |
| } |
| |
| // Compute the child's vertical margins. |
| child->calcVerticalMargins(); |
| |
| // Add in the child's marginTop to our height. |
| setHeight(height() + child->marginTop()); |
| |
| // Now do a layout. |
| child->layoutIfNeeded(); |
| |
| // We can place the child now, using our value of box-align. |
| int childX = borderLeft() + paddingLeft(); |
| switch (style()->boxAlign()) { |
| case BCENTER: |
| case BBASELINE: // Baseline just maps to center for vertical boxes |
| childX += child->marginLeft() + max(0, (contentWidth() - (child->width() + child->marginLeft() + child->marginRight()))/2); |
| break; |
| case BEND: |
| if (style()->direction() == RTL) |
| childX += child->marginLeft(); |
| else |
| childX += contentWidth() - child->marginRight() - child->width(); |
| break; |
| default: // BSTART/BSTRETCH |
| if (style()->direction() == LTR) |
| childX += child->marginLeft(); |
| else |
| childX += contentWidth() - child->marginRight() - child->width(); |
| break; |
| } |
| |
| // Place the child. |
| placeChild(child, childX, height()); |
| setHeight(height() + child->height() + child->marginBottom()); |
| |
| if (child->isRenderBlock()) |
| toRenderBlock(child)->addVisualOverflow(toRenderBlock(child)->floatRect()); |
| |
| // See if this child has made our overflow need to grow. |
| m_overflowWidth = max(child->x() + child->overflowWidth(false), m_overflowWidth); |
| m_overflowLeft = min(child->x() + child->overflowLeft(false), m_overflowLeft); |
| |
| child = iterator.next(); |
| } |
| |
| yPos = height(); |
| |
| if (!iterator.first() && hasLineIfEmpty()) |
| setHeight(height() + lineHeight(true, true)); |
| |
| setHeight(height() + toAdd); |
| |
| // Negative margins can cause our height to shrink below our minimal height (border/padding). |
| // If this happens, ensure that the computed height is increased to the minimal height. |
| if (height() < minHeight) |
| setHeight(minHeight); |
| |
| // Always make sure our overflowheight is at least our height. |
| if (m_overflowHeight < height()) |
| m_overflowHeight = height(); |
| |
| // Now we have to calc our height, so we know how much space we have remaining. |
| oldHeight = height(); |
| calcHeight(); |
| if (oldHeight != height()) |
| heightSpecified = true; |
| |
| remainingSpace = borderTop() + paddingTop() + contentHeight() - yPos; |
| |
| if (m_flexingChildren) |
| haveFlex = false; // We're done. |
| else if (haveFlex) { |
| // We have some flexible objects. See if we need to grow/shrink them at all. |
| if (!remainingSpace) |
| break; |
| |
| // Allocate the remaining space among the flexible objects. If we are trying to |
| // grow, then we go from the lowest flex group to the highest flex group. For shrinking, |
| // we go from the highest flex group to the lowest group. |
| bool expanding = remainingSpace > 0; |
| unsigned int start = expanding ? lowestFlexGroup : highestFlexGroup; |
| unsigned int end = expanding? highestFlexGroup : lowestFlexGroup; |
| for (unsigned int i = start; i <= end && remainingSpace; i++) { |
| // Always start off by assuming the group can get all the remaining space. |
| int groupRemainingSpace = remainingSpace; |
| do { |
| // Flexing consists of multiple passes, since we have to change ratios every time an object hits its max/min-width |
| // For a given pass, we always start off by computing the totalFlex of all objects that can grow/shrink at all, and |
| // computing the allowed growth before an object hits its min/max width (and thus |
| // forces a totalFlex recomputation). |
| int groupRemainingSpaceAtBeginning = groupRemainingSpace; |
| float totalFlex = 0.0f; |
| child = iterator.first(); |
| while (child) { |
| if (allowedChildFlex(child, expanding, i)) |
| totalFlex += child->style()->boxFlex(); |
| child = iterator.next(); |
| } |
| child = iterator.first(); |
| int spaceAvailableThisPass = groupRemainingSpace; |
| while (child) { |
| int allowedFlex = allowedChildFlex(child, expanding, i); |
| if (allowedFlex) { |
| int projectedFlex = (allowedFlex == INT_MAX) ? allowedFlex : (int)(allowedFlex * (totalFlex / child->style()->boxFlex())); |
| spaceAvailableThisPass = expanding ? min(spaceAvailableThisPass, projectedFlex) : max(spaceAvailableThisPass, projectedFlex); |
| } |
| child = iterator.next(); |
| } |
| |
| // The flex groups may not have any flexible objects this time around. |
| if (!spaceAvailableThisPass || totalFlex == 0.0f) { |
| // If we just couldn't grow/shrink any more, then it's time to transition to the next flex group. |
| groupRemainingSpace = 0; |
| continue; |
| } |
| |
| // Now distribute the space to objects. |
| child = iterator.first(); |
| while (child && spaceAvailableThisPass && totalFlex) { |
| if (allowedChildFlex(child, expanding, i)) { |
| int spaceAdd = (int)(spaceAvailableThisPass * (child->style()->boxFlex()/totalFlex)); |
| if (spaceAdd) { |
| child->setOverrideSize(child->overrideHeight() + spaceAdd); |
| m_flexingChildren = true; |
| relayoutChildren = true; |
| } |
| |
| spaceAvailableThisPass -= spaceAdd; |
| remainingSpace -= spaceAdd; |
| groupRemainingSpace -= spaceAdd; |
| |
| totalFlex -= child->style()->boxFlex(); |
| } |
| child = iterator.next(); |
| } |
| if (groupRemainingSpace == groupRemainingSpaceAtBeginning) { |
| // this is not advancing, avoid getting stuck by distributing the remaining pixels |
| child = iterator.first(); |
| int spaceAdd = groupRemainingSpace > 0 ? 1 : -1; |
| while (child && groupRemainingSpace) { |
| if (allowedChildFlex(child, expanding, i)) { |
| child->setOverrideSize(child->overrideHeight() + spaceAdd); |
| m_flexingChildren = true; |
| relayoutChildren = true; |
| remainingSpace -= spaceAdd; |
| groupRemainingSpace -= spaceAdd; |
| } |
| child = iterator.next(); |
| } |
| } |
| } while (groupRemainingSpace); |
| } |
| |
| // We didn't find any children that could grow. |
| if (haveFlex && !m_flexingChildren) |
| haveFlex = false; |
| } |
| } while (haveFlex); |
| |
| RenderBlock::finishDelayUpdateScrollInfo(); |
| |
| if (style()->boxPack() != BSTART && remainingSpace > 0) { |
| // Children must be repositioned. |
| int offset = 0; |
| if (style()->boxPack() == BJUSTIFY) { |
| // Determine the total number of children. |
| int totalChildren = 0; |
| child = iterator.first(); |
| while (child) { |
| if (child->isPositioned()) { |
| child = iterator.next(); |
| continue; |
| } |
| totalChildren++; |
| child = iterator.next(); |
| } |
| |
| // Iterate over the children and space them out according to the |
| // justification level. |
| if (totalChildren > 1) { |
| totalChildren--; |
| bool firstChild = true; |
| child = iterator.first(); |
| while (child) { |
| if (child->isPositioned()) { |
| child = iterator.next(); |
| continue; |
| } |
| |
| if (firstChild) { |
| firstChild = false; |
| child = iterator.next(); |
| continue; |
| } |
| |
| offset += remainingSpace/totalChildren; |
| remainingSpace -= (remainingSpace/totalChildren); |
| totalChildren--; |
| placeChild(child, child->x(), child->y()+offset); |
| child = iterator.next(); |
| } |
| } |
| } else { |
| if (style()->boxPack() == BCENTER) |
| offset += remainingSpace/2; |
| else // END |
| offset += remainingSpace; |
| child = iterator.first(); |
| while (child) { |
| if (child->isPositioned()) { |
| child = iterator.next(); |
| continue; |
| } |
| placeChild(child, child->x(), child->y()+offset); |
| child = iterator.next(); |
| } |
| } |
| } |
| |
| child = iterator.first(); |
| while (child && child->isPositioned()) { |
| child = iterator.next(); |
| } |
| |
| if (child) { |
| m_overflowTop = min(child->y() + child->overflowTop(false), m_overflowTop); |
| |
| RenderBox* lastChild = child; |
| while ((child = iterator.next())) { |
| if (!child->isPositioned()) |
| lastChild = child; |
| } |
| m_overflowHeight = max(lastChild->y() + lastChild->overflowHeight(false), m_overflowHeight); |
| } |
| |
| // So that the calcHeight in layoutBlock() knows to relayout positioned objects because of |
| // a height change, we revert our height back to the intrinsic height before returning. |
| if (heightSpecified) |
| setHeight(oldHeight); |
| } |
| |
| void RenderFlexibleBox::placeChild(RenderBox* child, int x, int y) |
| { |
| IntRect oldRect(child->x(), child->y() , child->width(), child->height()); |
| |
| // Place the child. |
| child->setLocation(x, y); |
| |
| // If the child moved, we have to repaint it as well as any floating/positioned |
| // descendants. An exception is if we need a layout. In this case, we know we're going to |
| // repaint ourselves (and the child) anyway. |
| if (!selfNeedsLayout() && child->checkForRepaintDuringLayout()) |
| child->repaintDuringLayoutIfMoved(oldRect); |
| } |
| |
| int RenderFlexibleBox::allowedChildFlex(RenderBox* child, bool expanding, unsigned int group) |
| { |
| if (child->isPositioned() || child->style()->boxFlex() == 0.0f || child->style()->boxFlexGroup() != group) |
| return 0; |
| |
| if (expanding) { |
| if (isHorizontal()) { |
| // FIXME: For now just handle fixed values. |
| int maxW = INT_MAX; |
| int w = child->overrideWidth() - (child->borderLeft() + child->borderRight() + child->paddingLeft() + child->paddingRight()); |
| if (!child->style()->maxWidth().isUndefined() && |
| child->style()->maxWidth().isFixed()) |
| maxW = child->style()->maxWidth().value(); |
| else if (child->style()->maxWidth().type() == Intrinsic) |
| maxW = child->maxPrefWidth(); |
| else if (child->style()->maxWidth().type() == MinIntrinsic) |
| maxW = child->minPrefWidth(); |
| if (maxW == INT_MAX) |
| return maxW; |
| return max(0, maxW - w); |
| } else { |
| // FIXME: For now just handle fixed values. |
| int maxH = INT_MAX; |
| int h = child->overrideHeight() - (child->borderTop() + child->borderBottom() + child->paddingTop() + child->paddingBottom()); |
| if (!child->style()->maxHeight().isUndefined() && |
| child->style()->maxHeight().isFixed()) |
| maxH = child->style()->maxHeight().value(); |
| if (maxH == INT_MAX) |
| return maxH; |
| return max(0, maxH - h); |
| } |
| } |
| |
| // FIXME: For now just handle fixed values. |
| if (isHorizontal()) { |
| int minW = child->minPrefWidth(); |
| int w = child->overrideWidth() - (child->borderLeft() + child->borderRight() + child->paddingLeft() + child->paddingRight()); |
| if (child->style()->minWidth().isFixed()) |
| minW = child->style()->minWidth().value(); |
| else if (child->style()->minWidth().type() == Intrinsic) |
| minW = child->maxPrefWidth(); |
| else if (child->style()->minWidth().type() == MinIntrinsic) |
| minW = child->minPrefWidth(); |
| |
| int allowedShrinkage = min(0, minW - w); |
| return allowedShrinkage; |
| } else { |
| if (child->style()->minHeight().isFixed()) { |
| int minH = child->style()->minHeight().value(); |
| int h = child->overrideHeight() - (child->borderLeft() + child->borderRight() + child->paddingLeft() + child->paddingRight()); |
| int allowedShrinkage = min(0, minH - h); |
| return allowedShrinkage; |
| } |
| } |
| |
| return 0; |
| } |
| |
| const char *RenderFlexibleBox::renderName() const |
| { |
| if (isFloating()) |
| return "RenderFlexibleBox (floating)"; |
| if (isPositioned()) |
| return "RenderFlexibleBox (positioned)"; |
| if (isRelPositioned()) |
| return "RenderFlexibleBox (relative positioned)"; |
| return "RenderFlexibleBox"; |
| } |
| |
| } // namespace WebCore |