diff options
author | calamity@chromium.org <calamity@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-06-25 10:06:04 +0000 |
---|---|---|
committer | calamity@chromium.org <calamity@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-06-25 10:06:04 +0000 |
commit | 89628cd99442e57b64689af07b0a4cec34d95304 (patch) | |
tree | 0e6b5f66028476d96ffe4822fb3c1af8c8d279df | |
parent | 0af747d4923863ff6807d44320eafd39ea4a2a19 (diff) | |
download | chromium_src-89628cd99442e57b64689af07b0a4cec34d95304.zip chromium_src-89628cd99442e57b64689af07b0a4cec34d95304.tar.gz chromium_src-89628cd99442e57b64689af07b0a4cec34d95304.tar.bz2 |
Add cross axis alignment for BoxLayout.
This CL adds a CrossAxisAlignment setting to BoxLayout which functions
like the CSS align-items property. This property aligns the children
items of a view in the non-orientation axis of the BoxLayout.
BUG=386475
Committed: https://src.chromium.org/viewvc/chrome?view=rev&revision=279257
Review URL: https://codereview.chromium.org/333403005
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@279656 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | ui/app_list/views/start_page_view.cc | 4 | ||||
-rw-r--r-- | ui/views/layout/box_layout.cc | 103 | ||||
-rw-r--r-- | ui/views/layout/box_layout.h | 47 | ||||
-rw-r--r-- | ui/views/layout/box_layout_unittest.cc | 97 | ||||
-rw-r--r-- | ui/views/test/test_views.cc | 8 | ||||
-rw-r--r-- | ui/views/test/test_views.h | 7 |
6 files changed, 225 insertions, 41 deletions
diff --git a/ui/app_list/views/start_page_view.cc b/ui/app_list/views/start_page_view.cc index 6aea513..c81fbb9 100644 --- a/ui/app_list/views/start_page_view.cc +++ b/ui/app_list/views/start_page_view.cc @@ -29,7 +29,7 @@ const int kTopMargin = 30; const int kInstantContainerSpacing = 20; // WebView constants. -const int kWebViewWidth = 200; +const int kWebViewWidth = 500; const int kWebViewHeight = 105; // DummySearchBoxView constants. @@ -130,6 +130,8 @@ void StartPageView::InitInstantContainer() { gfx::Insets(kTopMargin, 0, kInstantContainerSpacing, 0)); instant_layout_manager->set_main_axis_alignment( views::BoxLayout::MAIN_AXIS_ALIGNMENT_END); + instant_layout_manager->set_cross_axis_alignment( + views::BoxLayout::CROSS_AXIS_ALIGNMENT_CENTER); instant_container_->SetLayoutManager(instant_layout_manager); views::View* web_view = view_delegate_->CreateStartPageWebView( diff --git a/ui/views/layout/box_layout.cc b/ui/views/layout/box_layout.cc index c33264c..91504ad 100644 --- a/ui/views/layout/box_layout.cc +++ b/ui/views/layout/box_layout.cc @@ -19,7 +19,8 @@ BoxLayout::BoxLayout(BoxLayout::Orientation orientation, inside_border_vertical_spacing, inside_border_horizontal_spacing), between_child_spacing_(between_child_spacing), - main_axis_alignment_(MAIN_AXIS_ALIGNMENT_START) { + main_axis_alignment_(MAIN_AXIS_ALIGNMENT_START), + cross_axis_alignment_(CROSS_AXIS_ALIGNMENT_STRETCH) { } BoxLayout::~BoxLayout() { @@ -38,13 +39,8 @@ void BoxLayout::Layout(View* host) { View* child = host->child_at(i); if (!child->visible()) continue; - if (orientation_ == kHorizontal) { - total_main_axis_size += - child->GetPreferredSize().width() + between_child_spacing_; - } else { - total_main_axis_size += child->GetHeightForWidth(child_area.width()) + - between_child_spacing_; - } + total_main_axis_size += MainAxisSizeForView(child, child_area.width()) + + between_child_spacing_; ++num_visible; } @@ -76,21 +72,28 @@ void BoxLayout::Layout(View* host) { } } - int x = child_area.x(); - int y = child_area.y(); + int main_position = MainAxisPosition(child_area); for (int i = 0; i < host->child_count(); ++i) { View* child = host->child_at(i); if (child->visible()) { - gfx::Rect bounds(x, y, child_area.width(), child_area.height()); - if (orientation_ == kHorizontal) { - bounds.set_width(child->GetPreferredSize().width() + padding); - if (bounds.width() > 0) - x += bounds.width() + between_child_spacing_; - } else { - bounds.set_height(child->GetHeightForWidth(bounds.width()) + padding); - if (bounds.height() > 0) - y += bounds.height() + between_child_spacing_; + 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); } + 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); @@ -119,26 +122,64 @@ int BoxLayout::GetPreferredHeightForWidth(const View* host, int width) const { return GetPreferredSizeForChildWidth(host, child_width).height(); } -int BoxLayout::MainAxisSize(const gfx::Rect& child_area) const { - return orientation_ == kHorizontal ? child_area.width() : child_area.height(); +int BoxLayout::MainAxisSize(const gfx::Rect& rect) const { + return orientation_ == kHorizontal ? rect.width() : rect.height(); } -int BoxLayout::MainAxisPosition(const gfx::Rect& child_area) const { - return orientation_ == kHorizontal ? child_area.x() : child_area.y(); +int BoxLayout::MainAxisPosition(const gfx::Rect& rect) const { + return orientation_ == kHorizontal ? rect.x() : rect.y(); } -void BoxLayout::SetMainAxisSize(int size, gfx::Rect* child_area) const { +void BoxLayout::SetMainAxisSize(int size, gfx::Rect* rect) const { if (orientation_ == kHorizontal) - child_area->set_width(size); + rect->set_width(size); else - child_area->set_height(size); + rect->set_height(size); } -void BoxLayout::SetMainAxisPosition(int position, gfx::Rect* child_area) const { +void BoxLayout::SetMainAxisPosition(int position, gfx::Rect* rect) const { if (orientation_ == kHorizontal) - child_area->set_x(position); + 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 - child_area->set_y(position); + 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, @@ -170,7 +211,9 @@ gfx::Size BoxLayout::GetPreferredSizeForChildWidth(const View* host, if (!child->visible()) continue; - int extra_height = child->GetHeightForWidth(child_area_width); + // 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_; diff --git a/ui/views/layout/box_layout.h b/ui/views/layout/box_layout.h index e1d22ac..54fa502 100644 --- a/ui/views/layout/box_layout.h +++ b/ui/views/layout/box_layout.h @@ -47,8 +47,16 @@ class VIEWS_EXPORT BoxLayout : public LayoutManager { // in-between the child views. }; - // TODO(calamity): Add CrossAxisAlignment property to allow cross axis - // alignment. + // This specifies where along the cross axis the children should be laid out. + // e.g. a horizontal layout of CROSS_AXIS_ALIGNMENT_END will result in the + // child views being bottom-aligned. + enum CrossAxisAlignment { + // This causes the child view to stretch to fit the host in the cross axis. + CROSS_AXIS_ALIGNMENT_STRETCH, + CROSS_AXIS_ALIGNMENT_START, + CROSS_AXIS_ALIGNMENT_CENTER, + CROSS_AXIS_ALIGNMENT_END, + }; // Use |inside_border_horizontal_spacing| and // |inside_border_vertical_spacing| to add additional space between the child @@ -64,6 +72,10 @@ class VIEWS_EXPORT BoxLayout : public LayoutManager { main_axis_alignment_ = main_axis_alignment; } + void set_cross_axis_alignment(CrossAxisAlignment cross_axis_alignment) { + cross_axis_alignment_ = cross_axis_alignment; + } + void set_inside_border_insets(const gfx::Insets& insets) { inside_border_insets_ = insets; } @@ -75,13 +87,28 @@ class VIEWS_EXPORT BoxLayout : public LayoutManager { int width) const OVERRIDE; private: - // Returns the size and position along the main axis of |child_area|. - int MainAxisSize(const gfx::Rect& child_area) const; - int MainAxisPosition(const gfx::Rect& child_area) 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; + + // Sets the size and position along the main axis of |rect|. + void SetMainAxisSize(int size, gfx::Rect* rect) const; + void SetMainAxisPosition(int position, gfx::Rect* rect) const; - // Sets the size and position along the main axis of |child_area|. - void SetMainAxisSize(int size, gfx::Rect* child_area) const; - void SetMainAxisPosition(int position, gfx::Rect* child_area) const; + // Returns the size and position along the cross axis of |rect|. + int CrossAxisSize(const gfx::Rect& rect) const; + int CrossAxisPosition(const gfx::Rect& rect) const; + + // Sets the size and position along the cross axis of |rect|. + void SetCrossAxisSize(int size, gfx::Rect* rect) const; + void SetCrossAxisPosition(int size, gfx::Rect* rect) const; + + // Returns the main axis size for the given view. |child_area_width| is needed + // to calculate the height of the view when the orientation is vertical. + int MainAxisSizeForView(const View* view, int child_area_width) const; + + // Returns the cross axis size for the given view. + int CrossAxisSizeForView(const View* view) const; // The preferred size for the dialog given the width of the child area. gfx::Size GetPreferredSizeForChildWidth(const View* host, @@ -103,6 +130,10 @@ class VIEWS_EXPORT BoxLayout : public LayoutManager { // MAIN_AXIS_ALIGNMENT_START by default. MainAxisAlignment main_axis_alignment_; + // The alignment of children in the cross axis. This is + // CROSS_AXIS_ALIGNMENT_STRETCH by default. + CrossAxisAlignment cross_axis_alignment_; + DISALLOW_IMPLICIT_CONSTRUCTORS(BoxLayout); }; diff --git a/ui/views/layout/box_layout_unittest.cc b/ui/views/layout/box_layout_unittest.cc index 42f8f42..5f58b15 100644 --- a/ui/views/layout/box_layout_unittest.cc +++ b/ui/views/layout/box_layout_unittest.cc @@ -162,7 +162,8 @@ TEST_F(BoxLayoutTest, UseHeightForWidth) { layout_.reset(new BoxLayout(BoxLayout::kVertical, 0, 0, 0)); View* v1 = new StaticSizedView(gfx::Size(20, 10)); host_->AddChildView(v1); - View* v2 = new ProportionallySizedView(2); + ProportionallySizedView* v2 = new ProportionallySizedView(2); + v2->set_preferred_width(10); host_->AddChildView(v2); EXPECT_EQ(gfx::Size(20, 50), layout_->GetPreferredSize(host_.get())); @@ -172,6 +173,18 @@ TEST_F(BoxLayoutTest, UseHeightForWidth) { EXPECT_EQ(gfx::Rect(0, 10, 20, 40), v2->bounds()); EXPECT_EQ(110, layout_->GetPreferredHeightForWidth(host_.get(), 50)); + + // Test without horizontal stretching of the views. + layout_->set_cross_axis_alignment(BoxLayout::CROSS_AXIS_ALIGNMENT_END); + EXPECT_EQ(gfx::Size(20, 30).ToString(), + layout_->GetPreferredSize(host_.get()).ToString()); + + host_->SetBounds(0, 0, 20, 30); + layout_->Layout(host_.get()); + EXPECT_EQ(gfx::Rect(0, 0, 20, 10), v1->bounds()); + EXPECT_EQ(gfx::Rect(10, 10, 10, 20), v2->bounds()); + + EXPECT_EQ(30, layout_->GetPreferredHeightForWidth(host_.get(), 50)); } TEST_F(BoxLayoutTest, EmptyPreferredSize) { @@ -266,4 +279,86 @@ TEST_F(BoxLayoutTest, MainAxisAlignmentVertical) { EXPECT_EQ(gfx::Rect(10, 80, 20, 10).ToString(), v2->bounds().ToString()); } +TEST_F(BoxLayoutTest, CrossAxisAlignmentHorizontal) { + layout_.reset(new BoxLayout(BoxLayout::kHorizontal, 10, 10, 10)); + + View* v1 = new StaticSizedView(gfx::Size(20, 20)); + host_->AddChildView(v1); + View* v2 = new StaticSizedView(gfx::Size(10, 10)); + host_->AddChildView(v2); + + host_->SetBounds(0, 0, 100, 60); + + // Stretch children to fill the available height by default. + layout_->Layout(host_.get()); + EXPECT_EQ(gfx::Rect(10, 10, 20, 40).ToString(), v1->bounds().ToString()); + EXPECT_EQ(gfx::Rect(40, 10, 10, 40).ToString(), v2->bounds().ToString()); + + // Ensure same results for CROSS_AXIS_ALIGNMENT_STRETCH. + layout_->set_cross_axis_alignment(BoxLayout::CROSS_AXIS_ALIGNMENT_STRETCH); + layout_->Layout(host_.get()); + EXPECT_EQ(gfx::Rect(10, 10, 20, 40).ToString(), v1->bounds().ToString()); + EXPECT_EQ(gfx::Rect(40, 10, 10, 40).ToString(), v2->bounds().ToString()); + + // Aligns children to the start vertically. + layout_->set_cross_axis_alignment(BoxLayout::CROSS_AXIS_ALIGNMENT_START); + layout_->Layout(host_.get()); + EXPECT_EQ(gfx::Rect(10, 10, 20, 20).ToString(), v1->bounds().ToString()); + EXPECT_EQ(gfx::Rect(40, 10, 10, 10).ToString(), v2->bounds().ToString()); + + // Aligns children to the center vertically. + layout_->set_cross_axis_alignment(BoxLayout::CROSS_AXIS_ALIGNMENT_CENTER); + layout_->Layout(host_.get()); + EXPECT_EQ(gfx::Rect(10, 20, 20, 20).ToString(), v1->bounds().ToString()); + EXPECT_EQ(gfx::Rect(40, 25, 10, 10).ToString(), v2->bounds().ToString()); + + // Aligns children to the end of the host vertically, accounting for the + // inside border spacing. + layout_->set_cross_axis_alignment(BoxLayout::CROSS_AXIS_ALIGNMENT_END); + layout_->Layout(host_.get()); + EXPECT_EQ(gfx::Rect(10, 30, 20, 20).ToString(), v1->bounds().ToString()); + EXPECT_EQ(gfx::Rect(40, 40, 10, 10).ToString(), v2->bounds().ToString()); +} + +TEST_F(BoxLayoutTest, CrossAxisAlignmentVertical) { + layout_.reset(new BoxLayout(BoxLayout::kVertical, 10, 10, 10)); + + View* v1 = new StaticSizedView(gfx::Size(20, 20)); + host_->AddChildView(v1); + View* v2 = new StaticSizedView(gfx::Size(10, 10)); + host_->AddChildView(v2); + + host_->SetBounds(0, 0, 60, 100); + + // Stretch children to fill the available width by default. + layout_->Layout(host_.get()); + EXPECT_EQ(gfx::Rect(10, 10, 40, 20).ToString(), v1->bounds().ToString()); + EXPECT_EQ(gfx::Rect(10, 40, 40, 10).ToString(), v2->bounds().ToString()); + + // Ensure same results for CROSS_AXIS_ALIGNMENT_STRETCH. + layout_->set_cross_axis_alignment(BoxLayout::CROSS_AXIS_ALIGNMENT_STRETCH); + layout_->Layout(host_.get()); + EXPECT_EQ(gfx::Rect(10, 10, 40, 20).ToString(), v1->bounds().ToString()); + EXPECT_EQ(gfx::Rect(10, 40, 40, 10).ToString(), v2->bounds().ToString()); + + // Aligns children to the start horizontally. + layout_->set_cross_axis_alignment(BoxLayout::CROSS_AXIS_ALIGNMENT_START); + layout_->Layout(host_.get()); + EXPECT_EQ(gfx::Rect(10, 10, 20, 20).ToString(), v1->bounds().ToString()); + EXPECT_EQ(gfx::Rect(10, 40, 10, 10).ToString(), v2->bounds().ToString()); + + // Aligns children to the center horizontally. + layout_->set_cross_axis_alignment(BoxLayout::CROSS_AXIS_ALIGNMENT_CENTER); + layout_->Layout(host_.get()); + EXPECT_EQ(gfx::Rect(20, 10, 20, 20).ToString(), v1->bounds().ToString()); + EXPECT_EQ(gfx::Rect(25, 40, 10, 10).ToString(), v2->bounds().ToString()); + + // Aligns children to the end of the host horizontally, accounting for the + // inside border spacing. + layout_->set_cross_axis_alignment(BoxLayout::CROSS_AXIS_ALIGNMENT_END); + layout_->Layout(host_.get()); + EXPECT_EQ(gfx::Rect(30, 10, 20, 20).ToString(), v1->bounds().ToString()); + EXPECT_EQ(gfx::Rect(40, 40, 10, 10).ToString(), v2->bounds().ToString()); +} + } // namespace views diff --git a/ui/views/test/test_views.cc b/ui/views/test/test_views.cc index 0c391b0..320ec8e 100644 --- a/ui/views/test/test_views.cc +++ b/ui/views/test/test_views.cc @@ -15,7 +15,7 @@ gfx::Size StaticSizedView::GetPreferredSize() const { } ProportionallySizedView::ProportionallySizedView(int factor) - : factor_(factor) {} + : factor_(factor), preferred_width_(-1) {} ProportionallySizedView::~ProportionallySizedView() {} @@ -23,4 +23,10 @@ int ProportionallySizedView::GetHeightForWidth(int w) const { return w * factor_; } +gfx::Size ProportionallySizedView::GetPreferredSize() const { + if (preferred_width_ >= 0) + return gfx::Size(preferred_width_, GetHeightForWidth(preferred_width_)); + return View::GetPreferredSize(); +} + } // namespace views diff --git a/ui/views/test/test_views.h b/ui/views/test/test_views.h index 7d0cabf..be6f778 100644 --- a/ui/views/test/test_views.h +++ b/ui/views/test/test_views.h @@ -5,6 +5,7 @@ #ifndef UI_VIEWS_TEST_TEST_VIEWS_H_ #define UI_VIEWS_TEST_TEST_VIEWS_H_ +#include "base/memory/scoped_ptr.h" #include "ui/views/view.h" namespace views { @@ -29,13 +30,19 @@ class ProportionallySizedView : public View { explicit ProportionallySizedView(int factor); virtual ~ProportionallySizedView(); + void set_preferred_width(int width) { preferred_width_ = width; } + virtual int GetHeightForWidth(int w) const OVERRIDE; + virtual gfx::Size GetPreferredSize() const OVERRIDE; private: // The multiplicative factor between width and height, i.e. // height = width * factor_. int factor_; + // The width used as the preferred size. -1 if not used. + int preferred_width_; + DISALLOW_COPY_AND_ASSIGN(ProportionallySizedView); }; |