diff options
author | alekseys@chromium.org <alekseys@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-01-12 20:57:44 +0000 |
---|---|---|
committer | alekseys@chromium.org <alekseys@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-01-12 20:57:44 +0000 |
commit | 84f024c021c2a8e009026516483897546faf86fa (patch) | |
tree | af3d146edbd064dbdfedaad04041231559d7abd0 /views | |
parent | c8fd6c617808b668f28962dc8c139d878c861e07 (diff) | |
download | chromium_src-84f024c021c2a8e009026516483897546faf86fa.zip chromium_src-84f024c021c2a8e009026516483897546faf86fa.tar.gz chromium_src-84f024c021c2a8e009026516483897546faf86fa.tar.bz2 |
Streamline the layout of the BrowserView's children TabContents views.
Modify SingleSplitView to calculate its children view's bounds, but do not actually resize them
and change BrowserViewLayout accordingly (BrowserViewLayout resizes all views now).
Do all reserved contents rect calculations before resizing TabContents views.
Rationale: to do all BrowserView layout related actions in the context of
BrowserViewLayout::Layout call and to minimize actual contents re-layouts.
BUG=51084
TEST=All tests should pass
Review URL: http://codereview.chromium.org/5606012
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@71230 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'views')
-rw-r--r-- | views/controls/single_split_view.cc | 149 | ||||
-rw-r--r-- | views/controls/single_split_view.h | 58 | ||||
-rw-r--r-- | views/controls/single_split_view_unittest.cc | 176 | ||||
-rw-r--r-- | views/examples/single_split_view_example.h | 3 | ||||
-rw-r--r-- | views/views.gyp | 1 |
5 files changed, 328 insertions, 59 deletions
diff --git a/views/controls/single_split_view.cc b/views/controls/single_split_view.cc index f997f31..7248200 100644 --- a/views/controls/single_split_view.cc +++ b/views/controls/single_split_view.cc @@ -23,10 +23,12 @@ static const int kDividerSize = 4; SingleSplitView::SingleSplitView(View* leading, View* trailing, - Orientation orientation) + Orientation orientation, + Observer* observer) : is_horizontal_(orientation == HORIZONTAL_SPLIT), divider_offset_(-1), - resize_leading_on_bounds_change_(true) { + resize_leading_on_bounds_change_(true), + observer_(observer) { AddChildView(leading); AddChildView(trailing); #if defined(OS_WIN) @@ -38,57 +40,21 @@ SingleSplitView::SingleSplitView(View* leading, void SingleSplitView::DidChangeBounds(const gfx::Rect& previous, const gfx::Rect& current) { - if (resize_leading_on_bounds_change_) { - // We do not update divider_offset_ on minimize (to zero) and on restore - // (to largest value). As a result we get back to the original value upon - // window restore. - bool is_minimize_or_restore = - previous.height() == 0 || current.height() == 0; - if (!is_minimize_or_restore) { - if (is_horizontal_) - divider_offset_ += current.width() - previous.width(); - else - divider_offset_ += current.height() - previous.height(); - - if (divider_offset_ < 0) - divider_offset_ = kDividerSize; - } - } + divider_offset_ = CalculateDividerOffset(divider_offset_, previous, current); View::DidChangeBounds(previous, current); } void SingleSplitView::Layout() { - if (GetChildViewCount() != 2) - return; - - View* leading = GetChildViewAt(0); - View* trailing = GetChildViewAt(1); - - if (!leading->IsVisible() && !trailing->IsVisible()) - return; - - if (width() == 0 || height() == 0) { - // We are most likely minimized - do not touch divider offset. - return; - } else if (!trailing->IsVisible()) { - leading->SetBounds(0, 0, width(), height()); - } else if (!leading->IsVisible()) { - trailing->SetBounds(0, 0, width(), height()); - } else { - if (divider_offset_ < 0) - divider_offset_ = (GetPrimaryAxisSize() - kDividerSize) / 2; - else - divider_offset_ = std::min(divider_offset_, - GetPrimaryAxisSize() - kDividerSize); - - if (is_horizontal_) { - leading->SetBounds(0, 0, divider_offset_, height()); - trailing->SetBounds(divider_offset_ + kDividerSize, 0, - width() - divider_offset_ - kDividerSize, height()); - } else { - leading->SetBounds(0, 0, width(), divider_offset_); - trailing->SetBounds(0, divider_offset_ + kDividerSize, - width(), height() - divider_offset_ - kDividerSize); + gfx::Rect leading_bounds; + gfx::Rect trailing_bounds; + CalculateChildrenBounds(bounds(), &leading_bounds, &trailing_bounds); + + if (GetChildViewCount() > 0) { + if (GetChildViewAt(0)->IsVisible()) + GetChildViewAt(0)->SetBounds(leading_bounds); + if (GetChildViewCount() > 1) { + if (GetChildViewAt(1)->IsVisible()) + GetChildViewAt(1)->SetBounds(trailing_bounds); } } @@ -140,11 +106,56 @@ gfx::NativeCursor SingleSplitView::GetCursorForPoint( return NULL; } +void SingleSplitView::CalculateChildrenBounds( + const gfx::Rect& bounds, + gfx::Rect* leading_bounds, + gfx::Rect* trailing_bounds) const { + bool is_leading_visible = + GetChildViewCount() > 0 && GetChildViewAt(0)->IsVisible(); + bool is_trailing_visible = + GetChildViewCount() > 1 && GetChildViewAt(1)->IsVisible(); + + if (!is_leading_visible && !is_trailing_visible) { + *leading_bounds = gfx::Rect(); + *trailing_bounds = gfx::Rect(); + return; + } + + int divider_at; + + if (!is_trailing_visible) { + divider_at = GetPrimaryAxisSize(bounds.width(), bounds.height()); + } else if (!is_leading_visible) { + divider_at = 0; + } else { + divider_at = + CalculateDividerOffset(divider_offset_, this->bounds(), bounds); + divider_at = NormalizeDividerOffset(divider_at, bounds); + } + + int divider_size = + !is_leading_visible || !is_trailing_visible ? 0 : kDividerSize; + + if (is_horizontal_) { + *leading_bounds = gfx::Rect(0, 0, divider_at, bounds.height()); + *trailing_bounds = + gfx::Rect(divider_at + divider_size, 0, + std::max(0, bounds.width() - divider_at - divider_size), + bounds.height()); + } else { + *leading_bounds = gfx::Rect(0, 0, bounds.width(), divider_at); + *trailing_bounds = + gfx::Rect(0, divider_at + divider_size, bounds.width(), + std::max(0, bounds.height() - divider_at - divider_size)); + } +} + bool SingleSplitView::OnMousePressed(const MouseEvent& event) { if (!IsPointInDivider(event.location())) return false; drag_info_.initial_mouse_offset = GetPrimaryAxisSize(event.x(), event.y()); - drag_info_.initial_divider_offset = divider_offset_; + drag_info_.initial_divider_offset = + NormalizeDividerOffset(divider_offset_, bounds()); return true; } @@ -166,7 +177,8 @@ bool SingleSplitView::OnMouseDragged(const MouseEvent& event) { if (new_size != divider_offset_) { set_divider_offset(new_size); - Layout(); + if (!observer_ || observer_->SplitHandleMoved(this)) + Layout(); } return true; } @@ -177,7 +189,8 @@ void SingleSplitView::OnMouseReleased(const MouseEvent& event, bool canceled) { if (canceled && drag_info_.initial_divider_offset != divider_offset_) { set_divider_offset(drag_info_.initial_divider_offset); - Layout(); + if (!observer_ || observer_->SplitHandleMoved(this)) + Layout(); } } @@ -199,4 +212,36 @@ bool SingleSplitView::IsPointInDivider(const gfx::Point& p) { divider_relative_offset < kDividerSize); } +int SingleSplitView::CalculateDividerOffset( + int divider_offset, + const gfx::Rect& previous_bounds, + const gfx::Rect& new_bounds) const { + if (resize_leading_on_bounds_change_ && divider_offset != -1) { + // We do not update divider_offset on minimize (to zero) and on restore + // (to largest value). As a result we get back to the original value upon + // window restore. + bool is_minimize_or_restore = + previous_bounds.height() == 0 || new_bounds.height() == 0; + if (!is_minimize_or_restore) { + if (is_horizontal_) + divider_offset += new_bounds.width() - previous_bounds.width(); + else + divider_offset += new_bounds.height() - previous_bounds.height(); + + if (divider_offset < 0) + divider_offset = kDividerSize; + } + } + return divider_offset; +} + +int SingleSplitView::NormalizeDividerOffset(int divider_offset, + const gfx::Rect& bounds) const { + int primary_axis_size = GetPrimaryAxisSize(bounds.width(), bounds.height()); + if (divider_offset < 0) + return (primary_axis_size - kDividerSize) / 2; + return std::min(divider_offset, + std::max(primary_axis_size - kDividerSize, 0)); +} + } // namespace views diff --git a/views/controls/single_split_view.h b/views/controls/single_split_view.h index c3814b1..a1b0534 100644 --- a/views/controls/single_split_view.h +++ b/views/controls/single_split_view.h @@ -6,12 +6,16 @@ #define VIEWS_CONTROLS_SINGLE_SPLIT_VIEW_H_ #pragma once +#include "base/gtest_prod_util.h" #include "views/view.h" namespace views { -// SingleSplitView lays out two views horizontally. A splitter exists between -// the two views that the user can drag around to resize the views. +// SingleSplitView lays out two views next to each other, either horizontally +// or vertically. A splitter exists between the two views that the user can +// drag around to resize the views. +// Observer's SplitHandleMoved notification helps to monitor user initiated +// layout changes. class SingleSplitView : public views::View { public: enum Orientation { @@ -19,7 +23,21 @@ class SingleSplitView : public views::View { VERTICAL_SPLIT }; - SingleSplitView(View* leading, View* trailing, Orientation orientation); + class Observer { + public: + // Invoked when split handle is moved by the user. |source|'s divider_offset + // is already set to the new value, but Layout has not happened yet. + // Returns false if the layout has been handled by the observer, returns + // true if |source| should do it by itself. + virtual bool SplitHandleMoved(SingleSplitView* source) = 0; + protected: + virtual ~Observer() {} + }; + + SingleSplitView(View* leading, + View* trailing, + Orientation orientation, + Observer* observer); virtual void DidChangeBounds(const gfx::Rect& previous, const gfx::Rect& current); @@ -36,10 +54,14 @@ class SingleSplitView : public views::View { virtual gfx::NativeCursor GetCursorForPoint(Event::EventType event_type, const gfx::Point& p); + Orientation orientation() const { + return is_horizontal_ ? HORIZONTAL_SPLIT : VERTICAL_SPLIT; + } + void set_divider_offset(int divider_offset) { divider_offset_ = divider_offset; } - int divider_offset() { return divider_offset_; } + int divider_offset() const { return divider_offset_; } // Sets whether the leading component is resized when the split views size // changes. The default is true. A value of false results in the trailing @@ -48,21 +70,42 @@ class SingleSplitView : public views::View { resize_leading_on_bounds_change_ = resize; } + // Calculates ideal leading and trailing view bounds according to the given + // split view |bounds|, current divider offset and children visiblity. + // Does not change children view bounds. + void CalculateChildrenBounds(const gfx::Rect& bounds, + gfx::Rect* leading_bounds, + gfx::Rect* trailing_bounds) const; + protected: virtual bool OnMousePressed(const MouseEvent& event); virtual bool OnMouseDragged(const MouseEvent& event); virtual void OnMouseReleased(const MouseEvent& event, bool canceled); private: + // This test calls OnMouse* functions. + FRIEND_TEST_ALL_PREFIXES(SingleSplitViewTest, MouseDrag); + // Returns true if |x| or |y| is over the divider. bool IsPointInDivider(const gfx::Point& p); + // Calculates the new |divider_offset| based on the changes of split view + // bounds. + int CalculateDividerOffset( + int divider_offset, + const gfx::Rect& previous_bounds, + const gfx::Rect& new_bounds) const; + + // Returns divider offset within primary axis size range for given split + // view |bounds|. + int NormalizeDividerOffset(int divider_offset, const gfx::Rect& bounds) const; + // Returns width in case of horizontal split and height otherwise. - int GetPrimaryAxisSize() { + int GetPrimaryAxisSize() const { return GetPrimaryAxisSize(width(), height()); } - int GetPrimaryAxisSize(int h, int v) { + int GetPrimaryAxisSize(int h, int v) const { return is_horizontal_ ? h : v; } @@ -84,6 +127,9 @@ class SingleSplitView : public views::View { bool resize_leading_on_bounds_change_; + // Observer to notify about user initiated handle movements. Not own by us. + Observer* observer_; + DISALLOW_COPY_AND_ASSIGN(SingleSplitView); }; diff --git a/views/controls/single_split_view_unittest.cc b/views/controls/single_split_view_unittest.cc new file mode 100644 index 0000000..b788a37 --- /dev/null +++ b/views/controls/single_split_view_unittest.cc @@ -0,0 +1,176 @@ +// Copyright (c) 2010 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 "base/logging.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "views/controls/single_split_view.h" + +using ::testing::_; +using ::testing::Return; + +namespace { + +static void VerifySplitViewLayout(const views::SingleSplitView& split) { + ASSERT_EQ(2, split.GetChildViewCount()); + + views::View* leading = split.GetChildViewAt(0); + views::View* trailing = split.GetChildViewAt(1); + + if (split.bounds().IsEmpty()) { + EXPECT_TRUE(leading->bounds().IsEmpty()); + EXPECT_TRUE(trailing->bounds().IsEmpty()); + return; + } + + EXPECT_FALSE(leading->bounds().IsEmpty()); + EXPECT_FALSE(trailing->bounds().IsEmpty()); + EXPECT_FALSE(leading->bounds().Intersects(trailing->bounds())); + + if (split.orientation() == views::SingleSplitView::HORIZONTAL_SPLIT) { + EXPECT_EQ(leading->bounds().height(), split.bounds().height()); + EXPECT_EQ(trailing->bounds().height(), split.bounds().height()); + EXPECT_LT(leading->bounds().width() + trailing->bounds().width(), + split.bounds().width()); + } else if (split.orientation() == views::SingleSplitView::VERTICAL_SPLIT) { + EXPECT_EQ(leading->bounds().width(), split.bounds().width()); + EXPECT_EQ(trailing->bounds().width(), split.bounds().width()); + EXPECT_LT(leading->bounds().height() + trailing->bounds().height(), + split.bounds().height()); + } else { + NOTREACHED(); + } +} + +class MockObserver : public views::SingleSplitView::Observer { + public: + MOCK_METHOD1(SplitHandleMoved, bool(views::SingleSplitView*)); +}; + +} // namespace + +namespace views { + +TEST(SingleSplitViewTest, Resize) { + // Test cases to iterate through for horizontal and vertical split views. + struct TestCase { + // Split view resize policy for this test case. + bool resize_leading_on_bounds_change; + // Split view size to set. + int primary_axis_size; + int secondary_axis_size; + // Expected divider offset. + int divider_offset; + } test_cases[] = { + // The initial split size is 100x100, divider at 33. + { true, 100, 100, 33 }, + // Grow the split view, leading view should grow. + { true, 1000, 100, 933 }, + // Shrink the split view, leading view should shrink. + { true, 200, 100, 133 }, + // Minimize the split view, divider should not move. + { true, 0, 0, 133 }, + // Restore the split view, divider should not move. + { false, 500, 100, 133 }, + // Resize the split view by secondary axis, divider should not move. + { false, 500, 600, 133 } + }; + + SingleSplitView::Orientation orientations[] = { + SingleSplitView::HORIZONTAL_SPLIT, + SingleSplitView::VERTICAL_SPLIT + }; + + for (size_t orientation = 0; orientation < arraysize(orientations); + ++orientation) { + // Create a split view. + SingleSplitView split( + new View(), new View(), orientations[orientation], NULL); + + // Set initial size and divider offset. + EXPECT_EQ(test_cases[0].primary_axis_size, + test_cases[0].secondary_axis_size); + split.SetBounds(0, 0, test_cases[0].primary_axis_size, + test_cases[0].secondary_axis_size); + split.set_divider_offset(test_cases[0].divider_offset); + split.Layout(); + + // Run all test cases. + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) { + split.set_resize_leading_on_bounds_change( + test_cases[i].resize_leading_on_bounds_change); + if (split.orientation() == SingleSplitView::HORIZONTAL_SPLIT) { + split.SetBounds(0, 0, test_cases[i].primary_axis_size, + test_cases[i].secondary_axis_size); + } else { + split.SetBounds(0, 0, test_cases[i].secondary_axis_size, + test_cases[i].primary_axis_size); + } + + EXPECT_EQ(test_cases[i].divider_offset, split.divider_offset()); + VerifySplitViewLayout(split); + } + + // Special cases, one of the child views is hidden. + split.GetChildViewAt(0)->SetVisible(false); + split.Layout(); + + EXPECT_EQ(split.bounds().size(), + split.GetChildViewAt(1)->bounds().size()); + + split.GetChildViewAt(0)->SetVisible(true); + split.GetChildViewAt(1)->SetVisible(false); + split.Layout(); + + EXPECT_EQ(split.bounds().size(), + split.GetChildViewAt(0)->bounds().size()); + } +} + +TEST(SingleSplitViewTest, MouseDrag) { + MockObserver observer; + SingleSplitView split( + new View(), new View(), SingleSplitView::VERTICAL_SPLIT, &observer); + + ON_CALL(observer, SplitHandleMoved(_)) + .WillByDefault(Return(true)); + // SplitHandleMoved is expected to be called once for every mouse move. + EXPECT_CALL(observer, SplitHandleMoved(_)) + .Times(2); + + split.SetBounds(0, 0, 10, 100); + const int kInitialDividerOffset = 33; + const int kMouseOffset = 2; // Mouse offset in the divider. + const int kMouseMoveDelta = 7; + split.set_divider_offset(kInitialDividerOffset); + split.Layout(); + + // Drag divider to the right, in 2 steps. + MouseEvent mouse_pressed( + Event::ET_MOUSE_PRESSED, 7, kInitialDividerOffset + kMouseOffset, 0); + ASSERT_TRUE(split.OnMousePressed(mouse_pressed)); + EXPECT_EQ(kInitialDividerOffset, split.divider_offset()); + + MouseEvent mouse_dragged_1( + Event::ET_MOUSE_DRAGGED, 5, + kInitialDividerOffset + kMouseOffset + kMouseMoveDelta, 0); + ASSERT_TRUE(split.OnMouseDragged(mouse_dragged_1)); + EXPECT_EQ(kInitialDividerOffset + kMouseMoveDelta, split.divider_offset()); + + MouseEvent mouse_dragged_2( + Event::ET_MOUSE_DRAGGED, 6, + kInitialDividerOffset + kMouseOffset + kMouseMoveDelta * 2, 0); + ASSERT_TRUE(split.OnMouseDragged(mouse_dragged_2)); + EXPECT_EQ(kInitialDividerOffset + kMouseMoveDelta * 2, + split.divider_offset()); + + MouseEvent mouse_released( + Event::ET_MOUSE_RELEASED, 7, + kInitialDividerOffset + kMouseOffset + kMouseMoveDelta * 2, 0); + split.OnMouseReleased(mouse_released, false); + EXPECT_EQ(kInitialDividerOffset + kMouseMoveDelta * 2, + split.divider_offset()); +} + +} // namespace views diff --git a/views/examples/single_split_view_example.h b/views/examples/single_split_view_example.h index 3eaea7c..73d69a6 100644 --- a/views/examples/single_split_view_example.h +++ b/views/examples/single_split_view_example.h @@ -29,7 +29,8 @@ class SingleSplitViewExample : public ExampleBase { single_split_view_ = new views::SingleSplitView( splitted_view_1, splitted_view_2, - views::SingleSplitView::HORIZONTAL_SPLIT); + views::SingleSplitView::HORIZONTAL_SPLIT, + NULL); splitted_view_1->SetColor(SK_ColorYELLOW, SK_ColorCYAN); diff --git a/views/views.gyp b/views/views.gyp index b8d75f9..191fc7d 100644 --- a/views/views.gyp +++ b/views/views.gyp @@ -428,6 +428,7 @@ 'box_layout_unittest.cc', 'controls/label_unittest.cc', 'controls/progress_bar_unittest.cc', + 'controls/single_split_view_unittest.cc', 'controls/tabbed_pane/tabbed_pane_unittest.cc', 'controls/table/table_view_unittest.cc', 'controls/textfield/native_textfield_views_unittest.cc', |