browser_non_client_frame_view_ash.cc revision 424c4d7b64af9d0d8fd9624f381f469654d5e3d2
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/wm/frame_painter.h" 8#include "ash/wm/workspace/frame_caption_button_container_view.h" 9#include "chrome/browser/themes/theme_properties.h" 10#include "chrome/browser/ui/browser.h" 11#include "chrome/browser/ui/immersive_fullscreen_configuration.h" 12#include "chrome/browser/ui/views/avatar_menu_button.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/immersive_mode_controller.h" 16#include "chrome/browser/ui/views/tab_icon_view.h" 17#include "chrome/browser/ui/views/tabs/tab_strip.h" 18#include "content/public/browser/web_contents.h" 19#include "grit/ash_resources.h" 20#include "grit/theme_resources.h" 21#include "ui/aura/client/aura_constants.h" 22#include "ui/aura/window.h" 23#include "ui/base/accessibility/accessible_view_state.h" 24#include "ui/base/hit_test.h" 25#include "ui/base/l10n/l10n_util.h" 26#include "ui/base/layout.h" 27#include "ui/base/resource/resource_bundle.h" 28#include "ui/base/theme_provider.h" 29#include "ui/compositor/layer_animator.h" 30#include "ui/gfx/canvas.h" 31#include "ui/gfx/image/image_skia.h" 32#include "ui/views/controls/label.h" 33#include "ui/views/layout/layout_constants.h" 34#include "ui/views/widget/widget.h" 35#include "ui/views/widget/widget_delegate.h" 36 37namespace { 38 39// The avatar ends 2 px above the bottom of the tabstrip (which, given the 40// way the tabstrip draws its bottom edge, will appear like a 1 px gap to the 41// user). 42const int kAvatarBottomSpacing = 2; 43// There are 2 px on each side of the avatar (between the frame border and 44// it on the left, and between it and the tabstrip on the right). 45const int kAvatarSideSpacing = 2; 46// Space between left edge of window and tabstrip. 47const int kTabstripLeftSpacing = 0; 48// Space between right edge of tabstrip and maximize button. 49const int kTabstripRightSpacing = 10; 50// Height of the shadow of the content area, at the top of the toolbar. 51const int kContentShadowHeight = 1; 52// Space between top of window and top of tabstrip for tall headers, such as 53// for restored windows, apps, etc. 54const int kTabstripTopSpacingTall = 7; 55// Space between top of window and top of tabstrip for short headers, such as 56// for maximized windows, pop-ups, etc. 57const int kTabstripTopSpacingShort = 0; 58// Height of the shadow in the tab image, used to ensure clicks in the shadow 59// area still drag restored windows. This keeps the clickable area large enough 60// to hit easily. 61const int kTabShadowHeight = 4; 62 63} // namespace 64 65/////////////////////////////////////////////////////////////////////////////// 66// BrowserNonClientFrameViewAsh, public: 67 68// static 69const char BrowserNonClientFrameViewAsh::kViewClassName[] = 70 "BrowserNonClientFrameViewAsh"; 71 72BrowserNonClientFrameViewAsh::BrowserNonClientFrameViewAsh( 73 BrowserFrame* frame, BrowserView* browser_view) 74 : BrowserNonClientFrameView(frame, browser_view), 75 caption_button_container_(NULL), 76 window_icon_(NULL), 77 frame_painter_(new ash::FramePainter) { 78} 79 80BrowserNonClientFrameViewAsh::~BrowserNonClientFrameViewAsh() { 81} 82 83void BrowserNonClientFrameViewAsh::Init() { 84 caption_button_container_ = new ash::FrameCaptionButtonContainerView(this, 85 frame(), ash::FrameCaptionButtonContainerView::MINIMIZE_ALLOWED); 86 AddChildView(caption_button_container_); 87 88 // Initializing the TabIconView is expensive, so only do it if we need to. 89 if (browser_view()->ShouldShowWindowIcon()) { 90 window_icon_ = new TabIconView(this); 91 window_icon_->set_is_light(true); 92 AddChildView(window_icon_); 93 window_icon_->Update(); 94 } 95 96 // Create incognito icon if necessary. 97 UpdateAvatarInfo(); 98 99 // Frame painter handles layout. 100 frame_painter_->Init(frame(), window_icon_, caption_button_container_); 101} 102 103/////////////////////////////////////////////////////////////////////////////// 104// BrowserNonClientFrameView overrides: 105 106gfx::Rect BrowserNonClientFrameViewAsh::GetBoundsForTabStrip( 107 views::View* tabstrip) const { 108 if (!tabstrip) 109 return gfx::Rect(); 110 111 // When the tab strip is painted in the immersive fullscreen light bar style, 112 // the caption buttons and the avatar button are not visible. However, their 113 // bounds are still used to compute the tab strip bounds so that the tabs have 114 // the same horizontal position when the tab strip is painted in the immersive 115 // light bar style as when the top-of-window views are revealed. 116 TabStripInsets insets(GetTabStripInsets(false)); 117 return gfx::Rect(insets.left, insets.top, 118 std::max(0, width() - insets.left - insets.right), 119 tabstrip->GetPreferredSize().height()); 120} 121 122BrowserNonClientFrameView::TabStripInsets 123BrowserNonClientFrameViewAsh::GetTabStripInsets(bool force_restored) const { 124 int left = avatar_button() ? kAvatarSideSpacing + 125 browser_view()->GetOTRAvatarIcon().width() + kAvatarSideSpacing : 126 kTabstripLeftSpacing; 127 int right = frame_painter_->GetRightInset() + kTabstripRightSpacing; 128 return TabStripInsets(NonClientTopBorderHeight(force_restored), left, right); 129} 130 131int BrowserNonClientFrameViewAsh::GetThemeBackgroundXInset() const { 132 return frame_painter_->GetThemeBackgroundXInset(); 133} 134 135void BrowserNonClientFrameViewAsh::UpdateThrobber(bool running) { 136 if (window_icon_) 137 window_icon_->Update(); 138} 139 140/////////////////////////////////////////////////////////////////////////////// 141// views::NonClientFrameView overrides: 142 143gfx::Rect BrowserNonClientFrameViewAsh::GetBoundsForClientView() const { 144 int top_height = NonClientTopBorderHeight(false); 145 return frame_painter_->GetBoundsForClientView(top_height, bounds()); 146} 147 148gfx::Rect BrowserNonClientFrameViewAsh::GetWindowBoundsForClientBounds( 149 const gfx::Rect& client_bounds) const { 150 int top_height = NonClientTopBorderHeight(false); 151 return frame_painter_->GetWindowBoundsForClientBounds(top_height, 152 client_bounds); 153} 154 155int BrowserNonClientFrameViewAsh::NonClientHitTest(const gfx::Point& point) { 156 int hit_test = frame_painter_->NonClientHitTest(this, point); 157 // When the window is restored we want a large click target above the tabs 158 // to drag the window, so redirect clicks in the tab's shadow to caption. 159 if (hit_test == HTCLIENT && 160 !(frame()->IsMaximized() || frame()->IsFullscreen())) { 161 // Convert point to client coordinates. 162 gfx::Point client_point(point); 163 View::ConvertPointToTarget(this, frame()->client_view(), &client_point); 164 // Report hits in shadow at top of tabstrip as caption. 165 gfx::Rect tabstrip_bounds(browser_view()->tabstrip()->bounds()); 166 if (client_point.y() < tabstrip_bounds.y() + kTabShadowHeight) 167 hit_test = HTCAPTION; 168 } 169 return hit_test; 170} 171 172void BrowserNonClientFrameViewAsh::GetWindowMask(const gfx::Size& size, 173 gfx::Path* window_mask) { 174 // Aura does not use window masks. 175} 176 177void BrowserNonClientFrameViewAsh::ResetWindowControls() { 178 // Hide the caption buttons in immersive fullscreen when the tab light bar 179 // is visible because it's confusing when the user hovers or clicks in the 180 // top-right of the screen and hits one. 181 bool button_visibility = !UseImmersiveLightbarHeaderStyle(); 182 caption_button_container_->SetVisible(button_visibility); 183 184 caption_button_container_->ResetWindowControls(); 185} 186 187void BrowserNonClientFrameViewAsh::UpdateWindowIcon() { 188 if (window_icon_) 189 window_icon_->SchedulePaint(); 190} 191 192void BrowserNonClientFrameViewAsh::UpdateWindowTitle() { 193 if (!frame()->IsFullscreen()) 194 frame_painter_->SchedulePaintForTitle(BrowserFrame::GetTitleFont()); 195} 196 197/////////////////////////////////////////////////////////////////////////////// 198// views::View overrides: 199 200void BrowserNonClientFrameViewAsh::OnPaint(gfx::Canvas* canvas) { 201 if (!ShouldPaint()) 202 return; 203 204 if (UseImmersiveLightbarHeaderStyle()) { 205 PaintImmersiveLightbarStyleHeader(canvas); 206 return; 207 } 208 209 // The primary header image changes based on window activation state and 210 // theme, so we look it up for each paint. 211 int theme_frame_image_id = GetThemeFrameImageId(); 212 int theme_frame_overlay_image_id = GetThemeFrameOverlayImageId(); 213 214 ui::ThemeProvider* theme_provider = GetThemeProvider(); 215 ash::FramePainter::Themed header_themed = ash::FramePainter::THEMED_NO; 216 if (theme_provider->HasCustomImage(theme_frame_image_id) || 217 (theme_frame_overlay_image_id != 0 && 218 theme_provider->HasCustomImage(theme_frame_overlay_image_id))) { 219 header_themed = ash::FramePainter::THEMED_YES; 220 } 221 222 if (frame_painter_->ShouldUseMinimalHeaderStyle(header_themed)) 223 theme_frame_image_id = IDR_AURA_WINDOW_HEADER_BASE_MINIMAL; 224 225 frame_painter_->PaintHeader( 226 this, 227 canvas, 228 ShouldPaintAsActive() ? 229 ash::FramePainter::ACTIVE : ash::FramePainter::INACTIVE, 230 theme_frame_image_id, 231 theme_frame_overlay_image_id); 232 if (browser_view()->ShouldShowWindowTitle()) 233 frame_painter_->PaintTitleBar(this, canvas, BrowserFrame::GetTitleFont()); 234 if (browser_view()->IsToolbarVisible()) 235 PaintToolbarBackground(canvas); 236 else 237 PaintContentEdge(canvas); 238} 239 240void BrowserNonClientFrameViewAsh::Layout() { 241 frame_painter_->LayoutHeader(this, UseShortHeader()); 242 if (avatar_button()) 243 LayoutAvatar(); 244 BrowserNonClientFrameView::Layout(); 245} 246 247const char* BrowserNonClientFrameViewAsh::GetClassName() const { 248 return kViewClassName; 249} 250 251bool BrowserNonClientFrameViewAsh::HitTestRect(const gfx::Rect& rect) const { 252 if (!views::View::HitTestRect(rect)) { 253 // |rect| is outside BrowserNonClientFrameViewAsh's bounds. 254 return false; 255 } 256 // If the rect is outside the bounds of the client area, claim it. 257 // TODO(tdanderson): Implement View::ConvertRectToTarget(). 258 gfx::Point rect_in_client_view_coords_origin(rect.origin()); 259 View::ConvertPointToTarget(this, frame()->client_view(), 260 &rect_in_client_view_coords_origin); 261 gfx::Rect rect_in_client_view_coords( 262 rect_in_client_view_coords_origin, rect.size()); 263 if (!frame()->client_view()->HitTestRect(rect_in_client_view_coords)) 264 return true; 265 266 // Otherwise, claim |rect| only if it is above the bottom of the tabstrip in 267 // a non-tab portion. 268 TabStrip* tabstrip = browser_view()->tabstrip(); 269 if (!tabstrip || !browser_view()->IsTabStripVisible()) 270 return false; 271 272 gfx::Point rect_in_tabstrip_coords_origin(rect.origin()); 273 View::ConvertPointToTarget(this, tabstrip, 274 &rect_in_tabstrip_coords_origin); 275 gfx::Rect rect_in_tabstrip_coords(rect_in_tabstrip_coords_origin, 276 rect.size()); 277 278 if (rect_in_tabstrip_coords.bottom() > tabstrip->GetLocalBounds().bottom()) { 279 // |rect| is below the tabstrip. 280 return false; 281 } 282 283 if (tabstrip->HitTestRect(rect_in_tabstrip_coords)) { 284 // Claim |rect| if it is in a non-tab portion of the tabstrip. 285 // TODO(tdanderson): Pass |rect_in_tabstrip_coords| instead of its center 286 // point to TabStrip::IsPositionInWindowCaption() once 287 // GetEventHandlerForRect() is implemented. 288 return tabstrip->IsPositionInWindowCaption( 289 rect_in_tabstrip_coords.CenterPoint()); 290 } 291 292 // We claim |rect| because it is above the bottom of the tabstrip, but 293 // not in the tabstrip. In particular, the window controls are right of 294 // the tabstrip. 295 return true; 296} 297 298void BrowserNonClientFrameViewAsh::GetAccessibleState( 299 ui::AccessibleViewState* state) { 300 state->role = ui::AccessibilityTypes::ROLE_TITLEBAR; 301} 302 303gfx::Size BrowserNonClientFrameViewAsh::GetMinimumSize() { 304 return frame_painter_->GetMinimumSize(this); 305} 306 307void BrowserNonClientFrameViewAsh::OnThemeChanged() { 308 BrowserNonClientFrameView::OnThemeChanged(); 309 frame_painter_->OnThemeChanged(); 310} 311 312/////////////////////////////////////////////////////////////////////////////// 313// chrome::TabIconViewModel overrides: 314 315bool BrowserNonClientFrameViewAsh::ShouldTabIconViewAnimate() const { 316 // This function is queried during the creation of the window as the 317 // TabIconView we host is initialized, so we need to NULL check the selected 318 // WebContents because in this condition there is not yet a selected tab. 319 content::WebContents* current_tab = browser_view()->GetActiveWebContents(); 320 return current_tab ? current_tab->IsLoading() : false; 321} 322 323gfx::ImageSkia BrowserNonClientFrameViewAsh::GetFaviconForTabIconView() { 324 views::WidgetDelegate* delegate = frame()->widget_delegate(); 325 if (!delegate) 326 return gfx::ImageSkia(); 327 return delegate->GetWindowIcon(); 328} 329 330/////////////////////////////////////////////////////////////////////////////// 331// BrowserNonClientFrameViewAsh, private: 332 333int BrowserNonClientFrameViewAsh::NonClientTopBorderHeight( 334 bool force_restored) const { 335 if (force_restored) 336 return kTabstripTopSpacingTall; 337 if (!ShouldPaint() || UseImmersiveLightbarHeaderStyle()) 338 return 0; 339 // Windows with tab strips need a smaller non-client area. 340 if (browser_view()->IsTabStripVisible()) { 341 if (UseShortHeader()) 342 return kTabstripTopSpacingShort; 343 return kTabstripTopSpacingTall; 344 } 345 // For windows without a tab strip (popups, etc.) ensure we have enough space 346 // to see the window caption buttons. 347 return caption_button_container_->bounds().bottom() - kContentShadowHeight; 348} 349 350bool BrowserNonClientFrameViewAsh::UseShortHeader() const { 351 // Restored browser -> tall header 352 // Maximized browser -> short header 353 // Fullscreen browser, no immersive reveal -> hidden or super short light bar 354 // Fullscreen browser, immersive reveal -> short header 355 // Popup&App window -> tall header 356 // Panels use short header and are handled via ash::PanelFrameView. 357 // Dialogs use short header and are handled via ash::CustomFrameViewAsh. 358 Browser* browser = browser_view()->browser(); 359 switch (browser->type()) { 360 case Browser::TYPE_TABBED: 361 return frame()->IsMaximized() || frame()->IsFullscreen(); 362 case Browser::TYPE_POPUP: 363 return false; 364 default: 365 NOTREACHED(); 366 return false; 367 } 368} 369 370bool BrowserNonClientFrameViewAsh::UseImmersiveLightbarHeaderStyle() const { 371 ImmersiveModeController* immersive_controller = 372 browser_view()->immersive_mode_controller(); 373 return immersive_controller->IsEnabled() && 374 !immersive_controller->IsRevealed() && 375 !immersive_controller->ShouldHideTabIndicators(); 376} 377 378void BrowserNonClientFrameViewAsh::LayoutAvatar() { 379 DCHECK(avatar_button()); 380 gfx::ImageSkia incognito_icon = browser_view()->GetOTRAvatarIcon(); 381 382 int avatar_bottom = GetTabStripInsets(false).top + 383 browser_view()->GetTabStripHeight() - kAvatarBottomSpacing; 384 int avatar_restored_y = avatar_bottom - incognito_icon.height(); 385 int avatar_y = (frame()->IsMaximized() || frame()->IsFullscreen()) ? 386 NonClientTopBorderHeight(false) + kContentShadowHeight : 387 avatar_restored_y; 388 389 // Hide the incognito icon in immersive fullscreen when the tab light bar is 390 // visible because the header is too short for the icognito icon to be 391 // recognizable. 392 bool avatar_visible = !UseImmersiveLightbarHeaderStyle(); 393 int avatar_height = avatar_visible ? avatar_bottom - avatar_y : 0; 394 395 gfx::Rect avatar_bounds(kAvatarSideSpacing, 396 avatar_y, 397 incognito_icon.width(), 398 avatar_height); 399 avatar_button()->SetBoundsRect(avatar_bounds); 400 avatar_button()->SetVisible(avatar_visible); 401} 402 403bool BrowserNonClientFrameViewAsh::ShouldPaint() const { 404 if (!frame()->IsFullscreen()) 405 return true; 406 407 // There is nothing to paint for traditional (tab) fullscreen. 408 ImmersiveModeController* immersive_controller = 409 browser_view()->immersive_mode_controller(); 410 if (!immersive_controller->IsEnabled()) 411 return false; 412 413 // Need to paint during an immersive fullscreen reveal or when the immersive 414 // light bar is visible. 415 return immersive_controller->IsRevealed() || 416 !immersive_controller->ShouldHideTabIndicators(); 417} 418 419void BrowserNonClientFrameViewAsh::PaintImmersiveLightbarStyleHeader( 420 gfx::Canvas* canvas) { 421 // The light bar header is not themed because theming it does not look good. 422 gfx::ImageSkia* frame_image = GetThemeProvider()->GetImageSkiaNamed( 423 IDR_AURA_WINDOW_HEADER_BASE_MINIMAL); 424 canvas->TileImageInt(*frame_image, 0, 0, width(), frame_image->height()); 425} 426 427void BrowserNonClientFrameViewAsh::PaintToolbarBackground(gfx::Canvas* canvas) { 428 gfx::Rect toolbar_bounds(browser_view()->GetToolbarBounds()); 429 if (toolbar_bounds.IsEmpty()) 430 return; 431 gfx::Point toolbar_origin(toolbar_bounds.origin()); 432 View::ConvertPointToTarget(browser_view(), this, &toolbar_origin); 433 toolbar_bounds.set_origin(toolbar_origin); 434 435 int x = toolbar_bounds.x(); 436 int w = toolbar_bounds.width(); 437 int y = toolbar_bounds.y(); 438 int h = toolbar_bounds.height(); 439 440 // Gross hack: We split the toolbar images into two pieces, since sometimes 441 // (popup mode) the toolbar isn't tall enough to show the whole image. The 442 // split happens between the top shadow section and the bottom gradient 443 // section so that we never break the gradient. 444 int split_point = kFrameShadowThickness * 2; 445 int bottom_y = y + split_point; 446 ui::ThemeProvider* tp = GetThemeProvider(); 447 int bottom_edge_height = h - split_point; 448 449 canvas->FillRect(gfx::Rect(x, bottom_y, w, bottom_edge_height), 450 tp->GetColor(ThemeProperties::COLOR_TOOLBAR)); 451 452 // Paint the main toolbar image. Since this image is also used to draw the 453 // tab background, we must use the tab strip offset to compute the image 454 // source y position. If you have to debug this code use an image editor 455 // to paint a diagonal line through the toolbar image and ensure it lines up 456 // across the tab and toolbar. 457 gfx::ImageSkia* theme_toolbar = tp->GetImageSkiaNamed(IDR_THEME_TOOLBAR); 458 canvas->TileImageInt( 459 *theme_toolbar, 460 x + GetThemeBackgroundXInset(), 461 bottom_y - GetTabStripInsets(false).top, 462 x, bottom_y, 463 w, theme_toolbar->height()); 464 465 // The content area line has a shadow that extends a couple of pixels above 466 // the toolbar bounds. 467 const int kContentShadowHeight = 2; 468 gfx::ImageSkia* toolbar_top = tp->GetImageSkiaNamed(IDR_TOOLBAR_SHADE_TOP); 469 canvas->TileImageInt(*toolbar_top, 470 0, 0, 471 x, y - kContentShadowHeight, 472 w, split_point + kContentShadowHeight + 1); 473 474 // Draw the "lightening" shade line around the edges of the toolbar. 475 gfx::ImageSkia* toolbar_left = tp->GetImageSkiaNamed(IDR_TOOLBAR_SHADE_LEFT); 476 canvas->TileImageInt(*toolbar_left, 477 0, 0, 478 x + kClientEdgeThickness, 479 y + kClientEdgeThickness + kContentShadowHeight, 480 toolbar_left->width(), theme_toolbar->height()); 481 gfx::ImageSkia* toolbar_right = 482 tp->GetImageSkiaNamed(IDR_TOOLBAR_SHADE_RIGHT); 483 canvas->TileImageInt(*toolbar_right, 484 0, 0, 485 w - toolbar_right->width() - 2 * kClientEdgeThickness, 486 y + kClientEdgeThickness + kContentShadowHeight, 487 toolbar_right->width(), theme_toolbar->height()); 488 489 // Draw the content/toolbar separator. 490 canvas->FillRect( 491 gfx::Rect(x + kClientEdgeThickness, 492 toolbar_bounds.bottom() - kClientEdgeThickness, 493 w - (2 * kClientEdgeThickness), 494 kClientEdgeThickness), 495 ThemeProperties::GetDefaultColor( 496 ThemeProperties::COLOR_TOOLBAR_SEPARATOR)); 497} 498 499void BrowserNonClientFrameViewAsh::PaintContentEdge(gfx::Canvas* canvas) { 500 canvas->FillRect(gfx::Rect(0, caption_button_container_->bounds().bottom(), 501 width(), kClientEdgeThickness), 502 ThemeProperties::GetDefaultColor( 503 ThemeProperties::COLOR_TOOLBAR_SEPARATOR)); 504} 505 506int BrowserNonClientFrameViewAsh::GetThemeFrameImageId() const { 507 bool is_incognito = browser_view()->IsOffTheRecord() && 508 !browser_view()->IsGuestSession(); 509 if (browser_view()->IsBrowserTypeNormal()) { 510 // Use the standard resource ids to allow users to theme the frames. 511 if (ShouldPaintAsActive()) { 512 return is_incognito ? 513 IDR_THEME_FRAME_INCOGNITO : IDR_THEME_FRAME; 514 } 515 return is_incognito ? 516 IDR_THEME_FRAME_INCOGNITO_INACTIVE : IDR_THEME_FRAME_INACTIVE; 517 } 518 // Never theme app and popup windows. 519 if (ShouldPaintAsActive()) { 520 return is_incognito ? 521 IDR_AURA_WINDOW_HEADER_BASE_INCOGNITO_ACTIVE : 522 IDR_AURA_WINDOW_HEADER_BASE_ACTIVE; 523 } 524 return is_incognito ? 525 IDR_AURA_WINDOW_HEADER_BASE_INCOGNITO_INACTIVE : 526 IDR_AURA_WINDOW_HEADER_BASE_INACTIVE; 527} 528 529int BrowserNonClientFrameViewAsh::GetThemeFrameOverlayImageId() const { 530 ui::ThemeProvider* tp = GetThemeProvider(); 531 if (tp->HasCustomImage(IDR_THEME_FRAME_OVERLAY) && 532 browser_view()->IsBrowserTypeNormal() && 533 !browser_view()->IsOffTheRecord()) { 534 return ShouldPaintAsActive() ? 535 IDR_THEME_FRAME_OVERLAY : IDR_THEME_FRAME_OVERLAY_INACTIVE; 536 } 537 return 0; 538} 539