|
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/. */ |
|
5 |
|
6 // |
|
7 // Eric Vaughan |
|
8 // Netscape Communications |
|
9 // |
|
10 // See documentation in associated header file |
|
11 // |
|
12 |
|
13 #include "nsStackLayout.h" |
|
14 #include "nsCOMPtr.h" |
|
15 #include "nsBoxLayoutState.h" |
|
16 #include "nsBox.h" |
|
17 #include "nsBoxFrame.h" |
|
18 #include "nsGkAtoms.h" |
|
19 #include "nsIContent.h" |
|
20 #include "nsNameSpaceManager.h" |
|
21 |
|
22 using namespace mozilla; |
|
23 |
|
24 nsBoxLayout* nsStackLayout::gInstance = nullptr; |
|
25 |
|
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) |
|
30 |
|
31 nsresult |
|
32 NS_NewStackLayout( nsIPresShell* aPresShell, nsCOMPtr<nsBoxLayout>& aNewLayout) |
|
33 { |
|
34 if (!nsStackLayout::gInstance) { |
|
35 nsStackLayout::gInstance = new nsStackLayout(); |
|
36 NS_IF_ADDREF(nsStackLayout::gInstance); |
|
37 } |
|
38 // we have not instance variables so just return our static one. |
|
39 aNewLayout = nsStackLayout::gInstance; |
|
40 return NS_OK; |
|
41 } |
|
42 |
|
43 /*static*/ void |
|
44 nsStackLayout::Shutdown() |
|
45 { |
|
46 NS_IF_RELEASE(gInstance); |
|
47 } |
|
48 |
|
49 nsStackLayout::nsStackLayout() |
|
50 { |
|
51 } |
|
52 |
|
53 /* |
|
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. |
|
56 * |
|
57 * Only children which have -moz-stack-sizing set to stretch-to-fit |
|
58 * (the default) will be included in the size computations. |
|
59 */ |
|
60 |
|
61 nsSize |
|
62 nsStackLayout::GetPrefSize(nsIFrame* aBox, nsBoxLayoutState& aState) |
|
63 { |
|
64 nsSize prefSize (0, 0); |
|
65 |
|
66 nsIFrame* child = aBox->GetChildBox(); |
|
67 while (child) { |
|
68 if (child->StyleXUL()->mStretchStack) { |
|
69 nsSize pref = child->GetPrefSize(aState); |
|
70 |
|
71 AddMargin(child, pref); |
|
72 nsMargin offset; |
|
73 GetOffset(aState, child, offset); |
|
74 pref.width += offset.LeftRight(); |
|
75 pref.height += offset.TopBottom(); |
|
76 AddLargestSize(prefSize, pref); |
|
77 } |
|
78 |
|
79 child = child->GetNextBox(); |
|
80 } |
|
81 |
|
82 AddBorderAndPadding(aBox, prefSize); |
|
83 |
|
84 return prefSize; |
|
85 } |
|
86 |
|
87 nsSize |
|
88 nsStackLayout::GetMinSize(nsIFrame* aBox, nsBoxLayoutState& aState) |
|
89 { |
|
90 nsSize minSize (0, 0); |
|
91 |
|
92 nsIFrame* child = aBox->GetChildBox(); |
|
93 while (child) { |
|
94 if (child->StyleXUL()->mStretchStack) { |
|
95 nsSize min = child->GetMinSize(aState); |
|
96 |
|
97 AddMargin(child, min); |
|
98 nsMargin offset; |
|
99 GetOffset(aState, child, offset); |
|
100 min.width += offset.LeftRight(); |
|
101 min.height += offset.TopBottom(); |
|
102 AddLargestSize(minSize, min); |
|
103 } |
|
104 |
|
105 child = child->GetNextBox(); |
|
106 } |
|
107 |
|
108 AddBorderAndPadding(aBox, minSize); |
|
109 |
|
110 return minSize; |
|
111 } |
|
112 |
|
113 nsSize |
|
114 nsStackLayout::GetMaxSize(nsIFrame* aBox, nsBoxLayoutState& aState) |
|
115 { |
|
116 nsSize maxSize (NS_INTRINSICSIZE, NS_INTRINSICSIZE); |
|
117 |
|
118 nsIFrame* child = aBox->GetChildBox(); |
|
119 while (child) { |
|
120 if (child->StyleXUL()->mStretchStack) { |
|
121 nsSize min = child->GetMinSize(aState); |
|
122 nsSize max = child->GetMaxSize(aState); |
|
123 |
|
124 max = nsBox::BoundsCheckMinMax(min, max); |
|
125 |
|
126 AddMargin(child, max); |
|
127 nsMargin offset; |
|
128 GetOffset(aState, child, offset); |
|
129 max.width += offset.LeftRight(); |
|
130 max.height += offset.TopBottom(); |
|
131 AddSmallestSize(maxSize, max); |
|
132 } |
|
133 |
|
134 child = child->GetNextBox(); |
|
135 } |
|
136 |
|
137 AddBorderAndPadding(aBox, maxSize); |
|
138 |
|
139 return maxSize; |
|
140 } |
|
141 |
|
142 |
|
143 nscoord |
|
144 nsStackLayout::GetAscent(nsIFrame* aBox, nsBoxLayoutState& aState) |
|
145 { |
|
146 nscoord vAscent = 0; |
|
147 |
|
148 nsIFrame* child = aBox->GetChildBox(); |
|
149 while (child) { |
|
150 nscoord ascent = child->GetBoxAscent(aState); |
|
151 nsMargin margin; |
|
152 child->GetMargin(margin); |
|
153 ascent += margin.top; |
|
154 if (ascent > vAscent) |
|
155 vAscent = ascent; |
|
156 |
|
157 child = child->GetNextBox(); |
|
158 } |
|
159 |
|
160 return vAscent; |
|
161 } |
|
162 |
|
163 uint8_t |
|
164 nsStackLayout::GetOffset(nsBoxLayoutState& aState, nsIFrame* aChild, nsMargin& aOffset) |
|
165 { |
|
166 aOffset = nsMargin(0, 0, 0, 0); |
|
167 |
|
168 // get the left, right, top and bottom offsets |
|
169 |
|
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)) |
|
174 return 0; |
|
175 |
|
176 uint8_t offsetSpecified = 0; |
|
177 nsIContent* content = aChild->GetContent(); |
|
178 if (content) { |
|
179 bool ltr = aChild->StyleVisibility()->mDirection == NS_STYLE_DIRECTION_LTR; |
|
180 nsAutoString value; |
|
181 nsresult error; |
|
182 |
|
183 content->GetAttr(kNameSpaceID_None, nsGkAtoms::start, value); |
|
184 if (!value.IsEmpty()) { |
|
185 value.Trim("%"); |
|
186 if (ltr) { |
|
187 aOffset.left = |
|
188 nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error)); |
|
189 offsetSpecified |= SPECIFIED_LEFT; |
|
190 } else { |
|
191 aOffset.right = |
|
192 nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error)); |
|
193 offsetSpecified |= SPECIFIED_RIGHT; |
|
194 } |
|
195 } |
|
196 |
|
197 content->GetAttr(kNameSpaceID_None, nsGkAtoms::end, value); |
|
198 if (!value.IsEmpty()) { |
|
199 value.Trim("%"); |
|
200 if (ltr) { |
|
201 aOffset.right = |
|
202 nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error)); |
|
203 offsetSpecified |= SPECIFIED_RIGHT; |
|
204 } else { |
|
205 aOffset.left = |
|
206 nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error)); |
|
207 offsetSpecified |= SPECIFIED_LEFT; |
|
208 } |
|
209 } |
|
210 |
|
211 content->GetAttr(kNameSpaceID_None, nsGkAtoms::left, value); |
|
212 if (!value.IsEmpty()) { |
|
213 value.Trim("%"); |
|
214 aOffset.left = |
|
215 nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error)); |
|
216 offsetSpecified |= SPECIFIED_LEFT; |
|
217 } |
|
218 |
|
219 content->GetAttr(kNameSpaceID_None, nsGkAtoms::right, value); |
|
220 if (!value.IsEmpty()) { |
|
221 value.Trim("%"); |
|
222 aOffset.right = |
|
223 nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error)); |
|
224 offsetSpecified |= SPECIFIED_RIGHT; |
|
225 } |
|
226 |
|
227 content->GetAttr(kNameSpaceID_None, nsGkAtoms::top, value); |
|
228 if (!value.IsEmpty()) { |
|
229 value.Trim("%"); |
|
230 aOffset.top = |
|
231 nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error)); |
|
232 offsetSpecified |= SPECIFIED_TOP; |
|
233 } |
|
234 |
|
235 content->GetAttr(kNameSpaceID_None, nsGkAtoms::bottom, value); |
|
236 if (!value.IsEmpty()) { |
|
237 value.Trim("%"); |
|
238 aOffset.bottom = |
|
239 nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error)); |
|
240 offsetSpecified |= SPECIFIED_BOTTOM; |
|
241 } |
|
242 } |
|
243 |
|
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); |
|
248 } |
|
249 |
|
250 return offsetSpecified; |
|
251 } |
|
252 |
|
253 |
|
254 NS_IMETHODIMP |
|
255 nsStackLayout::Layout(nsIFrame* aBox, nsBoxLayoutState& aState) |
|
256 { |
|
257 nsRect clientRect; |
|
258 aBox->GetClientRect(clientRect); |
|
259 |
|
260 bool grow; |
|
261 |
|
262 do { |
|
263 nsIFrame* child = aBox->GetChildBox(); |
|
264 grow = false; |
|
265 |
|
266 while (child) |
|
267 { |
|
268 nsMargin margin; |
|
269 child->GetMargin(margin); |
|
270 nsRect childRect(clientRect); |
|
271 childRect.Deflate(margin); |
|
272 |
|
273 if (childRect.width < 0) |
|
274 childRect.width = 0; |
|
275 |
|
276 if (childRect.height < 0) |
|
277 childRect.height = 0; |
|
278 |
|
279 nsRect oldRect(child->GetRect()); |
|
280 bool sizeChanged = !oldRect.IsEqualEdges(childRect); |
|
281 |
|
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 |
|
285 nsMargin margin; |
|
286 child->GetMargin(margin); |
|
287 |
|
288 // obtain our offset from the top left border of the stack's content box. |
|
289 nsMargin offset; |
|
290 uint8_t offsetSpecified = GetOffset(aState, child, offset); |
|
291 |
|
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. |
|
298 // |
|
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); |
|
308 } |
|
309 else { |
|
310 childRect.width = child->GetPrefSize(aState).width; |
|
311 } |
|
312 } |
|
313 else if (offsetSpecified & SPECIFIED_RIGHT) { |
|
314 childRect.width = child->GetPrefSize(aState).width; |
|
315 childRect.x = clientRect.XMost() - offset.right - margin.right - childRect.width; |
|
316 } |
|
317 |
|
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); |
|
325 } |
|
326 else { |
|
327 childRect.height = child->GetPrefSize(aState).height; |
|
328 } |
|
329 } |
|
330 else if (offsetSpecified & SPECIFIED_BOTTOM) { |
|
331 childRect.height = child->GetPrefSize(aState).height; |
|
332 childRect.y = clientRect.YMost() - offset.bottom - margin.bottom - childRect.height; |
|
333 } |
|
334 } |
|
335 |
|
336 // Now place the child. |
|
337 child->SetBounds(aState, childRect); |
|
338 |
|
339 // Flow the child. |
|
340 child->Layout(aState); |
|
341 |
|
342 // Get the child's new rect. |
|
343 childRect = child->GetRect(); |
|
344 childRect.Inflate(margin); |
|
345 |
|
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(); |
|
350 grow = true; |
|
351 } |
|
352 |
|
353 if (offset.TopBottom() + childRect.height > clientRect.height) { |
|
354 clientRect.height = childRect.height + offset.TopBottom(); |
|
355 grow = true; |
|
356 } |
|
357 } |
|
358 } |
|
359 |
|
360 child = child->GetNextBox(); |
|
361 } |
|
362 } while (grow); |
|
363 |
|
364 // if some HTML inside us got bigger we need to force ourselves to |
|
365 // get bigger |
|
366 nsRect bounds(aBox->GetRect()); |
|
367 nsMargin bp; |
|
368 aBox->GetBorderAndPadding(bp); |
|
369 clientRect.Inflate(bp); |
|
370 |
|
371 if (clientRect.width > bounds.width || clientRect.height > bounds.height) |
|
372 { |
|
373 if (clientRect.width > bounds.width) |
|
374 bounds.width = clientRect.width; |
|
375 if (clientRect.height > bounds.height) |
|
376 bounds.height = clientRect.height; |
|
377 |
|
378 aBox->SetBounds(aState, bounds); |
|
379 } |
|
380 |
|
381 return NS_OK; |
|
382 } |
|
383 |