browser_non_client_frame_view_ash.cc revision 23730a6e56a168d1879203e4b3819bb36e3d8f1f
1// Copyright (c) 2012 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_non_client_frame_view_ash.h" 6 7#include "ash/ash_switches.h" 8#include "ash/frame/caption_buttons/frame_caption_button_container_view.h" 9#include "ash/frame/default_header_painter.h" 10#include "ash/frame/frame_border_hit_test_controller.h" 11#include "ash/frame/header_painter_util.h" 12#include "base/command_line.h" 13#include "chrome/browser/themes/theme_properties.h" 14#include "chrome/browser/ui/browser.h" 15#include "chrome/browser/ui/views/avatar_label.h" 16#include "chrome/browser/ui/views/avatar_menu_button.h" 17#include "chrome/browser/ui/views/frame/browser_frame.h" 18#include "chrome/browser/ui/views/frame/browser_header_painter_ash.h" 19#include "chrome/browser/ui/views/frame/browser_view.h" 20#include "chrome/browser/ui/views/frame/immersive_mode_controller.h" 21#include "chrome/browser/ui/views/tab_icon_view.h" 22#include "chrome/browser/ui/views/tabs/tab_strip.h" 23#include "chrome/common/chrome_switches.h" 24#include "content/public/browser/web_contents.h" 25#include "grit/ash_resources.h" 26#include "grit/theme_resources.h" 27#include "ui/accessibility/ax_view_state.h" 28#include "ui/aura/client/aura_constants.h" 29#include "ui/aura/window.h" 30#include "ui/base/hit_test.h" 31#include "ui/base/l10n/l10n_util.h" 32#include "ui/base/layout.h" 33#include "ui/base/resource/resource_bundle.h" 34#include "ui/base/theme_provider.h" 35#include "ui/compositor/layer_animator.h" 36#include "ui/gfx/canvas.h" 37#include "ui/gfx/image/image_skia.h" 38#include "ui/gfx/rect_conversions.h" 39#include "ui/views/controls/label.h" 40#include "ui/views/layout/layout_constants.h" 41#include "ui/views/widget/widget.h" 42#include "ui/views/widget/widget_delegate.h" 43 44namespace { 45 46// The avatar ends 2 px above the bottom of the tabstrip (which, given the 47// way the tabstrip draws its bottom edge, will appear like a 1 px gap to the 48// user). 49const int kAvatarBottomSpacing = 2; 50// There are 2 px on each side of the avatar (between the frame border and 51// it on the left, and between it and the tabstrip on the right). 52const int kAvatarSideSpacing = 2; 53// Space between left edge of window and tabstrip. 54const int kTabstripLeftSpacing = 0; 55// Space between right edge of tabstrip and maximize button. 56const int kTabstripRightSpacing = 10; 57// Height of the shadow of the content area, at the top of the toolbar. 58const int kContentShadowHeight = 1; 59// Space between top of window and top of tabstrip for tall headers, such as 60// for restored windows, apps, etc. 61const int kTabstripTopSpacingTall = 7; 62// Space between top of window and top of tabstrip for short headers, such as 63// for maximized windows, pop-ups, etc. 64const int kTabstripTopSpacingShort = 0; 65// Height of the shadow in the tab image, used to ensure clicks in the shadow 66// area still drag restored windows. This keeps the clickable area large enough 67// to hit easily. 68const int kTabShadowHeight = 4; 69 70} // namespace 71 72/////////////////////////////////////////////////////////////////////////////// 73// BrowserNonClientFrameViewAsh, public: 74 75// static 76const char BrowserNonClientFrameViewAsh::kViewClassName[] = 77 "BrowserNonClientFrameViewAsh"; 78 79BrowserNonClientFrameViewAsh::BrowserNonClientFrameViewAsh( 80 BrowserFrame* frame, BrowserView* browser_view) 81 : BrowserNonClientFrameView(frame, browser_view), 82 caption_button_container_(NULL), 83 window_icon_(NULL), 84 frame_border_hit_test_controller_( 85 new ash::FrameBorderHitTestController(frame)) { 86} 87 88BrowserNonClientFrameViewAsh::~BrowserNonClientFrameViewAsh() { 89} 90 91void BrowserNonClientFrameViewAsh::Init() { 92 caption_button_container_ = new ash::FrameCaptionButtonContainerView(frame(), 93 ash::FrameCaptionButtonContainerView::MINIMIZE_ALLOWED); 94 AddChildView(caption_button_container_); 95 96 // Initializing the TabIconView is expensive, so only do it if we need to. 97 if (browser_view()->ShouldShowWindowIcon()) { 98 window_icon_ = new TabIconView(this, NULL); 99 window_icon_->set_is_light(true); 100 AddChildView(window_icon_); 101 window_icon_->Update(); 102 } 103 104 // Create incognito icon if necessary. 105 UpdateAvatarInfo(); 106 107 // HeaderPainter handles layout. 108 if (UsePackagedAppHeaderStyle()) { 109 ash::DefaultHeaderPainter* header_painter = new ash::DefaultHeaderPainter; 110 header_painter_.reset(header_painter); 111 header_painter->Init(frame(), this, window_icon_, 112 caption_button_container_); 113 } else { 114 BrowserHeaderPainterAsh* header_painter = new BrowserHeaderPainterAsh; 115 header_painter_.reset(header_painter); 116 header_painter->Init(frame(), browser_view(), this, window_icon_, 117 caption_button_container_); 118 } 119} 120 121/////////////////////////////////////////////////////////////////////////////// 122// BrowserNonClientFrameView overrides: 123 124gfx::Rect BrowserNonClientFrameViewAsh::GetBoundsForTabStrip( 125 views::View* tabstrip) const { 126 if (!tabstrip) 127 return gfx::Rect(); 128 129 // When the tab strip is painted in the immersive fullscreen light bar style, 130 // the caption buttons and the avatar button are not visible. However, their 131 // bounds are still used to compute the tab strip bounds so that the tabs have 132 // the same horizontal position when the tab strip is painted in the immersive 133 // light bar style as when the top-of-window views are revealed. 134 int left_inset = GetTabStripLeftInset(); 135 int right_inset = GetTabStripRightInset(); 136 return gfx::Rect(left_inset, 137 GetTopInset(), 138 std::max(0, width() - left_inset - right_inset), 139 tabstrip->GetPreferredSize().height()); 140} 141 142int BrowserNonClientFrameViewAsh::GetTopInset() const { 143 if (!ShouldPaint() || UseImmersiveLightbarHeaderStyle()) 144 return 0; 145 146 if (browser_view()->IsTabStripVisible()) { 147 if (frame()->IsMaximized() || frame()->IsFullscreen()) 148 return kTabstripTopSpacingShort; 149 else 150 return kTabstripTopSpacingTall; 151 } 152 153 if (UsePackagedAppHeaderStyle()) 154 return header_painter_->GetHeaderHeightForPainting(); 155 156 int caption_buttons_bottom = caption_button_container_->bounds().bottom(); 157 158 // The toolbar partially overlaps the caption buttons. 159 if (browser_view()->IsToolbarVisible()) 160 return caption_buttons_bottom - kContentShadowHeight; 161 162 return caption_buttons_bottom + kClientEdgeThickness; 163} 164 165int BrowserNonClientFrameViewAsh::GetThemeBackgroundXInset() const { 166 return ash::HeaderPainterUtil::GetThemeBackgroundXInset(); 167} 168 169void BrowserNonClientFrameViewAsh::UpdateThrobber(bool running) { 170 if (window_icon_) 171 window_icon_->Update(); 172} 173 174/////////////////////////////////////////////////////////////////////////////// 175// views::NonClientFrameView overrides: 176 177gfx::Rect BrowserNonClientFrameViewAsh::GetBoundsForClientView() const { 178 // The ClientView must be flush with the top edge of the widget so that the 179 // web contents can take up the entire screen in immersive fullscreen (with 180 // or without the top-of-window views revealed). When in immersive fullscreen 181 // and the top-of-window views are revealed, the TopContainerView paints the 182 // window header by redirecting paints from its background to 183 // BrowserNonClientFrameViewAsh. 184 return bounds(); 185} 186 187gfx::Rect BrowserNonClientFrameViewAsh::GetWindowBoundsForClientBounds( 188 const gfx::Rect& client_bounds) const { 189 return client_bounds; 190} 191 192int BrowserNonClientFrameViewAsh::NonClientHitTest(const gfx::Point& point) { 193 int hit_test = ash::FrameBorderHitTestController::NonClientHitTest(this, 194 caption_button_container_, point); 195 196 // See if the point is actually within the avatar menu button or within 197 // the avatar label. 198 if (hit_test == HTCAPTION && ((avatar_button() && 199 avatar_button()->GetMirroredBounds().Contains(point)) || 200 (avatar_label() && avatar_label()->GetMirroredBounds().Contains(point)))) 201 return HTCLIENT; 202 203 // When the window is restored we want a large click target above the tabs 204 // to drag the window, so redirect clicks in the tab's shadow to caption. 205 if (hit_test == HTCLIENT && 206 !(frame()->IsMaximized() || frame()->IsFullscreen())) { 207 // Convert point to client coordinates. 208 gfx::Point client_point(point); 209 View::ConvertPointToTarget(this, frame()->client_view(), &client_point); 210 // Report hits in shadow at top of tabstrip as caption. 211 gfx::Rect tabstrip_bounds(browser_view()->tabstrip()->bounds()); 212 if (client_point.y() < tabstrip_bounds.y() + kTabShadowHeight) 213 hit_test = HTCAPTION; 214 } 215 return hit_test; 216} 217 218void BrowserNonClientFrameViewAsh::GetWindowMask(const gfx::Size& size, 219 gfx::Path* window_mask) { 220 // Aura does not use window masks. 221} 222 223void BrowserNonClientFrameViewAsh::ResetWindowControls() { 224 // Hide the caption buttons in immersive fullscreen when the tab light bar 225 // is visible because it's confusing when the user hovers or clicks in the 226 // top-right of the screen and hits one. 227 bool button_visibility = !UseImmersiveLightbarHeaderStyle(); 228 caption_button_container_->SetVisible(button_visibility); 229 230 caption_button_container_->ResetWindowControls(); 231} 232 233void BrowserNonClientFrameViewAsh::UpdateWindowIcon() { 234 if (window_icon_) 235 window_icon_->SchedulePaint(); 236} 237 238void BrowserNonClientFrameViewAsh::UpdateWindowTitle() { 239 if (!frame()->IsFullscreen()) 240 header_painter_->SchedulePaintForTitle(); 241} 242 243/////////////////////////////////////////////////////////////////////////////// 244// views::View overrides: 245 246void BrowserNonClientFrameViewAsh::OnPaint(gfx::Canvas* canvas) { 247 if (!ShouldPaint()) 248 return; 249 250 if (UseImmersiveLightbarHeaderStyle()) { 251 PaintImmersiveLightbarStyleHeader(canvas); 252 return; 253 } 254 255 caption_button_container_->SetPaintAsActive(ShouldPaintAsActive()); 256 257 ash::HeaderPainter::Mode header_mode = ShouldPaintAsActive() ? 258 ash::HeaderPainter::MODE_ACTIVE : ash::HeaderPainter::MODE_INACTIVE; 259 header_painter_->PaintHeader(canvas, header_mode); 260 if (browser_view()->IsToolbarVisible()) 261 PaintToolbarBackground(canvas); 262 else if (!UsePackagedAppHeaderStyle()) 263 PaintContentEdge(canvas); 264} 265 266void BrowserNonClientFrameViewAsh::Layout() { 267 // The header must be laid out before computing |painted_height| because the 268 // computation of |painted_height| for app and popup windows depends on the 269 // position of the window controls. 270 header_painter_->LayoutHeader(); 271 272 int painted_height = 0; 273 if (browser_view()->IsTabStripVisible()) { 274 painted_height = GetTopInset() + 275 browser_view()->tabstrip()->GetPreferredSize().height(); 276 } else if (browser_view()->IsToolbarVisible()) { 277 // Paint the header so that it overlaps with the top few pixels of the 278 // toolbar because the top few pixels of the toolbar are not opaque. 279 painted_height = GetTopInset() + kFrameShadowThickness * 2; 280 } else { 281 painted_height = GetTopInset(); 282 } 283 header_painter_->SetHeaderHeightForPainting(painted_height); 284 if (avatar_button()) 285 LayoutAvatar(); 286 BrowserNonClientFrameView::Layout(); 287} 288 289const char* BrowserNonClientFrameViewAsh::GetClassName() const { 290 return kViewClassName; 291} 292 293bool BrowserNonClientFrameViewAsh::HitTestRect(const gfx::Rect& rect) const { 294 if (!views::View::HitTestRect(rect)) { 295 // |rect| is outside BrowserNonClientFrameViewAsh's bounds. 296 return false; 297 } 298 299 TabStrip* tabstrip = browser_view()->tabstrip(); 300 if (tabstrip && browser_view()->IsTabStripVisible()) { 301 // Claim |rect| only if it is above the bottom of the tabstrip in a non-tab 302 // portion. 303 gfx::RectF rect_in_tabstrip_coords_f(rect); 304 View::ConvertRectToTarget(this, tabstrip, &rect_in_tabstrip_coords_f); 305 gfx::Rect rect_in_tabstrip_coords = gfx::ToEnclosingRect( 306 rect_in_tabstrip_coords_f); 307 308 if (rect_in_tabstrip_coords.y() > tabstrip->height()) 309 return false; 310 311 return !tabstrip->HitTestRect(rect_in_tabstrip_coords) || 312 tabstrip->IsRectInWindowCaption(rect_in_tabstrip_coords); 313 } 314 315 // Claim |rect| if it is above the top of the topmost view in the client area. 316 return rect.y() < GetTopInset(); 317} 318 319void BrowserNonClientFrameViewAsh::GetAccessibleState( 320 ui::AXViewState* state) { 321 state->role = ui::AX_ROLE_TITLE_BAR; 322} 323 324gfx::Size BrowserNonClientFrameViewAsh::GetMinimumSize() { 325 gfx::Size min_client_view_size(frame()->client_view()->GetMinimumSize()); 326 int min_width = std::max(header_painter_->GetMinimumHeaderWidth(), 327 min_client_view_size.width()); 328 if (browser_view()->IsTabStripVisible()) { 329 // Ensure that the minimum width is enough to hold a minimum width tab strip 330 // at its usual insets. 331 int min_tabstrip_width = 332 browser_view()->tabstrip()->GetMinimumSize().width(); 333 min_width = std::max(min_width, 334 min_tabstrip_width + GetTabStripLeftInset() + GetTabStripRightInset()); 335 } 336 return gfx::Size(min_width, min_client_view_size.height()); 337} 338 339/////////////////////////////////////////////////////////////////////////////// 340// chrome::TabIconViewModel overrides: 341 342bool BrowserNonClientFrameViewAsh::ShouldTabIconViewAnimate() const { 343 // This function is queried during the creation of the window as the 344 // TabIconView we host is initialized, so we need to NULL check the selected 345 // WebContents because in this condition there is not yet a selected tab. 346 content::WebContents* current_tab = browser_view()->GetActiveWebContents(); 347 return current_tab ? current_tab->IsLoading() : false; 348} 349 350gfx::ImageSkia BrowserNonClientFrameViewAsh::GetFaviconForTabIconView() { 351 views::WidgetDelegate* delegate = frame()->widget_delegate(); 352 if (!delegate) 353 return gfx::ImageSkia(); 354 return delegate->GetWindowIcon(); 355} 356 357/////////////////////////////////////////////////////////////////////////////// 358// BrowserNonClientFrameViewAsh, private: 359 360int BrowserNonClientFrameViewAsh::GetTabStripLeftInset() const { 361 return avatar_button() ? kAvatarSideSpacing + 362 browser_view()->GetOTRAvatarIcon().width() + kAvatarSideSpacing : 363 kTabstripLeftSpacing; 364} 365 366int BrowserNonClientFrameViewAsh::GetTabStripRightInset() const { 367 return caption_button_container_->GetPreferredSize().width() + 368 kTabstripRightSpacing; 369} 370 371bool BrowserNonClientFrameViewAsh::UseImmersiveLightbarHeaderStyle() const { 372 ImmersiveModeController* immersive_controller = 373 browser_view()->immersive_mode_controller(); 374 return immersive_controller->IsEnabled() && 375 !immersive_controller->IsRevealed() && 376 browser_view()->IsTabStripVisible(); 377} 378 379bool BrowserNonClientFrameViewAsh::UsePackagedAppHeaderStyle() const { 380 // Non streamlined hosted apps do not have a toolbar or tabstrip. Their header 381 // should look the same as the header for packaged apps. Streamlined hosted 382 // apps have a toolbar so should use the browser header style. 383 return browser_view()->browser()->is_app() && 384 !CommandLine::ForCurrentProcess()->HasSwitch( 385 switches::kEnableStreamlinedHostedApps); 386} 387 388void BrowserNonClientFrameViewAsh::LayoutAvatar() { 389 DCHECK(avatar_button()); 390#if !defined(OS_CHROMEOS) 391 // ChromeOS shows avatar on V1 app. 392 DCHECK(browser_view()->IsTabStripVisible()); 393#endif 394 gfx::ImageSkia incognito_icon = browser_view()->GetOTRAvatarIcon(); 395 396 int avatar_bottom = GetTopInset() + 397 browser_view()->GetTabStripHeight() - kAvatarBottomSpacing; 398 int avatar_restored_y = avatar_bottom - incognito_icon.height(); 399 int avatar_y = 400 (browser_view()->IsTabStripVisible() && 401 (frame()->IsMaximized() || frame()->IsFullscreen())) ? 402 GetTopInset() + kContentShadowHeight : avatar_restored_y; 403 404 // Hide the incognito icon in immersive fullscreen when the tab light bar is 405 // visible because the header is too short for the icognito icon to be 406 // recognizable. 407 bool avatar_visible = !UseImmersiveLightbarHeaderStyle(); 408 int avatar_height = avatar_visible ? avatar_bottom - avatar_y : 0; 409 410 gfx::Rect avatar_bounds(kAvatarSideSpacing, 411 avatar_y, 412 incognito_icon.width(), 413 avatar_height); 414 avatar_button()->SetBoundsRect(avatar_bounds); 415 avatar_button()->SetVisible(avatar_visible); 416} 417 418bool BrowserNonClientFrameViewAsh::ShouldPaint() const { 419 if (!frame()->IsFullscreen()) 420 return true; 421 422 // We need to paint when in immersive fullscreen and either: 423 // - The top-of-window views are revealed. 424 // - The lightbar style tabstrip is visible. 425 ImmersiveModeController* immersive_mode_controller = 426 browser_view()->immersive_mode_controller(); 427 return immersive_mode_controller->IsEnabled() && 428 (immersive_mode_controller->IsRevealed() || 429 UseImmersiveLightbarHeaderStyle()); 430} 431 432void BrowserNonClientFrameViewAsh::PaintImmersiveLightbarStyleHeader( 433 gfx::Canvas* canvas) { 434 // The light bar header is not themed because theming it does not look good. 435 gfx::ImageSkia* frame_image = GetThemeProvider()->GetImageSkiaNamed( 436 IDR_AURA_BROWSER_WINDOW_HEADER_BASE_MAXIMIZED); 437 canvas->TileImageInt(*frame_image, 0, 0, width(), frame_image->height()); 438} 439 440void BrowserNonClientFrameViewAsh::PaintToolbarBackground(gfx::Canvas* canvas) { 441 gfx::Rect toolbar_bounds(browser_view()->GetToolbarBounds()); 442 if (toolbar_bounds.IsEmpty()) 443 return; 444 gfx::Point toolbar_origin(toolbar_bounds.origin()); 445 View::ConvertPointToTarget(browser_view(), this, &toolbar_origin); 446 toolbar_bounds.set_origin(toolbar_origin); 447 448 int x = toolbar_bounds.x(); 449 int w = toolbar_bounds.width(); 450 int y = toolbar_bounds.y(); 451 int h = toolbar_bounds.height(); 452 453 // Gross hack: We split the toolbar images into two pieces, since sometimes 454 // (popup mode) the toolbar isn't tall enough to show the whole image. The 455 // split happens between the top shadow section and the bottom gradient 456 // section so that we never break the gradient. 457 // NOTE(pkotwicz): If the computation for |bottom_y| is changed, Layout() must 458 // be changed as well. 459 int split_point = kFrameShadowThickness * 2; 460 int bottom_y = y + split_point; 461 ui::ThemeProvider* tp = GetThemeProvider(); 462 int bottom_edge_height = h - split_point; 463 464 canvas->FillRect(gfx::Rect(x, bottom_y, w, bottom_edge_height), 465 tp->GetColor(ThemeProperties::COLOR_TOOLBAR)); 466 467 // Paint the main toolbar image. Since this image is also used to draw the 468 // tab background, we must use the tab strip offset to compute the image 469 // source y position. If you have to debug this code use an image editor 470 // to paint a diagonal line through the toolbar image and ensure it lines up 471 // across the tab and toolbar. 472 gfx::ImageSkia* theme_toolbar = tp->GetImageSkiaNamed(IDR_THEME_TOOLBAR); 473 canvas->TileImageInt( 474 *theme_toolbar, 475 x + GetThemeBackgroundXInset(), 476 bottom_y - GetTopInset(), 477 x, bottom_y, 478 w, theme_toolbar->height()); 479 480 // The content area line has a shadow that extends a couple of pixels above 481 // the toolbar bounds. 482 const int kContentShadowHeight = 2; 483 gfx::ImageSkia* toolbar_top = tp->GetImageSkiaNamed(IDR_TOOLBAR_SHADE_TOP); 484 canvas->TileImageInt(*toolbar_top, 485 0, 0, 486 x, y - kContentShadowHeight, 487 w, split_point + kContentShadowHeight + 1); 488 489 // Draw the "lightening" shade line around the edges of the toolbar. 490 gfx::ImageSkia* toolbar_left = tp->GetImageSkiaNamed(IDR_TOOLBAR_SHADE_LEFT); 491 canvas->TileImageInt(*toolbar_left, 492 0, 0, 493 x + kClientEdgeThickness, 494 y + kClientEdgeThickness + kContentShadowHeight, 495 toolbar_left->width(), theme_toolbar->height()); 496 gfx::ImageSkia* toolbar_right = 497 tp->GetImageSkiaNamed(IDR_TOOLBAR_SHADE_RIGHT); 498 canvas->TileImageInt(*toolbar_right, 499 0, 0, 500 w - toolbar_right->width() - 2 * kClientEdgeThickness, 501 y + kClientEdgeThickness + kContentShadowHeight, 502 toolbar_right->width(), theme_toolbar->height()); 503 504 // Draw the content/toolbar separator. 505 canvas->FillRect( 506 gfx::Rect(x + kClientEdgeThickness, 507 toolbar_bounds.bottom() - kClientEdgeThickness, 508 w - (2 * kClientEdgeThickness), 509 kClientEdgeThickness), 510 ThemeProperties::GetDefaultColor( 511 ThemeProperties::COLOR_TOOLBAR_SEPARATOR)); 512} 513 514void BrowserNonClientFrameViewAsh::PaintContentEdge(gfx::Canvas* canvas) { 515 DCHECK(!UsePackagedAppHeaderStyle()); 516 canvas->FillRect(gfx::Rect(0, caption_button_container_->bounds().bottom(), 517 width(), kClientEdgeThickness), 518 ThemeProperties::GetDefaultColor( 519 ThemeProperties::COLOR_TOOLBAR_SEPARATOR)); 520} 521