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 total_main_axis_size
+=
65 MainAxisSizeForView(child
, child_area
.width()) + between_child_spacing_
;
67 flex_sum
+= GetFlexForView(child
);
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
;
77 int position
= MainAxisPosition(child_area
);
78 int size
= MainAxisSize(child_area
);
80 switch (main_axis_alignment_
) {
81 case MAIN_AXIS_ALIGNMENT_START
:
83 case MAIN_AXIS_ALIGNMENT_CENTER
:
84 position
+= main_free_space
/ 2;
85 size
= total_main_axis_size
;
87 case MAIN_AXIS_ALIGNMENT_END
:
88 position
+= main_free_space
;
89 size
= total_main_axis_size
;
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
);
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())
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
;
121 SetCrossAxisPosition(position
, &bounds
);
122 SetCrossAxisSize(CrossAxisSizeForView(child
), &bounds
);
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
;
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_
;
144 // Clamp child view bounds to |child_area|.
145 bounds
.Intersect(child_area
);
146 child
->SetBoundsRect(bounds
);
149 // Flex views should have grown/shrunk to consume all free space.
151 DCHECK_EQ(total_padding
, main_free_space
);
154 gfx::Size
BoxLayout::GetPreferredSize(const View
* host
) const {
155 DCHECK_EQ(host_
, host
);
156 // Calculate the child views' preferred width.
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())
164 width
= std::max(width
, child
->GetPreferredSize().width());
166 width
= std::max(width
, minimum_cross_axis_size_
);
169 return GetPreferredSizeForChildWidth(host
, width
);
172 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();
178 void BoxLayout::Installed(View
* host
) {
183 void BoxLayout::Uninstalled(View
* host
) {
184 DCHECK_EQ(host_
, host
);
189 void BoxLayout::ViewRemoved(View
* host
, View
* view
) {
190 ClearFlexForView(view
);
193 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_
;
201 int BoxLayout::MainAxisSize(const gfx::Rect
& rect
) const {
202 return orientation_
== kHorizontal
? rect
.width() : rect
.height();
205 int BoxLayout::MainAxisPosition(const gfx::Rect
& rect
) const {
206 return orientation_
== kHorizontal
? rect
.x() : rect
.y();
209 void BoxLayout::SetMainAxisSize(int size
, gfx::Rect
* rect
) const {
210 if (orientation_
== kHorizontal
)
211 rect
->set_width(size
);
213 rect
->set_height(size
);
216 void BoxLayout::SetMainAxisPosition(int position
, gfx::Rect
* rect
) const {
217 if (orientation_
== kHorizontal
)
218 rect
->set_x(position
);
220 rect
->set_y(position
);
223 int BoxLayout::CrossAxisSize(const gfx::Rect
& rect
) const {
224 return orientation_
== kVertical
? rect
.width() : rect
.height();
227 int BoxLayout::CrossAxisPosition(const gfx::Rect
& rect
) const {
228 return orientation_
== kVertical
? rect
.x() : rect
.y();
231 void BoxLayout::SetCrossAxisSize(int size
, gfx::Rect
* rect
) const {
232 if (orientation_
== kVertical
)
233 rect
->set_width(size
);
235 rect
->set_height(size
);
238 void BoxLayout::SetCrossAxisPosition(int position
, gfx::Rect
* rect
) const {
239 if (orientation_
== kVertical
)
240 rect
->set_x(position
);
242 rect
->set_y(position
);
245 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
252 : view
->GetPreferredSize().width());
255 int BoxLayout::CrossAxisSizeForView(const View
* view
) const {
256 return orientation_
== kVertical
257 ? view
->GetPreferredSize().width()
258 : view
->GetHeightForWidth(view
->GetPreferredSize().width());
261 gfx::Size
BoxLayout::GetPreferredSizeForChildWidth(const View
* host
,
262 int child_area_width
) const {
263 gfx::Rect child_area_bounds
;
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.
270 for (int i
= 0; i
< host
->child_count(); ++i
) {
271 const View
* child
= host
->child_at(i
);
272 if (!child
->visible())
275 gfx::Size
size(child
->GetPreferredSize());
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_
;
283 child_area_bounds
.set_height(
284 std::max(child_area_bounds
.height(), minimum_cross_axis_size_
));
287 for (int i
= 0; i
< host
->child_count(); ++i
) {
288 const View
* child
= host
->child_at(i
);
289 if (!child
->visible())
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
;
301 child_area_bounds
.set_width(child_area_width
);
302 child_area_bounds
.set_height(height
);
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());
310 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());