diff options
Diffstat (limited to 'layout/xul/nsSprocketLayout.cpp')
-rw-r--r-- | layout/xul/nsSprocketLayout.cpp | 1652 |
1 files changed, 1652 insertions, 0 deletions
diff --git a/layout/xul/nsSprocketLayout.cpp b/layout/xul/nsSprocketLayout.cpp new file mode 100644 index 000000000..8ce73566b --- /dev/null +++ b/layout/xul/nsSprocketLayout.cpp @@ -0,0 +1,1652 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// +// Eric Vaughan +// Netscape Communications +// +// See documentation in associated header file +// + +#include "nsBoxLayoutState.h" +#include "nsSprocketLayout.h" +#include "nsPresContext.h" +#include "nsCOMPtr.h" +#include "nsIContent.h" +#include "nsIPresShell.h" +#include "nsContainerFrame.h" +#include "nsBoxFrame.h" +#include "StackArena.h" +#include "mozilla/Likely.h" +#include <algorithm> + +nsBoxLayout* nsSprocketLayout::gInstance = nullptr; + +//#define DEBUG_GROW + +#define DEBUG_SPRING_SIZE 8 +#define DEBUG_BORDER_SIZE 2 +#define COIL_SIZE 8 + + +nsresult +NS_NewSprocketLayout(nsCOMPtr<nsBoxLayout>& aNewLayout) +{ + if (!nsSprocketLayout::gInstance) { + nsSprocketLayout::gInstance = new nsSprocketLayout(); + NS_IF_ADDREF(nsSprocketLayout::gInstance); + } + // we have not instance variables so just return our static one. + aNewLayout = nsSprocketLayout::gInstance; + return NS_OK; +} + +/*static*/ void +nsSprocketLayout::Shutdown() +{ + NS_IF_RELEASE(gInstance); +} + +nsSprocketLayout::nsSprocketLayout() +{ +} + +bool +nsSprocketLayout::IsXULHorizontal(nsIFrame* aBox) +{ + return (aBox->GetStateBits() & NS_STATE_IS_HORIZONTAL) != 0; +} + +void +nsSprocketLayout::GetFrameState(nsIFrame* aBox, nsFrameState& aState) +{ + aState = aBox->GetStateBits(); +} + +static uint8_t +GetFrameDirection(nsIFrame* aBox) +{ + return aBox->StyleVisibility()->mDirection; +} + +static void +HandleBoxPack(nsIFrame* aBox, const nsFrameState& aFrameState, nscoord& aX, nscoord& aY, + const nsRect& aOriginalRect, const nsRect& aClientRect) +{ + // In the normal direction we lay out our kids in the positive direction (e.g., |x| will get + // bigger for a horizontal box, and |y| will get bigger for a vertical box). In the reverse + // direction, the opposite is true. We'll be laying out each child at a smaller |x| or + // |y|. + uint8_t frameDirection = GetFrameDirection(aBox); + + if (aFrameState & NS_STATE_IS_HORIZONTAL) { + if (aFrameState & NS_STATE_IS_DIRECTION_NORMAL) { + // The normal direction. |x| increases as we move through our children. + aX = aClientRect.x; + } + else { + // The reverse direction. |x| decreases as we move through our children. + aX = aClientRect.x + aOriginalRect.width; + } + // |y| is always in the normal direction in horizontal boxes + aY = aClientRect.y; + } + else { + // take direction property into account for |x| in vertical boxes + if (frameDirection == NS_STYLE_DIRECTION_LTR) { + // The normal direction. |x| increases as we move through our children. + aX = aClientRect.x; + } + else { + // The reverse direction. |x| decreases as we move through our children. + aX = aClientRect.x + aOriginalRect.width; + } + if (aFrameState & NS_STATE_IS_DIRECTION_NORMAL) { + // The normal direction. |y| increases as we move through our children. + aY = aClientRect.y; + } + else { + // The reverse direction. |y| decreases as we move through our children. + aY = aClientRect.y + aOriginalRect.height; + } + } + + // Get our pack/alignment information. + nsIFrame::Halignment halign = aBox->GetXULHAlign(); + nsIFrame::Valignment valign = aBox->GetXULVAlign(); + + // The following code handles box PACKING. Packing comes into play in the case where the computed size for + // all of our children (now stored in our client rect) is smaller than the size available for + // the box (stored in |aOriginalRect|). + // + // Here we adjust our |x| and |y| variables accordingly so that we start at the beginning, + // middle, or end of the box. + // + // XXXdwh JUSTIFY needs to be implemented! + if (aFrameState & NS_STATE_IS_HORIZONTAL) { + switch(halign) { + case nsBoxFrame::hAlign_Left: + break; // Nothing to do. The default initialized us properly. + + case nsBoxFrame::hAlign_Center: + if (aFrameState & NS_STATE_IS_DIRECTION_NORMAL) + aX += (aOriginalRect.width - aClientRect.width)/2; + else + aX -= (aOriginalRect.width - aClientRect.width)/2; + break; + + case nsBoxFrame::hAlign_Right: + if (aFrameState & NS_STATE_IS_DIRECTION_NORMAL) + aX += (aOriginalRect.width - aClientRect.width); + else + aX -= (aOriginalRect.width - aClientRect.width); + break; // Nothing to do for the reverse dir. The default initialized us properly. + } + } else { + switch(valign) { + case nsBoxFrame::vAlign_Top: + case nsBoxFrame::vAlign_BaseLine: // This value is technically impossible to specify for pack. + break; // Don't do anything. We were initialized correctly. + + case nsBoxFrame::vAlign_Middle: + if (aFrameState & NS_STATE_IS_DIRECTION_NORMAL) + aY += (aOriginalRect.height - aClientRect.height)/2; + else + aY -= (aOriginalRect.height - aClientRect.height)/2; + break; + + case nsBoxFrame::vAlign_Bottom: + if (aFrameState & NS_STATE_IS_DIRECTION_NORMAL) + aY += (aOriginalRect.height - aClientRect.height); + else + aY -= (aOriginalRect.height - aClientRect.height); + break; + } + } +} + +NS_IMETHODIMP +nsSprocketLayout::XULLayout(nsIFrame* aBox, nsBoxLayoutState& aState) +{ + // See if we are collapsed. If we are, then simply iterate over all our + // children and give them a rect of 0 width and height. + if (aBox->IsXULCollapsed()) { + nsIFrame* child = nsBox::GetChildXULBox(aBox); + while(child) + { + nsBoxFrame::LayoutChildAt(aState, child, nsRect(0,0,0,0)); + child = nsBox::GetNextXULBox(child); + } + return NS_OK; + } + + nsBoxLayoutState::AutoReflowDepth depth(aState); + mozilla::AutoStackArena arena; + + // ----- figure out our size ---------- + const nsSize originalSize = aBox->GetSize(); + + // -- make sure we remove our border and padding ---- + nsRect clientRect; + aBox->GetXULClientRect(clientRect); + + // |originalClientRect| represents the rect of the entire box (excluding borders + // and padding). We store it here because we're going to use |clientRect| to hold + // the required size for all our kids. As an example, consider an hbox with a + // specified width of 300. If the kids total only 150 pixels of width, then + // we have 150 pixels left over. |clientRect| is going to hold a width of 150 and + // is going to be adjusted based off the value of the PACK property. If flexible + // objects are in the box, then the two rects will match. + nsRect originalClientRect(clientRect); + + // The frame state contains cached knowledge about our box, such as our orientation + // and direction. + nsFrameState frameState = nsFrameState(0); + GetFrameState(aBox, frameState); + + // Build a list of our children's desired sizes and computed sizes + nsBoxSize* boxSizes = nullptr; + nsComputedBoxSize* computedBoxSizes = nullptr; + + nscoord min = 0; + nscoord max = 0; + int32_t flexes = 0; + PopulateBoxSizes(aBox, aState, boxSizes, min, max, flexes); + + // The |size| variable will hold the total size of children along the axis of + // the box. Continuing with the example begun in the comment above, size would + // be 150 pixels. + nscoord size = clientRect.width; + if (!IsXULHorizontal(aBox)) + size = clientRect.height; + ComputeChildSizes(aBox, aState, size, boxSizes, computedBoxSizes); + + // After the call to ComputeChildSizes, the |size| variable contains the + // total required size of all the children. We adjust our clientRect in the + // appropriate dimension to match this size. In our example, we now assign + // 150 pixels into the clientRect.width. + // + // The variables |min| and |max| hold the minimum required size box must be + // in the OPPOSITE orientation, e.g., for a horizontal box, |min| is the minimum + // height we require to enclose our children, and |max| is the maximum height + // required to enclose our children. + if (IsXULHorizontal(aBox)) { + clientRect.width = size; + if (clientRect.height < min) + clientRect.height = min; + + if (frameState & NS_STATE_AUTO_STRETCH) { + if (clientRect.height > max) + clientRect.height = max; + } + } else { + clientRect.height = size; + if (clientRect.width < min) + clientRect.width = min; + + if (frameState & NS_STATE_AUTO_STRETCH) { + if (clientRect.width > max) + clientRect.width = max; + } + } + + // With the sizes computed, now it's time to lay out our children. + bool finished; + nscoord passes = 0; + + // We flow children at their preferred locations (along with the appropriate computed flex). + // After we flow a child, it is possible that the child will change its size. If/when this happens, + // we have to do another pass. Typically only 2 passes are required, but the code is prepared to + // do as many passes as are necessary to achieve equilibrium. + nscoord x = 0; + nscoord y = 0; + nscoord origX = 0; + nscoord origY = 0; + + // |childResized| lets us know if a child changed its size after we attempted to lay it out at + // the specified size. If this happens, we usually have to do another pass. + bool childResized = false; + + // |passes| stores our number of passes. If for any reason we end up doing more than, say, 10 + // passes, we assert to indicate that something is seriously screwed up. + passes = 0; + do + { +#ifdef DEBUG_REFLOW + if (passes > 0) { + AddIndents(); + printf("ChildResized doing pass: %d\n", passes); + } +#endif + + // Always assume that we're done. This will change if, for example, children don't stay + // the same size after being flowed. + finished = true; + + // Handle box packing. + HandleBoxPack(aBox, frameState, x, y, originalClientRect, clientRect); + + // Now that packing is taken care of we set up a few additional + // tracking variables. + origX = x; + origY = y; + + // Now we iterate over our box children and our box size lists in + // parallel. For each child, we look at its sizes and figure out + // where to place it. + nsComputedBoxSize* childComputedBoxSize = computedBoxSizes; + nsBoxSize* childBoxSize = boxSizes; + + nsIFrame* child = nsBox::GetChildXULBox(aBox); + + int32_t count = 0; + while (child || (childBoxSize && childBoxSize->bogus)) + { + // If for some reason, our lists are not the same length, we guard + // by bailing out of the loop. + if (childBoxSize == nullptr) { + NS_NOTREACHED("Lists not the same length."); + break; + } + + nscoord width = clientRect.width; + nscoord height = clientRect.height; + + if (!childBoxSize->bogus) { + // We have a valid box size entry. This entry already contains information about our + // sizes along the axis of the box (e.g., widths in a horizontal box). If our default + // ALIGN is not stretch, however, then we also need to know the child's size along the + // opposite axis. + if (!(frameState & NS_STATE_AUTO_STRETCH)) { + nsSize prefSize = child->GetXULPrefSize(aState); + nsSize minSize = child->GetXULMinSize(aState); + nsSize maxSize = child->GetXULMaxSize(aState); + prefSize = nsBox::BoundsCheck(minSize, prefSize, maxSize); + + AddMargin(child, prefSize); + width = std::min(prefSize.width, originalClientRect.width); + height = std::min(prefSize.height, originalClientRect.height); + } + } + + // Obtain the computed size along the axis of the box for this child from the computedBoxSize entry. + // We store the result in |width| for horizontal boxes and |height| for vertical boxes. + if (frameState & NS_STATE_IS_HORIZONTAL) + width = childComputedBoxSize->size; + else + height = childComputedBoxSize->size; + + // Adjust our x/y for the left/right spacing. + if (frameState & NS_STATE_IS_HORIZONTAL) { + if (frameState & NS_STATE_IS_DIRECTION_NORMAL) + x += (childBoxSize->left); + else + x -= (childBoxSize->right); + } else { + if (frameState & NS_STATE_IS_DIRECTION_NORMAL) + y += (childBoxSize->left); + else + y -= (childBoxSize->right); + } + + // Now we build a child rect. + nscoord rectX = x; + nscoord rectY = y; + if (!(frameState & NS_STATE_IS_DIRECTION_NORMAL)) { + if (frameState & NS_STATE_IS_HORIZONTAL) + rectX -= width; + else + rectY -= height; + } + + // We now create an accurate child rect based off our computed size information. + nsRect childRect(rectX, rectY, width, height); + + // Sanity check against our clientRect. It is possible that a child specified + // a size that is too large to fit. If that happens, then we have to grow + // our client rect. Remember, clientRect is not the total rect of the enclosing + // box. It currently holds our perception of how big the children needed to + // be. + if (childRect.width > clientRect.width) + clientRect.width = childRect.width; + + if (childRect.height > clientRect.height) + clientRect.height = childRect.height; + + // Either |nextX| or |nextY| is updated by this function call, according + // to our axis. + nscoord nextX = x; + nscoord nextY = y; + + ComputeChildsNextPosition(aBox, x, y, nextX, nextY, childRect); + + // Now we further update our nextX/Y along our axis. + // We also set childRect.y/x along the opposite axis appropriately for a + // stretch alignment. (Non-stretch alignment is handled below.) + if (frameState & NS_STATE_IS_HORIZONTAL) { + if (frameState & NS_STATE_IS_DIRECTION_NORMAL) + nextX += (childBoxSize->right); + else + nextX -= (childBoxSize->left); + childRect.y = originalClientRect.y; + } + else { + if (frameState & NS_STATE_IS_DIRECTION_NORMAL) + nextY += (childBoxSize->right); + else + nextY -= (childBoxSize->left); + if (GetFrameDirection(aBox) == NS_STYLE_DIRECTION_LTR) { + childRect.x = originalClientRect.x; + } else { + // keep the right edge of the box the same + childRect.x = clientRect.x + originalClientRect.width - childRect.width; + } + } + + // If we encounter a completely bogus box size, we just leave this child completely + // alone and continue through the loop to the next child. + if (childBoxSize->bogus) + { + childComputedBoxSize = childComputedBoxSize->next; + childBoxSize = childBoxSize->next; + count++; + x = nextX; + y = nextY; + continue; + } + + nsMargin margin(0,0,0,0); + + bool layout = true; + + // Deflate the rect of our child by its margin. + child->GetXULMargin(margin); + childRect.Deflate(margin); + if (childRect.width < 0) + childRect.width = 0; + if (childRect.height < 0) + childRect.height = 0; + + // Now we're trying to figure out if we have to lay out this child, i.e., to call + // the child's XULLayout method. + if (passes > 0) { + layout = false; + } else { + // Always perform layout if we are dirty or have dirty children + if (!NS_SUBTREE_DIRTY(child)) + layout = false; + } + + nsRect oldRect(child->GetRect()); + + // Non-stretch alignment will be handled in AlignChildren(), so don't + // change child out-of-axis positions yet. + if (!(frameState & NS_STATE_AUTO_STRETCH)) { + if (frameState & NS_STATE_IS_HORIZONTAL) { + childRect.y = oldRect.y; + } else { + childRect.x = oldRect.x; + } + } + + // We computed a childRect. Now we want to set the bounds of the child to be that rect. + // If our old rect is different, then we know our size changed and we cache that fact + // in the |sizeChanged| variable. + + child->SetXULBounds(aState, childRect); + bool sizeChanged = (childRect.width != oldRect.width || + childRect.height != oldRect.height); + + if (sizeChanged) { + // Our size is different. Sanity check against our maximum allowed size to ensure + // we didn't exceed it. + nsSize minSize = child->GetXULMinSize(aState); + nsSize maxSize = child->GetXULMaxSize(aState); + maxSize = nsBox::BoundsCheckMinMax(minSize, maxSize); + + // make sure the size is in our max size. + if (childRect.width > maxSize.width) + childRect.width = maxSize.width; + + if (childRect.height > maxSize.height) + childRect.height = maxSize.height; + + // set it again + child->SetXULBounds(aState, childRect); + } + + // If we already determined that layout was required or if our size has changed, then + // we make sure to call layout on the child, since its children may need to be shifted + // around as a result of the size change. + if (layout || sizeChanged) + child->XULLayout(aState); + + // If the child was a block or inline (e.g., HTML) it may have changed its rect *during* layout. + // We have to check for this. + nsRect newChildRect(child->GetRect()); + + if (!newChildRect.IsEqualInterior(childRect)) { +#ifdef DEBUG_GROW + child->XULDumpBox(stdout); + printf(" GREW from (%d,%d) -> (%d,%d)\n", childRect.width, childRect.height, newChildRect.width, newChildRect.height); +#endif + newChildRect.Inflate(margin); + childRect.Inflate(margin); + + // The child changed size during layout. The ChildResized method handles this + // scenario. + ChildResized(aBox, + aState, + child, + childBoxSize, + childComputedBoxSize, + boxSizes, + computedBoxSizes, + childRect, + newChildRect, + clientRect, + flexes, + finished); + + // We note that a child changed size, which means that another pass will be required. + childResized = true; + + // Now that a child resized, it's entirely possible that OUR rect is too small. Now we + // ensure that |originalClientRect| is grown to accommodate the size of |clientRect|. + if (clientRect.width > originalClientRect.width) + originalClientRect.width = clientRect.width; + + if (clientRect.height > originalClientRect.height) + originalClientRect.height = clientRect.height; + + if (!(frameState & NS_STATE_IS_DIRECTION_NORMAL)) { + // Our childRect had its XMost() or YMost() (depending on our layout + // direction), positioned at a certain point. Ensure that the + // newChildRect satisfies the same constraint. Note that this is + // just equivalent to adjusting the x/y by the difference in + // width/height between childRect and newChildRect. So we don't need + // to reaccount for the left and right of the box layout state again. + if (frameState & NS_STATE_IS_HORIZONTAL) + newChildRect.x = childRect.XMost() - newChildRect.width; + else + newChildRect.y = childRect.YMost() - newChildRect.height; + } + + if (!(frameState & NS_STATE_IS_HORIZONTAL)) { + if (GetFrameDirection(aBox) != NS_STYLE_DIRECTION_LTR) { + // keep the right edge the same + newChildRect.x = childRect.XMost() - newChildRect.width; + } + } + + // If the child resized then recompute its position. + ComputeChildsNextPosition(aBox, x, y, nextX, nextY, newChildRect); + + if (newChildRect.width >= margin.left + margin.right && newChildRect.height >= margin.top + margin.bottom) + newChildRect.Deflate(margin); + + if (childRect.width >= margin.left + margin.right && childRect.height >= margin.top + margin.bottom) + childRect.Deflate(margin); + + child->SetXULBounds(aState, newChildRect); + + // If we are the first box that changed size, then we don't need to do a second pass + if (count == 0) + finished = true; + } + + // Now update our x/y finally. + x = nextX; + y = nextY; + + // Move to the next child. + childComputedBoxSize = childComputedBoxSize->next; + childBoxSize = childBoxSize->next; + + child = nsBox::GetNextXULBox(child); + count++; + } + + // Sanity-checking code to ensure we don't do an infinite # of passes. + passes++; + NS_ASSERTION(passes < 10, "A Box's child is constantly growing!!!!!"); + if (passes > 10) + break; + } while (false == finished); + + // Get rid of our size lists. + while(boxSizes) + { + nsBoxSize* toDelete = boxSizes; + boxSizes = boxSizes->next; + delete toDelete; + } + + while(computedBoxSizes) + { + nsComputedBoxSize* toDelete = computedBoxSizes; + computedBoxSizes = computedBoxSizes->next; + delete toDelete; + } + + if (childResized) { + // See if one of our children forced us to get bigger + nsRect tmpClientRect(originalClientRect); + nsMargin bp(0,0,0,0); + aBox->GetXULBorderAndPadding(bp); + tmpClientRect.Inflate(bp); + + if (tmpClientRect.width > originalSize.width || tmpClientRect.height > originalSize.height) + { + // if it did reset our bounds. + nsRect bounds(aBox->GetRect()); + if (tmpClientRect.width > originalSize.width) + bounds.width = tmpClientRect.width; + + if (tmpClientRect.height > originalSize.height) + bounds.height = tmpClientRect.height; + + aBox->SetXULBounds(aState, bounds); + } + } + + // Because our size grew, we now have to readjust because of box packing. Repack + // in order to update our x and y to the correct values. + HandleBoxPack(aBox, frameState, x, y, originalClientRect, clientRect); + + // Compare against our original x and y and only worry about adjusting the children if + // we really did have to change the positions because of packing (typically for 'center' + // or 'end' pack values). + if (x != origX || y != origY) { + nsIFrame* child = nsBox::GetChildXULBox(aBox); + + // reposition all our children + while (child) + { + nsRect childRect(child->GetRect()); + childRect.x += (x - origX); + childRect.y += (y - origY); + child->SetXULBounds(aState, childRect); + child = nsBox::GetNextXULBox(child); + } + } + + // Perform out-of-axis alignment for non-stretch alignments + if (!(frameState & NS_STATE_AUTO_STRETCH)) { + AlignChildren(aBox, aState); + } + + // That's it! If you made it this far without having a nervous breakdown, + // congratulations! Go get yourself a beer. + return NS_OK; +} + +void +nsSprocketLayout::PopulateBoxSizes(nsIFrame* aBox, nsBoxLayoutState& aState, nsBoxSize*& aBoxSizes, nscoord& aMinSize, nscoord& aMaxSize, int32_t& aFlexes) +{ + // used for the equal size flag + nscoord biggestPrefWidth = 0; + nscoord biggestMinWidth = 0; + nscoord smallestMaxWidth = NS_INTRINSICSIZE; + + nsFrameState frameState = nsFrameState(0); + GetFrameState(aBox, frameState); + + //if (frameState & NS_STATE_CURRENTLY_IN_DEBUG) + // printf("In debug\n"); + + aMinSize = 0; + aMaxSize = NS_INTRINSICSIZE; + + bool isHorizontal; + + if (IsXULHorizontal(aBox)) + isHorizontal = true; + else + isHorizontal = false; + + // this is a nice little optimization + // it turns out that if we only have 1 flexable child + // then it does not matter what its preferred size is + // there is nothing to flex it relative. This is great + // because we can avoid asking for a preferred size in this + // case. Why is this good? Well you might have html inside it + // and asking html for its preferred size is rather expensive. + // so we can just optimize it out this way. + + // set flexes + nsIFrame* child = nsBox::GetChildXULBox(aBox); + + aFlexes = 0; + nsBoxSize* currentBox = nullptr; + +#if 0 + nsBoxSize* start = aBoxSizes; + + while(child) + { + // ok if we started with a list move down the list + // until we reach the end. Then start looking at childen. + // This feature is used extensively for Grid. + nscoord flex = 0; + + if (!start) { + if (!currentBox) { + aBoxSizes = new (aState) nsBoxSize(); + currentBox = aBoxSizes; + } else { + currentBox->next = new (aState) nsBoxSize(); + currentBox = currentBox->next; + } + + + flex = child->GetXULFlex(); + + currentBox->flex = flex; + currentBox->collapsed = child->IsXULCollapsed(); + } else { + flex = start->flex; + start = start->next; + } + + if (flex > 0) + aFlexes++; + + child = GetNextXULBox(child); + } +#endif + + // get pref, min, max + child = nsBox::GetChildXULBox(aBox); + currentBox = aBoxSizes; + nsBoxSize* last = nullptr; + + nscoord maxFlex = 0; + int32_t childCount = 0; + + while(child) + { + while (currentBox && currentBox->bogus) { + last = currentBox; + currentBox = currentBox->next; + } + ++childCount; + nsSize pref(0,0); + nsSize minSize(0,0); + nsSize maxSize(NS_INTRINSICSIZE,NS_INTRINSICSIZE); + nscoord ascent = 0; + bool collapsed = child->IsXULCollapsed(); + + if (!collapsed) { + // only one flexible child? Cool we will just make its preferred size + // 0 then and not even have to ask for it. + //if (flexes != 1) { + + pref = child->GetXULPrefSize(aState); + minSize = child->GetXULMinSize(aState); + maxSize = nsBox::BoundsCheckMinMax(minSize, child->GetXULMaxSize(aState)); + ascent = child->GetXULBoxAscent(aState); + nsMargin margin; + child->GetXULMargin(margin); + ascent += margin.top; + //} + + pref = nsBox::BoundsCheck(minSize, pref, maxSize); + + AddMargin(child, pref); + AddMargin(child, minSize); + AddMargin(child, maxSize); + } + + if (!currentBox) { + // create one. + currentBox = new (aState) nsBoxSize(); + if (!aBoxSizes) { + aBoxSizes = currentBox; + last = aBoxSizes; + } else { + last->next = currentBox; + last = currentBox; + } + + nscoord minWidth; + nscoord maxWidth; + nscoord prefWidth; + + // get sizes from child + if (isHorizontal) { + minWidth = minSize.width; + maxWidth = maxSize.width; + prefWidth = pref.width; + } else { + minWidth = minSize.height; + maxWidth = maxSize.height; + prefWidth = pref.height; + } + + nscoord flex = child->GetXULFlex(); + + // set them if you collapsed you are not flexible. + if (collapsed) { + currentBox->flex = 0; + } + else { + if (flex > maxFlex) { + maxFlex = flex; + } + currentBox->flex = flex; + } + + // we specified all our children are equal size; + if (frameState & NS_STATE_EQUAL_SIZE) { + + if (prefWidth > biggestPrefWidth) + biggestPrefWidth = prefWidth; + + if (minWidth > biggestMinWidth) + biggestMinWidth = minWidth; + + if (maxWidth < smallestMaxWidth) + smallestMaxWidth = maxWidth; + } else { // not we can set our children right now. + currentBox->pref = prefWidth; + currentBox->min = minWidth; + currentBox->max = maxWidth; + } + + NS_ASSERTION(minWidth <= prefWidth && prefWidth <= maxWidth,"Bad min, pref, max widths!"); + + } + + if (!isHorizontal) { + if (minSize.width > aMinSize) + aMinSize = minSize.width; + + if (maxSize.width < aMaxSize) + aMaxSize = maxSize.width; + + } else { + if (minSize.height > aMinSize) + aMinSize = minSize.height; + + if (maxSize.height < aMaxSize) + aMaxSize = maxSize.height; + } + + currentBox->collapsed = collapsed; + aFlexes += currentBox->flex; + + child = nsBox::GetNextXULBox(child); + + last = currentBox; + currentBox = currentBox->next; + + } + + if (childCount > 0) { + nscoord maxAllowedFlex = nscoord_MAX / childCount; + + if (MOZ_UNLIKELY(maxFlex > maxAllowedFlex)) { + // clamp all the flexes + currentBox = aBoxSizes; + while (currentBox) { + currentBox->flex = std::min(currentBox->flex, maxAllowedFlex); + currentBox = currentBox->next; + } + } + } +#ifdef DEBUG + else { + NS_ASSERTION(maxFlex == 0, "How did that happen?"); + } +#endif + + // we specified all our children are equal size; + if (frameState & NS_STATE_EQUAL_SIZE) { + smallestMaxWidth = std::max(smallestMaxWidth, biggestMinWidth); + biggestPrefWidth = nsBox::BoundsCheck(biggestMinWidth, biggestPrefWidth, smallestMaxWidth); + + currentBox = aBoxSizes; + + while(currentBox) + { + if (!currentBox->collapsed) { + currentBox->pref = biggestPrefWidth; + currentBox->min = biggestMinWidth; + currentBox->max = smallestMaxWidth; + } else { + currentBox->pref = 0; + currentBox->min = 0; + currentBox->max = 0; + } + currentBox = currentBox->next; + } + } + +} + +void +nsSprocketLayout::ComputeChildsNextPosition(nsIFrame* aBox, + const nscoord& aCurX, + const nscoord& aCurY, + nscoord& aNextX, + nscoord& aNextY, + const nsRect& aCurrentChildSize) +{ + // Get the position along the box axis for the child. + // The out-of-axis position is not set. + nsFrameState frameState = nsFrameState(0); + GetFrameState(aBox, frameState); + + if (IsXULHorizontal(aBox)) { + // horizontal box's children. + if (frameState & NS_STATE_IS_DIRECTION_NORMAL) + aNextX = aCurX + aCurrentChildSize.width; + else + aNextX = aCurX - aCurrentChildSize.width; + + } else { + // vertical box's children. + if (frameState & NS_STATE_IS_DIRECTION_NORMAL) + aNextY = aCurY + aCurrentChildSize.height; + else + aNextY = aCurY - aCurrentChildSize.height; + } +} + +void +nsSprocketLayout::AlignChildren(nsIFrame* aBox, + nsBoxLayoutState& aState) +{ + nsFrameState frameState = nsFrameState(0); + GetFrameState(aBox, frameState); + bool isHorizontal = (frameState & NS_STATE_IS_HORIZONTAL) != 0; + nsRect clientRect; + aBox->GetXULClientRect(clientRect); + + NS_PRECONDITION(!(frameState & NS_STATE_AUTO_STRETCH), + "Only AlignChildren() with non-stretch alignment"); + + // These are only calculated if needed + nsIFrame::Halignment halign; + nsIFrame::Valignment valign; + nscoord maxAscent = 0; + bool isLTR; + + if (isHorizontal) { + valign = aBox->GetXULVAlign(); + if (valign == nsBoxFrame::vAlign_BaseLine) { + maxAscent = aBox->GetXULBoxAscent(aState); + } + } else { + isLTR = GetFrameDirection(aBox) == NS_STYLE_DIRECTION_LTR; + halign = aBox->GetXULHAlign(); + } + + nsIFrame* child = nsBox::GetChildXULBox(aBox); + while (child) { + + nsMargin margin; + child->GetXULMargin(margin); + nsRect childRect = child->GetRect(); + + if (isHorizontal) { + const nscoord startAlign = clientRect.y + margin.top; + const nscoord endAlign = + clientRect.YMost() - margin.bottom - childRect.height; + + nscoord y = 0; + switch (valign) { + case nsBoxFrame::vAlign_Top: + y = startAlign; + break; + case nsBoxFrame::vAlign_Middle: + // Should this center the border box? + // This centers the margin box, the historical behavior. + y = (startAlign + endAlign) / 2; + break; + case nsBoxFrame::vAlign_Bottom: + y = endAlign; + break; + case nsBoxFrame::vAlign_BaseLine: + // Alignments don't force the box to grow (only sizes do), + // so keep the children within the box. + y = maxAscent - child->GetXULBoxAscent(aState); + y = std::max(startAlign, y); + y = std::min(y, endAlign); + break; + } + + childRect.y = y; + + } else { // vertical box + const nscoord leftAlign = clientRect.x + margin.left; + const nscoord rightAlign = + clientRect.XMost() - margin.right - childRect.width; + + nscoord x = 0; + switch (halign) { + case nsBoxFrame::hAlign_Left: // start + x = isLTR ? leftAlign : rightAlign; + break; + case nsBoxFrame::hAlign_Center: + x = (leftAlign + rightAlign) / 2; + break; + case nsBoxFrame::hAlign_Right: // end + x = isLTR ? rightAlign : leftAlign; + break; + } + + childRect.x = x; + } + + if (childRect.TopLeft() != child->GetPosition()) { + child->SetXULBounds(aState, childRect); + } + + child = nsBox::GetNextXULBox(child); + } +} + +void +nsSprocketLayout::ChildResized(nsIFrame* aBox, + nsBoxLayoutState& aState, + nsIFrame* aChild, + nsBoxSize* aChildBoxSize, + nsComputedBoxSize* aChildComputedSize, + nsBoxSize* aBoxSizes, + nsComputedBoxSize* aComputedBoxSizes, + const nsRect& aChildLayoutRect, + nsRect& aChildActualRect, + nsRect& aContainingRect, + int32_t aFlexes, + bool& aFinished) + +{ + nsRect childCurrentRect(aChildLayoutRect); + + bool isHorizontal = IsXULHorizontal(aBox); + nscoord childLayoutWidth = GET_WIDTH(aChildLayoutRect,isHorizontal); + nscoord& childActualWidth = GET_WIDTH(aChildActualRect,isHorizontal); + nscoord& containingWidth = GET_WIDTH(aContainingRect,isHorizontal); + + //nscoord childLayoutHeight = GET_HEIGHT(aChildLayoutRect,isHorizontal); + nscoord& childActualHeight = GET_HEIGHT(aChildActualRect,isHorizontal); + nscoord& containingHeight = GET_HEIGHT(aContainingRect,isHorizontal); + + bool recompute = false; + + // if we are a horizontal box see if the child will fit inside us. + if ( childActualHeight > containingHeight) { + // if we are a horizontal box and the child is bigger than our height + + // ok if the height changed then we need to reflow everyone but us at the new height + // so we will set the changed index to be us. And signal that we need a new pass. + + nsSize min = aChild->GetXULMinSize(aState); + nsSize max = nsBox::BoundsCheckMinMax(min, aChild->GetXULMaxSize(aState)); + AddMargin(aChild, max); + + if (isHorizontal) + childActualHeight = max.height < childActualHeight ? max.height : childActualHeight; + else + childActualHeight = max.width < childActualHeight ? max.width : childActualHeight; + + // only set if it changes + if (childActualHeight > containingHeight) { + containingHeight = childActualHeight; + + // remember we do not need to clear the resized list because changing the height of a horizontal box + // will not affect the width of any of its children because block flow left to right, top to bottom. Just trust me + // on this one. + aFinished = false; + + // only recompute if there are flexes. + if (aFlexes > 0) { + // relayout everything + recompute = true; + InvalidateComputedSizes(aComputedBoxSizes); + nsComputedBoxSize* node = aComputedBoxSizes; + + while(node) { + node->resized = false; + node = node->next; + } + + } + } + } + + if (childActualWidth > childLayoutWidth) { + nsSize min = aChild->GetXULMinSize(aState); + nsSize max = nsBox::BoundsCheckMinMax(min, aChild->GetXULMaxSize(aState)); + + AddMargin(aChild, max); + + // our width now becomes the new size + + if (isHorizontal) + childActualWidth = max.width < childActualWidth ? max.width : childActualWidth; + else + childActualWidth = max.height < childActualWidth ? max.height : childActualWidth; + + if (childActualWidth > childLayoutWidth) { + aChildComputedSize->size = childActualWidth; + aChildBoxSize->min = childActualWidth; + if (aChildBoxSize->pref < childActualWidth) + aChildBoxSize->pref = childActualWidth; + if (aChildBoxSize->max < childActualWidth) + aChildBoxSize->max = childActualWidth; + + // if we have flexible elements with us then reflex things. Otherwise we can skip doing it. + if (aFlexes > 0) { + InvalidateComputedSizes(aComputedBoxSizes); + + nsComputedBoxSize* node = aComputedBoxSizes; + aChildComputedSize->resized = true; + + while(node) { + if (node->resized) + node->valid = true; + + node = node->next; + } + + recompute = true; + aFinished = false; + } else { + containingWidth += aChildComputedSize->size - childLayoutWidth; + } + } + } + + if (recompute) + ComputeChildSizes(aBox, aState, containingWidth, aBoxSizes, aComputedBoxSizes); + + if (!childCurrentRect.IsEqualInterior(aChildActualRect)) { + // the childRect includes the margin + // make sure we remove it before setting + // the bounds. + nsMargin margin(0,0,0,0); + aChild->GetXULMargin(margin); + nsRect rect(aChildActualRect); + if (rect.width >= margin.left + margin.right && rect.height >= margin.top + margin.bottom) + rect.Deflate(margin); + + aChild->SetXULBounds(aState, rect); + aChild->XULLayout(aState); + } + +} + +void +nsSprocketLayout::InvalidateComputedSizes(nsComputedBoxSize* aComputedBoxSizes) +{ + while(aComputedBoxSizes) { + aComputedBoxSizes->valid = false; + aComputedBoxSizes = aComputedBoxSizes->next; + } +} + +void +nsSprocketLayout::ComputeChildSizes(nsIFrame* aBox, + nsBoxLayoutState& aState, + nscoord& aGivenSize, + nsBoxSize* aBoxSizes, + nsComputedBoxSize*& aComputedBoxSizes) +{ + + //nscoord onePixel = aState.PresContext()->IntScaledPixelsToTwips(1); + + int32_t sizeRemaining = aGivenSize; + int32_t spacerConstantsRemaining = 0; + + // ----- calculate the spacers constants and the size remaining ----- + + if (!aComputedBoxSizes) + aComputedBoxSizes = new (aState) nsComputedBoxSize(); + + nsBoxSize* boxSizes = aBoxSizes; + nsComputedBoxSize* computedBoxSizes = aComputedBoxSizes; + int32_t count = 0; + int32_t validCount = 0; + + while (boxSizes) + { + + NS_ASSERTION((boxSizes->min <= boxSizes->pref && boxSizes->pref <= boxSizes->max),"bad pref, min, max size"); + + + // ignore collapsed children + // if (boxSizes->collapsed) + // { + // computedBoxSizes->valid = true; + // computedBoxSizes->size = boxSizes->pref; + // validCount++; + // boxSizes->flex = 0; + // }// else { + + if (computedBoxSizes->valid) { + sizeRemaining -= computedBoxSizes->size; + validCount++; + } else { + if (boxSizes->flex == 0) + { + computedBoxSizes->valid = true; + computedBoxSizes->size = boxSizes->pref; + validCount++; + } + + spacerConstantsRemaining += boxSizes->flex; + sizeRemaining -= boxSizes->pref; + } + + sizeRemaining -= (boxSizes->left + boxSizes->right); + + //} + + boxSizes = boxSizes->next; + + if (boxSizes && !computedBoxSizes->next) + computedBoxSizes->next = new (aState) nsComputedBoxSize(); + + computedBoxSizes = computedBoxSizes->next; + count++; + } + + // everything accounted for? + if (validCount < count) + { + // ----- Ok we are give a size to fit into so stretch or squeeze to fit + // ----- Make sure we look at our min and max size + bool limit = true; + for (int pass=1; true == limit; pass++) + { + limit = false; + boxSizes = aBoxSizes; + computedBoxSizes = aComputedBoxSizes; + + while (boxSizes) { + + // ignore collapsed spacers + + // if (!boxSizes->collapsed) { + + nscoord pref = 0; + nscoord max = NS_INTRINSICSIZE; + nscoord min = 0; + nscoord flex = 0; + + pref = boxSizes->pref; + min = boxSizes->min; + max = boxSizes->max; + flex = boxSizes->flex; + + // ----- look at our min and max limits make sure we aren't too small or too big ----- + if (!computedBoxSizes->valid) { + int32_t newSize = pref + int32_t(int64_t(sizeRemaining) * flex / spacerConstantsRemaining); + + if (newSize<=min) { + computedBoxSizes->size = min; + computedBoxSizes->valid = true; + spacerConstantsRemaining -= flex; + sizeRemaining += pref; + sizeRemaining -= min; + limit = true; + } else if (newSize>=max) { + computedBoxSizes->size = max; + computedBoxSizes->valid = true; + spacerConstantsRemaining -= flex; + sizeRemaining += pref; + sizeRemaining -= max; + limit = true; + } + } + // } + boxSizes = boxSizes->next; + computedBoxSizes = computedBoxSizes->next; + } + } + } + + // ---- once we have removed and min and max issues just stretch us out in the remaining space + // ---- or shrink us. Depends on the size remaining and the spacer constants + aGivenSize = 0; + boxSizes = aBoxSizes; + computedBoxSizes = aComputedBoxSizes; + + while (boxSizes) { + + // ignore collapsed spacers + // if (!(boxSizes && boxSizes->collapsed)) { + + nscoord pref = 0; + nscoord flex = 0; + pref = boxSizes->pref; + flex = boxSizes->flex; + + if (!computedBoxSizes->valid) { + computedBoxSizes->size = pref + int32_t(int64_t(sizeRemaining) * flex / spacerConstantsRemaining); + computedBoxSizes->valid = true; + } + + aGivenSize += (boxSizes->left + boxSizes->right); + aGivenSize += computedBoxSizes->size; + + // } + + boxSizes = boxSizes->next; + computedBoxSizes = computedBoxSizes->next; + } +} + + +nsSize +nsSprocketLayout::GetXULPrefSize(nsIFrame* aBox, nsBoxLayoutState& aState) +{ + nsSize vpref (0, 0); + bool isHorizontal = IsXULHorizontal(aBox); + + nscoord biggestPref = 0; + + // run through all the children and get their min, max, and preferred sizes + // return us the size of the box + + nsIFrame* child = nsBox::GetChildXULBox(aBox); + nsFrameState frameState = nsFrameState(0); + GetFrameState(aBox, frameState); + bool isEqual = !!(frameState & NS_STATE_EQUAL_SIZE); + int32_t count = 0; + + while (child) + { + // ignore collapsed children + if (!child->IsXULCollapsed()) + { + nsSize pref = child->GetXULPrefSize(aState); + AddMargin(child, pref); + + if (isEqual) { + if (isHorizontal) + { + if (pref.width > biggestPref) + biggestPref = pref.width; + } else { + if (pref.height > biggestPref) + biggestPref = pref.height; + } + } + + AddLargestSize(vpref, pref, isHorizontal); + count++; + } + + child = nsBox::GetNextXULBox(child); + } + + if (isEqual) { + if (isHorizontal) + vpref.width = biggestPref*count; + else + vpref.height = biggestPref*count; + } + + // now add our border and padding + AddBorderAndPadding(aBox, vpref); + + return vpref; +} + +nsSize +nsSprocketLayout::GetXULMinSize(nsIFrame* aBox, nsBoxLayoutState& aState) +{ + nsSize minSize (0, 0); + bool isHorizontal = IsXULHorizontal(aBox); + + nscoord biggestMin = 0; + + + // run through all the children and get their min, max, and preferred sizes + // return us the size of the box + + nsIFrame* child = nsBox::GetChildXULBox(aBox); + nsFrameState frameState = nsFrameState(0); + GetFrameState(aBox, frameState); + bool isEqual = !!(frameState & NS_STATE_EQUAL_SIZE); + int32_t count = 0; + + while (child) + { + // ignore collapsed children + if (!child->IsXULCollapsed()) + { + nsSize min = child->GetXULMinSize(aState); + nsSize pref(0,0); + + // if the child is not flexible then + // its min size is its pref size. + if (child->GetXULFlex() == 0) { + pref = child->GetXULPrefSize(aState); + if (isHorizontal) + min.width = pref.width; + else + min.height = pref.height; + } + + if (isEqual) { + if (isHorizontal) + { + if (min.width > biggestMin) + biggestMin = min.width; + } else { + if (min.height > biggestMin) + biggestMin = min.height; + } + } + + AddMargin(child, min); + AddLargestSize(minSize, min, isHorizontal); + count++; + } + + child = nsBox::GetNextXULBox(child); + } + + + if (isEqual) { + if (isHorizontal) + minSize.width = biggestMin*count; + else + minSize.height = biggestMin*count; + } + + // now add our border and padding + AddBorderAndPadding(aBox, minSize); + + return minSize; +} + +nsSize +nsSprocketLayout::GetXULMaxSize(nsIFrame* aBox, nsBoxLayoutState& aState) +{ + + bool isHorizontal = IsXULHorizontal(aBox); + + nscoord smallestMax = NS_INTRINSICSIZE; + nsSize maxSize (NS_INTRINSICSIZE, NS_INTRINSICSIZE); + + // run through all the children and get their min, max, and preferred sizes + // return us the size of the box + + nsIFrame* child = nsBox::GetChildXULBox(aBox); + nsFrameState frameState = nsFrameState(0); + GetFrameState(aBox, frameState); + bool isEqual = !!(frameState & NS_STATE_EQUAL_SIZE); + int32_t count = 0; + + while (child) + { + // ignore collapsed children + if (!child->IsXULCollapsed()) + { + // if completely redefined don't even ask our child for its size. + nsSize min = child->GetXULMinSize(aState); + nsSize max = nsBox::BoundsCheckMinMax(min, child->GetXULMaxSize(aState)); + + AddMargin(child, max); + AddSmallestSize(maxSize, max, isHorizontal); + + if (isEqual) { + if (isHorizontal) + { + if (max.width < smallestMax) + smallestMax = max.width; + } else { + if (max.height < smallestMax) + smallestMax = max.height; + } + } + count++; + } + + child = nsBox::GetNextXULBox(child); + } + + if (isEqual) { + if (isHorizontal) { + if (smallestMax != NS_INTRINSICSIZE) + maxSize.width = smallestMax*count; + else + maxSize.width = NS_INTRINSICSIZE; + } else { + if (smallestMax != NS_INTRINSICSIZE) + maxSize.height = smallestMax*count; + else + maxSize.height = NS_INTRINSICSIZE; + } + } + + // now add our border and padding + AddBorderAndPadding(aBox, maxSize); + + return maxSize; +} + + +nscoord +nsSprocketLayout::GetAscent(nsIFrame* aBox, nsBoxLayoutState& aState) +{ + nscoord vAscent = 0; + + bool isHorizontal = IsXULHorizontal(aBox); + + // run through all the children and get their min, max, and preferred sizes + // return us the size of the box + + nsIFrame* child = nsBox::GetChildXULBox(aBox); + + while (child) + { + // ignore collapsed children + //if (!child->IsXULCollapsed()) + //{ + // if completely redefined don't even ask our child for its size. + nscoord ascent = child->GetXULBoxAscent(aState); + + nsMargin margin; + child->GetXULMargin(margin); + ascent += margin.top; + + if (isHorizontal) + { + if (ascent > vAscent) + vAscent = ascent; + } else { + if (vAscent == 0) + vAscent = ascent; + } + //} + + child = nsBox::GetNextXULBox(child); + } + + nsMargin borderPadding; + aBox->GetXULBorderAndPadding(borderPadding); + + return vAscent + borderPadding.top; +} + +void +nsSprocketLayout::SetLargestSize(nsSize& aSize1, const nsSize& aSize2, bool aIsHorizontal) +{ + if (aIsHorizontal) + { + if (aSize1.height < aSize2.height) + aSize1.height = aSize2.height; + } else { + if (aSize1.width < aSize2.width) + aSize1.width = aSize2.width; + } +} + +void +nsSprocketLayout::SetSmallestSize(nsSize& aSize1, const nsSize& aSize2, bool aIsHorizontal) +{ + if (aIsHorizontal) + { + if (aSize1.height > aSize2.height) + aSize1.height = aSize2.height; + } else { + if (aSize1.width > aSize2.width) + aSize1.width = aSize2.width; + + } +} + +void +nsSprocketLayout::AddLargestSize(nsSize& aSize, const nsSize& aSizeToAdd, bool aIsHorizontal) +{ + if (aIsHorizontal) + AddCoord(aSize.width, aSizeToAdd.width); + else + AddCoord(aSize.height, aSizeToAdd.height); + + SetLargestSize(aSize, aSizeToAdd, aIsHorizontal); +} + +void +nsSprocketLayout::AddCoord(nscoord& aCoord, nscoord aCoordToAdd) +{ + if (aCoord != NS_INTRINSICSIZE) + { + if (aCoordToAdd == NS_INTRINSICSIZE) + aCoord = aCoordToAdd; + else + aCoord += aCoordToAdd; + } +} +void +nsSprocketLayout::AddSmallestSize(nsSize& aSize, const nsSize& aSizeToAdd, bool aIsHorizontal) +{ + if (aIsHorizontal) + AddCoord(aSize.width, aSizeToAdd.width); + else + AddCoord(aSize.height, aSizeToAdd.height); + + SetSmallestSize(aSize, aSizeToAdd, aIsHorizontal); +} + +bool +nsSprocketLayout::GetDefaultFlex(int32_t& aFlex) +{ + aFlex = 0; + return true; +} + +nsComputedBoxSize::nsComputedBoxSize() +{ + resized = false; + valid = false; + size = 0; + next = nullptr; +} + +nsBoxSize::nsBoxSize() +{ + pref = 0; + min = 0; + max = NS_INTRINSICSIZE; + collapsed = false; + left = 0; + right = 0; + flex = 0; + next = nullptr; + bogus = false; +} + + +void* +nsBoxSize::operator new(size_t sz, nsBoxLayoutState& aState) CPP_THROW_NEW +{ + return mozilla::AutoStackArena::Allocate(sz); +} + + +void +nsBoxSize::operator delete(void* aPtr, size_t sz) +{ +} + + +void* +nsComputedBoxSize::operator new(size_t sz, nsBoxLayoutState& aState) CPP_THROW_NEW +{ + return mozilla::AutoStackArena::Allocate(sz); +} + +void +nsComputedBoxSize::operator delete(void* aPtr, size_t sz) +{ +} |