summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/browser/ui/views/frame/browser_view.cc70
-rw-r--r--chrome/browser/ui/views/frame/browser_view.h12
-rw-r--r--chrome/browser/ui/views/frame/browser_view_layout.cc113
-rw-r--r--chrome/browser/ui/views/frame/browser_view_layout.h16
-rw-r--r--chrome/browser/ui/views/tab_contents/tab_contents_container.cc40
-rw-r--r--chrome/browser/ui/views/tab_contents/tab_contents_container.h28
-rw-r--r--chrome/chrome_tests.gypi1
-rw-r--r--views/controls/single_split_view.cc149
-rw-r--r--views/controls/single_split_view.h58
-rw-r--r--views/controls/single_split_view_unittest.cc176
-rw-r--r--views/examples/single_split_view_example.h3
-rw-r--r--views/views.gyp1
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',