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