| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ui/views/layout/box_layout.h" |
| |
| #include "ui/gfx/rect.h" |
| #include "ui/views/view.h" |
| |
| namespace views { |
| |
| BoxLayout::BoxLayout(BoxLayout::Orientation orientation, |
| int inside_border_horizontal_spacing, |
| int inside_border_vertical_spacing, |
| int between_child_spacing) |
| : orientation_(orientation), |
| inside_border_insets_(inside_border_vertical_spacing, |
| inside_border_horizontal_spacing, |
| inside_border_vertical_spacing, |
| inside_border_horizontal_spacing), |
| between_child_spacing_(between_child_spacing), |
| main_axis_alignment_(MAIN_AXIS_ALIGNMENT_START), |
| cross_axis_alignment_(CROSS_AXIS_ALIGNMENT_STRETCH), |
| default_flex_(0), |
| minimum_cross_axis_size_(0), |
| host_(NULL) { |
| } |
| |
| BoxLayout::~BoxLayout() { |
| } |
| |
| void BoxLayout::SetFlexForView(const View* view, int flex_weight) { |
| DCHECK(host_); |
| DCHECK(view); |
| DCHECK_EQ(host_, view->parent()); |
| DCHECK_GE(flex_weight, 0); |
| flex_map_[view] = flex_weight; |
| } |
| |
| void BoxLayout::ClearFlexForView(const View* view) { |
| DCHECK(view); |
| flex_map_.erase(view); |
| } |
| |
| void BoxLayout::SetDefaultFlex(int default_flex) { |
| DCHECK_GE(default_flex, 0); |
| default_flex_ = default_flex; |
| } |
| |
| void BoxLayout::Layout(View* host) { |
| DCHECK_EQ(host_, host); |
| gfx::Rect child_area(host->GetLocalBounds()); |
| child_area.Inset(host->GetInsets()); |
| child_area.Inset(inside_border_insets_); |
| |
| int total_main_axis_size = 0; |
| int num_visible = 0; |
| int flex_sum = 0; |
| // Calculate the total size of children in the main axis. |
| for (int i = 0; i < host->child_count(); ++i) { |
| View* child = host->child_at(i); |
| if (!child->visible()) |
| continue; |
| total_main_axis_size += |
| MainAxisSizeForView(child, child_area.width()) + between_child_spacing_; |
| ++num_visible; |
| flex_sum += GetFlexForView(child); |
| } |
| |
| if (!num_visible) |
| return; |
| |
| total_main_axis_size -= between_child_spacing_; |
| // Free space can be negative indicating that the views want to overflow. |
| int main_free_space = MainAxisSize(child_area) - total_main_axis_size; |
| { |
| int position = MainAxisPosition(child_area); |
| int size = MainAxisSize(child_area); |
| if (!flex_sum) { |
| switch (main_axis_alignment_) { |
| case MAIN_AXIS_ALIGNMENT_START: |
| break; |
| case MAIN_AXIS_ALIGNMENT_CENTER: |
| position += main_free_space / 2; |
| size = total_main_axis_size; |
| break; |
| case MAIN_AXIS_ALIGNMENT_END: |
| position += main_free_space; |
| size = total_main_axis_size; |
| break; |
| default: |
| NOTREACHED(); |
| break; |
| } |
| } |
| gfx::Rect new_child_area(child_area); |
| SetMainAxisPosition(position, &new_child_area); |
| SetMainAxisSize(size, &new_child_area); |
| child_area.Intersect(new_child_area); |
| } |
| |
| int main_position = MainAxisPosition(child_area); |
| int total_padding = 0; |
| int current_flex = 0; |
| for (int i = 0; i < host->child_count(); ++i) { |
| View* child = host->child_at(i); |
| if (!child->visible()) |
| continue; |
| |
| // Calculate cross axis size. |
| gfx::Rect bounds(child_area); |
| SetMainAxisPosition(main_position, &bounds); |
| if (cross_axis_alignment_ != CROSS_AXIS_ALIGNMENT_STRETCH) { |
| int free_space = CrossAxisSize(bounds) - CrossAxisSizeForView(child); |
| int position = CrossAxisPosition(bounds); |
| if (cross_axis_alignment_ == CROSS_AXIS_ALIGNMENT_CENTER) { |
| position += free_space / 2; |
| } else if (cross_axis_alignment_ == CROSS_AXIS_ALIGNMENT_END) { |
| position += free_space; |
| } |
| SetCrossAxisPosition(position, &bounds); |
| SetCrossAxisSize(CrossAxisSizeForView(child), &bounds); |
| } |
| |
| // Calculate flex padding. |
| int current_padding = 0; |
| if (GetFlexForView(child) > 0) { |
| current_flex += GetFlexForView(child); |
| int quot = (main_free_space * current_flex) / flex_sum; |
| int rem = (main_free_space * current_flex) % flex_sum; |
| current_padding = quot - total_padding; |
| // Use the current remainder to round to the nearest pixel. |
| if (std::abs(rem) * 2 >= flex_sum) |
| current_padding += main_free_space > 0 ? 1 : -1; |
| total_padding += current_padding; |
| } |
| |
| // Set main axis size. |
| int child_main_axis_size = MainAxisSizeForView(child, child_area.width()); |
| SetMainAxisSize(child_main_axis_size + current_padding, &bounds); |
| if (MainAxisSize(bounds) > 0 || GetFlexForView(child) > 0) |
| main_position += MainAxisSize(bounds) + between_child_spacing_; |
| |
| // Clamp child view bounds to |child_area|. |
| bounds.Intersect(child_area); |
| child->SetBoundsRect(bounds); |
| } |
| |
| // Flex views should have grown/shrunk to consume all free space. |
| if (flex_sum) |
| DCHECK_EQ(total_padding, main_free_space); |
| } |
| |
| gfx::Size BoxLayout::GetPreferredSize(const View* host) const { |
| DCHECK_EQ(host_, host); |
| // Calculate the child views' preferred width. |
| int width = 0; |
| if (orientation_ == kVertical) { |
| for (int i = 0; i < host->child_count(); ++i) { |
| const View* child = host->child_at(i); |
| if (!child->visible()) |
| continue; |
| |
| width = std::max(width, child->GetPreferredSize().width()); |
| } |
| width = std::max(width, minimum_cross_axis_size_); |
| } |
| |
| return GetPreferredSizeForChildWidth(host, width); |
| } |
| |
| int BoxLayout::GetPreferredHeightForWidth(const View* host, int width) const { |
| DCHECK_EQ(host_, host); |
| int child_width = width - NonChildSize(host).width(); |
| return GetPreferredSizeForChildWidth(host, child_width).height(); |
| } |
| |
| void BoxLayout::Installed(View* host) { |
| DCHECK(!host_); |
| host_ = host; |
| } |
| |
| void BoxLayout::Uninstalled(View* host) { |
| DCHECK_EQ(host_, host); |
| host_ = NULL; |
| flex_map_.clear(); |
| } |
| |
| void BoxLayout::ViewRemoved(View* host, View* view) { |
| ClearFlexForView(view); |
| } |
| |
| int BoxLayout::GetFlexForView(const View* view) const { |
| std::map<const View*, int>::const_iterator it = flex_map_.find(view); |
| if (it == flex_map_.end()) |
| return default_flex_; |
| |
| return it->second; |
| } |
| |
| int BoxLayout::MainAxisSize(const gfx::Rect& rect) const { |
| return orientation_ == kHorizontal ? rect.width() : rect.height(); |
| } |
| |
| int BoxLayout::MainAxisPosition(const gfx::Rect& rect) const { |
| return orientation_ == kHorizontal ? rect.x() : rect.y(); |
| } |
| |
| void BoxLayout::SetMainAxisSize(int size, gfx::Rect* rect) const { |
| if (orientation_ == kHorizontal) |
| rect->set_width(size); |
| else |
| rect->set_height(size); |
| } |
| |
| void BoxLayout::SetMainAxisPosition(int position, gfx::Rect* rect) const { |
| if (orientation_ == kHorizontal) |
| rect->set_x(position); |
| else |
| rect->set_y(position); |
| } |
| |
| int BoxLayout::CrossAxisSize(const gfx::Rect& rect) const { |
| return orientation_ == kVertical ? rect.width() : rect.height(); |
| } |
| |
| int BoxLayout::CrossAxisPosition(const gfx::Rect& rect) const { |
| return orientation_ == kVertical ? rect.x() : rect.y(); |
| } |
| |
| void BoxLayout::SetCrossAxisSize(int size, gfx::Rect* rect) const { |
| if (orientation_ == kVertical) |
| rect->set_width(size); |
| else |
| rect->set_height(size); |
| } |
| |
| void BoxLayout::SetCrossAxisPosition(int position, gfx::Rect* rect) const { |
| if (orientation_ == kVertical) |
| rect->set_x(position); |
| else |
| rect->set_y(position); |
| } |
| |
| int BoxLayout::MainAxisSizeForView(const View* view, |
| int child_area_width) const { |
| return orientation_ == kHorizontal |
| ? view->GetPreferredSize().width() |
| : view->GetHeightForWidth(cross_axis_alignment_ == |
| CROSS_AXIS_ALIGNMENT_STRETCH |
| ? child_area_width |
| : view->GetPreferredSize().width()); |
| } |
| |
| int BoxLayout::CrossAxisSizeForView(const View* view) const { |
| return orientation_ == kVertical |
| ? view->GetPreferredSize().width() |
| : view->GetHeightForWidth(view->GetPreferredSize().width()); |
| } |
| |
| gfx::Size BoxLayout::GetPreferredSizeForChildWidth(const View* host, |
| int child_area_width) const { |
| gfx::Rect child_area_bounds; |
| |
| if (orientation_ == kHorizontal) { |
| // Horizontal layouts ignore |child_area_width|, meaning they mimic the |
| // default behavior of GridLayout::GetPreferredHeightForWidth(). |
| // TODO(estade): fix this if it ever becomes a problem. |
| int position = 0; |
| for (int i = 0; i < host->child_count(); ++i) { |
| const View* child = host->child_at(i); |
| if (!child->visible()) |
| continue; |
| |
| gfx::Size size(child->GetPreferredSize()); |
| if (size.IsEmpty()) |
| continue; |
| |
| gfx::Rect child_bounds(position, 0, size.width(), size.height()); |
| child_area_bounds.Union(child_bounds); |
| position += size.width() + between_child_spacing_; |
| } |
| child_area_bounds.set_height( |
| std::max(child_area_bounds.height(), minimum_cross_axis_size_)); |
| } else { |
| int height = 0; |
| for (int i = 0; i < host->child_count(); ++i) { |
| const View* child = host->child_at(i); |
| if (!child->visible()) |
| continue; |
| |
| // Use the child area width for getting the height if the child is |
| // supposed to stretch. Use its preferred size otherwise. |
| int extra_height = MainAxisSizeForView(child, child_area_width); |
| // Only add |between_child_spacing_| if this is not the only child. |
| if (height != 0 && extra_height > 0) |
| height += between_child_spacing_; |
| height += extra_height; |
| } |
| |
| child_area_bounds.set_width(child_area_width); |
| child_area_bounds.set_height(height); |
| } |
| |
| gfx::Size non_child_size = NonChildSize(host); |
| return gfx::Size(child_area_bounds.width() + non_child_size.width(), |
| child_area_bounds.height() + non_child_size.height()); |
| } |
| |
| gfx::Size BoxLayout::NonChildSize(const View* host) const { |
| gfx::Insets insets(host->GetInsets()); |
| return gfx::Size(insets.width() + inside_border_insets_.width(), |
| insets.height() + inside_border_insets_.height()); |
| } |
| |
| } // namespace views |