// 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