1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "ui/views/layout/box_layout.h"
6
7 #include <algorithm>
8
9 #include "ui/gfx/geometry/rect.h"
10 #include "ui/views/view_class_properties.h"
11
12 namespace views {
13
14 namespace {
15
16 // Returns the maximum of the given insets along the given |axis|.
17 // NOTE: |axis| is different from |orientation_|; it specifies the actual
18 // desired axis.
19 enum class Axis { kHorizontal, kVertical };
20
MaxAxisInsets(Axis axis,const gfx::Insets & leading1,const gfx::Insets & leading2,const gfx::Insets & trailing1,const gfx::Insets & trailing2)21 gfx::Insets MaxAxisInsets(Axis axis,
22 const gfx::Insets& leading1,
23 const gfx::Insets& leading2,
24 const gfx::Insets& trailing1,
25 const gfx::Insets& trailing2) {
26 if (axis == Axis::kHorizontal) {
27 return gfx::Insets(0, std::max(leading1.left(), leading2.left()), 0,
28 std::max(trailing1.right(), trailing2.right()));
29 }
30 return gfx::Insets(std::max(leading1.top(), leading2.top()), 0,
31 std::max(trailing1.bottom(), trailing2.bottom()), 0);
32 }
33
34 } // namespace
35
36 BoxLayout::ViewWrapper::ViewWrapper() = default;
37
ViewWrapper(const BoxLayout * layout,View * view)38 BoxLayout::ViewWrapper::ViewWrapper(const BoxLayout* layout, View* view)
39 : view_(view), layout_(layout) {
40 gfx::Insets* margins = view_ ? view_->GetProperty(kMarginsKey) : nullptr;
41 if (margins)
42 margins_ = *margins;
43 }
44
45 BoxLayout::ViewWrapper::~ViewWrapper() = default;
46
GetHeightForWidth(int width) const47 int BoxLayout::ViewWrapper::GetHeightForWidth(int width) const {
48 // When collapse_margins_spacing_ is true, the BoxLayout handles the margin
49 // calculations because it has to compare and use only the largest of several
50 // adjacent margins or border insets.
51 if (layout_->collapse_margins_spacing_)
52 return view_->GetHeightForWidth(width);
53 // When collapse_margins_spacing_ is false, the view margins are included in
54 // the "virtual" size of the view. The view itself is unaware of this, so this
55 // information has to be excluded before the call to View::GetHeightForWidth()
56 // and added back in to the result.
57 // If the orientation_ is kVertical, the cross-axis is the actual view width.
58 // This is because the cross-axis margins are always handled by the layout.
59 if (layout_->orientation_ == Orientation::kHorizontal) {
60 return view_->GetHeightForWidth(std::max(0, width - margins_.width())) +
61 margins_.height();
62 }
63 return view_->GetHeightForWidth(width) + margins_.height();
64 }
65
GetPreferredSize() const66 gfx::Size BoxLayout::ViewWrapper::GetPreferredSize() const {
67 gfx::Size preferred_size = view_->GetPreferredSize();
68 if (!layout_->collapse_margins_spacing_)
69 preferred_size.Enlarge(margins_.width(), margins_.height());
70 return preferred_size;
71 }
72
SetBoundsRect(const gfx::Rect & bounds)73 void BoxLayout::ViewWrapper::SetBoundsRect(const gfx::Rect& bounds) {
74 gfx::Rect new_bounds = bounds;
75 if (!layout_->collapse_margins_spacing_) {
76 if (layout_->orientation_ == Orientation::kHorizontal) {
77 new_bounds.set_x(bounds.x() + margins_.left());
78 new_bounds.set_width(std::max(0, bounds.width() - margins_.width()));
79 } else {
80 new_bounds.set_y(bounds.y() + margins_.top());
81 new_bounds.set_height(std::max(0, bounds.height() - margins_.height()));
82 }
83 }
84 view_->SetBoundsRect(new_bounds);
85 }
86
visible() const87 bool BoxLayout::ViewWrapper::visible() const {
88 return view_->GetVisible();
89 }
90
BoxLayout(BoxLayout::Orientation orientation,const gfx::Insets & inside_border_insets,int between_child_spacing,bool collapse_margins_spacing)91 BoxLayout::BoxLayout(BoxLayout::Orientation orientation,
92 const gfx::Insets& inside_border_insets,
93 int between_child_spacing,
94 bool collapse_margins_spacing)
95 : orientation_(orientation),
96 inside_border_insets_(inside_border_insets),
97 between_child_spacing_(between_child_spacing),
98 collapse_margins_spacing_(collapse_margins_spacing) {}
99
100 BoxLayout::~BoxLayout() = default;
101
SetOrientation(Orientation orientation)102 void BoxLayout::SetOrientation(Orientation orientation) {
103 if (orientation_ != orientation) {
104 orientation_ = orientation;
105 InvalidateLayout();
106 }
107 }
108
GetOrientation() const109 BoxLayout::Orientation BoxLayout::GetOrientation() const {
110 return orientation_;
111 }
112
SetCollapseMarginsSpacing(bool collapse_margins_spacing)113 void BoxLayout::SetCollapseMarginsSpacing(bool collapse_margins_spacing) {
114 if (collapse_margins_spacing != collapse_margins_spacing_) {
115 collapse_margins_spacing_ = collapse_margins_spacing;
116 InvalidateLayout();
117 }
118 }
119
GetCollapseMarginsSpacing() const120 bool BoxLayout::GetCollapseMarginsSpacing() const {
121 return collapse_margins_spacing_;
122 }
123
SetFlexForView(const View * view,int flex_weight,bool use_min_size)124 void BoxLayout::SetFlexForView(const View* view,
125 int flex_weight,
126 bool use_min_size) {
127 DCHECK(host_);
128 DCHECK(view);
129 DCHECK_EQ(host_, view->parent());
130 DCHECK_GE(flex_weight, 0);
131 flex_map_[view].flex_weight = flex_weight;
132 flex_map_[view].use_min_size = use_min_size;
133 }
134
ClearFlexForView(const View * view)135 void BoxLayout::ClearFlexForView(const View* view) {
136 DCHECK(view);
137 flex_map_.erase(view);
138 }
139
SetDefaultFlex(int default_flex)140 void BoxLayout::SetDefaultFlex(int default_flex) {
141 DCHECK_GE(default_flex, 0);
142 default_flex_ = default_flex;
143 }
144
GetDefaultFlex() const145 int BoxLayout::GetDefaultFlex() const {
146 return default_flex_;
147 }
148
Layout(View * host)149 void BoxLayout::Layout(View* host) {
150 DCHECK_EQ(host_, host);
151 gfx::Rect child_area(host->GetContentsBounds());
152
153 AdjustMainAxisForMargin(&child_area);
154 gfx::Insets max_cross_axis_margin;
155 if (!collapse_margins_spacing_) {
156 AdjustCrossAxisForInsets(&child_area);
157 max_cross_axis_margin = CrossAxisMaxViewMargin();
158 }
159 if (child_area.IsEmpty())
160 return;
161
162 int total_main_axis_size = 0;
163 int num_visible = 0;
164 int flex_sum = 0;
165 // Calculate the total size of children in the main axis.
166 for (auto i = host->children().cbegin(); i != host->children().cend(); ++i) {
167 const ViewWrapper child(this, *i);
168 if (!child.visible())
169 continue;
170 int flex = GetFlexForView(child.view());
171 int child_main_axis_size = MainAxisSizeForView(child, child_area.width());
172 if (child_main_axis_size == 0 && flex == 0)
173 continue;
174 total_main_axis_size +=
175 child_main_axis_size +
176 MainAxisMarginBetweenViews(
177 child, ViewWrapper(this, NextVisibleView(std::next(i))));
178 ++num_visible;
179 flex_sum += flex;
180 }
181
182 if (!num_visible)
183 return;
184
185 total_main_axis_size -= between_child_spacing_;
186 // Free space can be negative indicating that the views want to overflow.
187 int main_free_space = MainAxisSize(child_area) - total_main_axis_size;
188 int main_position = MainAxisPosition(child_area);
189 {
190 int size = MainAxisSize(child_area);
191 if (!flex_sum) {
192 switch (main_axis_alignment_) {
193 case MainAxisAlignment::kStart:
194 break;
195 case MainAxisAlignment::kCenter:
196 main_position += main_free_space / 2;
197 size = total_main_axis_size;
198 break;
199 case MainAxisAlignment::kEnd:
200 main_position += main_free_space;
201 size = total_main_axis_size;
202 break;
203 default:
204 NOTREACHED();
205 break;
206 }
207 }
208 gfx::Rect new_child_area(child_area);
209 SetMainAxisPosition(main_position, &new_child_area);
210 SetMainAxisSize(size, &new_child_area);
211 child_area.Intersect(new_child_area);
212 }
213
214 int total_padding = 0;
215 int current_flex = 0;
216 for (auto i = host->children().cbegin(); i != host->children().cend(); ++i) {
217 ViewWrapper child(this, *i);
218 if (!child.visible())
219 continue;
220
221 // TODO(bruthig): Fix this. The main axis should be calculated before
222 // the cross axis size because child Views may calculate their cross axis
223 // size based on their main axis size. See https://crbug.com/682266.
224
225 // Calculate cross axis size.
226 gfx::Rect bounds(child_area);
227 gfx::Rect min_child_area(child_area);
228 gfx::Insets child_margins;
229 if (collapse_margins_spacing_) {
230 child_margins = MaxAxisInsets(orientation_ == Orientation::kVertical
231 ? Axis::kHorizontal
232 : Axis::kVertical,
233 child.margins(), inside_border_insets_,
234 child.margins(), inside_border_insets_);
235 } else {
236 child_margins = child.margins();
237 }
238
239 if (cross_axis_alignment_ == CrossAxisAlignment::kStretch ||
240 cross_axis_alignment_ == CrossAxisAlignment::kCenter) {
241 InsetCrossAxis(&min_child_area, CrossAxisLeadingInset(child_margins),
242 CrossAxisTrailingInset(child_margins));
243 }
244
245 SetMainAxisPosition(main_position, &bounds);
246 if (cross_axis_alignment_ != CrossAxisAlignment::kStretch) {
247 int cross_axis_margin_size = CrossAxisMarginSizeForView(child);
248 int view_cross_axis_size =
249 CrossAxisSizeForView(child) - cross_axis_margin_size;
250 int free_space = CrossAxisSize(bounds) - view_cross_axis_size;
251 int position = CrossAxisPosition(bounds);
252 if (cross_axis_alignment_ == CrossAxisAlignment::kCenter) {
253 if (view_cross_axis_size > CrossAxisSize(min_child_area))
254 view_cross_axis_size = CrossAxisSize(min_child_area);
255 position += free_space / 2;
256 position = std::max(position, CrossAxisLeadingEdge(min_child_area));
257 } else if (cross_axis_alignment_ == CrossAxisAlignment::kEnd) {
258 position += free_space - CrossAxisTrailingInset(max_cross_axis_margin);
259 if (!collapse_margins_spacing_)
260 InsetCrossAxis(&min_child_area,
261 CrossAxisLeadingInset(child.margins()),
262 CrossAxisTrailingInset(max_cross_axis_margin));
263 } else {
264 position += CrossAxisLeadingInset(max_cross_axis_margin);
265 if (!collapse_margins_spacing_)
266 InsetCrossAxis(&min_child_area,
267 CrossAxisLeadingInset(max_cross_axis_margin),
268 CrossAxisTrailingInset(child.margins()));
269 }
270 SetCrossAxisPosition(position, &bounds);
271 SetCrossAxisSize(view_cross_axis_size, &bounds);
272 }
273
274 // Calculate flex padding.
275 int current_padding = 0;
276 int child_flex = GetFlexForView(child.view());
277 if (child_flex > 0) {
278 current_flex += child_flex;
279 int quot = (main_free_space * current_flex) / flex_sum;
280 int rem = (main_free_space * current_flex) % flex_sum;
281 current_padding = quot - total_padding;
282 // Use the current remainder to round to the nearest pixel.
283 if (std::abs(rem) * 2 >= flex_sum)
284 current_padding += main_free_space > 0 ? 1 : -1;
285 total_padding += current_padding;
286 }
287
288 // Set main axis size.
289 // TODO(bruthig): Use the allocated width to determine the cross axis size.
290 // See https://crbug.com/682266.
291 int child_main_axis_size = MainAxisSizeForView(child, child_area.width());
292 int child_min_size = GetMinimumSizeForView(child.view());
293 if (child_min_size > 0 && !collapse_margins_spacing_)
294 child_min_size += child.margins().width();
295 SetMainAxisSize(
296 std::max(child_min_size, child_main_axis_size + current_padding),
297 &bounds);
298 if (MainAxisSize(bounds) > 0 || GetFlexForView(child.view()) > 0) {
299 main_position +=
300 MainAxisSize(bounds) +
301 MainAxisMarginBetweenViews(
302 child, ViewWrapper(this, NextVisibleView(std::next(i))));
303 }
304
305 // Clamp child view bounds to |child_area|.
306 bounds.Intersect(min_child_area);
307 child.SetBoundsRect(bounds);
308 }
309
310 // Flex views should have grown/shrunk to consume all free space.
311 if (flex_sum)
312 DCHECK_EQ(total_padding, main_free_space);
313 }
314
GetPreferredSize(const View * host) const315 gfx::Size BoxLayout::GetPreferredSize(const View* host) const {
316 DCHECK_EQ(host_, host);
317 // Calculate the child views' preferred width.
318 int width = 0;
319 if (orientation_ == Orientation::kVertical) {
320 // Calculating the child views' overall preferred width is a little involved
321 // because of the way the margins interact with |cross_axis_alignment_|.
322 int leading = 0;
323 int trailing = 0;
324 gfx::Rect child_view_area;
325 for (View* view : host_->children()) {
326 const ViewWrapper child(this, view);
327 if (!child.visible())
328 continue;
329
330 // We need to bypass the ViewWrapper GetPreferredSize() to get the actual
331 // raw view size because the margins along the cross axis are handled
332 // below.
333 gfx::Size child_size = child.view()->GetPreferredSize();
334 gfx::Insets child_margins;
335 if (collapse_margins_spacing_) {
336 child_margins = MaxAxisInsets(Axis::kHorizontal, child.margins(),
337 inside_border_insets_, child.margins(),
338 inside_border_insets_);
339 } else {
340 child_margins = child.margins();
341 }
342
343 // The value of |cross_axis_alignment_| will determine how the view's
344 // margins interact with each other or the |inside_border_insets_|.
345 if (cross_axis_alignment_ == CrossAxisAlignment::kStart) {
346 leading = std::max(leading, CrossAxisLeadingInset(child_margins));
347 width = std::max(
348 width, child_size.width() + CrossAxisTrailingInset(child_margins));
349 } else if (cross_axis_alignment_ == CrossAxisAlignment::kEnd) {
350 trailing = std::max(trailing, CrossAxisTrailingInset(child_margins));
351 width = std::max(
352 width, child_size.width() + CrossAxisLeadingInset(child_margins));
353 } else {
354 // We don't have a rectangle which can be used to calculate a common
355 // center-point, so a single known point (0) along the horizontal axis
356 // is used. This is OK because we're only interested in the overall
357 // width and not the position.
358 gfx::Rect child_bounds =
359 gfx::Rect(-(child_size.width() / 2), 0, child_size.width(),
360 child_size.height());
361 child_bounds.Inset(-child.margins().left(), 0, -child.margins().right(),
362 0);
363 child_view_area.Union(child_bounds);
364 width = std::max(width, child_view_area.width());
365 }
366 }
367 width = std::max(width + leading + trailing, minimum_cross_axis_size_);
368 }
369
370 return GetPreferredSizeForChildWidth(host, width);
371 }
372
GetPreferredHeightForWidth(const View * host,int width) const373 int BoxLayout::GetPreferredHeightForWidth(const View* host, int width) const {
374 DCHECK_EQ(host_, host);
375 int child_width = width - NonChildSize(host).width();
376 return GetPreferredSizeForChildWidth(host, child_width).height();
377 }
378
Installed(View * host)379 void BoxLayout::Installed(View* host) {
380 DCHECK(!host_);
381 host_ = host;
382 }
383
ViewRemoved(View * host,View * view)384 void BoxLayout::ViewRemoved(View* host, View* view) {
385 ClearFlexForView(view);
386 }
387
GetFlexForView(const View * view) const388 int BoxLayout::GetFlexForView(const View* view) const {
389 auto it = flex_map_.find(view);
390 if (it == flex_map_.end())
391 return default_flex_;
392
393 return it->second.flex_weight;
394 }
395
GetMinimumSizeForView(const View * view) const396 int BoxLayout::GetMinimumSizeForView(const View* view) const {
397 auto it = flex_map_.find(view);
398 if (it == flex_map_.end() || !it->second.use_min_size)
399 return 0;
400
401 return (orientation_ == Orientation::kHorizontal)
402 ? view->GetMinimumSize().width()
403 : view->GetMinimumSize().height();
404 }
405
MainAxisSize(const gfx::Rect & rect) const406 int BoxLayout::MainAxisSize(const gfx::Rect& rect) const {
407 return orientation_ == Orientation::kHorizontal ? rect.width()
408 : rect.height();
409 }
410
MainAxisPosition(const gfx::Rect & rect) const411 int BoxLayout::MainAxisPosition(const gfx::Rect& rect) const {
412 return orientation_ == Orientation::kHorizontal ? rect.x() : rect.y();
413 }
414
SetMainAxisSize(int size,gfx::Rect * rect) const415 void BoxLayout::SetMainAxisSize(int size, gfx::Rect* rect) const {
416 if (orientation_ == Orientation::kHorizontal)
417 rect->set_width(size);
418 else
419 rect->set_height(size);
420 }
421
SetMainAxisPosition(int position,gfx::Rect * rect) const422 void BoxLayout::SetMainAxisPosition(int position, gfx::Rect* rect) const {
423 if (orientation_ == Orientation::kHorizontal)
424 rect->set_x(position);
425 else
426 rect->set_y(position);
427 }
428
CrossAxisSize(const gfx::Rect & rect) const429 int BoxLayout::CrossAxisSize(const gfx::Rect& rect) const {
430 return orientation_ == Orientation::kVertical ? rect.width() : rect.height();
431 }
432
CrossAxisPosition(const gfx::Rect & rect) const433 int BoxLayout::CrossAxisPosition(const gfx::Rect& rect) const {
434 return orientation_ == Orientation::kVertical ? rect.x() : rect.y();
435 }
436
SetCrossAxisSize(int size,gfx::Rect * rect) const437 void BoxLayout::SetCrossAxisSize(int size, gfx::Rect* rect) const {
438 if (orientation_ == Orientation::kVertical)
439 rect->set_width(size);
440 else
441 rect->set_height(size);
442 }
443
SetCrossAxisPosition(int position,gfx::Rect * rect) const444 void BoxLayout::SetCrossAxisPosition(int position, gfx::Rect* rect) const {
445 if (orientation_ == Orientation::kVertical)
446 rect->set_x(position);
447 else
448 rect->set_y(position);
449 }
450
MainAxisSizeForView(const ViewWrapper & view,int child_area_width) const451 int BoxLayout::MainAxisSizeForView(const ViewWrapper& view,
452 int child_area_width) const {
453 if (orientation_ == Orientation::kHorizontal) {
454 return view.GetPreferredSize().width();
455 } else {
456 // To calculate the height we use the preferred width of the child
457 // unless we're asked to stretch or the preferred width exceeds the
458 // available width.
459 return view.GetHeightForWidth(
460 cross_axis_alignment_ == CrossAxisAlignment::kStretch
461 ? child_area_width
462 : std::min(child_area_width, view.GetPreferredSize().width()));
463 }
464 }
465
MainAxisLeadingInset(const gfx::Insets & insets) const466 int BoxLayout::MainAxisLeadingInset(const gfx::Insets& insets) const {
467 return orientation_ == Orientation::kHorizontal ? insets.left()
468 : insets.top();
469 }
470
MainAxisTrailingInset(const gfx::Insets & insets) const471 int BoxLayout::MainAxisTrailingInset(const gfx::Insets& insets) const {
472 return orientation_ == Orientation::kHorizontal ? insets.right()
473 : insets.bottom();
474 }
475
CrossAxisLeadingEdge(const gfx::Rect & rect) const476 int BoxLayout::CrossAxisLeadingEdge(const gfx::Rect& rect) const {
477 return orientation_ == Orientation::kVertical ? rect.x() : rect.y();
478 }
479
CrossAxisLeadingInset(const gfx::Insets & insets) const480 int BoxLayout::CrossAxisLeadingInset(const gfx::Insets& insets) const {
481 return orientation_ == Orientation::kVertical ? insets.left() : insets.top();
482 }
483
CrossAxisTrailingInset(const gfx::Insets & insets) const484 int BoxLayout::CrossAxisTrailingInset(const gfx::Insets& insets) const {
485 return orientation_ == Orientation::kVertical ? insets.right()
486 : insets.bottom();
487 }
488
MainAxisMarginBetweenViews(const ViewWrapper & leading,const ViewWrapper & trailing) const489 int BoxLayout::MainAxisMarginBetweenViews(const ViewWrapper& leading,
490 const ViewWrapper& trailing) const {
491 if (!collapse_margins_spacing_ || !leading.view() || !trailing.view())
492 return between_child_spacing_;
493 return std::max(between_child_spacing_,
494 std::max(MainAxisTrailingInset(leading.margins()),
495 MainAxisLeadingInset(trailing.margins())));
496 }
497
MainAxisOuterMargin() const498 gfx::Insets BoxLayout::MainAxisOuterMargin() const {
499 if (collapse_margins_spacing_) {
500 const ViewWrapper first(this, FirstVisibleView());
501 const ViewWrapper last(this, LastVisibleView());
502 return MaxAxisInsets(orientation_ == Orientation::kHorizontal
503 ? Axis::kHorizontal
504 : Axis::kVertical,
505 inside_border_insets_, first.margins(),
506 inside_border_insets_, last.margins());
507 }
508 return MaxAxisInsets(orientation_ == Orientation::kHorizontal
509 ? Axis::kHorizontal
510 : Axis::kVertical,
511 inside_border_insets_, gfx::Insets(),
512 inside_border_insets_, gfx::Insets());
513 }
514
CrossAxisMaxViewMargin() const515 gfx::Insets BoxLayout::CrossAxisMaxViewMargin() const {
516 int leading = 0;
517 int trailing = 0;
518 for (View* view : host_->children()) {
519 const ViewWrapper child(this, view);
520 if (!child.visible())
521 continue;
522 leading = std::max(leading, CrossAxisLeadingInset(child.margins()));
523 trailing = std::max(trailing, CrossAxisTrailingInset(child.margins()));
524 }
525 if (orientation_ == Orientation::kVertical)
526 return gfx::Insets(0, leading, 0, trailing);
527 return gfx::Insets(leading, 0, trailing, 0);
528 }
529
AdjustMainAxisForMargin(gfx::Rect * rect) const530 void BoxLayout::AdjustMainAxisForMargin(gfx::Rect* rect) const {
531 rect->Inset(MainAxisOuterMargin());
532 }
533
AdjustCrossAxisForInsets(gfx::Rect * rect) const534 void BoxLayout::AdjustCrossAxisForInsets(gfx::Rect* rect) const {
535 rect->Inset(orientation_ == Orientation::kVertical
536 ? gfx::Insets(0, inside_border_insets_.left(), 0,
537 inside_border_insets_.right())
538 : gfx::Insets(inside_border_insets_.top(), 0,
539 inside_border_insets_.bottom(), 0));
540 }
541
CrossAxisSizeForView(const ViewWrapper & view) const542 int BoxLayout::CrossAxisSizeForView(const ViewWrapper& view) const {
543 // TODO(bruthig): For horizontal case use the available width and not the
544 // preferred width. See https://crbug.com/682266.
545 return orientation_ == Orientation::kVertical
546 ? view.GetPreferredSize().width()
547 : view.GetHeightForWidth(view.GetPreferredSize().width());
548 }
549
CrossAxisMarginSizeForView(const ViewWrapper & view) const550 int BoxLayout::CrossAxisMarginSizeForView(const ViewWrapper& view) const {
551 return collapse_margins_spacing_ ? 0
552 : (orientation_ == Orientation::kVertical
553 ? view.margins().width()
554 : view.margins().height());
555 }
556
CrossAxisLeadingMarginForView(const ViewWrapper & view) const557 int BoxLayout::CrossAxisLeadingMarginForView(const ViewWrapper& view) const {
558 return collapse_margins_spacing_ ? 0 : CrossAxisLeadingInset(view.margins());
559 }
560
InsetCrossAxis(gfx::Rect * rect,int leading,int trailing) const561 void BoxLayout::InsetCrossAxis(gfx::Rect* rect,
562 int leading,
563 int trailing) const {
564 if (orientation_ == Orientation::kVertical)
565 rect->Inset(leading, 0, trailing, 0);
566 else
567 rect->Inset(0, leading, 0, trailing);
568 }
569
GetPreferredSizeForChildWidth(const View * host,int child_area_width) const570 gfx::Size BoxLayout::GetPreferredSizeForChildWidth(const View* host,
571 int child_area_width) const {
572 DCHECK_EQ(host, host_);
573 gfx::Rect child_area_bounds;
574
575 if (orientation_ == Orientation::kHorizontal) {
576 // Horizontal layouts ignore |child_area_width|, meaning they mimic the
577 // default behavior of GridLayout::GetPreferredHeightForWidth().
578 // TODO(estade|bruthig): Fix this See // https://crbug.com/682266.
579 int position = 0;
580 gfx::Insets max_margins = CrossAxisMaxViewMargin();
581 for (auto i = host->children().cbegin(); i != host->children().cend();
582 ++i) {
583 const ViewWrapper child(this, *i);
584 if (!child.visible())
585 continue;
586
587 gfx::Size size(child.view()->GetPreferredSize());
588 if (size.IsEmpty())
589 continue;
590
591 gfx::Rect child_bounds = gfx::Rect(
592 position, 0,
593 size.width() +
594 (!collapse_margins_spacing_ ? child.margins().width() : 0),
595 size.height());
596 gfx::Insets child_margins;
597 if (collapse_margins_spacing_)
598 child_margins = MaxAxisInsets(Axis::kVertical, child.margins(),
599 inside_border_insets_, child.margins(),
600 inside_border_insets_);
601 else
602 child_margins = child.margins();
603
604 if (cross_axis_alignment_ == CrossAxisAlignment::kStart) {
605 child_bounds.Inset(0, -CrossAxisLeadingInset(max_margins), 0,
606 -child_margins.bottom());
607 child_bounds.set_origin(gfx::Point(position, 0));
608 } else if (cross_axis_alignment_ == CrossAxisAlignment::kEnd) {
609 child_bounds.Inset(0, -child_margins.top(), 0,
610 -CrossAxisTrailingInset(max_margins));
611 child_bounds.set_origin(gfx::Point(position, 0));
612 } else {
613 child_bounds.set_origin(
614 gfx::Point(position, -(child_bounds.height() / 2)));
615 child_bounds.Inset(0, -child_margins.top(), 0, -child_margins.bottom());
616 }
617
618 child_area_bounds.Union(child_bounds);
619 position += child_bounds.width() +
620 MainAxisMarginBetweenViews(
621 child, ViewWrapper(this, NextVisibleView(std::next(i))));
622 }
623 child_area_bounds.set_height(
624 std::max(child_area_bounds.height(), minimum_cross_axis_size_));
625 } else {
626 int height = 0;
627 for (auto i = host->children().cbegin(); i != host->children().cend();
628 ++i) {
629 const ViewWrapper child(this, *i);
630 if (!child.visible())
631 continue;
632
633 const ViewWrapper next(this, NextVisibleView(std::next(i)));
634 // Use the child area width for getting the height if the child is
635 // supposed to stretch. Use its preferred size otherwise.
636 int extra_height = MainAxisSizeForView(child, child_area_width);
637 // Only add |between_child_spacing_| if this is not the only child.
638 if (next.view() && extra_height > 0)
639 height += MainAxisMarginBetweenViews(child, next);
640 height += extra_height;
641 }
642
643 child_area_bounds.set_width(child_area_width);
644 child_area_bounds.set_height(height);
645 }
646
647 gfx::Size non_child_size = NonChildSize(host_);
648 return gfx::Size(child_area_bounds.width() + non_child_size.width(),
649 child_area_bounds.height() + non_child_size.height());
650 }
651
NonChildSize(const View * host) const652 gfx::Size BoxLayout::NonChildSize(const View* host) const {
653 gfx::Insets insets(host->GetInsets());
654 if (!collapse_margins_spacing_)
655 return gfx::Size(insets.width() + inside_border_insets_.width(),
656 insets.height() + inside_border_insets_.height());
657 gfx::Insets main_axis = MainAxisOuterMargin();
658 gfx::Insets cross_axis = inside_border_insets_;
659 return gfx::Size(insets.width() + main_axis.width() + cross_axis.width(),
660 insets.height() + main_axis.height() + cross_axis.height());
661 }
662
NextVisibleView(View::Views::const_iterator pos) const663 View* BoxLayout::NextVisibleView(View::Views::const_iterator pos) const {
664 const auto i = std::find_if(pos, host_->children().cend(),
665 [](const View* v) { return v->GetVisible(); });
666 return (i == host_->children().cend()) ? nullptr : *i;
667 }
668
FirstVisibleView() const669 View* BoxLayout::FirstVisibleView() const {
670 return NextVisibleView(host_->children().cbegin());
671 }
672
LastVisibleView() const673 View* BoxLayout::LastVisibleView() const {
674 const auto& children = host_->children();
675 const auto i = std::find_if(children.crbegin(), children.crend(),
676 [](const View* v) { return v->GetVisible(); });
677 return (i == children.crend()) ? nullptr : *i;
678 }
679
680 } // namespace views
681