| OLD | NEW |
| (Empty) |
| 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 | |
| 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 | |
| 29 BoxLayout::~BoxLayout() { | |
| 30 } | |
| 31 | |
| 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 | |
| 40 void BoxLayout::ClearFlexForView(const View* view) { | |
| 41 DCHECK(view); | |
| 42 flex_map_.erase(view); | |
| 43 } | |
| 44 | |
| 45 void BoxLayout::SetDefaultFlex(int default_flex) { | |
| 46 DCHECK_GE(default_flex, 0); | |
| 47 default_flex_ = default_flex; | |
| 48 } | |
| 49 | |
| 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 | |
| 154 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 | |
| 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(); | |
| 176 } | |
| 177 | |
| 178 void BoxLayout::Installed(View* host) { | |
| 179 DCHECK(!host_); | |
| 180 host_ = host; | |
| 181 } | |
| 182 | |
| 183 void BoxLayout::Uninstalled(View* host) { | |
| 184 DCHECK_EQ(host_, host); | |
| 185 host_ = NULL; | |
| 186 flex_map_.clear(); | |
| 187 } | |
| 188 | |
| 189 void BoxLayout::ViewRemoved(View* host, View* view) { | |
| 190 ClearFlexForView(view); | |
| 191 } | |
| 192 | |
| 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_; | |
| 197 | |
| 198 return it->second; | |
| 199 } | |
| 200 | |
| 201 int BoxLayout::MainAxisSize(const gfx::Rect& rect) const { | |
| 202 return orientation_ == kHorizontal ? rect.width() : rect.height(); | |
| 203 } | |
| 204 | |
| 205 int BoxLayout::MainAxisPosition(const gfx::Rect& rect) const { | |
| 206 return orientation_ == kHorizontal ? rect.x() : rect.y(); | |
| 207 } | |
| 208 | |
| 209 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 | |
| 216 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 | |
| 223 int BoxLayout::CrossAxisSize(const gfx::Rect& rect) const { | |
| 224 return orientation_ == kVertical ? rect.width() : rect.height(); | |
| 225 } | |
| 226 | |
| 227 int BoxLayout::CrossAxisPosition(const gfx::Rect& rect) const { | |
| 228 return orientation_ == kVertical ? rect.x() : rect.y(); | |
| 229 } | |
| 230 | |
| 231 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 | |
| 238 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 | |
| 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 | |
| 251 ? child_area_width | |
| 252 : view->GetPreferredSize().width()); | |
| 253 } | |
| 254 | |
| 255 int BoxLayout::CrossAxisSizeForView(const View* view) const { | |
| 256 return orientation_ == kVertical | |
| 257 ? view->GetPreferredSize().width() | |
| 258 : view->GetHeightForWidth(view->GetPreferredSize().width()); | |
| 259 } | |
| 260 | |
| 261 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 | |
| 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()); | |
| 314 } | |
| 315 | |
| 316 } // namespace views | |
| OLD | NEW |