1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 // Netscape Communications
10 // See documentation in associated header file
13 #include "nsStackLayout.h"
15 #include "nsBoxLayoutState.h"
17 #include "nsBoxFrame.h"
18 #include "nsGkAtoms.h"
19 #include "nsIContent.h"
20 #include "nsNameSpaceManager.h"
22 using namespace mozilla
;
24 nsBoxLayout
* nsStackLayout::gInstance
= nullptr;
26 #define SPECIFIED_LEFT (1 << NS_SIDE_LEFT)
27 #define SPECIFIED_RIGHT (1 << NS_SIDE_RIGHT)
28 #define SPECIFIED_TOP (1 << NS_SIDE_TOP)
29 #define SPECIFIED_BOTTOM (1 << NS_SIDE_BOTTOM)
32 NS_NewStackLayout( nsIPresShell
* aPresShell
, nsCOMPtr
<nsBoxLayout
>& aNewLayout
)
34 if (!nsStackLayout::gInstance
) {
35 nsStackLayout::gInstance
= new nsStackLayout();
36 NS_IF_ADDREF(nsStackLayout::gInstance
);
38 // we have not instance variables so just return our static one.
39 aNewLayout
= nsStackLayout::gInstance
;
44 nsStackLayout::Shutdown()
46 NS_IF_RELEASE(gInstance
);
49 nsStackLayout::nsStackLayout()
54 * Sizing: we are as wide as the widest child plus its left offset
55 * we are tall as the tallest child plus its top offset.
57 * Only children which have -moz-stack-sizing set to stretch-to-fit
58 * (the default) will be included in the size computations.
62 nsStackLayout::GetPrefSize(nsIFrame
* aBox
, nsBoxLayoutState
& aState
)
64 nsSize
prefSize (0, 0);
66 nsIFrame
* child
= nsBox::GetChildBox(aBox
);
68 if (child
->StyleXUL()->mStretchStack
) {
69 nsSize pref
= child
->GetPrefSize(aState
);
71 AddMargin(child
, pref
);
73 GetOffset(aState
, child
, offset
);
74 pref
.width
+= offset
.LeftRight();
75 pref
.height
+= offset
.TopBottom();
76 AddLargestSize(prefSize
, pref
);
79 child
= nsBox::GetNextBox(child
);
82 AddBorderAndPadding(aBox
, prefSize
);
88 nsStackLayout::GetMinSize(nsIFrame
* aBox
, nsBoxLayoutState
& aState
)
90 nsSize
minSize (0, 0);
92 nsIFrame
* child
= nsBox::GetChildBox(aBox
);
94 if (child
->StyleXUL()->mStretchStack
) {
95 nsSize min
= child
->GetMinSize(aState
);
97 AddMargin(child
, min
);
99 GetOffset(aState
, child
, offset
);
100 min
.width
+= offset
.LeftRight();
101 min
.height
+= offset
.TopBottom();
102 AddLargestSize(minSize
, min
);
105 child
= nsBox::GetNextBox(child
);
108 AddBorderAndPadding(aBox
, minSize
);
114 nsStackLayout::GetMaxSize(nsIFrame
* aBox
, nsBoxLayoutState
& aState
)
116 nsSize
maxSize (NS_INTRINSICSIZE
, NS_INTRINSICSIZE
);
118 nsIFrame
* child
= nsBox::GetChildBox(aBox
);
120 if (child
->StyleXUL()->mStretchStack
) {
121 nsSize min
= child
->GetMinSize(aState
);
122 nsSize max
= child
->GetMaxSize(aState
);
124 max
= nsBox::BoundsCheckMinMax(min
, max
);
126 AddMargin(child
, max
);
128 GetOffset(aState
, child
, offset
);
129 max
.width
+= offset
.LeftRight();
130 max
.height
+= offset
.TopBottom();
131 AddSmallestSize(maxSize
, max
);
134 child
= nsBox::GetNextBox(child
);
137 AddBorderAndPadding(aBox
, maxSize
);
144 nsStackLayout::GetAscent(nsIFrame
* aBox
, nsBoxLayoutState
& aState
)
148 nsIFrame
* child
= nsBox::GetChildBox(aBox
);
150 nscoord ascent
= child
->GetBoxAscent(aState
);
152 child
->GetMargin(margin
);
153 ascent
+= margin
.top
;
154 if (ascent
> vAscent
)
157 child
= nsBox::GetNextBox(child
);
164 nsStackLayout::GetOffset(nsBoxLayoutState
& aState
, nsIFrame
* aChild
, nsMargin
& aOffset
)
166 aOffset
= nsMargin(0, 0, 0, 0);
168 // get the left, right, top and bottom offsets
170 // As an optimization, we cache the fact that we are not positioned to avoid
171 // wasting time fetching attributes.
172 if (aChild
->IsBoxFrame() &&
173 (aChild
->GetStateBits() & NS_STATE_STACK_NOT_POSITIONED
))
176 uint8_t offsetSpecified
= 0;
177 nsIContent
* content
= aChild
->GetContent();
179 bool ltr
= aChild
->StyleVisibility()->mDirection
== NS_STYLE_DIRECTION_LTR
;
183 content
->GetAttr(kNameSpaceID_None
, nsGkAtoms::start
, value
);
184 if (!value
.IsEmpty()) {
188 nsPresContext::CSSPixelsToAppUnits(value
.ToInteger(&error
));
189 offsetSpecified
|= SPECIFIED_LEFT
;
192 nsPresContext::CSSPixelsToAppUnits(value
.ToInteger(&error
));
193 offsetSpecified
|= SPECIFIED_RIGHT
;
197 content
->GetAttr(kNameSpaceID_None
, nsGkAtoms::end
, value
);
198 if (!value
.IsEmpty()) {
202 nsPresContext::CSSPixelsToAppUnits(value
.ToInteger(&error
));
203 offsetSpecified
|= SPECIFIED_RIGHT
;
206 nsPresContext::CSSPixelsToAppUnits(value
.ToInteger(&error
));
207 offsetSpecified
|= SPECIFIED_LEFT
;
211 content
->GetAttr(kNameSpaceID_None
, nsGkAtoms::left
, value
);
212 if (!value
.IsEmpty()) {
215 nsPresContext::CSSPixelsToAppUnits(value
.ToInteger(&error
));
216 offsetSpecified
|= SPECIFIED_LEFT
;
219 content
->GetAttr(kNameSpaceID_None
, nsGkAtoms::right
, value
);
220 if (!value
.IsEmpty()) {
223 nsPresContext::CSSPixelsToAppUnits(value
.ToInteger(&error
));
224 offsetSpecified
|= SPECIFIED_RIGHT
;
227 content
->GetAttr(kNameSpaceID_None
, nsGkAtoms::top
, value
);
228 if (!value
.IsEmpty()) {
231 nsPresContext::CSSPixelsToAppUnits(value
.ToInteger(&error
));
232 offsetSpecified
|= SPECIFIED_TOP
;
235 content
->GetAttr(kNameSpaceID_None
, nsGkAtoms::bottom
, value
);
236 if (!value
.IsEmpty()) {
239 nsPresContext::CSSPixelsToAppUnits(value
.ToInteger(&error
));
240 offsetSpecified
|= SPECIFIED_BOTTOM
;
244 if (!offsetSpecified
&& aChild
->IsBoxFrame()) {
245 // If no offset was specified at all, then we cache this fact to avoid requerying
246 // CSS or the content model.
247 aChild
->AddStateBits(NS_STATE_STACK_NOT_POSITIONED
);
250 return offsetSpecified
;
255 nsStackLayout::Layout(nsIFrame
* aBox
, nsBoxLayoutState
& aState
)
258 aBox
->GetClientRect(clientRect
);
263 nsIFrame
* child
= nsBox::GetChildBox(aBox
);
269 child
->GetMargin(margin
);
270 nsRect
childRect(clientRect
);
271 childRect
.Deflate(margin
);
273 if (childRect
.width
< 0)
276 if (childRect
.height
< 0)
277 childRect
.height
= 0;
279 nsRect
oldRect(child
->GetRect());
280 bool sizeChanged
= !oldRect
.IsEqualEdges(childRect
);
282 // only lay out dirty children or children whose sizes have changed
283 if (sizeChanged
|| NS_SUBTREE_DIRTY(child
)) {
284 // add in the child's margin
286 child
->GetMargin(margin
);
288 // obtain our offset from the top left border of the stack's content box.
290 uint8_t offsetSpecified
= GetOffset(aState
, child
, offset
);
292 // Set the position and size based on which offsets have been specified:
293 // left only - offset from left edge, preferred width
294 // right only - offset from right edge, preferred width
295 // left and right - offset from left and right edges, width in between this
296 // neither - no offset, full width of stack
297 // Vertical direction is similar.
299 // Margins on the child are also included in the edge offsets
300 if (offsetSpecified
) {
301 if (offsetSpecified
& SPECIFIED_LEFT
) {
302 childRect
.x
= clientRect
.x
+ offset
.left
+ margin
.left
;
303 if (offsetSpecified
& SPECIFIED_RIGHT
) {
304 nsSize min
= child
->GetMinSize(aState
);
305 nsSize max
= child
->GetMaxSize(aState
);
306 nscoord width
= clientRect
.width
- offset
.LeftRight() - margin
.LeftRight();
307 childRect
.width
= clamped(width
, min
.width
, max
.width
);
310 childRect
.width
= child
->GetPrefSize(aState
).width
;
313 else if (offsetSpecified
& SPECIFIED_RIGHT
) {
314 childRect
.width
= child
->GetPrefSize(aState
).width
;
315 childRect
.x
= clientRect
.XMost() - offset
.right
- margin
.right
- childRect
.width
;
318 if (offsetSpecified
& SPECIFIED_TOP
) {
319 childRect
.y
= clientRect
.y
+ offset
.top
+ margin
.top
;
320 if (offsetSpecified
& SPECIFIED_BOTTOM
) {
321 nsSize min
= child
->GetMinSize(aState
);
322 nsSize max
= child
->GetMaxSize(aState
);
323 nscoord height
= clientRect
.height
- offset
.TopBottom() - margin
.TopBottom();
324 childRect
.height
= clamped(height
, min
.height
, max
.height
);
327 childRect
.height
= child
->GetPrefSize(aState
).height
;
330 else if (offsetSpecified
& SPECIFIED_BOTTOM
) {
331 childRect
.height
= child
->GetPrefSize(aState
).height
;
332 childRect
.y
= clientRect
.YMost() - offset
.bottom
- margin
.bottom
- childRect
.height
;
336 // Now place the child.
337 child
->SetBounds(aState
, childRect
);
340 child
->Layout(aState
);
342 // Get the child's new rect.
343 childRect
= child
->GetRect();
344 childRect
.Inflate(margin
);
346 if (child
->StyleXUL()->mStretchStack
) {
347 // Did the child push back on us and get bigger?
348 if (offset
.LeftRight() + childRect
.width
> clientRect
.width
) {
349 clientRect
.width
= childRect
.width
+ offset
.LeftRight();
353 if (offset
.TopBottom() + childRect
.height
> clientRect
.height
) {
354 clientRect
.height
= childRect
.height
+ offset
.TopBottom();
360 child
= nsBox::GetNextBox(child
);
364 // if some HTML inside us got bigger we need to force ourselves to
366 nsRect
bounds(aBox
->GetRect());
368 aBox
->GetBorderAndPadding(bp
);
369 clientRect
.Inflate(bp
);
371 if (clientRect
.width
> bounds
.width
|| clientRect
.height
> bounds
.height
)
373 if (clientRect
.width
> bounds
.width
)
374 bounds
.width
= clientRect
.width
;
375 if (clientRect
.height
> bounds
.height
)
376 bounds
.height
= clientRect
.height
;
378 aBox
->SetBounds(aState
, bounds
);