browser_view_layout.cc revision dc0f95d653279beabeb9817299e2902918ba123e
1// Copyright (c) 2011 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "chrome/browser/ui/views/frame/browser_view_layout.h" 6 7#include "chrome/browser/sidebar/sidebar_manager.h" 8#include "chrome/browser/ui/find_bar/find_bar.h" 9#include "chrome/browser/ui/find_bar/find_bar_controller.h" 10#include "chrome/browser/ui/view_ids.h" 11#include "chrome/browser/ui/views/bookmarks/bookmark_bar_view.h" 12#include "chrome/browser/ui/views/download_shelf_view.h" 13#include "chrome/browser/ui/views/frame/browser_frame.h" 14#include "chrome/browser/ui/views/frame/browser_view.h" 15#include "chrome/browser/ui/views/frame/contents_container.h" 16#include "chrome/browser/ui/views/tab_contents/tab_contents_container.h" 17#include "chrome/browser/ui/views/tabs/side_tab_strip.h" 18#include "chrome/browser/ui/views/tabs/tab_strip.h" 19#include "chrome/browser/ui/views/toolbar_view.h" 20#include "ui/gfx/point.h" 21#include "ui/gfx/scrollbar_size.h" 22#include "ui/gfx/size.h" 23#include "views/controls/single_split_view.h" 24#include "views/window/window.h" 25 26#if defined(OS_LINUX) 27#include "views/window/hit_test.h" 28#endif 29 30namespace { 31 32// The visible height of the shadow above the tabs. Clicks in this area are 33// treated as clicks to the frame, rather than clicks to the tab. 34const int kTabShadowSize = 2; 35// The vertical overlap between the TabStrip and the Toolbar. 36const int kToolbarTabStripVerticalOverlap = 3; 37// An offset distance between certain toolbars and the toolbar that preceded 38// them in layout. 39const int kSeparationLineHeight = 1; 40 41} // namespace 42 43//////////////////////////////////////////////////////////////////////////////// 44// BrowserViewLayout, public: 45 46BrowserViewLayout::BrowserViewLayout() 47 : tabstrip_(NULL), 48 toolbar_(NULL), 49 contents_split_(NULL), 50 contents_container_(NULL), 51 infobar_container_(NULL), 52 download_shelf_(NULL), 53 active_bookmark_bar_(NULL), 54 browser_view_(NULL), 55 find_bar_y_(0) { 56} 57 58BrowserViewLayout::~BrowserViewLayout() { 59} 60 61gfx::Size BrowserViewLayout::GetMinimumSize() { 62 // TODO(noname): In theory the tabstrip width should probably be 63 // (OTR + tabstrip + caption buttons) width. 64 gfx::Size tabstrip_size( 65 browser()->SupportsWindowFeature(Browser::FEATURE_TABSTRIP) ? 66 tabstrip_->GetMinimumSize() : gfx::Size()); 67 gfx::Size toolbar_size( 68 (browser()->SupportsWindowFeature(Browser::FEATURE_TOOLBAR) || 69 browser()->SupportsWindowFeature(Browser::FEATURE_LOCATIONBAR)) ? 70 toolbar_->GetMinimumSize() : gfx::Size()); 71 if (tabstrip_size.height() && toolbar_size.height()) 72 toolbar_size.Enlarge(0, -kToolbarTabStripVerticalOverlap); 73 gfx::Size bookmark_bar_size; 74 if (active_bookmark_bar_ && 75 browser()->SupportsWindowFeature(Browser::FEATURE_BOOKMARKBAR)) { 76 bookmark_bar_size = active_bookmark_bar_->GetMinimumSize(); 77 bookmark_bar_size.Enlarge(0, -(kSeparationLineHeight + 78 active_bookmark_bar_->GetToolbarOverlap(true))); 79 } 80 gfx::Size contents_size(contents_split_->GetMinimumSize()); 81 82 int min_height = tabstrip_size.height() + toolbar_size.height() + 83 bookmark_bar_size.height() + contents_size.height(); 84 int widths[] = { tabstrip_size.width(), toolbar_size.width(), 85 bookmark_bar_size.width(), contents_size.width() }; 86 int min_width = *std::max_element(&widths[0], &widths[arraysize(widths)]); 87 return gfx::Size(min_width, min_height); 88} 89 90gfx::Rect BrowserViewLayout::GetFindBarBoundingBox() const { 91 // This function returns the area the Find Bar can be laid out 92 // within. This basically implies the "user-perceived content 93 // area" of the browser window excluding the vertical 94 // scrollbar. This is not quite so straightforward as positioning 95 // based on the TabContentsContainer since the BookmarkBarView may 96 // be visible but not persistent (in the New Tab case) and we 97 // position the Find Bar over the top of it in that case since the 98 // BookmarkBarView is not _visually_ connected to the Toolbar. 99 100 // First determine the bounding box of the content area in Widget 101 // coordinates. 102 gfx::Rect bounding_box(contents_container_->bounds()); 103 104 gfx::Point topleft; 105 views::View::ConvertPointToWidget(contents_container_, &topleft); 106 bounding_box.set_origin(topleft); 107 108 // Adjust the position and size of the bounding box by the find bar offset 109 // calculated during the last Layout. 110 int height_delta = find_bar_y_ - bounding_box.y(); 111 bounding_box.set_y(find_bar_y_); 112 bounding_box.set_height(std::max(0, bounding_box.height() + height_delta)); 113 114 // Finally decrease the width of the bounding box by the width of 115 // the vertical scroll bar. 116 int scrollbar_width = gfx::scrollbar_size(); 117 bounding_box.set_width(std::max(0, bounding_box.width() - scrollbar_width)); 118 if (base::i18n::IsRTL()) 119 bounding_box.set_x(bounding_box.x() + scrollbar_width); 120 121 return bounding_box; 122} 123 124bool BrowserViewLayout::IsPositionInWindowCaption( 125 const gfx::Point& point) { 126 gfx::Point tabstrip_point(point); 127 views::View::ConvertPointToView(browser_view_, tabstrip_, &tabstrip_point); 128 return tabstrip_->IsPositionInWindowCaption(tabstrip_point); 129} 130 131int BrowserViewLayout::NonClientHitTest( 132 const gfx::Point& point) { 133 // Since the TabStrip only renders in some parts of the top of the window, 134 // the un-obscured area is considered to be part of the non-client caption 135 // area of the window. So we need to treat hit-tests in these regions as 136 // hit-tests of the titlebar. 137 138 views::View* parent = browser_view_->parent(); 139 140 gfx::Point point_in_browser_view_coords(point); 141 views::View::ConvertPointToView( 142 parent, browser_view_, &point_in_browser_view_coords); 143 144 // Determine if the TabStrip exists and is capable of being clicked on. We 145 // might be a popup window without a TabStrip. 146 if (browser_view_->IsTabStripVisible()) { 147 // See if the mouse pointer is within the bounds of the TabStrip. 148 gfx::Point point_in_tabstrip_coords(point); 149 views::View::ConvertPointToView(parent, tabstrip_, 150 &point_in_tabstrip_coords); 151 if (tabstrip_->HitTest(point_in_tabstrip_coords)) { 152 if (tabstrip_->IsPositionInWindowCaption(point_in_tabstrip_coords)) 153 return HTCAPTION; 154 return HTCLIENT; 155 } 156 157 // The top few pixels of the TabStrip are a drop-shadow - as we're pretty 158 // starved of dragable area, let's give it to window dragging (this also 159 // makes sense visually). 160 if (!browser_view_->IsMaximized() && 161 (point_in_browser_view_coords.y() < 162 (tabstrip_->y() + kTabShadowSize))) { 163 // We return HTNOWHERE as this is a signal to our containing 164 // NonClientView that it should figure out what the correct hit-test 165 // code is given the mouse position... 166 return HTNOWHERE; 167 } 168 } 169 170 // If the point's y coordinate is below the top of the toolbar and otherwise 171 // within the bounds of this view, the point is considered to be within the 172 // client area. 173 gfx::Rect bv_bounds = browser_view_->bounds(); 174 bv_bounds.Offset(0, toolbar_->y()); 175 bv_bounds.set_height(bv_bounds.height() - toolbar_->y()); 176 if (bv_bounds.Contains(point)) 177 return HTCLIENT; 178 179 // If the point's y coordinate is above the top of the toolbar, but not in 180 // the tabstrip (per previous checking in this function), then we consider it 181 // in the window caption (e.g. the area to the right of the tabstrip 182 // underneath the window controls). However, note that we DO NOT return 183 // HTCAPTION here, because when the window is maximized the window controls 184 // will fall into this space (since the BrowserView is sized to entire size 185 // of the window at that point), and the HTCAPTION value will cause the 186 // window controls not to work. So we return HTNOWHERE so that the caller 187 // will hit-test the window controls before finally falling back to 188 // HTCAPTION. 189 bv_bounds = browser_view_->bounds(); 190 bv_bounds.set_height(toolbar_->y()); 191 if (bv_bounds.Contains(point)) 192 return HTNOWHERE; 193 194 // If the point is somewhere else, delegate to the default implementation. 195 return browser_view_->views::ClientView::NonClientHitTest(point); 196} 197 198////////////////////////////////////////////////////////////////////////////// 199// BrowserViewLayout, views::LayoutManager implementation: 200 201void BrowserViewLayout::Installed(views::View* host) { 202 toolbar_ = NULL; 203 contents_split_ = NULL; 204 contents_container_ = NULL; 205 infobar_container_ = NULL; 206 download_shelf_ = NULL; 207 active_bookmark_bar_ = NULL; 208 tabstrip_ = NULL; 209 browser_view_ = static_cast<BrowserView*>(host); 210} 211 212void BrowserViewLayout::Uninstalled(views::View* host) {} 213 214void BrowserViewLayout::ViewAdded(views::View* host, views::View* view) { 215 switch (view->GetID()) { 216 case VIEW_ID_CONTENTS_SPLIT: { 217 contents_split_ = static_cast<views::SingleSplitView*>(view); 218 // We're installed as the LayoutManager before BrowserView creates the 219 // contents, so we have to set contents_container_ here rather than in 220 // Installed. 221 contents_container_ = browser_view_->contents_; 222 break; 223 } 224 case VIEW_ID_INFO_BAR_CONTAINER: 225 infobar_container_ = view; 226 break; 227 case VIEW_ID_DOWNLOAD_SHELF: 228 download_shelf_ = static_cast<DownloadShelfView*>(view); 229 break; 230 case VIEW_ID_BOOKMARK_BAR: 231 active_bookmark_bar_ = static_cast<BookmarkBarView*>(view); 232 break; 233 case VIEW_ID_TOOLBAR: 234 toolbar_ = static_cast<ToolbarView*>(view); 235 break; 236 case VIEW_ID_TAB_STRIP: 237 tabstrip_ = static_cast<BaseTabStrip*>(view); 238 break; 239 } 240} 241 242void BrowserViewLayout::ViewRemoved(views::View* host, views::View* view) { 243 switch (view->GetID()) { 244 case VIEW_ID_BOOKMARK_BAR: 245 active_bookmark_bar_ = NULL; 246 break; 247 } 248} 249 250void BrowserViewLayout::Layout(views::View* host) { 251 vertical_layout_rect_ = browser_view_->GetLocalBounds(); 252 int top = LayoutTabStrip(); 253 if (browser_view_->IsTabStripVisible() && !browser_view_->UseVerticalTabs()) { 254 tabstrip_->SetBackgroundOffset(gfx::Point( 255 tabstrip_->GetMirroredX() + browser_view_->GetMirroredX(), 256 browser_view_->frame()->GetHorizontalTabStripVerticalOffset(false))); 257 } 258 top = LayoutToolbar(top); 259 top = LayoutBookmarkAndInfoBars(top); 260 int bottom = LayoutDownloadShelf(browser_view_->height()); 261 int active_top_margin = GetTopMarginForActiveContent(); 262 top -= active_top_margin; 263 contents_container_->SetActiveTopMargin(active_top_margin); 264 LayoutTabContents(top, bottom); 265 // This must be done _after_ we lay out the TabContents since this 266 // code calls back into us to find the bounding box the find bar 267 // must be laid out within, and that code depends on the 268 // TabContentsContainer's bounds being up to date. 269 if (browser()->HasFindBarController()) { 270 browser()->GetFindBarController()->find_bar()->MoveWindowIfNecessary( 271 gfx::Rect(), true); 272 } 273} 274 275// Return the preferred size which is the size required to give each 276// children their respective preferred size. 277gfx::Size BrowserViewLayout::GetPreferredSize(views::View* host) { 278 return gfx::Size(); 279} 280 281////////////////////////////////////////////////////////////////////////////// 282// BrowserViewLayout, private: 283 284Browser* BrowserViewLayout::browser() { 285 return browser_view_->browser(); 286} 287 288const Browser* BrowserViewLayout::browser() const { 289 return browser_view_->browser(); 290} 291 292int BrowserViewLayout::LayoutTabStrip() { 293 if (!browser_view_->IsTabStripVisible()) { 294 tabstrip_->SetVisible(false); 295 tabstrip_->SetBounds(0, 0, 0, 0); 296 return 0; 297 } 298 299 gfx::Rect tabstrip_bounds( 300 browser_view_->frame()->GetBoundsForTabStrip(tabstrip_)); 301 gfx::Point tabstrip_origin(tabstrip_bounds.origin()); 302 views::View::ConvertPointToView(browser_view_->parent(), browser_view_, 303 &tabstrip_origin); 304 tabstrip_bounds.set_origin(tabstrip_origin); 305 306 if (browser_view_->UseVerticalTabs()) 307 vertical_layout_rect_.Inset(tabstrip_bounds.width(), 0, 0, 0); 308 309 tabstrip_->SetVisible(true); 310 tabstrip_->SetBoundsRect(tabstrip_bounds); 311 return browser_view_->UseVerticalTabs() ? 312 tabstrip_bounds.y() : tabstrip_bounds.bottom(); 313} 314 315int BrowserViewLayout::LayoutToolbar(int top) { 316 int browser_view_width = vertical_layout_rect_.width(); 317 bool visible = browser_view_->IsToolbarVisible(); 318 toolbar_->location_bar()->SetFocusable(visible); 319 int y = top; 320 if (!browser_view_->UseVerticalTabs()) { 321 y -= ((visible && browser_view_->IsTabStripVisible()) ? 322 kToolbarTabStripVerticalOverlap : 0); 323 } 324 int height = visible ? toolbar_->GetPreferredSize().height() : 0; 325 toolbar_->SetVisible(visible); 326 toolbar_->SetBounds(vertical_layout_rect_.x(), y, browser_view_width, height); 327 return y + height; 328} 329 330int BrowserViewLayout::LayoutBookmarkAndInfoBars(int top) { 331 find_bar_y_ = top + browser_view_->y() - 1; 332 if (active_bookmark_bar_) { 333 // If we're showing the Bookmark bar in detached style, then we 334 // need to show any Info bar _above_ the Bookmark bar, since the 335 // Bookmark bar is styled to look like it's part of the page. 336 if (active_bookmark_bar_->IsDetached()) 337 return LayoutBookmarkBar(LayoutInfoBar(top)); 338 // Otherwise, Bookmark bar first, Info bar second. 339 top = std::max(toolbar_->bounds().bottom(), LayoutBookmarkBar(top)); 340 } 341 find_bar_y_ = top + browser_view_->y() - 1; 342 return LayoutInfoBar(top); 343} 344 345int BrowserViewLayout::LayoutBookmarkBar(int top) { 346 DCHECK(active_bookmark_bar_); 347 int y = top; 348 if (!browser_view_->IsBookmarkBarVisible()) { 349 active_bookmark_bar_->SetVisible(false); 350 active_bookmark_bar_->SetBounds(0, y, browser_view_->width(), 0); 351 return y; 352 } 353 354 active_bookmark_bar_->set_infobar_visible(InfobarVisible()); 355 int bookmark_bar_height = active_bookmark_bar_->GetPreferredSize().height(); 356 y -= kSeparationLineHeight + active_bookmark_bar_->GetToolbarOverlap(false); 357 active_bookmark_bar_->SetVisible(true); 358 active_bookmark_bar_->SetBounds(vertical_layout_rect_.x(), y, 359 vertical_layout_rect_.width(), 360 bookmark_bar_height); 361 return y + bookmark_bar_height; 362} 363 364int BrowserViewLayout::LayoutInfoBar(int top) { 365 bool visible = InfobarVisible(); 366 int height = visible ? infobar_container_->GetPreferredSize().height() : 0; 367 infobar_container_->SetVisible(visible); 368 infobar_container_->SetBounds(vertical_layout_rect_.x(), top, 369 vertical_layout_rect_.width(), height); 370 return top + height; 371} 372 373// |browser_reserved_rect| is in browser_view_ coordinates. 374// |future_source_bounds| is in |source|'s parent coordinates. 375// |future_parent_offset| is required, since parent view is not moved yet. 376// Note that |future_parent_offset| is relative to browser_view_, not to 377// the parent view. 378void BrowserViewLayout::UpdateReservedContentsRect( 379 const gfx::Rect& browser_reserved_rect, 380 TabContentsContainer* source, 381 const gfx::Rect& future_source_bounds, 382 const gfx::Point& future_parent_offset) { 383 gfx::Point resize_corner_origin(browser_reserved_rect.origin()); 384 // Convert |resize_corner_origin| from browser_view_ to source's parent 385 // coordinates. 386 views::View::ConvertPointToView(browser_view_, source->parent(), 387 &resize_corner_origin); 388 // Create |reserved_rect| in source's parent coordinates. 389 gfx::Rect reserved_rect(resize_corner_origin, browser_reserved_rect.size()); 390 // Apply source's parent future offset to it. 391 reserved_rect.Offset(-future_parent_offset.x(), -future_parent_offset.y()); 392 if (future_source_bounds.Intersects(reserved_rect)) { 393 // |source| is not properly positioned yet to use ConvertPointToView, 394 // so convert it into |source|'s coordinates manually. 395 reserved_rect.Offset(-future_source_bounds.x(), -future_source_bounds.y()); 396 } else { 397 reserved_rect = gfx::Rect(); 398 } 399 400 source->SetReservedContentsRect(reserved_rect); 401} 402 403void BrowserViewLayout::LayoutTabContents(int top, int bottom) { 404 // The ultimate idea is to calcualte bounds and reserved areas for all 405 // contents views first and then resize them all, so every view 406 // (and its contents) is resized and laid out only once. 407 408 // The views hierarcy (see browser_view.h for more details): 409 // 1) Sidebar is not allowed: 410 // contents_split_ -> [contents_container_ | devtools] 411 // 2) Sidebar is allowed: 412 // contents_split_ -> 413 // [sidebar_split -> [contents_container_ | sidebar]] | devtools 414 415 gfx::Rect sidebar_split_bounds; 416 gfx::Rect contents_bounds; 417 gfx::Rect sidebar_bounds; 418 gfx::Rect devtools_bounds; 419 420 gfx::Rect contents_split_bounds(vertical_layout_rect_.x(), top, 421 vertical_layout_rect_.width(), 422 std::max(0, bottom - top)); 423 contents_split_->CalculateChildrenBounds( 424 contents_split_bounds, &sidebar_split_bounds, &devtools_bounds); 425 gfx::Point contents_split_offset( 426 contents_split_bounds.x() - contents_split_->bounds().x(), 427 contents_split_bounds.y() - contents_split_->bounds().y()); 428 gfx::Point sidebar_split_offset(contents_split_offset); 429 sidebar_split_offset.Offset(sidebar_split_bounds.x(), 430 sidebar_split_bounds.y()); 431 432 views::SingleSplitView* sidebar_split = browser_view_->sidebar_split_; 433 if (sidebar_split) { 434 DCHECK(sidebar_split == contents_split_->GetChildViewAt(0)); 435 sidebar_split->CalculateChildrenBounds( 436 sidebar_split_bounds, &contents_bounds, &sidebar_bounds); 437 } else { 438 contents_bounds = sidebar_split_bounds; 439 } 440 441 // Layout resize corner, sidebar mini tabs and calculate reserved contents 442 // rects here as all contents view bounds are already determined, but not yet 443 // set at this point, so contents will be laid out once at most. 444 // TODO(alekseys): layout sidebar minitabs and adjust reserved rect 445 // accordingly. 446 gfx::Rect browser_reserved_rect; 447 if (!browser_view_->frame_->GetWindow()->IsMaximized() && 448 !browser_view_->frame_->GetWindow()->IsFullscreen()) { 449 gfx::Size resize_corner_size = browser_view_->GetResizeCornerSize(); 450 if (!resize_corner_size.IsEmpty()) { 451 gfx::Rect bounds = browser_view_->GetContentsBounds(); 452 gfx::Point resize_corner_origin( 453 bounds.right() - resize_corner_size.width(), 454 bounds.bottom() - resize_corner_size.height()); 455 browser_reserved_rect = 456 gfx::Rect(resize_corner_origin, resize_corner_size); 457 } 458 } 459 460 UpdateReservedContentsRect(browser_reserved_rect, 461 browser_view_->contents_container_, 462 contents_bounds, 463 sidebar_split_offset); 464 if (sidebar_split) { 465 UpdateReservedContentsRect(browser_reserved_rect, 466 browser_view_->sidebar_container_, 467 sidebar_bounds, 468 sidebar_split_offset); 469 } 470 UpdateReservedContentsRect(browser_reserved_rect, 471 browser_view_->devtools_container_, 472 devtools_bounds, 473 contents_split_offset); 474 475 // Now it's safe to actually resize all contents views in the hierarchy. 476 contents_split_->SetBoundsRect(contents_split_bounds); 477 if (sidebar_split) 478 sidebar_split->SetBoundsRect(sidebar_split_bounds); 479} 480 481int BrowserViewLayout::GetTopMarginForActiveContent() { 482 if (!active_bookmark_bar_ || !browser_view_->IsBookmarkBarVisible() || 483 !active_bookmark_bar_->IsDetached()) { 484 return 0; 485 } 486 487 if (contents_split_->GetChildViewAt(1) && 488 contents_split_->GetChildViewAt(1)->IsVisible()) 489 return 0; 490 491 if (SidebarManager::IsSidebarAllowed()) { 492 views::View* sidebar_split = contents_split_->GetChildViewAt(0); 493 if (sidebar_split->GetChildViewAt(1) && 494 sidebar_split->GetChildViewAt(1)->IsVisible()) 495 return 0; 496 } 497 498 // Adjust for separator. 499 return active_bookmark_bar_->height() - kSeparationLineHeight; 500} 501 502int BrowserViewLayout::LayoutDownloadShelf(int bottom) { 503 // Re-layout the shelf either if it is visible or if it's close animation 504 // is currently running. 505 if (browser_view_->IsDownloadShelfVisible() || 506 (download_shelf_ && download_shelf_->IsClosing())) { 507 bool visible = browser()->SupportsWindowFeature( 508 Browser::FEATURE_DOWNLOADSHELF); 509 DCHECK(download_shelf_); 510 int height = visible ? download_shelf_->GetPreferredSize().height() : 0; 511 download_shelf_->SetVisible(visible); 512 download_shelf_->SetBounds(vertical_layout_rect_.x(), bottom - height, 513 vertical_layout_rect_.width(), height); 514 download_shelf_->Layout(); 515 bottom -= height; 516 } 517 return bottom; 518} 519 520bool BrowserViewLayout::InfobarVisible() const { 521 // NOTE: Can't check if the size IsEmpty() since it's always 0-width. 522 return browser()->SupportsWindowFeature(Browser::FEATURE_INFOBAR) && 523 (infobar_container_->GetPreferredSize().height() != 0); 524} 525