diff options
-rw-r--r-- | chrome/browser/ui/views/frame/browser_view.cc | 70 | ||||
-rw-r--r-- | chrome/browser/ui/views/frame/browser_view.h | 12 | ||||
-rw-r--r-- | chrome/browser/ui/views/frame/browser_view_layout.cc | 113 | ||||
-rw-r--r-- | chrome/browser/ui/views/frame/browser_view_layout.h | 16 | ||||
-rw-r--r-- | chrome/browser/ui/views/tab_contents/tab_contents_container.cc | 40 | ||||
-rw-r--r-- | chrome/browser/ui/views/tab_contents/tab_contents_container.h | 28 | ||||
-rw-r--r-- | chrome/chrome_tests.gypi | 1 | ||||
-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 |
12 files changed, 510 insertions, 157 deletions
diff --git a/chrome/browser/ui/views/frame/browser_view.cc b/chrome/browser/ui/views/frame/browser_view.cc index 1993374..a52745b 100644 --- a/chrome/browser/ui/views/frame/browser_view.cc +++ b/chrome/browser/ui/views/frame/browser_view.cc @@ -1348,10 +1348,8 @@ void BrowserView::PrepareForInstant() { } void BrowserView::ShowInstant(TabContents* preview_contents) { - if (!preview_container_) { + if (!preview_container_) preview_container_ = new TabContentsContainer(); - preview_container_->set_reserved_area_delegate(this); - } contents_->SetPreview(preview_container_, preview_contents); preview_container_->ChangeTabContents(preview_contents); @@ -1825,41 +1823,12 @@ void BrowserView::InfoBarSizeChanged(bool is_animating) { SelectedTabToolbarSizeChanged(is_animating); } -void BrowserView::UpdateReservedContentsRect( - const TabContentsContainer* source) { - RenderWidgetHostView* render_widget_host_view = - source->tab_contents() ? source->tab_contents()->GetRenderWidgetHostView() - : NULL; - if (!render_widget_host_view) - return; - - gfx::Rect reserved_rect; - - if (!frame_->GetWindow()->IsMaximized() && - !frame_->GetWindow()->IsFullscreen()) { - gfx::Size resize_corner_size = ResizeCorner::GetSize(); - if (!resize_corner_size.IsEmpty()) { - gfx::Point resize_corner_origin; - gfx::Rect bounds = GetLocalBounds(false); - resize_corner_origin.set_x(bounds.right() - resize_corner_size.width()); - resize_corner_origin.set_y(bounds.bottom() - resize_corner_size.height()); - - View::ConvertPointToView(this, source, &resize_corner_origin); - - gfx::Size container_size = source->size(); - - if (resize_corner_origin.x() < container_size.width() && - resize_corner_origin.y() < container_size.height()) { - reserved_rect = gfx::Rect(resize_corner_origin, resize_corner_size); - } - } - } - - // TODO(alekseys): for source == contents_container_, consult SidebarTabView - // for the current size to reserve. Something like this: - // reserved_rect = reserved_rect.Union(SidebarTabView::GetCurrentBounds()); - - render_widget_host_view->set_reserved_contents_rect(reserved_rect); +bool BrowserView::SplitHandleMoved(views::SingleSplitView* view) { + for (int i = 0; i < view->GetChildViewCount(); ++i) + view->GetChildViewAt(i)->InvalidateLayout(); + SchedulePaint(); + Layout(); + return false; } views::LayoutManager* BrowserView::CreateLayoutManager() const { @@ -1922,7 +1891,6 @@ void BrowserView::Init() { AddChildView(infobar_container_); contents_container_ = new TabContentsContainer; - contents_container_->set_reserved_area_delegate(this); contents_ = new ContentsContainer(contents_container_); SkColor bg_color = GetWidget()->GetThemeProvider()-> @@ -1931,14 +1899,14 @@ void BrowserView::Init() { bool sidebar_allowed = SidebarManager::IsSidebarAllowed(); if (sidebar_allowed) { sidebar_container_ = new TabContentsContainer; - sidebar_container_->set_reserved_area_delegate(this); sidebar_container_->SetID(VIEW_ID_SIDE_BAR_CONTAINER); sidebar_container_->SetVisible(false); sidebar_split_ = new views::SingleSplitView( contents_, sidebar_container_, - views::SingleSplitView::HORIZONTAL_SPLIT); + views::SingleSplitView::HORIZONTAL_SPLIT, + this); sidebar_split_->SetID(VIEW_ID_SIDE_BAR_SPLIT); sidebar_split_->SetAccessibleName( UTF16ToWide(l10n_util::GetStringUTF16(IDS_ACCNAME_SIDE_BAR))); @@ -1947,7 +1915,6 @@ void BrowserView::Init() { } devtools_container_ = new TabContentsContainer; - devtools_container_->set_reserved_area_delegate(this); devtools_container_->SetID(VIEW_ID_DEV_TOOLS_DOCKED); devtools_container_->SetVisible(false); @@ -1958,7 +1925,8 @@ void BrowserView::Init() { contents_split_ = new views::SingleSplitView( contents_view, devtools_container_, - views::SingleSplitView::VERTICAL_SPLIT); + views::SingleSplitView::VERTICAL_SPLIT, + this); contents_split_->SetID(VIEW_ID_CONTENTS_SPLIT); contents_split_->SetAccessibleName( UTF16ToWide(l10n_util::GetStringUTF16(IDS_ACCNAME_WEB_CONTENTS))); @@ -2102,7 +2070,8 @@ void BrowserView::UpdateSidebarForContents(TabContentsWrapper* tab_contents) { sidebar_split_->width() - sidebar_width); sidebar_container_->SetVisible(true); - sidebar_split_->Layout(); + sidebar_split_->InvalidateLayout(); + Layout(); } else if (should_hide) { // Store split offset when hiding sidebar only. g_browser_process->local_state()->SetInteger( @@ -2110,7 +2079,8 @@ void BrowserView::UpdateSidebarForContents(TabContentsWrapper* tab_contents) { sidebar_split_->width() - sidebar_split_->divider_offset()); sidebar_container_->SetVisible(false); - sidebar_split_->Layout(); + sidebar_split_->InvalidateLayout(); + Layout(); } } @@ -2147,7 +2117,8 @@ void BrowserView::UpdateDevToolsForContents(TabContentsWrapper* wrapper) { contents_split_->set_divider_offset(split_offset); devtools_container_->SetVisible(true); - contents_split_->Layout(); + contents_split_->InvalidateLayout(); + Layout(); } else if (should_hide) { // Store split offset when hiding devtools window only. g_browser_process->local_state()->SetInteger( @@ -2157,7 +2128,8 @@ void BrowserView::UpdateDevToolsForContents(TabContentsWrapper* wrapper) { devtools_focus_tracker_->FocusLastFocusedExternalView(); devtools_container_->SetVisible(false); - contents_split_->Layout(); + contents_split_->InvalidateLayout(); + Layout(); } } @@ -2567,6 +2539,10 @@ void BrowserView::ProcessTabSelected(TabContentsWrapper* new_contents, UpdateUIForContents(new_contents); } +gfx::Size BrowserView::GetResizeCornerSize() const { + return ResizeCorner::GetSize(); +} + #if !defined(OS_CHROMEOS) // static BrowserWindow* BrowserWindow::CreateBrowserWindow(Browser* browser) { diff --git a/chrome/browser/ui/views/frame/browser_view.h b/chrome/browser/ui/views/frame/browser_view.h index 79b0859..1825d64a 100644 --- a/chrome/browser/ui/views/frame/browser_view.h +++ b/chrome/browser/ui/views/frame/browser_view.h @@ -26,6 +26,7 @@ #include "chrome/browser/ui/views/unhandled_keyboard_event_handler.h" #include "chrome/common/notification_registrar.h" #include "gfx/native_widget_types.h" +#include "views/controls/single_split_view.h" #include "views/window/client_view.h" #include "views/window/window_delegate.h" @@ -53,6 +54,7 @@ class InfoBarContainer; class LocationBarView; class SideTabStrip; class StatusBubbleViews; +class TabContentsContainer; class TabStripModel; class ToolbarView; class ZoomMenuModel; @@ -66,7 +68,6 @@ class JumpList; namespace views { class ExternalFocusTracker; class Menu; -class SingleSplitView; } /////////////////////////////////////////////////////////////////////////////// @@ -84,7 +85,7 @@ class BrowserView : public BrowserBubbleHost, public views::WindowDelegate, public views::ClientView, public InfoBarContainer::Delegate, - public TabContentsContainer::ReservedAreaDelegate { + public views::SingleSplitView::Observer { public: // The browser view's class name. static const char kViewClassName[]; @@ -396,8 +397,8 @@ class BrowserView : public BrowserBubbleHost, // InfoBarContainer::Delegate overrides virtual void InfoBarSizeChanged(bool is_animating); - // TabContentsContainer::ReservedAreaDelegate overrides. - virtual void UpdateReservedContentsRect(const TabContentsContainer* source); + // views::SingleSplitView::Observer overrides: + virtual bool SplitHandleMoved(views::SingleSplitView* view); protected: // Appends to |toolbars| a pointer to each AccessiblePaneView that @@ -522,6 +523,9 @@ class BrowserView : public BrowserBubbleHost, void ProcessTabSelected(TabContentsWrapper* new_contents, bool change_tab_contents); + // Exposes resize corner size to BrowserViewLayout. + gfx::Size GetResizeCornerSize() const; + // Last focused view that issued a tab traversal. int last_focused_view_storage_id_; diff --git a/chrome/browser/ui/views/frame/browser_view_layout.cc b/chrome/browser/ui/views/frame/browser_view_layout.cc index 34549bd..6dc8cd0 100644 --- a/chrome/browser/ui/views/frame/browser_view_layout.cc +++ b/chrome/browser/ui/views/frame/browser_view_layout.cc @@ -13,10 +13,12 @@ #include "chrome/browser/ui/views/frame/browser_frame.h" #include "chrome/browser/ui/views/frame/browser_view.h" #include "chrome/browser/ui/views/frame/contents_container.h" +#include "chrome/browser/ui/views/tab_contents/tab_contents_container.h" #include "chrome/browser/ui/views/tabs/side_tab_strip.h" #include "chrome/browser/ui/views/tabs/tab_strip.h" #include "chrome/browser/ui/views/toolbar_view.h" #include "gfx/scrollbar_size.h" +#include "views/controls/single_split_view.h" #include "views/window/window.h" #if defined(OS_LINUX) @@ -207,9 +209,9 @@ void BrowserViewLayout::Uninstalled(views::View* host) {} void BrowserViewLayout::ViewAdded(views::View* host, views::View* view) { switch (view->GetID()) { case VIEW_ID_CONTENTS_SPLIT: { - contents_split_ = view; + contents_split_ = static_cast<views::SingleSplitView*>(view); // We're installed as the LayoutManager before BrowserView creates the - // contents, so we have to set contents_container_ here rather than + // contents, so we have to set contents_container_ here rather than in // Installed. contents_container_ = browser_view_->contents_; break; @@ -363,9 +365,112 @@ int BrowserViewLayout::LayoutInfoBar(int top) { return top + height; } +// |browser_reserved_rect| is in browser_view_ coordinates. +// |future_source_bounds| is in |source|'s parent coordinates. +// |future_parent_offset| is required, since parent view is not moved yet. +// Note that |future_parent_offset| is relative to browser_view_, not to +// the parent view. +void BrowserViewLayout::UpdateReservedContentsRect( + const gfx::Rect& browser_reserved_rect, + TabContentsContainer* source, + const gfx::Rect& future_source_bounds, + const gfx::Point& future_parent_offset) { + gfx::Point resize_corner_origin(browser_reserved_rect.origin()); + // Convert |resize_corner_origin| from browser_view_ to source's parent + // coordinates. + views::View::ConvertPointToView(browser_view_, source->GetParent(), + &resize_corner_origin); + // Create |reserved_rect| in source's parent coordinates. + gfx::Rect reserved_rect(resize_corner_origin, browser_reserved_rect.size()); + // Apply source's parent future offset to it. + reserved_rect.Offset(-future_parent_offset.x(), -future_parent_offset.y()); + if (future_source_bounds.Intersects(reserved_rect)) { + // |source| is not properly positioned yet to use ConvertPointToView, + // so convert it into |source|'s coordinates manually. + reserved_rect.Offset(-future_source_bounds.x(), -future_source_bounds.y()); + } else { + reserved_rect = gfx::Rect(); + } + + source->SetReservedContentsRect(reserved_rect); +} + void BrowserViewLayout::LayoutTabContents(int top, int bottom) { - contents_split_->SetBounds(vertical_layout_rect_.x(), top, - vertical_layout_rect_.width(), bottom - top); + // The ultimate idea is to calcualte bounds and reserved areas for all + // contents views first and then resize them all, so every view + // (and its contents) is resized and laid out only once. + + // The views hierarcy (see browser_view.h for more details): + // 1) Sidebar is not allowed: + // contents_split_ -> [contents_container_ | devtools] + // 2) Sidebar is allowed: + // contents_split_ -> + // [sidebar_split -> [contents_container_ | sidebar]] | devtools + + gfx::Rect sidebar_split_bounds; + gfx::Rect contents_bounds; + gfx::Rect sidebar_bounds; + gfx::Rect devtools_bounds; + + gfx::Rect contents_split_bounds(vertical_layout_rect_.x(), top, + vertical_layout_rect_.width(), + std::max(0, bottom - top)); + contents_split_->CalculateChildrenBounds( + contents_split_bounds, &sidebar_split_bounds, &devtools_bounds); + gfx::Point contents_split_offset( + contents_split_bounds.x() - contents_split_->bounds().x(), + contents_split_bounds.y() - contents_split_->bounds().y()); + gfx::Point sidebar_split_offset(contents_split_offset); + sidebar_split_offset.Offset(sidebar_split_bounds.x(), + sidebar_split_bounds.y()); + + views::SingleSplitView* sidebar_split = browser_view_->sidebar_split_; + if (sidebar_split) { + DCHECK(sidebar_split == contents_split_->GetChildViewAt(0)); + sidebar_split->CalculateChildrenBounds( + sidebar_split_bounds, &contents_bounds, &sidebar_bounds); + } else { + contents_bounds = sidebar_split_bounds; + } + + // Layout resize corner, sidebar mini tabs and calculate reserved contents + // rects here as all contents view bounds are already determined, but not yet + // set at this point, so contents will be laid out once at most. + // TODO(alekseys): layout sidebar minitabs and adjust reserved rect + // accordingly. + gfx::Rect browser_reserved_rect; + if (!browser_view_->frame_->GetWindow()->IsMaximized() && + !browser_view_->frame_->GetWindow()->IsFullscreen()) { + gfx::Size resize_corner_size = browser_view_->GetResizeCornerSize(); + if (!resize_corner_size.IsEmpty()) { + gfx::Rect bounds = browser_view_->GetLocalBounds(false); + gfx::Point resize_corner_origin( + bounds.right() - resize_corner_size.width(), + bounds.bottom() - resize_corner_size.height()); + browser_reserved_rect = + gfx::Rect(resize_corner_origin, resize_corner_size); + } + } + + UpdateReservedContentsRect(browser_reserved_rect, + browser_view_->contents_container_, + contents_bounds, + sidebar_split_offset); + if (sidebar_split) { + UpdateReservedContentsRect(browser_reserved_rect, + browser_view_->sidebar_container_, + sidebar_bounds, + sidebar_split_offset); + } + UpdateReservedContentsRect(browser_reserved_rect, + browser_view_->devtools_container_, + devtools_bounds, + contents_split_offset); + + // Now it's safe to actually resize all contents views in the hierarchy. + contents_split_->SetBounds(contents_split_bounds); + if (sidebar_split) + sidebar_split->SetBounds(sidebar_split_bounds); } int BrowserViewLayout::GetTopMarginForActiveContent() { diff --git a/chrome/browser/ui/views/frame/browser_view_layout.h b/chrome/browser/ui/views/frame/browser_view_layout.h index 279803c..1b680a1 100644 --- a/chrome/browser/ui/views/frame/browser_view_layout.h +++ b/chrome/browser/ui/views/frame/browser_view_layout.h @@ -14,8 +14,13 @@ class Browser; class BrowserView; class ContentsContainer; class DownloadShelfView; +class TabContentsContainer; class ToolbarView; +namespace views { +class SingleSplitView; +} + // The layout manager used in chrome browser. class BrowserViewLayout : public views::LayoutManager { public: @@ -61,6 +66,15 @@ class BrowserViewLayout : public views::LayoutManager { int LayoutBookmarkBar(int top); int LayoutInfoBar(int top); + // Updates |source|'s reserved contents rect by mapping BrowserView's + // |browser_reserved_rect| into |future_source_bounds| taking into + // account |source|'s |future_parent_offset| (offset is relative to + // browser_view_). + void UpdateReservedContentsRect(const gfx::Rect& browser_reserved_rect, + TabContentsContainer* source, + const gfx::Rect& future_source_bounds, + const gfx::Point& future_parent_offset); + // Layout the TabContents container, between the coordinates |top| and // |bottom|. void LayoutTabContents(int top, int bottom); @@ -88,7 +102,7 @@ class BrowserViewLayout : public views::LayoutManager { // Child views that the layout manager manages. BaseTabStrip* tabstrip_; ToolbarView* toolbar_; - views::View* contents_split_; + views::SingleSplitView* contents_split_; ContentsContainer* contents_container_; views::View* infobar_container_; DownloadShelfView* download_shelf_; diff --git a/chrome/browser/ui/views/tab_contents/tab_contents_container.cc b/chrome/browser/ui/views/tab_contents/tab_contents_container.cc index 06c3400..90af997 100644 --- a/chrome/browser/ui/views/tab_contents/tab_contents_container.cc +++ b/chrome/browser/ui/views/tab_contents/tab_contents_container.cc @@ -25,8 +25,7 @@ TabContentsContainer::TabContentsContainer() : native_container_(NULL), - tab_contents_(NULL), - reserved_area_delegate_(NULL) { + tab_contents_(NULL) { SetID(VIEW_ID_TAB_CONTAINER); } @@ -46,9 +45,6 @@ void TabContentsContainer::ChangeTabContents(TabContents* contents) { tab_contents_->WasHidden(); RemoveObservers(); } -#if !defined(TOUCH_UI) - TabContents* old_contents = tab_contents_; -#endif tab_contents_ = contents; // When detaching the last tab of the browser ChangeTabContents is invoked // with NULL. Don't attempt to do anything in that case. @@ -63,9 +59,7 @@ void TabContentsContainer::ChangeTabContents(TabContents* contents) { Layout(); } #else - RenderWidgetHostViewChanged( - old_contents ? old_contents->GetRenderWidgetHostView() : NULL, - tab_contents_->GetRenderWidgetHostView()); + RenderWidgetHostViewChanged(tab_contents_->GetRenderWidgetHostView()); native_container_->AttachContents(tab_contents_); #endif AddObservers(); @@ -82,6 +76,17 @@ void TabContentsContainer::SetFastResize(bool fast_resize) { native_container_->SetFastResize(fast_resize); } +void TabContentsContainer::SetReservedContentsRect( + const gfx::Rect& reserved_rect) { + cached_reserved_rect_ = reserved_rect; +#if !defined(TOUCH_UI) + if (tab_contents_ && tab_contents_->GetRenderWidgetHostView()) { + tab_contents_->GetRenderWidgetHostView()->set_reserved_contents_rect( + reserved_rect); + } +#endif +} + //////////////////////////////////////////////////////////////////////////////// // TabContentsContainer, NotificationObserver implementation: @@ -108,8 +113,6 @@ void TabContentsContainer::Layout() { views::View::Layout(); #else if (native_container_) { - if (reserved_area_delegate_) - reserved_area_delegate_->UpdateReservedContentsRect(this); native_container_->GetView()->SetBounds(0, 0, width(), height()); native_container_->GetView()->Layout(); } @@ -158,10 +161,8 @@ void TabContentsContainer::RenderViewHostChanged(RenderViewHost* old_host, #if defined(TOUCH_UI) NOTIMPLEMENTED(); // TODO(anicolao) #else - if (new_host) { - RenderWidgetHostViewChanged( - old_host ? old_host->view() : NULL, new_host->view()); - } + if (new_host) + RenderWidgetHostViewChanged(new_host->view()); native_container_->RenderViewHostChanged(old_host, new_host); #endif } @@ -174,12 +175,7 @@ void TabContentsContainer::TabContentsDestroyed(TabContents* contents) { } void TabContentsContainer::RenderWidgetHostViewChanged( - RenderWidgetHostView* old_view, RenderWidgetHostView* new_view) { - // Carry over the reserved rect, if possible. - if (old_view && new_view) { - new_view->set_reserved_contents_rect(old_view->reserved_contents_rect()); - } else { - if (reserved_area_delegate_) - reserved_area_delegate_->UpdateReservedContentsRect(this); - } + RenderWidgetHostView* new_view) { + if (new_view) + new_view->set_reserved_contents_rect(cached_reserved_rect_); } diff --git a/chrome/browser/ui/views/tab_contents/tab_contents_container.h b/chrome/browser/ui/views/tab_contents/tab_contents_container.h index 83633e8..1ba30ed 100644 --- a/chrome/browser/ui/views/tab_contents/tab_contents_container.h +++ b/chrome/browser/ui/views/tab_contents/tab_contents_container.h @@ -19,19 +19,6 @@ class TabContents; class TabContentsContainer : public views::View, public NotificationObserver { public: - // Interface to request the reserved contents area updates. - class ReservedAreaDelegate { - public: - // Notifies that |source|'s reserved contents area should be updated. - // Reserved contents area is a rect in tab contents view coordinates where - // contents should not be rendered (to display the resize corner, sidebar - // mini tabs or any other UI elements overlaying this container). - virtual void UpdateReservedContentsRect( - const TabContentsContainer* source) = 0; - protected: - virtual ~ReservedAreaDelegate() {} - }; - TabContentsContainer(); virtual ~TabContentsContainer(); @@ -50,9 +37,9 @@ class TabContentsContainer : public views::View, // so performance is better. void SetFastResize(bool fast_resize); - void set_reserved_area_delegate(ReservedAreaDelegate* delegate) { - reserved_area_delegate_ = delegate; - } + // Updates the current reserved rect in view coordinates where contents + // should not be rendered to draw the resize corner, sidebar mini tabs etc. + void SetReservedContentsRect(const gfx::Rect& reserved_rect); // Overridden from NotificationObserver: virtual void Observe(NotificationType type, @@ -84,8 +71,7 @@ class TabContentsContainer : public views::View, void TabContentsDestroyed(TabContents* contents); // Called when the RenderWidgetHostView of the hosted TabContents has changed. - void RenderWidgetHostViewChanged(RenderWidgetHostView* old_view, - RenderWidgetHostView* new_view); + void RenderWidgetHostViewChanged(RenderWidgetHostView* new_view); // An instance of a NativeTabContentsContainer object that holds the native // view handle associated with the attached TabContents. @@ -97,8 +83,10 @@ class TabContentsContainer : public views::View, // Handles registering for our notifications. NotificationRegistrar registrar_; - // Delegate for enquiring reserved contents area. Not owned by us. - ReservedAreaDelegate* reserved_area_delegate_; + // The current reserved rect in view coordinates where contents should not be + // rendered to draw the resize corner, sidebar mini tabs etc. + // Cached here to update ever changing renderers. + gfx::Rect cached_reserved_rect_; DISALLOW_COPY_AND_ASSIGN(TabContentsContainer); }; diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index 0f48b85..a0e0a99 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -1722,6 +1722,7 @@ '../views/box_layout_unittest.cc', '../views/controls/label_unittest.cc', '../views/controls/progress_bar_unittest.cc', + '../views/controls/single_split_view.cc', '../views/controls/tabbed_pane/tabbed_pane_unittest.cc', '../views/controls/table/table_view_unittest.cc', '../views/controls/textfield/textfield_views_model_unittest.cc', 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', |