1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
9 // Netscape Communications
11 // See documentation in associated header file
14 #include "nsStackLayout.h"
16 #include "nsBoxLayoutState.h"
18 #include "nsBoxFrame.h"
19 #include "nsGkAtoms.h"
20 #include "nsIContent.h"
21 #include "nsNameSpaceManager.h"
23 using namespace mozilla
;
25 nsBoxLayout
* nsStackLayout::gInstance
= nullptr;
27 #define SPECIFIED_LEFT (1 << eSideLeft)
28 #define SPECIFIED_RIGHT (1 << eSideRight)
29 #define SPECIFIED_TOP (1 << eSideTop)
30 #define SPECIFIED_BOTTOM (1 << eSideBottom)
32 nsresult
NS_NewStackLayout(nsCOMPtr
<nsBoxLayout
>& aNewLayout
) {
33 if (!nsStackLayout::gInstance
) {
34 nsStackLayout::gInstance
= new nsStackLayout();
35 NS_IF_ADDREF(nsStackLayout::gInstance
);
37 // we have not instance variables so just return our static one.
38 aNewLayout
= nsStackLayout::gInstance
;
43 void nsStackLayout::Shutdown() { NS_IF_RELEASE(gInstance
); }
45 nsStackLayout::nsStackLayout() {}
48 * Sizing: we are as wide as the widest child plus its left offset
49 * we are tall as the tallest child plus its top offset.
51 * Only children which have -moz-stack-sizing set to stretch-to-fit
52 * (the default) will be included in the size computations.
55 nsSize
nsStackLayout::GetXULPrefSize(nsIFrame
* aBox
, nsBoxLayoutState
& aState
) {
56 nsSize
prefSize(0, 0);
58 nsIFrame
* child
= nsBox::GetChildXULBox(aBox
);
60 auto stackSizing
= child
->StyleXUL()->mStackSizing
;
61 if (stackSizing
!= StyleStackSizing::Ignore
) {
62 nsSize pref
= child
->GetXULPrefSize(aState
);
64 AddMargin(child
, pref
);
66 GetOffset(child
, offset
);
67 pref
.width
+= offset
.LeftRight();
68 pref
.height
+= offset
.TopBottom();
70 if (pref
.width
> prefSize
.width
&&
71 stackSizing
!= StyleStackSizing::IgnoreHorizontal
) {
72 prefSize
.width
= pref
.width
;
74 if (pref
.height
> prefSize
.height
&&
75 stackSizing
!= StyleStackSizing::IgnoreVertical
) {
76 prefSize
.height
= pref
.height
;
80 child
= nsBox::GetNextXULBox(child
);
83 AddBorderAndPadding(aBox
, prefSize
);
88 nsSize
nsStackLayout::GetXULMinSize(nsIFrame
* aBox
, nsBoxLayoutState
& aState
) {
91 nsIFrame
* child
= nsBox::GetChildXULBox(aBox
);
93 auto stackSizing
= child
->StyleXUL()->mStackSizing
;
94 if (stackSizing
!= StyleStackSizing::Ignore
) {
95 nsSize min
= child
->GetXULMinSize(aState
);
97 AddMargin(child
, min
);
99 GetOffset(child
, offset
);
100 min
.width
+= offset
.LeftRight();
101 min
.height
+= offset
.TopBottom();
103 if (min
.width
> minSize
.width
&&
104 stackSizing
!= StyleStackSizing::IgnoreHorizontal
) {
105 minSize
.width
= min
.width
;
107 if (min
.height
> minSize
.height
&&
108 stackSizing
!= StyleStackSizing::IgnoreVertical
) {
109 minSize
.height
= min
.height
;
113 child
= nsBox::GetNextXULBox(child
);
116 AddBorderAndPadding(aBox
, minSize
);
121 nsSize
nsStackLayout::GetXULMaxSize(nsIFrame
* aBox
, nsBoxLayoutState
& aState
) {
122 nsSize
maxSize(NS_UNCONSTRAINEDSIZE
, NS_UNCONSTRAINEDSIZE
);
124 nsIFrame
* child
= nsBox::GetChildXULBox(aBox
);
126 auto stackSizing
= child
->StyleXUL()->mStackSizing
;
127 if (stackSizing
!= StyleStackSizing::Ignore
) {
128 nsSize min
= child
->GetXULMinSize(aState
);
129 nsSize max
= child
->GetXULMaxSize(aState
);
131 max
= nsBox::BoundsCheckMinMax(min
, max
);
133 AddMargin(child
, max
);
135 GetOffset(child
, offset
);
136 max
.width
+= offset
.LeftRight();
137 max
.height
+= offset
.TopBottom();
139 if (max
.width
< maxSize
.width
&&
140 stackSizing
!= StyleStackSizing::IgnoreHorizontal
) {
141 maxSize
.width
= max
.width
;
143 if (max
.height
< maxSize
.height
&&
144 stackSizing
!= StyleStackSizing::IgnoreVertical
) {
145 maxSize
.height
= max
.height
;
149 child
= nsBox::GetNextXULBox(child
);
152 AddBorderAndPadding(aBox
, maxSize
);
157 nscoord
nsStackLayout::GetAscent(nsIFrame
* aBox
, nsBoxLayoutState
& aState
) {
160 nsIFrame
* child
= nsBox::GetChildXULBox(aBox
);
162 nscoord ascent
= child
->GetXULBoxAscent(aState
);
164 child
->GetXULMargin(margin
);
165 ascent
+= margin
.top
;
166 if (ascent
> vAscent
) vAscent
= ascent
;
168 child
= nsBox::GetNextXULBox(child
);
174 uint8_t nsStackLayout::GetOffset(nsIFrame
* aChild
, nsMargin
& aOffset
) {
175 aOffset
= nsMargin(0, 0, 0, 0);
177 // get the left, right, top and bottom offsets
179 // As an optimization, we cache the fact that we are not positioned to avoid
180 // wasting time fetching attributes.
181 if (aChild
->IsXULBoxFrame() &&
182 (aChild
->GetStateBits() & NS_STATE_STACK_NOT_POSITIONED
))
185 uint8_t offsetSpecified
= 0;
186 nsIContent
* content
= aChild
->GetContent();
187 if (content
&& content
->IsElement()) {
188 bool ltr
= aChild
->StyleVisibility()->mDirection
== NS_STYLE_DIRECTION_LTR
;
192 content
->AsElement()->GetAttr(kNameSpaceID_None
, nsGkAtoms::start
, value
);
193 if (!value
.IsEmpty()) {
197 nsPresContext::CSSPixelsToAppUnits(value
.ToInteger(&error
));
198 offsetSpecified
|= SPECIFIED_LEFT
;
201 nsPresContext::CSSPixelsToAppUnits(value
.ToInteger(&error
));
202 offsetSpecified
|= SPECIFIED_RIGHT
;
206 content
->AsElement()->GetAttr(kNameSpaceID_None
, nsGkAtoms::end
, value
);
207 if (!value
.IsEmpty()) {
211 nsPresContext::CSSPixelsToAppUnits(value
.ToInteger(&error
));
212 offsetSpecified
|= SPECIFIED_RIGHT
;
215 nsPresContext::CSSPixelsToAppUnits(value
.ToInteger(&error
));
216 offsetSpecified
|= SPECIFIED_LEFT
;
220 content
->AsElement()->GetAttr(kNameSpaceID_None
, nsGkAtoms::left
, value
);
221 if (!value
.IsEmpty()) {
224 nsPresContext::CSSPixelsToAppUnits(value
.ToInteger(&error
));
225 offsetSpecified
|= SPECIFIED_LEFT
;
228 content
->AsElement()->GetAttr(kNameSpaceID_None
, nsGkAtoms::right
, value
);
229 if (!value
.IsEmpty()) {
232 nsPresContext::CSSPixelsToAppUnits(value
.ToInteger(&error
));
233 offsetSpecified
|= SPECIFIED_RIGHT
;
236 content
->AsElement()->GetAttr(kNameSpaceID_None
, nsGkAtoms::top
, value
);
237 if (!value
.IsEmpty()) {
239 aOffset
.top
= nsPresContext::CSSPixelsToAppUnits(value
.ToInteger(&error
));
240 offsetSpecified
|= SPECIFIED_TOP
;
243 content
->AsElement()->GetAttr(kNameSpaceID_None
, nsGkAtoms::bottom
, value
);
244 if (!value
.IsEmpty()) {
247 nsPresContext::CSSPixelsToAppUnits(value
.ToInteger(&error
));
248 offsetSpecified
|= SPECIFIED_BOTTOM
;
252 if (!offsetSpecified
&& aChild
->IsXULBoxFrame()) {
253 // If no offset was specified at all, then we cache this fact to avoid
254 // requerying CSS or the content model.
255 aChild
->AddStateBits(NS_STATE_STACK_NOT_POSITIONED
);
258 return offsetSpecified
;
262 nsStackLayout::XULLayout(nsIFrame
* aBox
, nsBoxLayoutState
& aState
) {
264 aBox
->GetXULClientRect(clientRect
);
269 nsIFrame
* child
= nsBox::GetChildXULBox(aBox
);
274 child
->GetXULMargin(margin
);
275 nsRect
childRect(clientRect
);
276 childRect
.Deflate(margin
);
278 if (childRect
.width
< 0) childRect
.width
= 0;
280 if (childRect
.height
< 0) childRect
.height
= 0;
282 nsRect
oldRect(child
->GetRect());
283 bool sizeChanged
= !oldRect
.IsEqualEdges(childRect
);
285 // only lay out dirty children or children whose sizes have changed
286 if (sizeChanged
|| NS_SUBTREE_DIRTY(child
)) {
287 // add in the child's margin
289 child
->GetXULMargin(margin
);
291 // obtain our offset from the top left border of the stack's content
294 uint8_t offsetSpecified
= GetOffset(child
, offset
);
296 // Set the position and size based on which offsets have been specified:
297 // left only - offset from left edge, preferred width
298 // right only - offset from right edge, preferred width
299 // left and right - offset from left and right edges, width in between
300 // this neither - no offset, full width of stack
301 // Vertical direction is similar.
303 // Margins on the child are also included in the edge offsets
304 if (offsetSpecified
) {
305 nsSize min
= child
->GetXULMinSize(aState
);
306 nsSize max
= child
->GetXULMaxSize(aState
);
307 if (offsetSpecified
& SPECIFIED_LEFT
) {
308 childRect
.x
= clientRect
.x
+ offset
.left
+ margin
.left
;
309 if (offsetSpecified
& SPECIFIED_RIGHT
) {
311 clientRect
.width
- offset
.LeftRight() - margin
.LeftRight();
312 childRect
.width
= clamped(width
, min
.width
, max
.width
);
314 nscoord width
= child
->GetXULPrefSize(aState
).width
;
315 childRect
.width
= clamped(width
, min
.width
, max
.width
);
317 } else if (offsetSpecified
& SPECIFIED_RIGHT
) {
318 nscoord width
= child
->GetXULPrefSize(aState
).width
;
319 childRect
.width
= clamped(width
, min
.width
, max
.width
);
320 childRect
.x
= clientRect
.XMost() - offset
.right
- margin
.right
-
324 if (offsetSpecified
& SPECIFIED_TOP
) {
325 childRect
.y
= clientRect
.y
+ offset
.top
+ margin
.top
;
326 if (offsetSpecified
& SPECIFIED_BOTTOM
) {
328 clientRect
.height
- offset
.TopBottom() - margin
.TopBottom();
329 childRect
.height
= clamped(height
, min
.height
, max
.height
);
331 nscoord height
= child
->GetXULPrefSize(aState
).height
;
332 childRect
.height
= clamped(height
, min
.height
, max
.height
);
334 } else if (offsetSpecified
& SPECIFIED_BOTTOM
) {
335 nscoord height
= child
->GetXULPrefSize(aState
).height
;
336 childRect
.height
= clamped(height
, min
.height
, max
.height
);
337 childRect
.y
= clientRect
.YMost() - offset
.bottom
- margin
.bottom
-
342 // Now place the child.
343 child
->SetXULBounds(aState
, childRect
);
346 child
->XULLayout(aState
);
348 // Get the child's new rect.
349 childRect
= child
->GetRect();
350 childRect
.Inflate(margin
);
352 auto stackSizing
= child
->StyleXUL()->mStackSizing
;
353 if (stackSizing
!= StyleStackSizing::Ignore
) {
354 // Did the child push back on us and get bigger?
355 if (offset
.LeftRight() + childRect
.width
> clientRect
.width
&&
356 stackSizing
!= StyleStackSizing::IgnoreHorizontal
) {
357 clientRect
.width
= childRect
.width
+ offset
.LeftRight();
361 if (offset
.TopBottom() + childRect
.height
> clientRect
.height
&&
362 stackSizing
!= StyleStackSizing::IgnoreVertical
) {
363 clientRect
.height
= childRect
.height
+ offset
.TopBottom();
369 child
= nsBox::GetNextXULBox(child
);
373 // if some HTML inside us got bigger we need to force ourselves to
375 nsRect
bounds(aBox
->GetRect());
377 aBox
->GetXULBorderAndPadding(bp
);
378 clientRect
.Inflate(bp
);
380 if (clientRect
.width
> bounds
.width
|| clientRect
.height
> bounds
.height
) {
381 if (clientRect
.width
> bounds
.width
) bounds
.width
= clientRect
.width
;
382 if (clientRect
.height
> bounds
.height
) bounds
.height
= clientRect
.height
;
384 aBox
->SetXULBounds(aState
, bounds
);