Line data Source code
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 << eSideLeft)
27 : #define SPECIFIED_RIGHT (1 << eSideRight)
28 : #define SPECIFIED_TOP (1 << eSideTop)
29 : #define SPECIFIED_BOTTOM (1 << eSideBottom)
30 :
31 : nsresult
32 16 : NS_NewStackLayout(nsCOMPtr<nsBoxLayout>& aNewLayout)
33 : {
34 16 : if (!nsStackLayout::gInstance) {
35 1 : nsStackLayout::gInstance = new nsStackLayout();
36 1 : NS_IF_ADDREF(nsStackLayout::gInstance);
37 : }
38 : // we have not instance variables so just return our static one.
39 16 : aNewLayout = nsStackLayout::gInstance;
40 16 : return NS_OK;
41 : }
42 :
43 : /*static*/ void
44 0 : nsStackLayout::Shutdown()
45 : {
46 0 : NS_IF_RELEASE(gInstance);
47 0 : }
48 :
49 1 : nsStackLayout::nsStackLayout()
50 : {
51 1 : }
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 43 : nsStackLayout::GetXULPrefSize(nsIFrame* aBox, nsBoxLayoutState& aState)
63 : {
64 43 : nsSize prefSize (0, 0);
65 :
66 43 : nsIFrame* child = nsBox::GetChildXULBox(aBox);
67 145 : while (child) {
68 51 : auto stackSizing = child->StyleXUL()->mStackSizing;
69 51 : if (stackSizing != StyleStackSizing::Ignore) {
70 51 : nsSize pref = child->GetXULPrefSize(aState);
71 :
72 51 : AddMargin(child, pref);
73 51 : nsMargin offset;
74 51 : GetOffset(child, offset);
75 51 : pref.width += offset.LeftRight();
76 51 : pref.height += offset.TopBottom();
77 :
78 51 : if (pref.width > prefSize.width &&
79 : stackSizing != StyleStackSizing::IgnoreHorizontal) {
80 45 : prefSize.width = pref.width;
81 : }
82 51 : if (pref.height > prefSize.height &&
83 : stackSizing != StyleStackSizing::IgnoreVertical) {
84 38 : prefSize.height = pref.height;
85 : }
86 : }
87 :
88 51 : child = nsBox::GetNextXULBox(child);
89 : }
90 :
91 43 : AddBorderAndPadding(aBox, prefSize);
92 :
93 43 : return prefSize;
94 : }
95 :
96 : nsSize
97 43 : nsStackLayout::GetXULMinSize(nsIFrame* aBox, nsBoxLayoutState& aState)
98 : {
99 43 : nsSize minSize (0, 0);
100 :
101 43 : nsIFrame* child = nsBox::GetChildXULBox(aBox);
102 145 : while (child) {
103 51 : auto stackSizing = child->StyleXUL()->mStackSizing;
104 51 : if (stackSizing != StyleStackSizing::Ignore) {
105 51 : nsSize min = child->GetXULMinSize(aState);
106 :
107 51 : AddMargin(child, min);
108 51 : nsMargin offset;
109 51 : GetOffset(child, offset);
110 51 : min.width += offset.LeftRight();
111 51 : min.height += offset.TopBottom();
112 :
113 51 : if (min.width > minSize.width &&
114 : stackSizing != StyleStackSizing::IgnoreHorizontal) {
115 42 : minSize.width = min.width;
116 : }
117 51 : if (min.height > minSize.height &&
118 : stackSizing != StyleStackSizing::IgnoreVertical) {
119 34 : minSize.height = min.height;
120 : }
121 : }
122 :
123 51 : child = nsBox::GetNextXULBox(child);
124 : }
125 :
126 43 : AddBorderAndPadding(aBox, minSize);
127 :
128 43 : return minSize;
129 : }
130 :
131 : nsSize
132 43 : nsStackLayout::GetXULMaxSize(nsIFrame* aBox, nsBoxLayoutState& aState)
133 : {
134 43 : nsSize maxSize (NS_INTRINSICSIZE, NS_INTRINSICSIZE);
135 :
136 43 : nsIFrame* child = nsBox::GetChildXULBox(aBox);
137 145 : while (child) {
138 51 : auto stackSizing = child->StyleXUL()->mStackSizing;
139 51 : if (stackSizing != StyleStackSizing::Ignore) {
140 51 : nsSize min = child->GetXULMinSize(aState);
141 51 : nsSize max = child->GetXULMaxSize(aState);
142 :
143 51 : max = nsBox::BoundsCheckMinMax(min, max);
144 :
145 51 : AddMargin(child, max);
146 51 : nsMargin offset;
147 51 : GetOffset(child, offset);
148 51 : max.width += offset.LeftRight();
149 51 : max.height += offset.TopBottom();
150 :
151 51 : if (max.width < maxSize.width &&
152 : stackSizing != StyleStackSizing::IgnoreHorizontal) {
153 6 : maxSize.width = max.width;
154 : }
155 51 : if (max.height < maxSize.height &&
156 : stackSizing != StyleStackSizing::IgnoreVertical) {
157 0 : maxSize.height = max.height;
158 : }
159 : }
160 :
161 51 : child = nsBox::GetNextXULBox(child);
162 : }
163 :
164 43 : AddBorderAndPadding(aBox, maxSize);
165 :
166 43 : return maxSize;
167 : }
168 :
169 :
170 : nscoord
171 72 : nsStackLayout::GetAscent(nsIFrame* aBox, nsBoxLayoutState& aState)
172 : {
173 72 : nscoord vAscent = 0;
174 :
175 72 : nsIFrame* child = nsBox::GetChildXULBox(aBox);
176 238 : while (child) {
177 83 : nscoord ascent = child->GetXULBoxAscent(aState);
178 83 : nsMargin margin;
179 83 : child->GetXULMargin(margin);
180 83 : ascent += margin.top;
181 83 : if (ascent > vAscent)
182 67 : vAscent = ascent;
183 :
184 83 : child = nsBox::GetNextXULBox(child);
185 : }
186 :
187 72 : return vAscent;
188 : }
189 :
190 : uint8_t
191 234 : nsStackLayout::GetOffset(nsIFrame* aChild, nsMargin& aOffset)
192 : {
193 234 : aOffset = nsMargin(0, 0, 0, 0);
194 :
195 : // get the left, right, top and bottom offsets
196 :
197 : // As an optimization, we cache the fact that we are not positioned to avoid
198 : // wasting time fetching attributes.
199 462 : if (aChild->IsXULBoxFrame() &&
200 228 : (aChild->GetStateBits() & NS_STATE_STACK_NOT_POSITIONED))
201 215 : return 0;
202 :
203 19 : uint8_t offsetSpecified = 0;
204 19 : nsIContent* content = aChild->GetContent();
205 19 : if (content) {
206 19 : bool ltr = aChild->StyleVisibility()->mDirection == NS_STYLE_DIRECTION_LTR;
207 38 : nsAutoString value;
208 : nsresult error;
209 :
210 19 : content->GetAttr(kNameSpaceID_None, nsGkAtoms::start, value);
211 19 : if (!value.IsEmpty()) {
212 0 : value.Trim("%");
213 0 : if (ltr) {
214 0 : aOffset.left =
215 0 : nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error));
216 0 : offsetSpecified |= SPECIFIED_LEFT;
217 : } else {
218 0 : aOffset.right =
219 0 : nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error));
220 0 : offsetSpecified |= SPECIFIED_RIGHT;
221 : }
222 : }
223 :
224 19 : content->GetAttr(kNameSpaceID_None, nsGkAtoms::end, value);
225 19 : if (!value.IsEmpty()) {
226 0 : value.Trim("%");
227 0 : if (ltr) {
228 0 : aOffset.right =
229 0 : nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error));
230 0 : offsetSpecified |= SPECIFIED_RIGHT;
231 : } else {
232 0 : aOffset.left =
233 0 : nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error));
234 0 : offsetSpecified |= SPECIFIED_LEFT;
235 : }
236 : }
237 :
238 19 : content->GetAttr(kNameSpaceID_None, nsGkAtoms::left, value);
239 19 : if (!value.IsEmpty()) {
240 0 : value.Trim("%");
241 0 : aOffset.left =
242 0 : nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error));
243 0 : offsetSpecified |= SPECIFIED_LEFT;
244 : }
245 :
246 19 : content->GetAttr(kNameSpaceID_None, nsGkAtoms::right, value);
247 19 : if (!value.IsEmpty()) {
248 0 : value.Trim("%");
249 0 : aOffset.right =
250 0 : nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error));
251 0 : offsetSpecified |= SPECIFIED_RIGHT;
252 : }
253 :
254 19 : content->GetAttr(kNameSpaceID_None, nsGkAtoms::top, value);
255 19 : if (!value.IsEmpty()) {
256 0 : value.Trim("%");
257 0 : aOffset.top =
258 0 : nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error));
259 0 : offsetSpecified |= SPECIFIED_TOP;
260 : }
261 :
262 19 : content->GetAttr(kNameSpaceID_None, nsGkAtoms::bottom, value);
263 19 : if (!value.IsEmpty()) {
264 0 : value.Trim("%");
265 0 : aOffset.bottom =
266 0 : nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error));
267 0 : offsetSpecified |= SPECIFIED_BOTTOM;
268 : }
269 : }
270 :
271 19 : if (!offsetSpecified && aChild->IsXULBoxFrame()) {
272 : // If no offset was specified at all, then we cache this fact to avoid requerying
273 : // CSS or the content model.
274 13 : aChild->AddStateBits(NS_STATE_STACK_NOT_POSITIONED);
275 : }
276 :
277 19 : return offsetSpecified;
278 : }
279 :
280 :
281 : NS_IMETHODIMP
282 79 : nsStackLayout::XULLayout(nsIFrame* aBox, nsBoxLayoutState& aState)
283 : {
284 158 : nsRect clientRect;
285 79 : aBox->GetXULClientRect(clientRect);
286 :
287 : bool grow;
288 :
289 79 : do {
290 79 : nsIFrame* child = nsBox::GetChildXULBox(aBox);
291 79 : grow = false;
292 :
293 255 : while (child)
294 : {
295 88 : nsMargin margin;
296 88 : child->GetXULMargin(margin);
297 176 : nsRect childRect(clientRect);
298 88 : childRect.Deflate(margin);
299 :
300 88 : if (childRect.width < 0)
301 0 : childRect.width = 0;
302 :
303 88 : if (childRect.height < 0)
304 0 : childRect.height = 0;
305 :
306 176 : nsRect oldRect(child->GetRect());
307 88 : bool sizeChanged = !oldRect.IsEqualEdges(childRect);
308 :
309 : // only lay out dirty children or children whose sizes have changed
310 88 : if (sizeChanged || NS_SUBTREE_DIRTY(child)) {
311 : // add in the child's margin
312 81 : nsMargin margin;
313 81 : child->GetXULMargin(margin);
314 :
315 : // obtain our offset from the top left border of the stack's content box.
316 81 : nsMargin offset;
317 81 : uint8_t offsetSpecified = GetOffset(child, offset);
318 :
319 : // Set the position and size based on which offsets have been specified:
320 : // left only - offset from left edge, preferred width
321 : // right only - offset from right edge, preferred width
322 : // left and right - offset from left and right edges, width in between this
323 : // neither - no offset, full width of stack
324 : // Vertical direction is similar.
325 : //
326 : // Margins on the child are also included in the edge offsets
327 81 : if (offsetSpecified) {
328 0 : nsSize min = child->GetXULMinSize(aState);
329 0 : nsSize max = child->GetXULMaxSize(aState);
330 0 : if (offsetSpecified & SPECIFIED_LEFT) {
331 0 : childRect.x = clientRect.x + offset.left + margin.left;
332 0 : if (offsetSpecified & SPECIFIED_RIGHT) {
333 0 : nscoord width = clientRect.width - offset.LeftRight() - margin.LeftRight();
334 0 : childRect.width = clamped(width, min.width, max.width);
335 : }
336 : else {
337 0 : nscoord width = child->GetXULPrefSize(aState).width;
338 0 : childRect.width = clamped(width, min.width, max.width);
339 : }
340 : }
341 0 : else if (offsetSpecified & SPECIFIED_RIGHT) {
342 0 : nscoord width = child->GetXULPrefSize(aState).width;
343 0 : childRect.width = clamped(width, min.width, max.width);
344 0 : childRect.x = clientRect.XMost() - offset.right - margin.right - childRect.width;
345 : }
346 :
347 0 : if (offsetSpecified & SPECIFIED_TOP) {
348 0 : childRect.y = clientRect.y + offset.top + margin.top;
349 0 : if (offsetSpecified & SPECIFIED_BOTTOM) {
350 0 : nscoord height = clientRect.height - offset.TopBottom() - margin.TopBottom();
351 0 : childRect.height = clamped(height, min.height, max.height);
352 : }
353 : else {
354 0 : nscoord height = child->GetXULPrefSize(aState).height;
355 0 : childRect.height = clamped(height, min.height, max.height);
356 : }
357 : }
358 0 : else if (offsetSpecified & SPECIFIED_BOTTOM) {
359 0 : nscoord height = child->GetXULPrefSize(aState).height;
360 0 : childRect.height = clamped(height, min.height, max.height);
361 0 : childRect.y = clientRect.YMost() - offset.bottom - margin.bottom - childRect.height;
362 : }
363 : }
364 :
365 : // Now place the child.
366 81 : child->SetXULBounds(aState, childRect);
367 :
368 : // Flow the child.
369 81 : child->XULLayout(aState);
370 :
371 : // Get the child's new rect.
372 81 : childRect = child->GetRect();
373 81 : childRect.Inflate(margin);
374 :
375 81 : auto stackSizing = child->StyleXUL()->mStackSizing;
376 81 : if (stackSizing != StyleStackSizing::Ignore) {
377 : // Did the child push back on us and get bigger?
378 81 : if (offset.LeftRight() + childRect.width > clientRect.width &&
379 : stackSizing != StyleStackSizing::IgnoreHorizontal) {
380 0 : clientRect.width = childRect.width + offset.LeftRight();
381 0 : grow = true;
382 : }
383 :
384 81 : if (offset.TopBottom() + childRect.height > clientRect.height &&
385 : stackSizing != StyleStackSizing::IgnoreVertical) {
386 0 : clientRect.height = childRect.height + offset.TopBottom();
387 0 : grow = true;
388 : }
389 : }
390 : }
391 :
392 88 : child = nsBox::GetNextXULBox(child);
393 : }
394 : } while (grow);
395 :
396 : // if some HTML inside us got bigger we need to force ourselves to
397 : // get bigger
398 158 : nsRect bounds(aBox->GetRect());
399 79 : nsMargin bp;
400 79 : aBox->GetXULBorderAndPadding(bp);
401 79 : clientRect.Inflate(bp);
402 :
403 79 : if (clientRect.width > bounds.width || clientRect.height > bounds.height)
404 : {
405 0 : if (clientRect.width > bounds.width)
406 0 : bounds.width = clientRect.width;
407 0 : if (clientRect.height > bounds.height)
408 0 : bounds.height = clientRect.height;
409 :
410 0 : aBox->SetXULBounds(aState, bounds);
411 : }
412 :
413 158 : return NS_OK;
414 : }
415 :
|