Commit 9cfa1257 authored by calamity@chromium.org's avatar calamity@chromium.org

Add Flex to views::BoxLayout.

This CL adds a per-view flex property to BoxLayout. The flex property functions
similarly to the CSS flexbox concept of flex, using the child view's preferred
size as the flex basis and then adding or removing space within each flexed
so that all views fit within the parent.

This CL also removes MAIN_AXIS_ALIGNMENT_FILL as it is superceded by
SetDefaultFlex().

BUG=386475

Review URL: https://codereview.chromium.org/360213002

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@285514 0039d316-1c4b-4281-b951-d872f2087c98
parent ee8d3b00
......@@ -336,7 +336,7 @@ void NetworkStateListDetailedView::CreateNetworkExtra() {
kTrayMenuBottomRowPadding,
kTrayMenuBottomRowPadding,
kTrayMenuBottomRowPaddingBetweenItems);
layout->set_main_axis_alignment(views::BoxLayout::MAIN_AXIS_ALIGNMENT_FILL);
layout->SetDefaultFlex(1);
bottom_row->SetLayoutManager(layout);
if (list_type_ != LIST_TYPE_VPN) {
......
......@@ -53,7 +53,7 @@ class TrayPopupItemContainer : public views::View {
}
views::BoxLayout* layout = new views::BoxLayout(
views::BoxLayout::kVertical, 0, 0, 0);
layout->set_main_axis_alignment(views::BoxLayout::MAIN_AXIS_ALIGNMENT_FILL);
layout->SetDefaultFlex(1);
SetLayoutManager(layout);
SetPaintToLayer(view->layer() != NULL);
if (view->layer())
......
......@@ -276,7 +276,7 @@ void TrayBackgroundView::TrayContainer::UpdateLayout() {
views::BoxLayout* layout =
new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 0);
layout->set_main_axis_alignment(views::BoxLayout::MAIN_AXIS_ALIGNMENT_FILL);
layout->SetDefaultFlex(1);
views::View::SetLayoutManager(layout);
} else {
SetBorder(views::Border::CreateEmptyBorder(
......@@ -287,7 +287,7 @@ void TrayBackgroundView::TrayContainer::UpdateLayout() {
views::BoxLayout* layout =
new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0);
layout->set_main_axis_alignment(views::BoxLayout::MAIN_AXIS_ALIGNMENT_FILL);
layout->SetDefaultFlex(1);
views::View::SetLayoutManager(layout);
}
PreferredSizeChanged();
......
......@@ -218,7 +218,7 @@ void AccessibilityDetailedView::AppendHelpEntries() {
kTrayMenuBottomRowPadding,
kTrayMenuBottomRowPadding,
kTrayMenuBottomRowPaddingBetweenItems);
layout->set_main_axis_alignment(views::BoxLayout::MAIN_AXIS_ALIGNMENT_FILL);
layout->SetDefaultFlex(1);
bottom_row->SetLayoutManager(layout);
ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
......
......@@ -30,8 +30,7 @@ BottomHomeView::BottomHomeView(app_list::AppListViewDelegate* view_delegate)
views::BoxLayout* items_layout = new views::BoxLayout(
views::BoxLayout::kHorizontal, 0, 0, 0);
items_layout->set_main_axis_alignment(
views::BoxLayout::MAIN_AXIS_ALIGNMENT_FILL);
items_layout->SetDefaultFlex(1);
items_container->SetLayoutManager(items_layout);
for (size_t i = 0; i < top_level->item_count(); ++i) {
app_list::TileItemView* tile_item_view = new app_list::TileItemView();
......
......@@ -49,7 +49,7 @@ MessageCenterWidgetDelegate::MessageCenterWidgetDelegate(
views::BoxLayout* layout =
new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0);
layout->set_main_axis_alignment(views::BoxLayout::MAIN_AXIS_ALIGNMENT_FILL);
layout->SetDefaultFlex(1);
SetLayoutManager(layout);
AddAccelerator(ui::Accelerator(ui::VKEY_ESCAPE, ui::EF_NONE));
......
......@@ -181,7 +181,7 @@ MessageListView::MessageListView(MessageCenterView* message_center_view,
weak_ptr_factory_(this) {
views::BoxLayout* layout =
new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 1);
layout->set_main_axis_alignment(views::BoxLayout::MAIN_AXIS_ALIGNMENT_FILL);
layout->SetDefaultFlex(1);
SetLayoutManager(layout);
// Set the margin to 0 for the layout. BoxLayout assumes the same margin
......
......@@ -389,7 +389,7 @@ gfx::Insets TrayBubbleView::GetBorderInsets() const {
void TrayBubbleView::Init() {
BoxLayout* layout = new BottomAlignedBoxLayout(this);
layout->set_main_axis_alignment(views::BoxLayout::MAIN_AXIS_ALIGNMENT_FILL);
layout->SetDefaultFlex(1);
SetLayoutManager(layout);
}
......
......@@ -20,88 +20,138 @@ BoxLayout::BoxLayout(BoxLayout::Orientation orientation,
inside_border_horizontal_spacing),
between_child_spacing_(between_child_spacing),
main_axis_alignment_(MAIN_AXIS_ALIGNMENT_START),
cross_axis_alignment_(CROSS_AXIS_ALIGNMENT_STRETCH) {
cross_axis_alignment_(CROSS_AXIS_ALIGNMENT_STRETCH),
default_flex_(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 padding = 0;
if (main_axis_alignment_ != MAIN_AXIS_ALIGNMENT_START) {
int total_main_axis_size = 0;
int num_visible = 0;
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;
}
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;
if (num_visible) {
total_main_axis_size -= between_child_spacing_;
int free_space = MainAxisSize(child_area) - total_main_axis_size;
int position = MainAxisPosition(child_area);
int size = MainAxisSize(child_area);
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_FILL:
padding = std::max(free_space / num_visible, 0);
case MAIN_AXIS_ALIGNMENT_START:
break;
case MAIN_AXIS_ALIGNMENT_CENTER:
position += free_space / 2;
position += main_free_space / 2;
size = total_main_axis_size;
break;
case MAIN_AXIS_ALIGNMENT_END:
position += free_space;
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);
}
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()) {
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);
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;
}
int child_main_axis_size = MainAxisSizeForView(child, child_area.width());
SetMainAxisSize(child_main_axis_size + padding, &bounds);
if (MainAxisSize(bounds) > 0)
main_position += MainAxisSize(bounds) + between_child_spacing_;
// Clamp child view bounds to |child_area|.
bounds.Intersect(child_area);
child->SetBoundsRect(bounds);
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) {
......@@ -118,10 +168,34 @@ gfx::Size BoxLayout::GetPreferredSize(const View* host) const {
}
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();
}
......
......@@ -5,6 +5,8 @@
#ifndef UI_VIEWS_LAYOUT_BOX_LAYOUT_H_
#define UI_VIEWS_LAYOUT_BOX_LAYOUT_H_
#include <map>
#include "base/basictypes.h"
#include "base/compiler_specific.h"
#include "ui/gfx/insets.h"
......@@ -38,11 +40,6 @@ class VIEWS_EXPORT BoxLayout : public LayoutManager {
MAIN_AXIS_ALIGNMENT_START,
MAIN_AXIS_ALIGNMENT_CENTER,
MAIN_AXIS_ALIGNMENT_END,
// This distributes extra space among the child views. This increases the
// size of child views along the main axis rather than the space between
// them.
MAIN_AXIS_ALIGNMENT_FILL,
// TODO(calamity): Add MAIN_AXIS_ALIGNMENT_JUSTIFY which spreads blank space
// in-between the child views.
};
......@@ -80,13 +77,35 @@ class VIEWS_EXPORT BoxLayout : public LayoutManager {
inside_border_insets_ = insets;
}
// Sets the flex weight for the given |view|. Using the preferred size as
// the basis, free space along the main axis is distributed to views in the
// ratio of their flex weights. Similarly, if the views will overflow the
// parent, space is subtracted in these ratios.
//
// A flex of 0 means this view is not resized. Flex values must not be
// negative.
void SetFlexForView(const View* view, int flex);
// Clears the flex for the given |view|, causing it to use the default
// flex.
void ClearFlexForView(const View* view);
// Sets the flex for views to use when none is specified.
void SetDefaultFlex(int default_flex);
// Overridden from views::LayoutManager:
virtual void Installed(View* host) OVERRIDE;
virtual void Uninstalled(View* host) OVERRIDE;
virtual void ViewRemoved(View* host, View* view) OVERRIDE;
virtual void Layout(View* host) OVERRIDE;
virtual gfx::Size GetPreferredSize(const View* host) const OVERRIDE;
virtual int GetPreferredHeightForWidth(const View* host,
int width) const OVERRIDE;
private:
// Returns the flex for the specified |view|.
int GetFlexForView(const View* view) const;
// Returns the size and position along the main axis of |rect|.
int MainAxisSize(const gfx::Rect& rect) const;
int MainAxisPosition(const gfx::Rect& rect) const;
......@@ -134,6 +153,15 @@ class VIEWS_EXPORT BoxLayout : public LayoutManager {
// CROSS_AXIS_ALIGNMENT_STRETCH by default.
CrossAxisAlignment cross_axis_alignment_;
// A map of views to their flex weights.
std::map<const View*, int> flex_map_;
// The flex weight for views if none is set. Defaults to 0.
int default_flex_;
// The view that this BoxLayout is managing the layout for.
views::View* host_;
DISALLOW_IMPLICIT_CONSTRUCTORS(BoxLayout);
};
......
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment