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.
5 #include "ui/views/layout/box_layout.h"
7 #include "ui/gfx/geometry/rect.h"
8 #include "ui/views/view.h"
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
),
25 minimum_cross_axis_size_(0),
29 BoxLayout::~BoxLayout() {
32 void BoxLayout::SetFlexForView(const View
* view
, int flex_weight
) {
35 DCHECK_EQ(host_
, view
->parent());
36 DCHECK_GE(flex_weight
, 0);
37 flex_map_
[view
] = flex_weight
;
40 void BoxLayout::ClearFlexForView(const View
* view
) {
42 flex_map_
.erase(view
);
45 void BoxLayout::SetDefaultFlex(int default_flex
) {
46 DCHECK_GE(default_flex
, 0);
47 default_flex_
= default_flex
;
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_
);
56 int total_main_axis_size
= 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())
64 int flex
= GetFlexForView(child
);
65 int child_main_axis_size
= MainAxisSizeForView(child
, child_area
.width());
66 if (child_main_axis_size
== 0 && flex
== 0)
68 total_main_axis_size
+= child_main_axis_size
+ between_child_spacing_
;
76 total_main_axis_size
-= between_child_spacing_
;
77 // Free space can be negative indicating that the views want to overflow.
78 int main_free_space
= MainAxisSize(child_area
) - total_main_axis_size
;
80 int position
= MainAxisPosition(child_area
);
81 int size
= MainAxisSize(child_area
);
83 switch (main_axis_alignment_
) {
84 case MAIN_AXIS_ALIGNMENT_START
:
86 case MAIN_AXIS_ALIGNMENT_CENTER
:
87 position
+= main_free_space
/ 2;
88 size
= total_main_axis_size
;
90 case MAIN_AXIS_ALIGNMENT_END
:
91 position
+= main_free_space
;
92 size
= total_main_axis_size
;
99 gfx::Rect
new_child_area(child_area
);
100 SetMainAxisPosition(position
, &new_child_area
);
101 SetMainAxisSize(size
, &new_child_area
);
102 child_area
.Intersect(new_child_area
);
105 int main_position
= MainAxisPosition(child_area
);
106 int total_padding
= 0;
107 int current_flex
= 0;
108 for (int i
= 0; i
< host
->child_count(); ++i
) {
109 View
* child
= host
->child_at(i
);
110 if (!child
->visible())
113 // Calculate cross axis size.
114 gfx::Rect
bounds(child_area
);
115 SetMainAxisPosition(main_position
, &bounds
);
116 if (cross_axis_alignment_
!= CROSS_AXIS_ALIGNMENT_STRETCH
) {
117 int free_space
= CrossAxisSize(bounds
) - CrossAxisSizeForView(child
);
118 int position
= CrossAxisPosition(bounds
);
119 if (cross_axis_alignment_
== CROSS_AXIS_ALIGNMENT_CENTER
) {
120 position
+= free_space
/ 2;
121 } else if (cross_axis_alignment_
== CROSS_AXIS_ALIGNMENT_END
) {
122 position
+= free_space
;
124 SetCrossAxisPosition(position
, &bounds
);
125 SetCrossAxisSize(CrossAxisSizeForView(child
), &bounds
);
128 // Calculate flex padding.
129 int current_padding
= 0;
130 if (GetFlexForView(child
) > 0) {
131 current_flex
+= GetFlexForView(child
);
132 int quot
= (main_free_space
* current_flex
) / flex_sum
;
133 int rem
= (main_free_space
* current_flex
) % flex_sum
;
134 current_padding
= quot
- total_padding
;
135 // Use the current remainder to round to the nearest pixel.
136 if (std::abs(rem
) * 2 >= flex_sum
)
137 current_padding
+= main_free_space
> 0 ? 1 : -1;
138 total_padding
+= current_padding
;
141 // Set main axis size.
142 int child_main_axis_size
= MainAxisSizeForView(child
, child_area
.width());
143 SetMainAxisSize(child_main_axis_size
+ current_padding
, &bounds
);
144 if (MainAxisSize(bounds
) > 0 || GetFlexForView(child
) > 0)
145 main_position
+= MainAxisSize(bounds
) + between_child_spacing_
;
147 // Clamp child view bounds to |child_area|.
148 bounds
.Intersect(child_area
);
149 child
->SetBoundsRect(bounds
);
152 // Flex views should have grown/shrunk to consume all free space.
154 DCHECK_EQ(total_padding
, main_free_space
);
157 gfx::Size
BoxLayout::GetPreferredSize(const View
* host
) const {
158 DCHECK_EQ(host_
, host
);
159 // Calculate the child views' preferred width.
161 if (orientation_
== kVertical
) {
162 for (int i
= 0; i
< host
->child_count(); ++i
) {
163 const View
* child
= host
->child_at(i
);
164 if (!child
->visible())
167 width
= std::max(width
, child
->GetPreferredSize().width());
169 width
= std::max(width
, minimum_cross_axis_size_
);
172 return GetPreferredSizeForChildWidth(host
, width
);
175 int BoxLayout::GetPreferredHeightForWidth(const View
* host
, int width
) const {
176 DCHECK_EQ(host_
, host
);
177 int child_width
= width
- NonChildSize(host
).width();
178 return GetPreferredSizeForChildWidth(host
, child_width
).height();
181 void BoxLayout::Installed(View
* host
) {
186 void BoxLayout::Uninstalled(View
* host
) {
187 DCHECK_EQ(host_
, host
);
192 void BoxLayout::ViewRemoved(View
* host
, View
* view
) {
193 ClearFlexForView(view
);
196 int BoxLayout::GetFlexForView(const View
* view
) const {
197 std::map
<const View
*, int>::const_iterator it
= flex_map_
.find(view
);
198 if (it
== flex_map_
.end())
199 return default_flex_
;
204 int BoxLayout::MainAxisSize(const gfx::Rect
& rect
) const {
205 return orientation_
== kHorizontal
? rect
.width() : rect
.height();
208 int BoxLayout::MainAxisPosition(const gfx::Rect
& rect
) const {
209 return orientation_
== kHorizontal
? rect
.x() : rect
.y();
212 void BoxLayout::SetMainAxisSize(int size
, gfx::Rect
* rect
) const {
213 if (orientation_
== kHorizontal
)
214 rect
->set_width(size
);
216 rect
->set_height(size
);
219 void BoxLayout::SetMainAxisPosition(int position
, gfx::Rect
* rect
) const {
220 if (orientation_
== kHorizontal
)
221 rect
->set_x(position
);
223 rect
->set_y(position
);
226 int BoxLayout::CrossAxisSize(const gfx::Rect
& rect
) const {
227 return orientation_
== kVertical
? rect
.width() : rect
.height();
230 int BoxLayout::CrossAxisPosition(const gfx::Rect
& rect
) const {
231 return orientation_
== kVertical
? rect
.x() : rect
.y();
234 void BoxLayout::SetCrossAxisSize(int size
, gfx::Rect
* rect
) const {
235 if (orientation_
== kVertical
)
236 rect
->set_width(size
);
238 rect
->set_height(size
);
241 void BoxLayout::SetCrossAxisPosition(int position
, gfx::Rect
* rect
) const {
242 if (orientation_
== kVertical
)
243 rect
->set_x(position
);
245 rect
->set_y(position
);
248 int BoxLayout::MainAxisSizeForView(const View
* view
,
249 int child_area_width
) const {
250 return orientation_
== kHorizontal
251 ? view
->GetPreferredSize().width()
252 : view
->GetHeightForWidth(cross_axis_alignment_
==
253 CROSS_AXIS_ALIGNMENT_STRETCH
255 : view
->GetPreferredSize().width());
258 int BoxLayout::CrossAxisSizeForView(const View
* view
) const {
259 return orientation_
== kVertical
260 ? view
->GetPreferredSize().width()
261 : view
->GetHeightForWidth(view
->GetPreferredSize().width());
264 gfx::Size
BoxLayout::GetPreferredSizeForChildWidth(const View
* host
,
265 int child_area_width
) const {
266 gfx::Rect child_area_bounds
;
268 if (orientation_
== kHorizontal
) {
269 // Horizontal layouts ignore |child_area_width|, meaning they mimic the
270 // default behavior of GridLayout::GetPreferredHeightForWidth().
271 // TODO(estade): fix this if it ever becomes a problem.
273 for (int i
= 0; i
< host
->child_count(); ++i
) {
274 const View
* child
= host
->child_at(i
);
275 if (!child
->visible())
278 gfx::Size
size(child
->GetPreferredSize());
282 gfx::Rect
child_bounds(position
, 0, size
.width(), size
.height());
283 child_area_bounds
.Union(child_bounds
);
284 position
+= size
.width() + between_child_spacing_
;
286 child_area_bounds
.set_height(
287 std::max(child_area_bounds
.height(), minimum_cross_axis_size_
));
290 for (int i
= 0; i
< host
->child_count(); ++i
) {
291 const View
* child
= host
->child_at(i
);
292 if (!child
->visible())
295 // Use the child area width for getting the height if the child is
296 // supposed to stretch. Use its preferred size otherwise.
297 int extra_height
= MainAxisSizeForView(child
, child_area_width
);
298 // Only add |between_child_spacing_| if this is not the only child.
299 if (height
!= 0 && extra_height
> 0)
300 height
+= between_child_spacing_
;
301 height
+= extra_height
;
304 child_area_bounds
.set_width(child_area_width
);
305 child_area_bounds
.set_height(height
);
308 gfx::Size non_child_size
= NonChildSize(host
);
309 return gfx::Size(child_area_bounds
.width() + non_child_size
.width(),
310 child_area_bounds
.height() + non_child_size
.height());
313 gfx::Size
BoxLayout::NonChildSize(const View
* host
) const {
314 gfx::Insets
insets(host
->GetInsets());
315 return gfx::Size(insets
.width() + inside_border_insets_
.width(),
316 insets
.height() + inside_border_insets_
.height());