michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: // michael@0: // Eric Vaughan michael@0: // Netscape Communications michael@0: // michael@0: // See documentation in associated header file michael@0: // michael@0: michael@0: #include "nsStackLayout.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nsBoxLayoutState.h" michael@0: #include "nsBox.h" michael@0: #include "nsBoxFrame.h" michael@0: #include "nsGkAtoms.h" michael@0: #include "nsIContent.h" michael@0: #include "nsNameSpaceManager.h" michael@0: michael@0: using namespace mozilla; michael@0: michael@0: nsBoxLayout* nsStackLayout::gInstance = nullptr; michael@0: michael@0: #define SPECIFIED_LEFT (1 << NS_SIDE_LEFT) michael@0: #define SPECIFIED_RIGHT (1 << NS_SIDE_RIGHT) michael@0: #define SPECIFIED_TOP (1 << NS_SIDE_TOP) michael@0: #define SPECIFIED_BOTTOM (1 << NS_SIDE_BOTTOM) michael@0: michael@0: nsresult michael@0: NS_NewStackLayout( nsIPresShell* aPresShell, nsCOMPtr& aNewLayout) michael@0: { michael@0: if (!nsStackLayout::gInstance) { michael@0: nsStackLayout::gInstance = new nsStackLayout(); michael@0: NS_IF_ADDREF(nsStackLayout::gInstance); michael@0: } michael@0: // we have not instance variables so just return our static one. michael@0: aNewLayout = nsStackLayout::gInstance; michael@0: return NS_OK; michael@0: } michael@0: michael@0: /*static*/ void michael@0: nsStackLayout::Shutdown() michael@0: { michael@0: NS_IF_RELEASE(gInstance); michael@0: } michael@0: michael@0: nsStackLayout::nsStackLayout() michael@0: { michael@0: } michael@0: michael@0: /* michael@0: * Sizing: we are as wide as the widest child plus its left offset michael@0: * we are tall as the tallest child plus its top offset. michael@0: * michael@0: * Only children which have -moz-stack-sizing set to stretch-to-fit michael@0: * (the default) will be included in the size computations. michael@0: */ michael@0: michael@0: nsSize michael@0: nsStackLayout::GetPrefSize(nsIFrame* aBox, nsBoxLayoutState& aState) michael@0: { michael@0: nsSize prefSize (0, 0); michael@0: michael@0: nsIFrame* child = aBox->GetChildBox(); michael@0: while (child) { michael@0: if (child->StyleXUL()->mStretchStack) { michael@0: nsSize pref = child->GetPrefSize(aState); michael@0: michael@0: AddMargin(child, pref); michael@0: nsMargin offset; michael@0: GetOffset(aState, child, offset); michael@0: pref.width += offset.LeftRight(); michael@0: pref.height += offset.TopBottom(); michael@0: AddLargestSize(prefSize, pref); michael@0: } michael@0: michael@0: child = child->GetNextBox(); michael@0: } michael@0: michael@0: AddBorderAndPadding(aBox, prefSize); michael@0: michael@0: return prefSize; michael@0: } michael@0: michael@0: nsSize michael@0: nsStackLayout::GetMinSize(nsIFrame* aBox, nsBoxLayoutState& aState) michael@0: { michael@0: nsSize minSize (0, 0); michael@0: michael@0: nsIFrame* child = aBox->GetChildBox(); michael@0: while (child) { michael@0: if (child->StyleXUL()->mStretchStack) { michael@0: nsSize min = child->GetMinSize(aState); michael@0: michael@0: AddMargin(child, min); michael@0: nsMargin offset; michael@0: GetOffset(aState, child, offset); michael@0: min.width += offset.LeftRight(); michael@0: min.height += offset.TopBottom(); michael@0: AddLargestSize(minSize, min); michael@0: } michael@0: michael@0: child = child->GetNextBox(); michael@0: } michael@0: michael@0: AddBorderAndPadding(aBox, minSize); michael@0: michael@0: return minSize; michael@0: } michael@0: michael@0: nsSize michael@0: nsStackLayout::GetMaxSize(nsIFrame* aBox, nsBoxLayoutState& aState) michael@0: { michael@0: nsSize maxSize (NS_INTRINSICSIZE, NS_INTRINSICSIZE); michael@0: michael@0: nsIFrame* child = aBox->GetChildBox(); michael@0: while (child) { michael@0: if (child->StyleXUL()->mStretchStack) { michael@0: nsSize min = child->GetMinSize(aState); michael@0: nsSize max = child->GetMaxSize(aState); michael@0: michael@0: max = nsBox::BoundsCheckMinMax(min, max); michael@0: michael@0: AddMargin(child, max); michael@0: nsMargin offset; michael@0: GetOffset(aState, child, offset); michael@0: max.width += offset.LeftRight(); michael@0: max.height += offset.TopBottom(); michael@0: AddSmallestSize(maxSize, max); michael@0: } michael@0: michael@0: child = child->GetNextBox(); michael@0: } michael@0: michael@0: AddBorderAndPadding(aBox, maxSize); michael@0: michael@0: return maxSize; michael@0: } michael@0: michael@0: michael@0: nscoord michael@0: nsStackLayout::GetAscent(nsIFrame* aBox, nsBoxLayoutState& aState) michael@0: { michael@0: nscoord vAscent = 0; michael@0: michael@0: nsIFrame* child = aBox->GetChildBox(); michael@0: while (child) { michael@0: nscoord ascent = child->GetBoxAscent(aState); michael@0: nsMargin margin; michael@0: child->GetMargin(margin); michael@0: ascent += margin.top; michael@0: if (ascent > vAscent) michael@0: vAscent = ascent; michael@0: michael@0: child = child->GetNextBox(); michael@0: } michael@0: michael@0: return vAscent; michael@0: } michael@0: michael@0: uint8_t michael@0: nsStackLayout::GetOffset(nsBoxLayoutState& aState, nsIFrame* aChild, nsMargin& aOffset) michael@0: { michael@0: aOffset = nsMargin(0, 0, 0, 0); michael@0: michael@0: // get the left, right, top and bottom offsets michael@0: michael@0: // As an optimization, we cache the fact that we are not positioned to avoid michael@0: // wasting time fetching attributes. michael@0: if (aChild->IsBoxFrame() && michael@0: (aChild->GetStateBits() & NS_STATE_STACK_NOT_POSITIONED)) michael@0: return 0; michael@0: michael@0: uint8_t offsetSpecified = 0; michael@0: nsIContent* content = aChild->GetContent(); michael@0: if (content) { michael@0: bool ltr = aChild->StyleVisibility()->mDirection == NS_STYLE_DIRECTION_LTR; michael@0: nsAutoString value; michael@0: nsresult error; michael@0: michael@0: content->GetAttr(kNameSpaceID_None, nsGkAtoms::start, value); michael@0: if (!value.IsEmpty()) { michael@0: value.Trim("%"); michael@0: if (ltr) { michael@0: aOffset.left = michael@0: nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error)); michael@0: offsetSpecified |= SPECIFIED_LEFT; michael@0: } else { michael@0: aOffset.right = michael@0: nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error)); michael@0: offsetSpecified |= SPECIFIED_RIGHT; michael@0: } michael@0: } michael@0: michael@0: content->GetAttr(kNameSpaceID_None, nsGkAtoms::end, value); michael@0: if (!value.IsEmpty()) { michael@0: value.Trim("%"); michael@0: if (ltr) { michael@0: aOffset.right = michael@0: nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error)); michael@0: offsetSpecified |= SPECIFIED_RIGHT; michael@0: } else { michael@0: aOffset.left = michael@0: nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error)); michael@0: offsetSpecified |= SPECIFIED_LEFT; michael@0: } michael@0: } michael@0: michael@0: content->GetAttr(kNameSpaceID_None, nsGkAtoms::left, value); michael@0: if (!value.IsEmpty()) { michael@0: value.Trim("%"); michael@0: aOffset.left = michael@0: nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error)); michael@0: offsetSpecified |= SPECIFIED_LEFT; michael@0: } michael@0: michael@0: content->GetAttr(kNameSpaceID_None, nsGkAtoms::right, value); michael@0: if (!value.IsEmpty()) { michael@0: value.Trim("%"); michael@0: aOffset.right = michael@0: nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error)); michael@0: offsetSpecified |= SPECIFIED_RIGHT; michael@0: } michael@0: michael@0: content->GetAttr(kNameSpaceID_None, nsGkAtoms::top, value); michael@0: if (!value.IsEmpty()) { michael@0: value.Trim("%"); michael@0: aOffset.top = michael@0: nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error)); michael@0: offsetSpecified |= SPECIFIED_TOP; michael@0: } michael@0: michael@0: content->GetAttr(kNameSpaceID_None, nsGkAtoms::bottom, value); michael@0: if (!value.IsEmpty()) { michael@0: value.Trim("%"); michael@0: aOffset.bottom = michael@0: nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error)); michael@0: offsetSpecified |= SPECIFIED_BOTTOM; michael@0: } michael@0: } michael@0: michael@0: if (!offsetSpecified && aChild->IsBoxFrame()) { michael@0: // If no offset was specified at all, then we cache this fact to avoid requerying michael@0: // CSS or the content model. michael@0: aChild->AddStateBits(NS_STATE_STACK_NOT_POSITIONED); michael@0: } michael@0: michael@0: return offsetSpecified; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsStackLayout::Layout(nsIFrame* aBox, nsBoxLayoutState& aState) michael@0: { michael@0: nsRect clientRect; michael@0: aBox->GetClientRect(clientRect); michael@0: michael@0: bool grow; michael@0: michael@0: do { michael@0: nsIFrame* child = aBox->GetChildBox(); michael@0: grow = false; michael@0: michael@0: while (child) michael@0: { michael@0: nsMargin margin; michael@0: child->GetMargin(margin); michael@0: nsRect childRect(clientRect); michael@0: childRect.Deflate(margin); michael@0: michael@0: if (childRect.width < 0) michael@0: childRect.width = 0; michael@0: michael@0: if (childRect.height < 0) michael@0: childRect.height = 0; michael@0: michael@0: nsRect oldRect(child->GetRect()); michael@0: bool sizeChanged = !oldRect.IsEqualEdges(childRect); michael@0: michael@0: // only lay out dirty children or children whose sizes have changed michael@0: if (sizeChanged || NS_SUBTREE_DIRTY(child)) { michael@0: // add in the child's margin michael@0: nsMargin margin; michael@0: child->GetMargin(margin); michael@0: michael@0: // obtain our offset from the top left border of the stack's content box. michael@0: nsMargin offset; michael@0: uint8_t offsetSpecified = GetOffset(aState, child, offset); michael@0: michael@0: // Set the position and size based on which offsets have been specified: michael@0: // left only - offset from left edge, preferred width michael@0: // right only - offset from right edge, preferred width michael@0: // left and right - offset from left and right edges, width in between this michael@0: // neither - no offset, full width of stack michael@0: // Vertical direction is similar. michael@0: // michael@0: // Margins on the child are also included in the edge offsets michael@0: if (offsetSpecified) { michael@0: if (offsetSpecified & SPECIFIED_LEFT) { michael@0: childRect.x = clientRect.x + offset.left + margin.left; michael@0: if (offsetSpecified & SPECIFIED_RIGHT) { michael@0: nsSize min = child->GetMinSize(aState); michael@0: nsSize max = child->GetMaxSize(aState); michael@0: nscoord width = clientRect.width - offset.LeftRight() - margin.LeftRight(); michael@0: childRect.width = clamped(width, min.width, max.width); michael@0: } michael@0: else { michael@0: childRect.width = child->GetPrefSize(aState).width; michael@0: } michael@0: } michael@0: else if (offsetSpecified & SPECIFIED_RIGHT) { michael@0: childRect.width = child->GetPrefSize(aState).width; michael@0: childRect.x = clientRect.XMost() - offset.right - margin.right - childRect.width; michael@0: } michael@0: michael@0: if (offsetSpecified & SPECIFIED_TOP) { michael@0: childRect.y = clientRect.y + offset.top + margin.top; michael@0: if (offsetSpecified & SPECIFIED_BOTTOM) { michael@0: nsSize min = child->GetMinSize(aState); michael@0: nsSize max = child->GetMaxSize(aState); michael@0: nscoord height = clientRect.height - offset.TopBottom() - margin.TopBottom(); michael@0: childRect.height = clamped(height, min.height, max.height); michael@0: } michael@0: else { michael@0: childRect.height = child->GetPrefSize(aState).height; michael@0: } michael@0: } michael@0: else if (offsetSpecified & SPECIFIED_BOTTOM) { michael@0: childRect.height = child->GetPrefSize(aState).height; michael@0: childRect.y = clientRect.YMost() - offset.bottom - margin.bottom - childRect.height; michael@0: } michael@0: } michael@0: michael@0: // Now place the child. michael@0: child->SetBounds(aState, childRect); michael@0: michael@0: // Flow the child. michael@0: child->Layout(aState); michael@0: michael@0: // Get the child's new rect. michael@0: childRect = child->GetRect(); michael@0: childRect.Inflate(margin); michael@0: michael@0: if (child->StyleXUL()->mStretchStack) { michael@0: // Did the child push back on us and get bigger? michael@0: if (offset.LeftRight() + childRect.width > clientRect.width) { michael@0: clientRect.width = childRect.width + offset.LeftRight(); michael@0: grow = true; michael@0: } michael@0: michael@0: if (offset.TopBottom() + childRect.height > clientRect.height) { michael@0: clientRect.height = childRect.height + offset.TopBottom(); michael@0: grow = true; michael@0: } michael@0: } michael@0: } michael@0: michael@0: child = child->GetNextBox(); michael@0: } michael@0: } while (grow); michael@0: michael@0: // if some HTML inside us got bigger we need to force ourselves to michael@0: // get bigger michael@0: nsRect bounds(aBox->GetRect()); michael@0: nsMargin bp; michael@0: aBox->GetBorderAndPadding(bp); michael@0: clientRect.Inflate(bp); michael@0: michael@0: if (clientRect.width > bounds.width || clientRect.height > bounds.height) michael@0: { michael@0: if (clientRect.width > bounds.width) michael@0: bounds.width = clientRect.width; michael@0: if (clientRect.height > bounds.height) michael@0: bounds.height = clientRect.height; michael@0: michael@0: aBox->SetBounds(aState, bounds); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: