• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "ui/gfx/rect.h"
8 #include "ui/views/view.h"
9 
10 namespace views {
11 
BoxLayout(BoxLayout::Orientation orientation,int inside_border_horizontal_spacing,int inside_border_vertical_spacing,int between_child_spacing)12 BoxLayout::BoxLayout(BoxLayout::Orientation orientation,
13                      int inside_border_horizontal_spacing,
14                      int inside_border_vertical_spacing,
15                      int between_child_spacing)
16     : orientation_(orientation),
17       inside_border_insets_(inside_border_vertical_spacing,
18                             inside_border_horizontal_spacing,
19                             inside_border_vertical_spacing,
20                             inside_border_horizontal_spacing),
21       between_child_spacing_(between_child_spacing),
22       main_axis_alignment_(MAIN_AXIS_ALIGNMENT_START),
23       cross_axis_alignment_(CROSS_AXIS_ALIGNMENT_STRETCH),
24       default_flex_(0),
25       minimum_cross_axis_size_(0),
26       host_(NULL) {
27 }
28 
~BoxLayout()29 BoxLayout::~BoxLayout() {
30 }
31 
SetFlexForView(const View * view,int flex_weight)32 void BoxLayout::SetFlexForView(const View* view, int flex_weight) {
33   DCHECK(host_);
34   DCHECK(view);
35   DCHECK_EQ(host_, view->parent());
36   DCHECK_GE(flex_weight, 0);
37   flex_map_[view] = flex_weight;
38 }
39 
ClearFlexForView(const View * view)40 void BoxLayout::ClearFlexForView(const View* view) {
41   DCHECK(view);
42   flex_map_.erase(view);
43 }
44 
SetDefaultFlex(int default_flex)45 void BoxLayout::SetDefaultFlex(int default_flex) {
46   DCHECK_GE(default_flex, 0);
47   default_flex_ = default_flex;
48 }
49 
Layout(View * host)50 void BoxLayout::Layout(View* host) {
51   DCHECK_EQ(host_, host);
52   gfx::Rect child_area(host->GetLocalBounds());
53   child_area.Inset(host->GetInsets());
54   child_area.Inset(inside_border_insets_);
55 
56   int total_main_axis_size = 0;
57   int num_visible = 0;
58   int flex_sum = 0;
59   // Calculate the total size of children in the main axis.
60   for (int i = 0; i < host->child_count(); ++i) {
61     View* child = host->child_at(i);
62     if (!child->visible())
63       continue;
64     total_main_axis_size +=
65         MainAxisSizeForView(child, child_area.width()) + between_child_spacing_;
66     ++num_visible;
67     flex_sum += GetFlexForView(child);
68   }
69 
70   if (!num_visible)
71     return;
72 
73   total_main_axis_size -= between_child_spacing_;
74   // Free space can be negative indicating that the views want to overflow.
75   int main_free_space = MainAxisSize(child_area) - total_main_axis_size;
76   {
77     int position = MainAxisPosition(child_area);
78     int size = MainAxisSize(child_area);
79     if (!flex_sum) {
80       switch (main_axis_alignment_) {
81         case MAIN_AXIS_ALIGNMENT_START:
82           break;
83         case MAIN_AXIS_ALIGNMENT_CENTER:
84           position += main_free_space / 2;
85           size = total_main_axis_size;
86           break;
87         case MAIN_AXIS_ALIGNMENT_END:
88           position += main_free_space;
89           size = total_main_axis_size;
90           break;
91         default:
92           NOTREACHED();
93           break;
94       }
95     }
96     gfx::Rect new_child_area(child_area);
97     SetMainAxisPosition(position, &new_child_area);
98     SetMainAxisSize(size, &new_child_area);
99     child_area.Intersect(new_child_area);
100   }
101 
102   int main_position = MainAxisPosition(child_area);
103   int total_padding = 0;
104   int current_flex = 0;
105   for (int i = 0; i < host->child_count(); ++i) {
106     View* child = host->child_at(i);
107     if (!child->visible())
108       continue;
109 
110     // Calculate cross axis size.
111     gfx::Rect bounds(child_area);
112     SetMainAxisPosition(main_position, &bounds);
113     if (cross_axis_alignment_ != CROSS_AXIS_ALIGNMENT_STRETCH) {
114       int free_space = CrossAxisSize(bounds) - CrossAxisSizeForView(child);
115       int position = CrossAxisPosition(bounds);
116       if (cross_axis_alignment_ == CROSS_AXIS_ALIGNMENT_CENTER) {
117         position += free_space / 2;
118       } else if (cross_axis_alignment_ == CROSS_AXIS_ALIGNMENT_END) {
119         position += free_space;
120       }
121       SetCrossAxisPosition(position, &bounds);
122       SetCrossAxisSize(CrossAxisSizeForView(child), &bounds);
123     }
124 
125     // Calculate flex padding.
126     int current_padding = 0;
127     if (GetFlexForView(child) > 0) {
128       current_flex += GetFlexForView(child);
129       int quot = (main_free_space * current_flex) / flex_sum;
130       int rem = (main_free_space * current_flex) % flex_sum;
131       current_padding = quot - total_padding;
132       // Use the current remainder to round to the nearest pixel.
133       if (std::abs(rem) * 2 >= flex_sum)
134         current_padding += main_free_space > 0 ? 1 : -1;
135       total_padding += current_padding;
136     }
137 
138     // Set main axis size.
139     int child_main_axis_size = MainAxisSizeForView(child, child_area.width());
140     SetMainAxisSize(child_main_axis_size + current_padding, &bounds);
141     if (MainAxisSize(bounds) > 0 || GetFlexForView(child) > 0)
142       main_position += MainAxisSize(bounds) + between_child_spacing_;
143 
144     // Clamp child view bounds to |child_area|.
145     bounds.Intersect(child_area);
146     child->SetBoundsRect(bounds);
147   }
148 
149   // Flex views should have grown/shrunk to consume all free space.
150   if (flex_sum)
151     DCHECK_EQ(total_padding, main_free_space);
152 }
153 
GetPreferredSize(const View * host) const154 gfx::Size BoxLayout::GetPreferredSize(const View* host) const {
155   DCHECK_EQ(host_, host);
156   // Calculate the child views' preferred width.
157   int width = 0;
158   if (orientation_ == kVertical) {
159     for (int i = 0; i < host->child_count(); ++i) {
160       const View* child = host->child_at(i);
161       if (!child->visible())
162         continue;
163 
164       width = std::max(width, child->GetPreferredSize().width());
165     }
166     width = std::max(width, minimum_cross_axis_size_);
167   }
168 
169   return GetPreferredSizeForChildWidth(host, width);
170 }
171 
GetPreferredHeightForWidth(const View * host,int width) const172 int BoxLayout::GetPreferredHeightForWidth(const View* host, int width) const {
173   DCHECK_EQ(host_, host);
174   int child_width = width - NonChildSize(host).width();
175   return GetPreferredSizeForChildWidth(host, child_width).height();
176 }
177 
Installed(View * host)178 void BoxLayout::Installed(View* host) {
179   DCHECK(!host_);
180   host_ = host;
181 }
182 
Uninstalled(View * host)183 void BoxLayout::Uninstalled(View* host) {
184   DCHECK_EQ(host_, host);
185   host_ = NULL;
186   flex_map_.clear();
187 }
188 
ViewRemoved(View * host,View * view)189 void BoxLayout::ViewRemoved(View* host, View* view) {
190   ClearFlexForView(view);
191 }
192 
GetFlexForView(const View * view) const193 int BoxLayout::GetFlexForView(const View* view) const {
194   std::map<const View*, int>::const_iterator it = flex_map_.find(view);
195   if (it == flex_map_.end())
196     return default_flex_;
197 
198   return it->second;
199 }
200 
MainAxisSize(const gfx::Rect & rect) const201 int BoxLayout::MainAxisSize(const gfx::Rect& rect) const {
202   return orientation_ == kHorizontal ? rect.width() : rect.height();
203 }
204 
MainAxisPosition(const gfx::Rect & rect) const205 int BoxLayout::MainAxisPosition(const gfx::Rect& rect) const {
206   return orientation_ == kHorizontal ? rect.x() : rect.y();
207 }
208 
SetMainAxisSize(int size,gfx::Rect * rect) const209 void BoxLayout::SetMainAxisSize(int size, gfx::Rect* rect) const {
210   if (orientation_ == kHorizontal)
211     rect->set_width(size);
212   else
213     rect->set_height(size);
214 }
215 
SetMainAxisPosition(int position,gfx::Rect * rect) const216 void BoxLayout::SetMainAxisPosition(int position, gfx::Rect* rect) const {
217   if (orientation_ == kHorizontal)
218     rect->set_x(position);
219   else
220     rect->set_y(position);
221 }
222 
CrossAxisSize(const gfx::Rect & rect) const223 int BoxLayout::CrossAxisSize(const gfx::Rect& rect) const {
224   return orientation_ == kVertical ? rect.width() : rect.height();
225 }
226 
CrossAxisPosition(const gfx::Rect & rect) const227 int BoxLayout::CrossAxisPosition(const gfx::Rect& rect) const {
228   return orientation_ == kVertical ? rect.x() : rect.y();
229 }
230 
SetCrossAxisSize(int size,gfx::Rect * rect) const231 void BoxLayout::SetCrossAxisSize(int size, gfx::Rect* rect) const {
232   if (orientation_ == kVertical)
233     rect->set_width(size);
234   else
235     rect->set_height(size);
236 }
237 
SetCrossAxisPosition(int position,gfx::Rect * rect) const238 void BoxLayout::SetCrossAxisPosition(int position, gfx::Rect* rect) const {
239   if (orientation_ == kVertical)
240     rect->set_x(position);
241   else
242     rect->set_y(position);
243 }
244 
MainAxisSizeForView(const View * view,int child_area_width) const245 int BoxLayout::MainAxisSizeForView(const View* view,
246                                    int child_area_width) const {
247   return orientation_ == kHorizontal
248              ? view->GetPreferredSize().width()
249              : view->GetHeightForWidth(cross_axis_alignment_ ==
250                                                CROSS_AXIS_ALIGNMENT_STRETCH
251                                            ? child_area_width
252                                            : view->GetPreferredSize().width());
253 }
254 
CrossAxisSizeForView(const View * view) const255 int BoxLayout::CrossAxisSizeForView(const View* view) const {
256   return orientation_ == kVertical
257              ? view->GetPreferredSize().width()
258              : view->GetHeightForWidth(view->GetPreferredSize().width());
259 }
260 
GetPreferredSizeForChildWidth(const View * host,int child_area_width) const261 gfx::Size BoxLayout::GetPreferredSizeForChildWidth(const View* host,
262                                                    int child_area_width) const {
263   gfx::Rect child_area_bounds;
264 
265   if (orientation_ == kHorizontal) {
266     // Horizontal layouts ignore |child_area_width|, meaning they mimic the
267     // default behavior of GridLayout::GetPreferredHeightForWidth().
268     // TODO(estade): fix this if it ever becomes a problem.
269     int position = 0;
270     for (int i = 0; i < host->child_count(); ++i) {
271       const View* child = host->child_at(i);
272       if (!child->visible())
273         continue;
274 
275       gfx::Size size(child->GetPreferredSize());
276       if (size.IsEmpty())
277         continue;
278 
279       gfx::Rect child_bounds(position, 0, size.width(), size.height());
280       child_area_bounds.Union(child_bounds);
281       position += size.width() + between_child_spacing_;
282     }
283     child_area_bounds.set_height(
284         std::max(child_area_bounds.height(), minimum_cross_axis_size_));
285   } else {
286     int height = 0;
287     for (int i = 0; i < host->child_count(); ++i) {
288       const View* child = host->child_at(i);
289       if (!child->visible())
290         continue;
291 
292       // Use the child area width for getting the height if the child is
293       // supposed to stretch. Use its preferred size otherwise.
294       int extra_height = MainAxisSizeForView(child, child_area_width);
295       // Only add |between_child_spacing_| if this is not the only child.
296       if (height != 0 && extra_height > 0)
297         height += between_child_spacing_;
298       height += extra_height;
299     }
300 
301     child_area_bounds.set_width(child_area_width);
302     child_area_bounds.set_height(height);
303   }
304 
305   gfx::Size non_child_size = NonChildSize(host);
306   return gfx::Size(child_area_bounds.width() + non_child_size.width(),
307                    child_area_bounds.height() + non_child_size.height());
308 }
309 
NonChildSize(const View * host) const310 gfx::Size BoxLayout::NonChildSize(const View* host) const {
311   gfx::Insets insets(host->GetInsets());
312   return gfx::Size(insets.width() + inside_border_insets_.width(),
313                    insets.height() + inside_border_insets_.height());
314 }
315 
316 } // namespace views
317